├── LICENSE ├── Makefile ├── README.md ├── docs ├── Consul部署说明.md ├── ECS主机监控.md ├── FAQ.md ├── blackbox站点监控.md ├── 使用一个redis_exporter监控所有的Redis实例.md ├── 如何优雅的使用一个mysqld_exporter监控所有的MySQL实例.md ├── 如何把主机自动同步到JumpServer.md └── 开发模式调试说明.md ├── flask-consul ├── .dockerignore ├── .gitignore ├── Dockerfile ├── Dockerfile-arm64 ├── Dockerfile.bak_aliyun ├── config.py ├── manager.py ├── requirements.txt ├── units │ ├── avd │ │ ├── avd_list.py │ │ └── avd_test.py │ ├── blackbox_manager.py │ ├── cloud │ │ ├── alicloud.py │ │ ├── awscloud.py │ │ ├── azurecloud.py │ │ ├── huaweicloud.py │ │ ├── notify.py │ │ ├── sync_clickhouse.py │ │ ├── sync_ecs.py │ │ ├── sync_mongodb.py │ │ ├── sync_polardb.py │ │ ├── sync_rds.py │ │ ├── sync_redis.py │ │ └── tencent_cloud.py │ ├── config_log.py │ ├── consul_kv.py │ ├── consul_manager.py │ ├── consul_svc.py │ ├── gen_config.py │ ├── jms │ │ └── sync_jms.py │ ├── json_response.py │ ├── ldap │ │ ├── LdapUser.py │ │ └── ldap_consul.py │ ├── myaes.py │ ├── mydes.py │ ├── prom │ │ ├── mysql_ali.py │ │ ├── mysql_huawei.py │ │ ├── mysql_tencent.py │ │ ├── redis_ali.py │ │ ├── redis_huawei.py │ │ └── redis_tencent.py │ ├── selfclickhouse_manager.py │ ├── selfmongodb_manager.py │ ├── selfnode_manager.py │ ├── selfpolardb_manager.py │ ├── selfrds_manager.py │ ├── selfredis_manager.py │ ├── token_auth.py │ └── upload.py └── views │ ├── avd.py │ ├── blackbox.py │ ├── clickhouse.py │ ├── consul.py │ ├── edit_cloud.py │ ├── exp.py │ ├── jms.py │ ├── jobs.py │ ├── ldap.py │ ├── login.py │ ├── mongodb.py │ ├── nodes.py │ ├── polardb.py │ ├── prom │ └── cloud_metrics.py │ ├── rds.py │ ├── redis.py │ ├── selfclickhouse.py │ ├── selfmongodb.py │ ├── selfnode.py │ ├── selfpolardb.py │ ├── selfrds.py │ └── selfredis.py ├── install ├── docker-compose │ ├── all_install.sh │ ├── consul_install_only.sh │ └── tensuns_install_only.sh └── k8s │ ├── install.sh │ └── readme.md ├── screenshot ├── README.md ├── blackbox1.PNG ├── blackbox2.PNG ├── blackbox3.PNG ├── blackbox4.PNG ├── bug.png ├── consul1.PNG ├── consul2.PNG ├── consul3.PNG ├── consul4.PNG ├── ecs1.PNG ├── ecs2.PNG ├── ecs3.PNG ├── ecs4.PNG └── tensuns-arch.png ├── thanks.png ├── tools ├── blackbox-input.py ├── blackbox-instance.list ├── del_consul_svc.sh ├── docker-build.sh ├── docker-push.sh ├── selfnode-input.py ├── selfnode-instance.list └── 当前已支持Web导入 └── vue-consul ├── .dockerignore ├── .editorconfig ├── .env.development ├── .env.production ├── .env.staging ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── Dockerfile ├── Dockerfile-arm64 ├── Dockerfile.tensuns ├── LICENSE ├── babel.config.js ├── build └── index.js ├── http-ops.conf ├── jest.config.js ├── jsconfig.json ├── mock ├── article.js ├── index.js ├── mock-server.js ├── table.js ├── user.js └── utils.js ├── package.json ├── postcss.config.js ├── public ├── blackbox.png ├── favicon.ico ├── index.html ├── mysql1.png ├── mysql2.png ├── node1.png ├── node2.png ├── redis1.png └── sl.png ├── src ├── App.vue ├── api │ ├── article.js │ ├── avd.js │ ├── blackbox.js │ ├── consul.js │ ├── edit.js │ ├── exp.js │ ├── jms.js │ ├── ldap.js │ ├── login.js │ ├── node-exporter.js │ ├── rds.js │ ├── redis.js │ ├── selfnode.js │ ├── selfrds.js │ ├── selfredis.js │ ├── table.js │ ├── user-ops.js │ └── user.js ├── assets │ ├── 404_images │ │ ├── 404.png │ │ └── 404_cloud.png │ └── login_images │ │ ├── SLH.png │ │ ├── bg.png │ │ └── tensuns.png ├── components │ ├── Breadcrumb │ │ └── index.vue │ ├── Hamburger │ │ └── index.vue │ ├── Pagination │ │ └── index.vue │ └── SvgIcon │ │ └── index.vue ├── directive │ └── waves │ │ ├── index.js │ │ ├── waves.css │ │ └── waves.js ├── filters │ └── index.js ├── icons │ ├── index.js │ ├── svg │ │ ├── dashboard.svg │ │ ├── example.svg │ │ ├── eye-open.svg │ │ ├── eye.svg │ │ ├── form.svg │ │ ├── link.svg │ │ ├── nested.svg │ │ ├── password.svg │ │ ├── table.svg │ │ ├── tree.svg │ │ └── user.svg │ └── svgo.yml ├── layout │ ├── components │ │ ├── AppMain.vue │ │ ├── Navbar.vue │ │ ├── Sidebar │ │ │ ├── FixiOSBug.js │ │ │ ├── Item.vue │ │ │ ├── Link.vue │ │ │ ├── Logo.vue │ │ │ ├── SidebarItem.vue │ │ │ └── index.vue │ │ └── index.js │ ├── index.vue │ └── mixin │ │ └── ResizeHandler.js ├── main.js ├── permission.js ├── router │ └── index.js ├── settings.js ├── store │ ├── getters.js │ ├── index.js │ └── modules │ │ ├── app.js │ │ ├── settings.js │ │ └── user.js ├── styles │ ├── element-ui.scss │ ├── index.scss │ ├── mixin.scss │ ├── sidebar.scss │ ├── transition.scss │ └── variables.scss ├── utils │ ├── auth.js │ ├── get-page-title.js │ ├── index.js │ ├── request-ops.js │ ├── request.js │ ├── scroll-to.js │ └── validate.js ├── vendor │ └── Export2Excel.js └── views │ ├── 404.vue │ ├── avd │ └── index.vue │ ├── blackbox │ ├── bconfig.vue │ ├── grafana.vue │ ├── index.vue │ ├── pconfig.vue │ └── rules.vue │ ├── consul │ ├── hosts.vue │ ├── instances.vue │ └── services.vue │ ├── dashboard │ └── index.vue │ ├── jms │ └── index.vue │ ├── ldap │ ├── index.vue │ ├── logo.vue │ └── user.vue │ ├── login │ └── index.vue │ ├── node-exporter │ ├── exp.vue │ ├── grafana.vue │ ├── index.vue │ ├── jobs.vue │ ├── lists.vue │ ├── pconfig.vue │ ├── rules.vue │ └── self.vue │ ├── rds │ ├── grafana.vue │ ├── index.vue │ ├── lists.vue │ ├── pconfig.vue │ ├── rules.vue │ └── self.vue │ └── redis │ ├── grafana.vue │ ├── index.vue │ ├── lists.vue │ ├── pconfig.vue │ ├── rules.vue │ └── self.vue ├── tests └── unit │ ├── .eslintrc.js │ ├── components │ ├── Breadcrumb.spec.js │ ├── Hamburger.spec.js │ └── SvgIcon.spec.js │ └── utils │ ├── formatTime.spec.js │ ├── param2Obj.spec.js │ ├── parseTime.spec.js │ └── validate.spec.js ├── vue.config.js └── vue.config.js.old /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2023 StarsL.cn 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | help: 2 | echo "Read Makefile" && echo "make build" && echo "make push ver=x.x.x" 3 | build: 4 | cd flask-consul && docker build -t flask-consul:latest . 5 | cd vue-consul && docker build -t nginx-consul:latest . 6 | #cd vue-consul && docker build -t nginx-consul:tensuns-latest -f Dockerfile.tensuns . 7 | echo -e "\n\n自行编译的版本,注意修改docker-compose.yml中的镜像地址为本地仓库后再启动。\nBlackbox-Manager:\nhttp://{ip}:1026\n" 8 | 9 | push: 10 | #docker login --username=starsliao@163.com registry.cn-shenzhen.aliyuncs.com 11 | docker tag nginx-consul:latest swr.cn-south-1.myhuaweicloud.com/starsl.cn/nginx-consul:latest 12 | docker tag nginx-consul:latest swr.cn-south-1.myhuaweicloud.com/starsl.cn/nginx-consul:${ver} 13 | docker tag flask-consul:latest swr.cn-south-1.myhuaweicloud.com/starsl.cn/flask-consul:latest 14 | docker tag flask-consul:latest swr.cn-south-1.myhuaweicloud.com/starsl.cn/flask-consul:${ver} 15 | docker push swr.cn-south-1.myhuaweicloud.com/starsl.cn/nginx-consul:latest 16 | docker push swr.cn-south-1.myhuaweicloud.com/starsl.cn/nginx-consul:${ver} 17 | docker push swr.cn-south-1.myhuaweicloud.com/starsl.cn/flask-consul:latest 18 | docker push swr.cn-south-1.myhuaweicloud.com/starsl.cn/flask-consul:${ver} 19 | #docker tag nginx-consul:tensuns-latest swr.cn-south-1.myhuaweicloud.com/starsl.cn/nginx-consul:tensuns-latest 20 | #docker push swr.cn-south-1.myhuaweicloud.com/starsl.cn/nginx-consul:tensuns-latest 21 | 22 | update: 23 | docker-compose pull && docker-compose up -d 24 | -------------------------------------------------------------------------------- /docs/Consul部署说明.md: -------------------------------------------------------------------------------- 1 | ### 安装 2 | > 以下为CentOS7安装说明,其它系统安装部分请参考官网: 3 | > https://developer.hashicorp.com/consul/downloads 4 | ```bash 5 | # 使用yum部署consul 6 | yum install -y yum-utils 7 | yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo 8 | yum -y install consul 9 | # 如果yum下载失败,可以直接下载RPM包安装 10 | wget https://rpm.releases.hashicorp.com/RHEL/7/x86_64/stable/consul-1.16.0-1.x86_64.rpm 11 | rpm -ivh ./consul-1.16.0-1.x86_64.rpm 12 | ``` 13 | 14 | ### 配置 15 | - **执行以下命令获取UUID,填写到下面配置末尾部分,作为最高权限的token** 16 | ```bash 17 | uuidgen 18 | ``` 19 | - **清空`/etc/consul.d/consul.hcl`,并使用以下完整配置内容** 20 | ```bash 21 | data_dir = "/opt/consul" 22 | log_level = "error" 23 | 24 | # 服务器有多个IP启动会报错请增加这行配置:填写服务器IP。 25 | advertise_addr = "192.168.x.x" 26 | 27 | client_addr = "0.0.0.0" 28 | ui_config{ 29 | enabled = true 30 | } 31 | ports = { 32 | grpc = -1 33 | https = -1 34 | dns = -1 35 | grpc_tls = -1 36 | serf_wan = -1 37 | } 38 | peering { 39 | enabled = false 40 | } 41 | connect { 42 | enabled = false 43 | } 44 | server = true 45 | bootstrap_expect=1 46 | acl = { 47 | enabled = true 48 | default_policy = "deny" 49 | enable_token_persistence = true 50 | tokens { 51 | initial_management = "生成的UUID" 52 | agent = "生成的UUID,和上面保持一致" 53 | } 54 | } 55 | ``` 56 | 57 | ### 启动服务 58 | 59 | ```bash 60 | mkdir /opt/consul 61 | chown -R consul:consul /opt/consul 62 | sed -i 's/Type=notify/Type=exec/g' /usr/lib/systemd/system/consul.service 63 | systemctl daemon-reload 64 | systemctl enable consul.service 65 | systemctl restart consul.service 66 | ``` 67 | ### 验证: 访问Consul自带WEB页面 68 | - 浏览器访问Consul的8500端口 69 | - 使用生成的UUID登录 70 | 71 | --- 72 | 73 | ### consul kv 备份还原 74 | ``` 75 | consul kv export --http-addr=http://127.0.0.1:8500 -token=xxxxxxxx '' > consul_kv_bak.json 76 | consul kv import --http-addr=http://127.0.0.1:8500 -token=xxxxxxxx @consul_kv_bak.json 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/ECS主机监控.md: -------------------------------------------------------------------------------- 1 | ### Consul字段设计说明 2 | - 服务首次启动时会创建一个随机秘钥,存放到consul_kv的`/ConsulManager/assets/secret/skey`,该秘钥用于对登录Token,各云厂商账号AKSK的加解密使用。 3 | - 云厂商的每个账号下的ECS实例信息:会存储到Consul的对应Services下的实例中,ECS的实例ID会作为ServiceID。 4 | - 云厂商的每个账号下的AKSK和分组信息:存放到consul_kv的`/ConsulManager/assets`下各个云厂商的目录下的`aksk`(加密存储)和`group`目录。 5 | - **分组字段:是采集云厂商用于资源分组的字段,阿里云:资源组,华为云:企业项目,腾讯云:所属项目。请在创建云主机时设置好属组。** 6 | - 新增的各个云厂商的ECS同步任务:会存放到consul_kv的`/ConsulManager/jobs`下,服务启动的时候会加载这些任务。 7 | - 每次同步任务的执行结果:会存储到consul_kv的`/ConsulManager/record/jobs`的各个云厂商目录下。 8 | - 新增云厂商的数据源之后会自动同步一次分组信息,ECS信息则会在设置的时间间隔之后才会同步,可以手动点击同步按钮同步一次。 9 | - Linux默认监控node_exporter的9100端口,Windows默认监控windows_exporter的9128端口。 10 | 11 | ### 配置说明 12 | ##### 1. 新增同步源 13 | - 在Web页面点击`Node 云主机监控/接入数据源`,点击`新增同步源`,输入各字段: 14 | - `账户`可随意填写,用来区分云厂商不同云账户的标识,支持中文,例如用主账户的名称。 15 | - 填写的`AKSK`需要有获取ECS的权限以及各云厂商分组信息的权限。(注意:腾讯云的分组信息因为在ECS中没有找到对应接口,是从`分布式数据库TDSQL-查询项目列表 (DCDB)`的接口中获取的,AKSK需要有对应权限。) 16 | - 选择好对应的ECS所在区域(暂支持国内),以及分组与ECS的刷新间隔,确认即可,分组信息会自动同步一次,ECS信息则会在设置的时间间隔之后才会同步,可以手动点击同步按钮同步一次。 17 | ##### 2. 查看云主机列表 18 | - 可以点击`查看`按钮或者在`云主机列表`中查看同步后的ECS信息。(华为云暂未获取到ECS到期日信息) 19 | ##### 3. 管理自建主机 20 | - 在Web页面点击`Node 云主机监控/自建主机管理`,点击`新增`,根据需要即可增加需要监控的自建主机,支持增删改查与批量操作(脚本)。 21 | ##### 3. 配置Prometheus 22 | - 点击`ECS 云主机监控/Prometheus 配置`,根据需要来生成各云账号、系统(均支持多选)的Prometheus配置信息(**有自建主机记得复选上`selfnode_exporter`**),复制配置后,追加到`prometheus.yml`的末尾,重启Prometheus即可。 23 | ##### 4. 查看Prometheus 24 | - 在Prometheus的Web页面中,点击`Status-Targets`,能看到新增的Job即表示数据同步到Prometheus。 25 | ##### 5. 导入Node Exporter Dashboard 26 | - 更新了主机监控的grafana看板,可匹配自动同步方式采集ECS信息字段的展示。 27 | - 优化了大量图表,使用新版表格重建,新增健康评分概念,并新增了整体资源消耗信息的一些图表。 28 | - **导入ID:8919** 29 | - 详细URL:[https://grafana.com/grafana/dashboards/8919](https://grafana.com/grafana/dashboards/8919) 30 | 31 | --- 32 | 33 | ### 批量导入自建主机脚本【现已支持直接在Web页面上批量导入自建主机!】 34 | 35 | 在项目仓库根目录的`tools`目录下:编辑`selfnode-instance.list`,写入监控目标的信息:机房/公司 租户/部门 区域/项目 分组/环境 名称 实例(ip:端口) 系统(linux/windows),每行一个,空格分隔。 36 | 37 | **注意:前5个字段组合起来必须唯一,作为一个监控项的ID。即Consul的ServiceID** 38 | 39 | 再修改导入脚本`selfnode-input.py`中的consul_token和consul_url,保存后执行`selfnode-input.py`,即可导入所有监控目标到Consul,并符合Prometheus的自动发现配置。 40 | 41 | ### 注意: 42 | 43 | ##### 主动关机的ECS,会在同步时候从Consul中清除,即会在Prometheus中去除(减少无效的告警),重新开机后会增加回去。 44 | ##### 各ECS的Node_exporter需要自行安装。 45 | ##### 【最近7天P99资源使用率】图表需要在Prometheus增加记录规则(采集1小时后出数据): 46 | ``` 47 | groups: #新rule文件需要加这行开头,追加旧的rule文件则不需要。 48 | - name: node_usage_record_rules 49 | interval: 1m 50 | rules: 51 | - record: cpu:usage:rate1m 52 | expr: (1 - avg(irate(node_cpu_seconds_total{mode="idle"}[3m])) by (instance,vendor,account,group,name)) * 100 53 | - record: mem:usage:rate1m 54 | expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 55 | ``` 56 | 57 | -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | ### 查后端实时日志(先进入docker-compose.yml所在目录) 2 | ``` 3 | docker-compose logs --tail=50 -f flask-consul 4 | ``` 5 | ### 查前端实时日志(先进入docker-compose.yml所在目录) 6 | ``` 7 | docker-compose logs --tail=50 -f nginx-consul 8 | ``` 9 | ### 后端启动报错requests.exceptions.ConnectionError: HTTPConnectionPool(host='xxx', port=8500) 10 | ![图片](https://user-images.githubusercontent.com/3349611/219944354-7be4c686-ff8e-4a03-8939-0fd6dedfb1b7.png) 11 | - 这是由于flask-consul容器无法连接到consul服务端,请检查容器到consul的网络是否通。 12 | - 可以检查下iptables防火墙规则,设置允许访问8500端口: 13 | ``` 14 | # 参考命令 15 | firewall-cmd --zone=public --add-port=8500/tcp --permanent 16 | 或 17 | iptables -A INPUT -p tcp -m state --state NEW -m tcp --dport 8500 -j ACCEPT 18 | ``` 19 | 20 | ### 检查consul连接是否正常? 21 | ``` 22 | # 进入容器: 23 | docker-compose exec flask-consul sh 24 | # 如果flask-consul容器无法正常启动,可以进入nginx-consul容器测试 25 | docker-compose exec nginx-consul sh 26 | # 执行检查: 27 | nc -vz {consul_ip} 8500 28 | ``` 29 | ### blackbox_exporter监控某个站点有异常,如何debug 30 | - 在blackbox检测该站点的链接末尾加上`&debug=true`,请求即可,例如: 31 | ``` 32 | http://10.0.0.26:9115/probe?module=http_2xx&target=https%3A%2F%2Fpayapp.weixin.qq.com&debug=true 33 | ``` 34 | - 在blackbox_exporter的启动命令增加参数`--log.level=debug`,即可开启blackbox_exporter的debug日志,systemd方式启动的日志追踪方式: 35 | ``` 36 | journalctl -u blackbox_exporter.service -n20 -f 37 | ``` 38 | ### prometheus配置告警规则后报错怎么办? 39 | - 进入prometheus所在的目录,执行如下命令即可检查: 40 | ``` 41 | ./promtool check rules ./rules.yml 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/blackbox站点监控.md: -------------------------------------------------------------------------------- 1 | ### Consul字段设计说明 2 | 3 | - 所有数据存在一个名为`blackbox_exporter`的Services项中,每个监控目标为一个子Service。 4 | - 每个Service使用Meta的kv保存监控目标的属性:`module`,`company`,`project`,`env`,`name`,`instance`,分别表示:监控类型,公司部门,项目,环境,名称,实例url。 5 | - **新增监控时,监控类型字段(`module`)和Blackbox配置中的`module`必须保持一致。** 6 | - **前5个字段合并即为consul的serviceID,作为唯一监控项标识** 7 | 8 | --- 9 | 10 | ### 配置Prometheus与Blackbox 11 | 12 | #### 原理:基于Consul实现Prometheus的自动发现功能配置 13 | - 把Consul每个service的Meta的KV关联到Prometheus每个指标的标签。 14 | - 根据每个指标的标签来对监控目标分类,分组,方便管理维护。 15 | ##### 1. 配置Blackbox_Exporter 16 | - 在Web页面点击`Blackbox 站点监控/Blackbox 配置`,点击`复制配置`。 17 | - 编辑blackbox_exporter的`blackbox.yml`,清空已有的配置,把复制的内容粘贴进去,重启blackbox_exporter。 18 | ##### 2. 配置Prometheus 19 | - 在Web页面点击`Blackbox 站点监控/Prometheus 配置`,点击`复制配置`。 20 | - 编辑Prometheus的`prometheus.yml`,把复制的内容追加到最后,reload或重启Prometheus。 21 | ##### 3. 配置Prometheus告警规则 22 | - 在Web页面点击`Blackbox 站点监控/告警规则`,点击`复制配置`。 23 | - 编辑Prometheus的`rules.yml`,把复制的内容追加到最后,reload或重启Prometheus。 24 | ##### 4. 查看Prometheus 25 | - 在Prometheus的Web页面中,点击Status-Targets,能看到新增的Job即表示数据同步到Prometheus。 26 | ##### 5. 导入Blackbox Exporter Dashboard 27 | - 支持Grafana 8,基于blackbox_exporter 0.19.0设计 28 | - 采用图表+曲线图方式展示TCP,ICMP,HTTPS的服务状态,各阶段请求延时,HTTPS证书信息等 29 | - 优化展示效果,支持监控目标的分组、分类级联展示,多服务同时对比展示。 30 | - **导入ID:9965** 31 | - 详细URL:[https://grafana.com/grafana/dashboards/9965](https://grafana.com/grafana/dashboards/9965) 32 | 33 | --- 34 | 35 | #### 批量导入脚本【现已支持直接在Web页面上批量导入监控站点!】 36 | 37 | 在项目仓库根目录的`tools`目录下:编辑`blackbox-instance.list`,写入监控目标的信息:监控类型,公司/部门,项目,环境,名称,实例url,每行一个,空格分隔。 38 | 39 | **注意:前5个字段组合起来必须唯一,作为一个监控项的ID。即Consul的ServiceID** 40 | 41 | 再修改导入脚本`blackbox-input.py`中的consul_token和consul_url,保存后执行`blackbox-input.py`,即可导入所有监控目标到Consul,并符合Prometheus的自动发现配置。 42 | -------------------------------------------------------------------------------- /docs/使用一个redis_exporter监控所有的Redis实例.md: -------------------------------------------------------------------------------- 1 | # 使用一个redis_exporter监控所有的Redis实例 2 | ### 一、如何在TenSunS中接入redis 3 | #### 1. 接入云厂商的Redis 4 | 1. 新增云账号的情况:目前新增时,支持多选区域,以及选择增加的资源类型,勾选REDIS即可接入自动同步云REDIS,记得设置好同步间隔。 5 | 6 | ![图片](https://user-images.githubusercontent.com/3349611/204356330-330865fd-6eea-48eb-88e1-757e7ea4a0b1.png) 7 | 8 | 9 | 2. 对已经添加过的账号,增加同步云REDIS资源:点击编辑云资源,选择好需要编辑的厂商、账号及区域,再勾选资源类型REDIS,配置上同步间隔即可增加自动同步云REDIS。 10 | 11 | ![图片](https://user-images.githubusercontent.com/3349611/204356547-3d6b8b57-33f4-4938-ac4a-cf9e5abe2a31.png) 12 | 13 | 14 | 3. 接入完成后,可手动点击同步按钮,完成首次同步;或者等待设定好的同步周期后会自动同步。 15 | 16 | ![图片](https://user-images.githubusercontent.com/3349611/204356757-be3e86da-dff6-44ca-8086-a033a9750067.png) 17 | 18 | 19 | 4. 同步完成后,可在`云资源管理`-`REDIS管理`-`云REDIS列表`,查看同步的云redis信息以及自定义实例监控的IP和端口(再次同步不会覆盖实例自定义的IP端口信息)。 20 | ![图片](https://user-images.githubusercontent.com/3349611/204357662-09f44475-9545-4667-abf1-29bbb78a4935.png) 21 | 22 | #### 2. 接入自建redis 23 | 1. 进入`云资源管理`-`REDIS管理`-`自建REDIS管理`,即可新增或批量导入自建的redis列表。 24 | ![图片](https://user-images.githubusercontent.com/3349611/208393735-bb7a0ee2-59ef-4a0c-8430-a5c32552d7cc.png) 25 | 26 | 27 | ### 二、部署一个支持多实例的redis_exporter 28 | 29 | > 官方仓库:https://github.com/oliver006/redis_exporter 30 | 31 | 新建一个`docker-compose.yml`,内容如下: 32 | 33 | ``` 34 | version: "3.2" 35 | services: 36 | redis-exporter: 37 | image: oliver006/redis_exporter 38 | container_name: redis-exporter 39 | restart: unless-stopped 40 | command: 41 | - "-redis.password-file=/redis_passwd.json" 42 | volumes: 43 | - /usr/share/zoneinfo/PRC:/etc/localtime 44 | - /data/redis-exporter/redis_passwd.json:/redis_passwd.json 45 | expose: 46 | - 9121 47 | network_mode: "host" 48 | ``` 49 | 新建一个redis的实例地址与密码文件,`/data/redis-exporter/redis_passwd.json`: 50 | ``` 51 | { 52 | "redis://xxxxxxxxxxx.dcs.huaweicloud.com:6379":"", 53 | "redis://aaaaaaaa.cn-south-1.dcs.myhuaweicloud.com:6379":"q1azw2sx" 54 | } 55 | ``` 56 | - docker-compose中挂载配置文件文件的本地路径注意根据实际情况修改。 57 | - 配置文件的格式为json,每行一个实例的信息格式为:"redis://`实例地址端口`":"`redis密码`" 58 | - `实例地址端口`请查看`云REDIS列表`或`自建redis管理`的`实例`字段。 59 | - 如redis无密码,保留空双引号即可`""`。 60 | 61 | 启动:`docker-compose up -d` 62 | 63 | ### 三、如何接入到Prometheus 64 | 点击菜单`云资源管理`-`REDIS管理`-`prometheus配置`: 65 | - 在右侧选择需要加入监控的云账号REDIS组,并且输入redis_exporter的IP和端口,点击生成配置,即可复制生成的JOB内容到prometheus。 66 | - 由于Redis_Exporter无法监控到云数据库的CPU、部分资源使用率的情况,所以TenSunS开发了Exporter功能,配置到Prometheus即可直接从云厂商采集到这些指标!选择需要采集指标的REDIS账号区域,TenSunS地址和端口,即可生成Prometheus的JOB配置。 67 | 68 | ![图片](https://user-images.githubusercontent.com/3349611/204361542-c922963d-e79e-4ffd-8e3b-d752bd198d7b.png) 69 | 70 | ### 四、参考告警规则 71 | ![图片](https://user-images.githubusercontent.com/3349611/204361766-6584b1db-c91f-438b-a74f-b475fbd511f8.png) 72 | 73 | ### 五、参考Grafana看板 74 | [GRAFANA:Redis Exporter Dashboard 中文版](https://grafana.com/grafana/dashboards/17507) 75 | 76 | ![图片](https://user-images.githubusercontent.com/3349611/204360251-d0486e7c-9a46-43c8-8397-b0dca521e0e9.png) 77 | 78 | -------------------------------------------------------------------------------- /docs/如何优雅的使用一个mysqld_exporter监控所有的MySQL实例.md: -------------------------------------------------------------------------------- 1 | # 如何优雅的使用一个mysqld_exporter监控所有的MySQL实例 2 | ### 一、如何在TenSunS中接入云厂商的数据库 3 | 1. 新增云账号的情况:目前新增时,支持多选区域,以及选择增加的资源类型,勾选MySQL即可接入自动同步云数据库,记得设置好同步间隔。 4 | 5 | ![图片](https://user-images.githubusercontent.com/3349611/199262165-3582e051-a924-4043-bc05-96643b17caca.png) 6 | 7 | 2. 对已经添加过的账号,增加同步云数据库资源:点击编辑云资源,选择好需要编辑的厂商、账号及区域,再勾选资源类型MySQL,配置上同步间隔即可增加自动同步云数据库。 8 | 9 | ![图片](https://user-images.githubusercontent.com/3349611/199264858-f2a325bf-fad2-4850-bc39-76e9271d883e.png) 10 | 11 | 3. 接入完成后,可手动点击同步按钮,完成首次同步;或者等待设定好的同步周期后会自动同步。 12 | 13 | ![图片](https://user-images.githubusercontent.com/3349611/199267039-a010ce6f-3e04-4e54-8e44-6bde7ff5a000.png) 14 | 15 | 4. 同步完成后,可在`云资源管理`-`MySQL管理`-`云MySQL列表`,查看同步的云数据库信息。 16 | ![图片](https://user-images.githubusercontent.com/3349611/199276321-f8523931-b56d-43ca-84bd-def33f70b8eb.png) 17 | 18 | 19 | ### 二、部署一个支持多实例的Mysqld_exporter 20 | 21 | > 官方main版本的代码已经支持多目标的mysqld_exporter,只是还没有发Releases。所以基于最新的main版本自行编译了一个mysqld_exporter,并且做成了docker镜像。 22 | 23 | 详细说明查看:https://github.com/starsliao/multi_mysqld_exporter 24 | 25 | 新建一个`docker-compose.yml`,内容如下: 26 | 27 | ``` 28 | version: "3.2" 29 | services: 30 | mysqld_exporter: 31 | image: swr.cn-south-1.myhuaweicloud.com/starsl.cn/mysqld_exporter:latest 32 | container_name: mysqld_exporter 33 | hostname: mysqld_exporter 34 | restart: always 35 | ports: 36 | - "9104:9104" 37 | volumes: 38 | - /usr/share/zoneinfo/PRC:/etc/localtime 39 | environment: 40 | MYSQLD_EXPORTER_PASSWORD: xxxxxxxxxxxxx 41 | entrypoint: 42 | - /bin/mysqld_exporter 43 | - --collect.info_schema.innodb_metrics 44 | - --collect.info_schema.tables 45 | - --collect.info_schema.processlist 46 | - --collect.info_schema.tables.databases=* 47 | - --mysqld.username=xxxxxxxxxx 48 | ``` 49 | 50 | - docker-compose中有2个变量:监控专用的mysql账号和密码,注意修改掉后再启动。 51 | - docker-compose配置方式是**所有的mysql实例都配置了一样的mysql监控账号和密码。** 52 | - 如果你有不同mysql实例需要配置不同监控账号密码的需求,请参考官方readme使用配置文件的方式启动。 53 | 54 | 启动:`docker-compose up -d` 55 | 56 | ### 三、如何接入到Prometheus 57 | 点击菜单`云资源管理`-`MySQL管理`-`prometheus配置` 58 | 在右侧选择需要加入监控的云账号RDS组,并且输入mysqld_exporter的IP和端口,点击生成配置,即可复制生成的JOB内容到prometheus。 59 | ![图片](https://user-images.githubusercontent.com/3349611/199271393-6a7083dc-e861-4ce1-b4da-4ef99aa72868.png) 60 | 61 | 62 | ### 四、参考告警规则 63 | ![图片](https://user-images.githubusercontent.com/3349611/199274588-85f39fa1-8401-41f5-b0eb-4059a5e45007.png) 64 | 65 | ### 五、参考Grafana看板 66 | [GRAFANA:Mysqld Exporter Dashboard 22_11_01中文版](https://grafana.com/grafana/dashboards/17320) 67 | 68 | ![mysql1](https://user-images.githubusercontent.com/3349611/199293017-ecd09b7d-4731-44f0-9cc8-eefdd59550a1.png) 69 | 70 | ![mysql2](https://user-images.githubusercontent.com/3349611/199293035-dd6a911c-838d-4f01-93d3-14bda375ee64.png) 71 | -------------------------------------------------------------------------------- /docs/开发模式调试说明.md: -------------------------------------------------------------------------------- 1 | ### 开发模式 2 | - python版本: <=3.10 3 | ``` 4 | python3 ./manager.py 5 | ``` 6 | - nodejs版本: >=14 7 | ``` 8 | npm run dev 9 | ``` 10 | - 建议部署nginx转发前后端:即可不修改前后端代码,直接启动前后端程序,访问nginx的端口即可. 11 | ``` 12 | server { 13 | listen 1026; 14 | location /api/ { 15 | proxy_pass http://127.0.0.1:2026; 16 | } 17 | location / { 18 | proxy_pass http://127.0.0.1:9528; 19 | } 20 | } 21 | ``` 22 | - 如果你不想部署nginx,直接使用前端转发方式访问TenSunS: 23 | 1. 修改后端代码配置允许跨域 24 | ``` 25 | https://github.com/starsliao/TenSunS/blob/main/flask-consul/manager.py 26 | ``` 27 | 2. 修改前端代码配置转发的后端地址 28 | ``` 29 | https://github.com/starsliao/TenSunS/blob/main/vue-consul/src/utils/request-ops.js 30 | ``` 31 | 3. 访问前端端口. 32 | -------------------------------------------------------------------------------- /flask-consul/.dockerignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.pyc 3 | -------------------------------------------------------------------------------- /flask-consul/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.pyc 3 | -------------------------------------------------------------------------------- /flask-consul/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.cn-shenzhen.aliyuncs.com/starsl/python:3.10-alpine 2 | ADD . /flask 3 | WORKDIR /flask 4 | RUN sed -i 's/dl-cdn.alpinelinux.org/repo.huaweicloud.com/g' /etc/apk/repositories \ 5 | && apk add --no-cache gcc libc-dev libffi-dev \ 6 | && rm -rf /var/cache/apk/* \ 7 | && pip3 install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple --no-cache-dir\ 8 | && pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple --no-cache-dir 9 | EXPOSE 2026 10 | CMD ["python3","./manager.py"] 11 | -------------------------------------------------------------------------------- /flask-consul/Dockerfile-arm64: -------------------------------------------------------------------------------- 1 | FROM arm64v8/python:3.10.9 2 | ADD . /flask 3 | WORKDIR /flask 4 | RUN pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple --no-cache-dir 5 | EXPOSE 2026 6 | CMD ["python3","./manager.py"] -------------------------------------------------------------------------------- /flask-consul/Dockerfile.bak_aliyun: -------------------------------------------------------------------------------- 1 | FROM python:3.10-alpine 2 | ADD . /flask 3 | WORKDIR /flask 4 | 5 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \ 6 | && apk add --no-cache gcc libc-dev libffi-dev \ 7 | && rm -rf /var/cache/apk/* \ 8 | && pip3 install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple \ 9 | && pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple 10 | EXPOSE 2026 11 | CMD ["python3","./manager.py"] 12 | -------------------------------------------------------------------------------- /flask-consul/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | consul_token = os.environ.get('consul_token','0a79caed-8a45-49b9-97a6-86e50e12b234') 4 | consul_url = os.environ.get('consul_url','http://10.5.148.67:8500/v1') 5 | admin_passwd = os.environ.get('admin_passwd','123456') 6 | log_level = os.environ.get('log_level','INFO') 7 | 8 | vendors = {'alicloud': '阿里云','tencent_cloud': '腾讯云','huaweicloud': '华为云', 'awscloud': 'AWS云'} 9 | # https://aws.amazon.com/cn/about-aws/global-infrastructure/regional-product-services 10 | regions = { 11 | "awscloud":{ 12 | "none": "无", 13 | "us-east-1": "美国东部(弗吉尼亚州北部)", 14 | "us-west-1": "美国西部(北加利福尼亚)", 15 | "ap-east-1": "亚太地区(香港)", 16 | "ap-south-1": "亚太地区(孟买)", 17 | "ap-northeast-2": "亚太地区(首尔)", 18 | "ap-southeast-1": "亚太地区(新加坡)", 19 | "ap-southeast-3": "亚太地区(雅加达)", 20 | "ap-northeast-1": "亚太地区(东京)", 21 | "eu-central-1": "欧洲(法兰克福)", 22 | "eu-west-2": "欧洲(伦敦)", 23 | "eu-west-3": "欧洲(巴黎)", 24 | }, 25 | "huaweicloud": { 26 | "none": "无", 27 | "cn-north-219": "华北-北京金融二", 28 | "cn-east-3": "华东-上海一", 29 | "cn-east-2": "华东-上海二", 30 | "cn-south-1": "华南-广州", 31 | "cn-north-1": "华北-北京一", 32 | "cn-north-4": "华北-北京四", 33 | "cn-southwest-2": "西南-贵阳一", 34 | "ap-southeast-1": "中国-香港", 35 | "ap-southeast-3": "新加坡", 36 | }, 37 | "alicloud": { 38 | "none": "无", 39 | "cn-qingdao": "华北1(青岛)", 40 | "cn-beijing": "华北2(北京)", 41 | "cn-zhangjiakou": "华北3(张家口)", 42 | "cn-huhehaote": "华北5(呼和浩特)", 43 | "cn-wulanchabu": "华北6(乌兰察布)", 44 | "cn-hangzhou": "华东1(杭州)", 45 | "cn-shanghai": "华东2(上海)", 46 | "cn-shenzhen": "华南1(深圳)", 47 | "cn-heyuan": "华南2(河源)", 48 | "cn-guangzhou": "华南3(广州)", 49 | "cn-chengdu": "西南1(成都)", 50 | "cn-hongkong": "中国(香港)", 51 | "cn-nanjing": "华东5(南京-本地地域)", 52 | "us-east-1": "美国东部1(弗吉尼亚)", 53 | "us-west-1": "美国(硅谷)", 54 | "eu-west-1": "英国(伦敦)", 55 | "eu-central-1": "德国(法兰克福)", 56 | "ap-southeast-1": "新加坡", 57 | "ap-northeast-1": "日本(东京)", 58 | "ap-south-1": "印度(孟买)", 59 | "ap-southeast-7": "泰国(曼谷)", 60 | }, 61 | "tencent_cloud": { 62 | "none": "无", 63 | "ap-nanjing": "华东地区(南京)", 64 | "ap-shanghai": "华东地区(上海)", 65 | "ap-guangzhou": "华南地区(广州)", 66 | "ap-beijing": "华北地区(北京)", 67 | "ap-tianjin": "华北地区(天津)", 68 | "ap-chengdu": "西南地区(成都)", 69 | "ap-chongqing": "西南地区(重庆)", 70 | "ap-hongkong": "港澳台地区(中国香港)", 71 | "ap-tokyo": "亚太东北(东京)", 72 | "ap-seoul": "亚太东北(首尔)", 73 | "ap-singapore": "亚太东南(新加坡)", 74 | "ap-mumbai": "亚太南部(孟买)", 75 | "na-ashburn": "美国东部(弗吉尼亚)", 76 | "na-siliconvalley": "美国西部(硅谷)", 77 | "eu-frankfurt": "欧洲地区(法兰克福)", 78 | }, 79 | } 80 | -------------------------------------------------------------------------------- /flask-consul/manager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from flask import Flask 3 | from units import consul_kv,consul_manager 4 | import uuid,sys 5 | from units.config_log import * 6 | 7 | if consul_manager.get_consul_ver() == False: 8 | sys.exit("请求consul异常, 程序退出.") 9 | 10 | skey_path = 'ConsulManager/assets/secret/' 11 | if consul_kv.get_kv_dict(skey_path + 'skey') == {}: 12 | from datetime import datetime 13 | now = datetime.strftime(datetime.now(), '%Y-%m-%d_%H-%M-%S') 14 | skeyuid = ''.join(str(uuid.uuid4()).split('-')) 15 | consul_kv.put_kv(skey_path + 'skey',{'sk':skeyuid}) 16 | consul_kv.put_kv(f'{skey_path}bak-skey{now}',{'sk':skeyuid}) 17 | logger.warning(f"【初始化SKey完成】") 18 | 19 | from views import login, blackbox, consul, jobs, nodes, selfnode, selfrds, selfredis, avd, exp, jms, edit_cloud, ldap, rds, redis 20 | from views.prom import cloud_metrics 21 | from units.cloud import huaweicloud,alicloud,tencent_cloud,awscloud 22 | from units.avd import avd_list 23 | from units.jms import sync_jms 24 | 25 | app = Flask(__name__) 26 | #非nginx调试,解决跨域CORS问题 27 | #CORS(app, supports_credentials=True) 28 | 29 | app.register_blueprint(login.blueprint) 30 | app.register_blueprint(blackbox.blueprint) 31 | app.register_blueprint(consul.blueprint) 32 | app.register_blueprint(jobs.blueprint) 33 | app.register_blueprint(nodes.blueprint) 34 | app.register_blueprint(selfnode.blueprint) 35 | app.register_blueprint(selfrds.blueprint) 36 | app.register_blueprint(selfredis.blueprint) 37 | app.register_blueprint(avd.blueprint) 38 | app.register_blueprint(exp.blueprint) 39 | app.register_blueprint(jms.blueprint) 40 | app.register_blueprint(edit_cloud.blueprint) 41 | app.register_blueprint(cloud_metrics.blueprint) 42 | app.register_blueprint(ldap.blueprint) 43 | app.register_blueprint(rds.blueprint) 44 | app.register_blueprint(redis.blueprint) 45 | 46 | class Config(object): 47 | JOBS = [] 48 | SCHEDULER_API_ENABLED = True 49 | 50 | ecs_jobs = consul_kv.get_kv_dict('ConsulManager/jobs') 51 | avd_jobs = consul_kv.get_kv_dict('ConsulManager/avd/jobs') 52 | exp_jobs = consul_kv.get_kv_dict('ConsulManager/exp/jobs') 53 | jms_jobs = consul_kv.get_kv_dict('ConsulManager/jms/jobs') 54 | init_jobs = { **ecs_jobs, **avd_jobs, **exp_jobs, **jms_jobs } 55 | 56 | if init_jobs is not None: 57 | for k,v in init_jobs.items(): 58 | logger.info(f"初始化任务:{k}:{v['args']},{v['minutes']}m") 59 | Config.JOBS = init_jobs.values() 60 | 61 | app.config.from_object(Config()) 62 | 63 | if __name__ == "__main__": 64 | scheduler = jobs.init() 65 | scheduler.init_app(app) 66 | scheduler.start() 67 | app.run(host="0.0.0.0", port=2026) 68 | -------------------------------------------------------------------------------- /flask-consul/requirements.txt: -------------------------------------------------------------------------------- 1 | Werkzeug==2.0.3 2 | itsdangerous==2.0.1 3 | flask==2.0.2 4 | flask-restful==0.3.9 5 | flask-cors==3.0.10 6 | Flask-HTTPAuth==4.5.0 7 | requests==2.27.1 8 | Flask-APScheduler==1.12.3 9 | xlrd==1.2.0 10 | #pyDes==2.0.1 11 | pycryptodome==3.14.1 12 | beautifulsoup4==4.11.1 13 | ldap3==2.9.1 14 | loguru==0.6.0 15 | huaweicloudsdkcore==3.1.11 16 | huaweicloudsdkecs==3.1.11 17 | huaweicloudsdkeps==3.1.11 18 | huaweicloudsdkbss==3.1.11 19 | huaweicloudsdkrds==3.1.11 20 | huaweicloudsdkces==3.1.11 21 | huaweicloudsdkdcs==3.1.11 22 | alibabacloud_resourcemanager20200331==2.1.1 23 | alibabacloud_ecs20140526==2.1.3 24 | alibabacloud_rds20140815==2.1.2 25 | alibabacloud_r_kvstore20150101==2.20.7 26 | alibabacloud_bssopenapi20171214==2.0.6 27 | aliyun-python-sdk-cms==7.0.32 28 | alibabacloud_dds20151201==8.1.1 29 | alibabacloud_polardb20170801==5.4.0 30 | alibabacloud_clickhouse20191111==3.1.12 31 | alibabacloud_clickhouse20230522==1.0.2 32 | tencentcloud-sdk-python-common==3.0.779 33 | tencentcloud-sdk-python-cvm==3.0.779 34 | tencentcloud-sdk-python-cdb==3.0.779 35 | tencentcloud-sdk-python-dcdb==3.0.779 36 | tencentcloud-sdk-python-billing==3.0.779 37 | tencentcloud-sdk-python-monitor==3.0.779 38 | tencentcloud-sdk-python-redis==3.0.779 39 | boto3==1.23.10 40 | -------------------------------------------------------------------------------- /flask-consul/units/avd/avd_test.py: -------------------------------------------------------------------------------- 1 | import sys,requests,hashlib,json 2 | from datetime import datetime 3 | from bs4 import BeautifulSoup 4 | 5 | def get_avd(): 6 | avd_url = 'https://avd.aliyun.com' 7 | res = requests.get(avd_url) # + '/high-risk/list') 8 | res.encoding = 'utf-8' 9 | soup = BeautifulSoup(res.text, 'html.parser') 10 | bugs = soup.select('tr') 11 | for index, avd_info in enumerate(bugs[1:]): 12 | avd = avd_info.select('td') 13 | avd_dict = {} 14 | avd_dict['avd_id'] = avd[0].getText(strip=True) 15 | avd_dict['avd_id_url'] = avd_url + avd[0].a.attrs['href'] 16 | avd_dict['avd_name'] = avd[1].getText(strip=True) 17 | avd_dict['avd_type'] = avd[2].button.attrs.get('title',avd[2].getText(strip=True)) 18 | avd_dict['avd_time'] = avd[3].getText(strip=True) 19 | avd_dict['avd_stat'] = avd[4].select('button')[1].attrs['title'] 20 | print(avd_dict) 21 | 22 | get_avd() 23 | -------------------------------------------------------------------------------- /flask-consul/units/cloud/notify.py: -------------------------------------------------------------------------------- 1 | import requests,json 2 | from units.config_log import * 3 | def wecom(webhook,content): 4 | headers = {'Content-Type': 'application/json'} 5 | params = {'msgtype': 'markdown', 'markdown': {'content' : content}} 6 | data = bytes(json.dumps(params), 'utf-8') 7 | response = requests.post(webhook, headers=headers, data=data) 8 | logger.info(f'【wecom】{response.json()}') 9 | 10 | def dingding(webhook,content,isatall=True): 11 | headers = {'Content-Type': 'application/json'} 12 | params = {"msgtype":"markdown","markdown":{"title":"资源告警","text":content},"at":{"isAtAll":isatall}} 13 | data = bytes(json.dumps(params), 'utf-8') 14 | response = requests.post(webhook, headers=headers, data=data) 15 | logger.info(f'【dingding】{response.json()}') 16 | 17 | def feishu(webhook,title,md,isatall=True): 18 | headers = {'Content-Type': 'application/json'} 19 | atall = "" if isatall else '' 20 | params = {"msg_type": "interactive", 21 | "card": {"header": {"title": {"tag": "plain_text","content": title},"template": "red"}, 22 | "elements": [{"tag": "markdown","content": f"{md}\n{atall}",}]}} 23 | data = json.dumps(params) 24 | response = requests.post(webhook, headers=headers, data=data) 25 | logger.info(f'【feishu】{response.json()}') 26 | -------------------------------------------------------------------------------- /flask-consul/units/cloud/sync_clickhouse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import requests,json 3 | from units import consul_kv 4 | from config import consul_token,consul_url,vendors,regions 5 | from units.config_log import * 6 | headers = {'X-Consul-Token': consul_token} 7 | geturl = f'{consul_url}/agent/services' 8 | delurl = f'{consul_url}/agent/service/deregister' 9 | puturl = f'{consul_url}/agent/service/register' 10 | def w2consul(vendor,account,region,clickhouse_dict): 11 | service_name = f'{vendor}_{account}_clickhouse' 12 | params = {'filter': f'Service == "{service_name}" and "{region}" in Tags and Meta.account == "{account}"'} 13 | try: 14 | consul_clickhouse_iid_list = requests.get(geturl, headers=headers, params=params).json().keys() 15 | except: 16 | consul_clickhouse_iid_list = [] 17 | 18 | #在consul中删除云厂商不存在的clickhouse 19 | for del_clickhouse in [x for x in consul_clickhouse_iid_list if x not in clickhouse_dict.keys()]: 20 | dereg = requests.put(f'{delurl}/{del_clickhouse}', headers=headers) 21 | if dereg.status_code == 200: 22 | logger.info(f"code: 20000, data: {account}-删除成功!") 23 | else: 24 | logger.info(f"code: 50000, data: {dereg.status_code}:{dereg.text}") 25 | off,on = 0,0 26 | for k,v in clickhouse_dict.items(): 27 | iid = k 28 | #对consul中关机的clickhouse做标记。 29 | if v['status'] in ['SHUTDOWN','非运行中']: 30 | off = off + 1 31 | tags = ['OFF',v['itype'],v['ver'], region] 32 | stat = 'off' 33 | else: 34 | on = on + 1 35 | tags = ['ON',v['itype'],v['ver'],region] 36 | stat = 'on' 37 | custom_clickhouse = consul_kv.get_value(f'ConsulManager/assets/sync_clickhouse_custom/{iid}') 38 | port = custom_clickhouse.get('port') 39 | ip = custom_clickhouse.get('ip') 40 | if port == None: 41 | port = v['port'] 42 | if ip == None: 43 | ip = v['ip'] 44 | instance = f'{ip}:{port}' 45 | data = { 46 | 'id': iid, 47 | 'name': service_name, 48 | 'Address': ip, 49 | 'port': port, 50 | 'tags': tags, 51 | 'Meta': { 52 | 'iid': iid, 53 | 'name': v['name'], 54 | 'region': regions[vendor].get(region,'未找到'), 55 | 'group': v['group'], 56 | 'instance': instance, 57 | 'account': account, 58 | 'itype': v['itype'], 59 | 'vendor': vendors.get(vendor,'未找到'), 60 | 'os': "clickhouse", 61 | 'ver': v['ver'], 62 | 'domain':v['domain'], 63 | 'exp': v['exp'], 64 | 'stat': stat, 65 | 'team': v.get('team','无') 66 | }, 67 | "check": { 68 | "tcp": f"{ip}:{port}", 69 | "interval": "60s" 70 | } 71 | } 72 | reg = requests.put(puturl, headers=headers, data=json.dumps(data)) 73 | if reg.status_code == 200: 74 | pass 75 | else: 76 | logger.info(f"{account}:code: 5000, data: {reg.status_code}:{reg.text}") 77 | return off,on 78 | -------------------------------------------------------------------------------- /flask-consul/units/cloud/sync_mongodb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import requests,json 3 | from units import consul_kv 4 | from config import consul_token,consul_url,vendors,regions 5 | from units.config_log import * 6 | headers = {'X-Consul-Token': consul_token} 7 | geturl = f'{consul_url}/agent/services' 8 | delurl = f'{consul_url}/agent/service/deregister' 9 | puturl = f'{consul_url}/agent/service/register' 10 | def w2consul(vendor,account,region,mongodb_dict): 11 | service_name = f'{vendor}_{account}_mongodb' 12 | params = {'filter': f'Service == "{service_name}" and "{region}" in Tags and Meta.account == "{account}"'} 13 | try: 14 | consul_mongodb_iid_list = requests.get(geturl, headers=headers, params=params).json().keys() 15 | except: 16 | consul_mongodb_iid_list = [] 17 | 18 | #在consul中删除云厂商不存在的mongodb 19 | for del_mongodb in [x for x in consul_mongodb_iid_list if x not in mongodb_dict.keys()]: 20 | dereg = requests.put(f'{delurl}/{del_mongodb}', headers=headers) 21 | if dereg.status_code == 200: 22 | logger.info(f"code: 20000, data: {account}-删除成功!") 23 | else: 24 | logger.info(f"code: 50000, data: {dereg.status_code}:{dereg.text}") 25 | off,on = 0,0 26 | for k,v in mongodb_dict.items(): 27 | iid = k 28 | #对consul中关机的mongodb做标记。 29 | if v['status'] in ['SHUTDOWN','非运行中']: 30 | off = off + 1 31 | tags = ['OFF',v['itype'],v['ver'], region] 32 | stat = 'off' 33 | else: 34 | on = on + 1 35 | tags = ['ON',v['itype'],v['ver'],region] 36 | stat = 'on' 37 | custom_mongodb = consul_kv.get_value(f'ConsulManager/assets/sync_mongodb_custom/{iid}') 38 | port = custom_mongodb.get('port') 39 | ip = custom_mongodb.get('ip') 40 | if port == None: 41 | port = v['port'] 42 | if ip == None: 43 | ip = v['ip'] 44 | instance = f'{ip}:{port}' 45 | data = { 46 | 'id': iid, 47 | 'name': service_name, 48 | 'Address': ip, 49 | 'port': port, 50 | 'tags': tags, 51 | 'Meta': { 52 | 'iid': iid, 53 | 'name': v['name'], 54 | 'region': regions[vendor].get(region,'未找到'), 55 | 'group': v['group'], 56 | 'instance': instance, 57 | 'account': account, 58 | 'itype': v['itype'], 59 | 'vendor': vendors.get(vendor,'未找到'), 60 | 'os': "mongodb", 61 | 'ver': v['ver'], 62 | 'domain':v['domain'], 63 | 'exp': v['exp'], 64 | 'stat': stat, 65 | 'team': v.get('team','无') 66 | }, 67 | "check": { 68 | "tcp": f"{ip}:{port}", 69 | "interval": "60s" 70 | } 71 | } 72 | reg = requests.put(puturl, headers=headers, data=json.dumps(data)) 73 | if reg.status_code == 200: 74 | pass 75 | else: 76 | logger.info(f"{account}:code: 5000, data: {reg.status_code}:{reg.text}") 77 | return off,on 78 | -------------------------------------------------------------------------------- /flask-consul/units/cloud/sync_polardb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import requests,json 3 | from units import consul_kv 4 | from config import consul_token,consul_url,vendors,regions 5 | from units.config_log import * 6 | headers = {'X-Consul-Token': consul_token} 7 | geturl = f'{consul_url}/agent/services' 8 | delurl = f'{consul_url}/agent/service/deregister' 9 | puturl = f'{consul_url}/agent/service/register' 10 | def w2consul(vendor,account,region,rds_dict): 11 | service_name = f'{vendor}_{account}_polardb' 12 | params = {'filter': f'Service == "{service_name}" and "{region}" in Tags and Meta.account == "{account}"'} 13 | try: 14 | consul_rds_iid_list = requests.get(geturl, headers=headers, params=params).json().keys() 15 | except: 16 | consul_rds_iid_list = [] 17 | 18 | #在consul中删除云厂商不存在的rds 19 | for del_rds in [x for x in consul_rds_iid_list if x not in rds_dict.keys()]: 20 | dereg = requests.put(f'{delurl}/{del_rds}', headers=headers) 21 | if dereg.status_code == 200: 22 | logger.info(f"code: 20000, data: {account}-删除成功!") 23 | else: 24 | logger.info(f"code: 50000, data: {dereg.status_code}:{dereg.text}") 25 | off,on = 0,0 26 | for k,v in rds_dict.items(): 27 | iid = k 28 | #对consul中关机的rds做标记。 29 | if v['status'] in ['SHUTDOWN','非运行中']: 30 | off = off + 1 31 | tags = ['OFF',v['itype'],v['ver'], region] 32 | stat = 'off' 33 | else: 34 | on = on + 1 35 | tags = ['ON',v['itype'],v['ver'],region] 36 | stat = 'on' 37 | custom_rds = consul_kv.get_value(f'ConsulManager/assets/sync_rds_custom/{iid}') 38 | port = custom_rds.get('port') 39 | ip = custom_rds.get('ip') 40 | if port == None: 41 | port = v['port'] 42 | if ip == None: 43 | ip = v['ip'] 44 | instance = f'{ip}:{port}' 45 | data = { 46 | 'id': iid, 47 | 'name': service_name, 48 | 'Address': ip, 49 | 'port': port, 50 | 'tags': tags, 51 | 'Meta': { 52 | 'iid': iid, 53 | 'name': v['name'], 54 | 'region': regions[vendor].get(region,'未找到'), 55 | 'group': v['group'], 56 | 'instance': instance, 57 | 'account': account, 58 | 'itype': v['itype'], 59 | 'vendor': vendors.get(vendor,'未找到'), 60 | 'os': "mysql", 61 | 'ver': v['ver'], 62 | 'domain':v['domain'], 63 | 'exp': v['exp'], 64 | 'stat': stat, 65 | 'team': v.get('team','无') 66 | }, 67 | "check": { 68 | "tcp": f"{ip}:{port}", 69 | "interval": "60s" 70 | } 71 | } 72 | reg = requests.put(puturl, headers=headers, data=json.dumps(data)) 73 | if reg.status_code == 200: 74 | pass 75 | else: 76 | logger.info(f"{account}:code: 5000, data: {reg.status_code}:{reg.text}") 77 | return off,on 78 | -------------------------------------------------------------------------------- /flask-consul/units/cloud/sync_rds.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import requests,json 3 | from units import consul_kv 4 | from config import consul_token,consul_url,vendors,regions 5 | from units.config_log import * 6 | headers = {'X-Consul-Token': consul_token} 7 | geturl = f'{consul_url}/agent/services' 8 | delurl = f'{consul_url}/agent/service/deregister' 9 | puturl = f'{consul_url}/agent/service/register' 10 | def w2consul(vendor,account,region,rds_dict): 11 | service_name = f'{vendor}_{account}_rds' 12 | params = {'filter': f'Service == "{service_name}" and "{region}" in Tags and Meta.account == "{account}"'} 13 | try: 14 | consul_rds_iid_list = requests.get(geturl, headers=headers, params=params).json().keys() 15 | except: 16 | consul_rds_iid_list = [] 17 | 18 | #在consul中删除云厂商不存在的rds 19 | for del_rds in [x for x in consul_rds_iid_list if x not in rds_dict.keys()]: 20 | dereg = requests.put(f'{delurl}/{del_rds}', headers=headers) 21 | if dereg.status_code == 200: 22 | logger.info(f"code: 20000, data: {account}-删除成功!") 23 | else: 24 | logger.info(f"code: 50000, data: {dereg.status_code}:{dereg.text}") 25 | off,on = 0,0 26 | for k,v in rds_dict.items(): 27 | iid = k 28 | #对consul中关机的rds做标记。 29 | if v['status'] in ['SHUTDOWN','非运行中']: 30 | off = off + 1 31 | tags = ['OFF',v['itype'],v['ver'], region] 32 | stat = 'off' 33 | else: 34 | on = on + 1 35 | tags = ['ON',v['itype'],v['ver'],region] 36 | stat = 'on' 37 | custom_rds = consul_kv.get_value(f'ConsulManager/assets/sync_rds_custom/{iid}') 38 | port = custom_rds.get('port') 39 | ip = custom_rds.get('ip') 40 | if port == None: 41 | port = v['port'] 42 | if ip == None: 43 | ip = v['ip'] 44 | instance = f'{ip}:{port}' 45 | data = { 46 | 'id': iid, 47 | 'name': service_name, 48 | 'Address': ip, 49 | 'port': port, 50 | 'tags': tags, 51 | 'Meta': { 52 | 'iid': iid, 53 | 'name': v['name'], 54 | 'region': regions[vendor].get(region,'未找到'), 55 | 'group': v['group'], 56 | 'instance': instance, 57 | 'account': account, 58 | 'itype': v['itype'], 59 | 'vendor': vendors.get(vendor,'未找到'), 60 | 'os': "mysql", 61 | 'disk': v['disk'], 62 | 'cpu': v['cpu'], 63 | 'mem': v['mem'], 64 | 'ver': v['ver'], 65 | 'domain':v['domain'], 66 | 'exp': v['exp'], 67 | 'stat': stat, 68 | 'team': v.get('team','无') 69 | }, 70 | "check": { 71 | "tcp": f"{ip}:{port}", 72 | "interval": "60s" 73 | } 74 | } 75 | reg = requests.put(puturl, headers=headers, data=json.dumps(data)) 76 | if reg.status_code == 200: 77 | pass 78 | else: 79 | logger.info(f"{account}:code: 5000, data: {reg.status_code}:{reg.text}") 80 | return off,on 81 | -------------------------------------------------------------------------------- /flask-consul/units/cloud/sync_redis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import requests,json 3 | from units import consul_kv 4 | from config import consul_token,consul_url,vendors,regions 5 | from units.config_log import * 6 | headers = {'X-Consul-Token': consul_token} 7 | geturl = f'{consul_url}/agent/services' 8 | delurl = f'{consul_url}/agent/service/deregister' 9 | puturl = f'{consul_url}/agent/service/register' 10 | def w2consul(vendor,account,region,redis_dict): 11 | service_name = f'{vendor}_{account}_redis' 12 | params = {'filter': f'Service == "{service_name}" and "{region}" in Tags and Meta.account == "{account}"'} 13 | try: 14 | consul_redis_iid_list = requests.get(geturl, headers=headers, params=params).json().keys() 15 | except: 16 | consul_redis_iid_list = [] 17 | 18 | #在consul中删除云厂商不存在的redis 19 | for del_redis in [x for x in consul_redis_iid_list if x not in redis_dict.keys()]: 20 | dereg = requests.put(f'{delurl}/{del_redis}', headers=headers) 21 | if dereg.status_code == 200: 22 | logger.info(f"code: 20000, data: {account}-删除成功!") 23 | else: 24 | logger.info(f"code: 50000, data: {dereg.status_code}:{dereg.text}") 25 | off,on = 0,0 26 | for k,v in redis_dict.items(): 27 | iid = k 28 | #对consul中关机的redis做标记。 29 | if v['status'] in ['SHUTDOWN','Unavailable','Inactive','Released','非运行中']: 30 | off = off + 1 31 | tags = ['OFF', v['itype'], v['ver'], region] 32 | stat = 'off' 33 | else: 34 | on = on + 1 35 | tags = ['ON', v['itype'], v['ver'], region] 36 | stat = 'on' 37 | custom_redis = consul_kv.get_value(f'ConsulManager/assets/sync_redis_custom/{iid}') 38 | port = custom_redis.get('port') 39 | ip = custom_redis.get('ip') 40 | if port == None: 41 | port = v['port'] 42 | if ip == None: 43 | ip = v['domain'] 44 | instance = f'{ip}:{port}' 45 | data = { 46 | 'id': iid, 47 | 'name': service_name, 48 | 'Address': ip, 49 | 'port': port, 50 | 'tags': tags, 51 | 'Meta': { 52 | 'iid': iid, 53 | 'name': v['name'], 54 | 'region': regions[vendor].get(region,'未找到'), 55 | 'group': v['group'], 56 | 'instance': instance, 57 | 'account': account, 58 | 'itype': v['itype'], 59 | 'vendor': vendors.get(vendor,'未找到'), 60 | 'os': "redis", 61 | 'mem': v['mem'], 62 | 'ver': v['ver'], 63 | 'ip':v['ip'], 64 | 'exp':v['exp'], 65 | 'stat': stat 66 | }, 67 | "check": { 68 | "tcp": f"{ip}:{port}", 69 | "interval": "60s" 70 | } 71 | } 72 | reg = requests.put(puturl, headers=headers, data=json.dumps(data)) 73 | if reg.status_code == 200: 74 | pass 75 | else: 76 | logger.info(f"{account}:code: 5000, data: {reg.status_code}:{reg.text}") 77 | return off,on 78 | -------------------------------------------------------------------------------- /flask-consul/units/config_log.py: -------------------------------------------------------------------------------- 1 | from config import log_level 2 | import sys 3 | from loguru import logger 4 | -------------------------------------------------------------------------------- /flask-consul/units/consul_svc.py: -------------------------------------------------------------------------------- 1 | import requests,json 2 | import sys 3 | sys.path.append("..") 4 | from config import consul_token,consul_url 5 | from units.config_log import * 6 | headers = {'X-Consul-Token': consul_token} 7 | 8 | def get_sid(iid): 9 | url = f'{consul_url}/agent/service/{iid}' 10 | response = requests.get(url, headers=headers) 11 | if response.status_code == 200: 12 | info = response.json() 13 | return {'code': 20000,'instance':info} 14 | else: 15 | return {'code': 50000, 'data': f'{response.status_code}:{response.text}'} 16 | 17 | def del_sid(iid): 18 | reg = requests.put(f'{consul_url}/agent/service/deregister/{iid}', headers=headers) 19 | if reg.status_code == 200: 20 | return {"code": 20000, "data": f"【{iid}】删除成功!"} 21 | else: 22 | return {"code": 50000, "data": f"{reg.status_code}【{iid}】{reg.text}"} 23 | 24 | def add_sid(instance_dict): 25 | reg = requests.put(f'{consul_url}/agent/service/register', headers=headers, data=json.dumps(instance_dict)) 26 | if reg.status_code == 200: 27 | return {"code": 20000, "data": f"增加成功!"} 28 | else: 29 | logger.info(f"{reg.status_code}:{reg.text}") 30 | return {"code": 50000, "data": f"{reg.status_code}:{reg.text}"} 31 | 32 | -------------------------------------------------------------------------------- /flask-consul/units/json_response.py: -------------------------------------------------------------------------------- 1 | """ 2 | 自定义返回数据格式 3 | """ 4 | 5 | def JsonResponse(data:str=None,msg:str=None,success:bool=None,code:int=200): 6 | """ 7 | An HttpResponse that allows its data to be rendered into 8 | arbitrary media types. 9 | """ 10 | data = {"data": data, "msg": msg, "success": success, "code": code} 11 | return data 12 | -------------------------------------------------------------------------------- /flask-consul/units/ldap/ldap_consul.py: -------------------------------------------------------------------------------- 1 | """ 2 | 截取前端ldap信息存入consul 3 | """ 4 | from units import consul_kv,myaes 5 | 6 | 7 | class Ldap_Consul(): 8 | 9 | """ 10 | 存储ldap信息 11 | """ 12 | @staticmethod 13 | def set_consul_args(**kwargs): 14 | kwargs['port'] = int(kwargs.get("port")) 15 | kwargs['password'] = myaes.encrypt(kwargs.get("password")) 16 | result = consul_kv.put_kv(f'ConsulManager/ldap/report', {**kwargs}) 17 | if result: 18 | return True 19 | return False 20 | 21 | 22 | """ 23 | 获取ldap信息进行链接服务端 24 | """ 25 | @staticmethod 26 | def get_consul_args(**kwargs): 27 | result = consul_kv.get_kv_dict("ConsulManager/ldap/report") 28 | try: 29 | result.get("ConsulManager/ldap/report").get("ldap_url") 30 | except Exception: 31 | return False 32 | return result.get("ConsulManager/ldap/report").get("ldap_url"),\ 33 | result.get("ConsulManager/ldap/report").get("port"),\ 34 | result.get("ConsulManager/ldap/report").get("rule"),\ 35 | myaes.decrypt(result.get("ConsulManager/ldap/report").get("password")),\ 36 | result.get("ConsulManager/ldap/report").get("ldapusr"),\ 37 | result.get("ConsulManager/ldap/report").get("allow") 38 | -------------------------------------------------------------------------------- /flask-consul/units/myaes.py: -------------------------------------------------------------------------------- 1 | # encoding:utf-8 2 | from base64 import b64encode,b64decode 3 | from Crypto.Util.Padding import pad,unpad 4 | from Crypto.Cipher import AES 5 | import consul_kv 6 | secret_key = consul_kv.get_value('ConsulManager/assets/secret/skey')['sk'].encode('utf8') 7 | 8 | def encrypt(data): 9 | data = data.encode('utf8') 10 | cipher = AES.new(secret_key, AES.MODE_CBC) 11 | encrypted_data = cipher.encrypt(pad(data, 16)) 12 | data = cipher.iv + encrypted_data 13 | return b64encode(data).decode('utf8') 14 | 15 | def decrypt(data): 16 | data = b64decode(data) 17 | iv = data[:16] 18 | cipher = AES.new(secret_key, AES.MODE_CBC, iv) 19 | data = unpad(cipher.decrypt(data[16:]), 16) 20 | return data.decode('utf8') 21 | -------------------------------------------------------------------------------- /flask-consul/units/mydes.py: -------------------------------------------------------------------------------- 1 | from pyDes import des, ECB, PAD_PKCS5 2 | import binascii, consul_kv 3 | secret_key = consul_kv.get_value('ConsulManager/assets/secret/skey')['sk'] 4 | key = secret_key[:8] 5 | iv = key 6 | k = des(key, ECB, iv, pad=None, padmode=PAD_PKCS5) 7 | 8 | def encrypt(s): 9 | en = k.encrypt(s, padmode=PAD_PKCS5) 10 | return binascii.b2a_hex(en).decode() 11 | 12 | def decrypt(s): 13 | de = k.decrypt(binascii.a2b_hex(s), padmode=PAD_PKCS5) 14 | return de.decode() 15 | -------------------------------------------------------------------------------- /flask-consul/units/prom/mysql_ali.py: -------------------------------------------------------------------------------- 1 | from aliyunsdkcore.client import AcsClient 2 | from aliyunsdkcms.request.v20190101.DescribeMetricLastRequest import DescribeMetricLastRequest 3 | from datetime import datetime 4 | from units import consul_kv 5 | import json 6 | from units.config_log import * 7 | 8 | def exporter(vendor,account,region): 9 | ak,sk = consul_kv.get_aksk(vendor,account) 10 | client_rdsmonit = AcsClient(ak, sk, region) 11 | request_rdsmonit = DescribeMetricLastRequest() 12 | request_rdsmonit.set_accept_format('json') 13 | request_rdsmonit.set_Namespace("acs_rds_dashboard") 14 | metric_name_dict = {"CpuUsage":["# HELP mysql_cpu_util CPU使用率","# TYPE mysql_cpu_util gauge"], 15 | "MemoryUsage":["# HELP mysql_mem_util 内存使用率","# TYPE mysql_mem_util gauge"], 16 | "DiskUsage":["# HELP mysql_disk_util 磁盘使用率","# TYPE mysql_disk_util gauge"], 17 | "IOPSUsage":["# HELP mysql_io_util 磁盘I/O使用率","# TYPE mysql_io_util gauge"], 18 | "ConnectionUsage":["# HELP mysql_conn_util 连接数使用率","# TYPE mysql_conn_util gauge"] 19 | } 20 | for i in metric_name_dict.keys(): 21 | request_rdsmonit.set_MetricName(i) 22 | response_rdsmonit = json.loads(client_rdsmonit.do_action_with_exception(request_rdsmonit)) 23 | try: 24 | instance = json.loads(response_rdsmonit["Datapoints"]) 25 | except: 26 | logger.error(f"{response_rdsmonit}") 27 | instance = [] 28 | prom_metric_name = metric_name_dict[i][0].split()[2] 29 | for j in instance: 30 | iid,max,ts = j["instanceId"],j["Maximum"],j["timestamp"] 31 | metric_name_dict[i].append(f'{prom_metric_name}{{iid="{iid}"}} {float(max)} {ts}') 32 | prom_metric_list = [] 33 | for x in metric_name_dict.values(): 34 | prom_metric_list = prom_metric_list + x 35 | return prom_metric_list 36 | -------------------------------------------------------------------------------- /flask-consul/units/prom/mysql_huawei.py: -------------------------------------------------------------------------------- 1 | from huaweicloudsdkcore.auth.credentials import BasicCredentials 2 | from huaweicloudsdkces.v1.region.ces_region import CesRegion 3 | from huaweicloudsdkces.v1 import * 4 | from datetime import datetime 5 | from units import consul_kv 6 | from units.config_log import * 7 | 8 | def exporter(vendor,account,region): 9 | ak,sk = consul_kv.get_aksk(vendor,account) 10 | credentials = BasicCredentials(ak, sk) 11 | client = CesClient.new_builder() \ 12 | .with_credentials(credentials) \ 13 | .with_region(CesRegion.value_of(region)) \ 14 | .build() 15 | metric_name_dict = {"rds001_cpu_util":["# HELP mysql_cpu_util CPU使用率","# TYPE mysql_cpu_util gauge"], 16 | "rds002_mem_util":["# HELP mysql_mem_util 内存使用率","# TYPE mysql_mem_util gauge"], 17 | "rds039_disk_util":["# HELP mysql_disk_util 磁盘使用率","# TYPE mysql_disk_util gauge"], 18 | "rds074_slow_queries":["# HELP mysql_slow_queries 每分钟慢SQL","# TYPE mysql_slow_queries gauge"], 19 | "rds081_vm_ioutils":["# HELP mysql_io_util 磁盘I/O使用率","# TYPE mysql_io_util gauge"], 20 | "rds072_conn_usage":["# HELP mysql_conn_util 连接数使用率","# TYPE mysql_conn_util gauge"] 21 | } 22 | metric_body_list = [] 23 | now = int(datetime.now().timestamp()*1000) 24 | rds_list = consul_kv.get_services_list_by_region(f'{vendor}_{account}_rds',region) 25 | for i in metric_name_dict.keys(): 26 | for rdsid in rds_list: 27 | metric_body_list.append(MetricInfo(namespace="SYS.RDS",metric_name=i,dimensions=[MetricsDimension(name="rds_cluster_id",value=rdsid)])) 28 | 29 | request = BatchListMetricDataRequest() 30 | metrics_len = len(metric_body_list) 31 | logger.info(f"metric_body_list长度(实例数*指标数): {metrics_len}") 32 | 33 | for i in range(0, metrics_len, 400): 34 | sub_metric_list = metric_body_list[i:i+400] 35 | request.body = BatchListMetricDataRequestBody(to=now,_from=now-180000,filter="max",period="1",metrics=sub_metric_list) 36 | response = client.batch_list_metric_data(request).to_dict() 37 | for i in response['metrics']: 38 | rdsid= i['dimensions'][0]['value'] 39 | try: 40 | value = i['datapoints'][-1]['max'] 41 | ts = i['datapoints'][-1]['timestamp'] 42 | except: 43 | value = -1 44 | ts = now 45 | metric = i['metric_name'] 46 | prom_metric_name = metric_name_dict[metric][0].split()[2] 47 | metric_name_dict[metric].append(f'{prom_metric_name}{{iid="{rdsid}"}} {float(value)} {ts}') 48 | prom_metric_list = [] 49 | for x in metric_name_dict.values(): 50 | prom_metric_list = prom_metric_list + x 51 | return prom_metric_list 52 | -------------------------------------------------------------------------------- /flask-consul/units/prom/mysql_tencent.py: -------------------------------------------------------------------------------- 1 | import json 2 | from tencentcloud.common import credential 3 | from tencentcloud.common.profile.client_profile import ClientProfile 4 | from tencentcloud.common.profile.http_profile import HttpProfile 5 | from tencentcloud.monitor.v20180724 import monitor_client, models 6 | from datetime import datetime,timedelta 7 | from units import consul_kv 8 | 9 | def exporter(vendor,account,region): 10 | ak,sk = consul_kv.get_aksk(vendor,account) 11 | cred = credential.Credential(ak,sk) 12 | client = monitor_client.MonitorClient(cred, region) 13 | req = models.GetMonitorDataRequest() 14 | metric_name_dict = {"CpuUseRate":["# HELP mysql_cpu_util CPU使用率","# TYPE mysql_cpu_util gauge"], 15 | "MemoryUseRate":["# HELP mysql_mem_util 内存使用率","# TYPE mysql_mem_util gauge"], 16 | "VolumeRate":["# HELP mysql_disk_util 磁盘使用率","# TYPE mysql_disk_util gauge"], 17 | "IopsUseRate":["# HELP mysql_io_util 磁盘I/O使用率","# TYPE mysql_io_util gauge"], 18 | "ConnectionUseRate":["# HELP mysql_conn_util 连接数使用率","# TYPE mysql_conn_util gauge"] 19 | } 20 | rds_list = consul_kv.get_services_list_by_region(f'{vendor}_{account}_rds',region) 21 | rds_list = list(rds_list) 22 | rds_list_10 = [rds_list[i:i + 10] for i in range(0, len(rds_list), 10)] 23 | for i in metric_name_dict.keys(): 24 | for rdss in rds_list_10: 25 | starttime = (datetime.now() + timedelta(minutes=-1)).strftime('%Y-%m-%dT%H:%M:%S+08:00') 26 | ins_list = [{"Dimensions":[{"Name":"InstanceId","Value":x}]} for x in rdss] 27 | params = {"Namespace":"QCE/CDB","MetricName":i,"Period":60,"StartTime":starttime,"Instances":ins_list} 28 | req.from_json_string(json.dumps(params)) 29 | resp = client.GetMonitorData(req) 30 | metric_list = resp.DataPoints 31 | for metrics in metric_list: 32 | iid = metrics.Dimensions[0].Value 33 | value = metrics.Values[-1] 34 | ts = metrics.Timestamps[-1]*1000 35 | prom_metric_name = metric_name_dict[i][0].split()[2] 36 | metric_name_dict[i].append(f'{prom_metric_name}{{iid="{iid}"}} {float(value)} {ts}') 37 | prom_metric_list = [] 38 | for x in metric_name_dict.values(): 39 | prom_metric_list = prom_metric_list + x 40 | return prom_metric_list 41 | -------------------------------------------------------------------------------- /flask-consul/units/prom/redis_ali.py: -------------------------------------------------------------------------------- 1 | from aliyunsdkcore.client import AcsClient 2 | from aliyunsdkcms.request.v20190101.DescribeMetricLastRequest import DescribeMetricLastRequest 3 | from datetime import datetime 4 | from units import consul_kv 5 | import json 6 | from units.config_log import * 7 | 8 | def exporter(vendor,account,region): 9 | ak,sk = consul_kv.get_aksk(vendor,account) 10 | client_redismonit = AcsClient(ak, sk, region) 11 | request_redismonit = DescribeMetricLastRequest() 12 | request_redismonit.set_accept_format('json') 13 | request_redismonit.set_Namespace("acs_kvstore") 14 | metric_name_dict = {"CpuUsage":["# HELP redis_cpu_util CPU使用率","# TYPE redis_cpu_util gauge"], 15 | "MemoryUsage":["# HELP redis_mem_util 内存使用率","# TYPE redis_mem_util gauge"], 16 | "ConnectionUsage":["# HELP redis_conn_util 连接数使用率","# TYPE redis_conn_util gauge"], 17 | "IntranetInRatio":["# HELP redis_netin_util 写入带宽使用率","# TYPE redis_netin_util gauge"], 18 | "IntranetOutRatio":["# HELP redis_netout_util 读取带宽使用率","# TYPE redis_netout_util gauge"] 19 | } 20 | for i in metric_name_dict.keys(): 21 | request_redismonit.set_MetricName(i) 22 | response_redismonit = json.loads(client_redismonit.do_action_with_exception(request_redismonit)) 23 | try: 24 | instance = json.loads(response_redismonit["Datapoints"]) 25 | except: 26 | logger.error(f"{response_redismonit}") 27 | instance = [] 28 | prom_metric_name = metric_name_dict[i][0].split()[2] 29 | for j in instance: 30 | iid,max,ts = j["instanceId"],j["Maximum"],j["timestamp"] 31 | metric_name_dict[i].append(f'{prom_metric_name}{{iid="{iid}"}} {float(max)} {ts}') 32 | prom_metric_list = [] 33 | for x in metric_name_dict.values(): 34 | prom_metric_list = prom_metric_list + x 35 | return prom_metric_list 36 | -------------------------------------------------------------------------------- /flask-consul/units/prom/redis_huawei.py: -------------------------------------------------------------------------------- 1 | from huaweicloudsdkcore.auth.credentials import BasicCredentials 2 | from huaweicloudsdkces.v1.region.ces_region import CesRegion 3 | from huaweicloudsdkces.v1 import * 4 | from datetime import datetime 5 | from units import consul_kv 6 | 7 | def exporter(vendor,account,region): 8 | ak,sk = consul_kv.get_aksk(vendor,account) 9 | credentials = BasicCredentials(ak, sk) 10 | client = CesClient.new_builder() \ 11 | .with_credentials(credentials) \ 12 | .with_region(CesRegion.value_of(region)) \ 13 | .build() 14 | metric_name_dict = {"cpu_usage":["# HELP redis_cpu_util CPU使用率","# TYPE redis_cpu_util gauge"], 15 | "memory_usage":["# HELP redis_mem_util 内存使用率","# TYPE redis_mem_util gauge"], 16 | "keyspace_hits_perc":["# HELP redis_hits_util 缓存命中率","# TYPE redis_hits_util gauge"], 17 | "total_connections_received":["# HELP redis_newconn_count 每分钟新建的连接数","# TYPE redis_newconn_count gauge"], 18 | "rx_controlled":["# HELP redis_rx_controlled 每分钟被流控的次数","# TYPE redis_rx_controlled gauge"], 19 | "is_slow_log_exist":["# HELP redis_slow_log 慢日志情况","# TYPE redis_slow_log gauge"], 20 | } 21 | metric_body_list = [] 22 | now = int(datetime.now().timestamp()*1000) 23 | redis_list = consul_kv.get_services_list_by_region(f'{vendor}_{account}_redis',region) 24 | for i in metric_name_dict.keys(): 25 | for id in redis_list: 26 | metric_body_list.append(MetricInfo(namespace="SYS.DCS",metric_name=i,dimensions=[MetricsDimension(name="dcs_instance_id",value=id)])) 27 | 28 | request = BatchListMetricDataRequest() 29 | request.body = BatchListMetricDataRequestBody(to=now,_from=now-180000,filter="max",period="1",metrics=metric_body_list) 30 | response = client.batch_list_metric_data(request).to_dict() 31 | for i in response['metrics']: 32 | id= i['dimensions'][0]['value'] 33 | try: 34 | value = i['datapoints'][-1]['max'] 35 | ts = i['datapoints'][-1]['timestamp'] 36 | except: 37 | value = -1 38 | ts = now 39 | metric = i['metric_name'] 40 | prom_metric_name = metric_name_dict[metric][0].split()[2] 41 | metric_name_dict[metric].append(f'{prom_metric_name}{{iid="{id}"}} {float(value)} {ts}') 42 | prom_metric_list = [] 43 | for x in metric_name_dict.values(): 44 | prom_metric_list = prom_metric_list + x 45 | return prom_metric_list 46 | -------------------------------------------------------------------------------- /flask-consul/units/prom/redis_tencent.py: -------------------------------------------------------------------------------- 1 | import json,traceback 2 | from tencentcloud.common import credential 3 | from tencentcloud.common.profile.client_profile import ClientProfile 4 | from tencentcloud.common.profile.http_profile import HttpProfile 5 | from tencentcloud.monitor.v20180724 import monitor_client, models 6 | from datetime import datetime,timedelta 7 | from units import consul_kv 8 | from units.config_log import * 9 | 10 | def exporter(vendor,account,region): 11 | ak,sk = consul_kv.get_aksk(vendor,account) 12 | cred = credential.Credential(ak,sk) 13 | client = monitor_client.MonitorClient(cred, region) 14 | req = models.GetMonitorDataRequest() 15 | metric_name_dict = {"CpuMaxUtil":["# HELP redis_cpu_util 实例中节点最大CPU使用率","# TYPE redis_cpu_util gauge"], 16 | "MemMaxUtil":["# HELP redis_mem_util 实例中节点最大内存使用率","# TYPE redis_mem_util gauge"], 17 | "ConnectionsUtil":["# HELP redis_conn_util 连接使用率","# TYPE redis_conn_util gauge"], 18 | "CmdBigValue":["# HELP redis_big_count 每秒请求命令大小超过32KB的执行次数","# TYPE redis_big_count gauge"], 19 | "CmdSlow":["# HELP redis_slow_count 执行时延大于slowlog-log-slower-than配置的命令次数","# TYPE redis_slow_count gauge"], 20 | "InFlowLimit":["# HELP redis_inlimit_count 入流量触发限流的次数","# TYPE redis_inlimit_count gauge"], 21 | "OutFlowLimit":["# HELP redis_outlimit_count 出流量触发限流的次数","# TYPE redis_outlimit_count gauge"] 22 | } 23 | redis_list = consul_kv.get_services_list_by_region(f'{vendor}_{account}_redis',region) 24 | redis_list = list(redis_list) 25 | redis_list_10 = [redis_list[i:i + 10] for i in range(0, len(redis_list), 10)] 26 | for i in metric_name_dict.keys(): 27 | for rediss in redis_list_10: 28 | starttime = (datetime.now() + timedelta(minutes=-1)).strftime('%Y-%m-%dT%H:%M:%S+08:00') 29 | ins_list = [{"Dimensions":[{"Name":"instanceid","Value":x}]} for x in rediss] 30 | params = {"Namespace":"QCE/REDIS_MEM","MetricName":i,"Period":60,"StartTime":starttime,"Instances":ins_list} 31 | req.from_json_string(json.dumps(params)) 32 | resp = client.GetMonitorData(req) 33 | metric_list = resp.DataPoints 34 | for metrics in metric_list: 35 | try: 36 | iid = metrics.Dimensions[0].Value 37 | value = metrics.Values[-1] 38 | ts = metrics.Timestamps[-1]*1000 39 | prom_metric_name = metric_name_dict[i][0].split()[2] 40 | metric_name_dict[i].append(f'{prom_metric_name}{{iid="{iid}"}} {float(value)} {ts}') 41 | except Exception as e: 42 | logger.error(f"【redis_tencent:prom-metrics-ERROR】{e}\n{traceback.format_exc()}") 43 | prom_metric_list = [] 44 | for x in metric_name_dict.values(): 45 | prom_metric_list = prom_metric_list + x 46 | return prom_metric_list 47 | -------------------------------------------------------------------------------- /flask-consul/units/token_auth.py: -------------------------------------------------------------------------------- 1 | from flask_httpauth import HTTPTokenAuth 2 | from itsdangerous import TimedJSONWebSignatureSerializer 3 | from units import consul_kv 4 | from units.config_log import * 5 | secret_key = consul_kv.get_value('ConsulManager/assets/secret/skey')['sk'] 6 | s = TimedJSONWebSignatureSerializer(secret_key,expires_in=28800) 7 | auth = HTTPTokenAuth() 8 | 9 | @auth.verify_token 10 | def verify_token(token): 11 | try: 12 | data = s.loads(token) 13 | except Exception as e: 14 | logger.error(f"【login】认证异常,{e}") 15 | return False 16 | return True 17 | 18 | @auth.error_handler 19 | def unauthorized(): 20 | return {"code": 50000, "data": f"403:认证异常,请重新登录!"}, 200 21 | -------------------------------------------------------------------------------- /flask-consul/views/avd.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | from flask_restful import reqparse, Resource, Api 3 | from flask_apscheduler import APScheduler 4 | from units import token_auth,consul_kv 5 | import json 6 | from .jobs import deljob,addjob,runjob 7 | blueprint = Blueprint('avd',__name__) 8 | api = Api(blueprint) 9 | 10 | parser = reqparse.RequestParser() 11 | parser.add_argument('avd_config_dict',type=dict) 12 | 13 | class Avd(Resource): 14 | decorators = [token_auth.auth.login_required] 15 | def get(self,stype): 16 | if stype == 'list': 17 | avd_dict = consul_kv.get_kv_dict('ConsulManager/avd/list') 18 | avd_list = list(avd_dict.values()) 19 | return {'code': 20000, 'avd_list': avd_list} 20 | if stype == 'config': 21 | avd_config = consul_kv.get_value('ConsulManager/avd/switch') 22 | return {'code': 20000, 'avd_config': avd_config} 23 | def post(self,stype): 24 | if stype == 'config': 25 | args = parser.parse_args() 26 | avd_config_dict = args['avd_config_dict'] 27 | consul_kv.put_kv('ConsulManager/avd/switch',avd_config_dict) 28 | avd_job_id = 'avd_list' 29 | avd_job_func = '__main__:avd_list.get_avd' 30 | avd_job_args = [] 31 | avd_job_interval = 60 32 | if avd_config_dict['switch']: 33 | addjob(avd_job_id,avd_job_func,avd_job_args,avd_job_interval) 34 | avd_job_dict = {'id':avd_job_id,'func':avd_job_func,'args':avd_job_args,'minutes':avd_job_interval, 35 | 'trigger': 'interval','replace_existing': True} 36 | consul_kv.put_kv('ConsulManager/avd/jobs/avd_list',avd_job_dict) 37 | runjob(avd_job_id) 38 | return {'code': 20000, 'data': '漏洞采集通知功能开启!'} 39 | else: 40 | deljob(avd_job_id) 41 | consul_kv.del_key('ConsulManager/avd/jobs/avd_list') 42 | consul_kv.del_key_all('ConsulManager/avd/list/') 43 | return {'code': 20000, 'data': '漏洞采集通知功能关闭!'} 44 | if stype == 'run': 45 | avd_config_dict = consul_kv.get_value('ConsulManager/avd/switch') 46 | if avd_config_dict.get('switch',False): 47 | consul_kv.del_key('ConsulManager/avd/list/0') 48 | runjob('avd_list') 49 | return {'code': 20000, 'data': '漏洞采集通知执行成功!'} 50 | else: 51 | return {'code': 50000, 'data': '漏洞采集功能未开启!'} 52 | api.add_resource(Avd, '/api/avd/') 53 | -------------------------------------------------------------------------------- /flask-consul/views/consul.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | from flask_restful import reqparse, Resource, Api 3 | import sys 4 | sys.path.append("..") 5 | from units import token_auth,consul_manager 6 | 7 | blueprint = Blueprint('consul',__name__) 8 | api = Api(blueprint) 9 | 10 | parser = reqparse.RequestParser() 11 | parser.add_argument('service_name',type=str) 12 | parser.add_argument('sid',type=str) 13 | parser.add_argument('instance_dict',type=dict) 14 | 15 | class ConsulApi(Resource): 16 | decorators = [token_auth.auth.login_required] 17 | def get(self, stype): 18 | if stype == 'services': 19 | return consul_manager.get_services() 20 | elif stype == 'services_name': 21 | return consul_manager.get_services_nameonly() 22 | elif stype == 'instances': 23 | args = parser.parse_args() 24 | return consul_manager.get_instances(args['service_name']) 25 | elif stype == 'hosts': 26 | return consul_manager.get_hosts() 27 | 28 | def post(self, stype): 29 | if stype == 'sid': 30 | args = parser.parse_args() 31 | return consul_manager.add_instance(args['instance_dict']) 32 | 33 | def put(self, stype): 34 | if stype == 'sid': 35 | args = parser.parse_args() 36 | resp_del = consul_manager.del_instance(args['sid']) 37 | resp_add = consul_manager.add_instance(args['instance_dict']) 38 | if resp_del["code"] == 20000 and resp_add["code"] == 20000: 39 | return {"code": 20000, "data": f"更新成功!"} 40 | else: 41 | return {"code": 50000, "data": f"更新失败!"} 42 | 43 | def delete(self, stype): 44 | if stype == 'sid': 45 | args = parser.parse_args() 46 | return consul_manager.del_instance(args['sid']) 47 | 48 | api.add_resource(ConsulApi, '/api/consul/') 49 | -------------------------------------------------------------------------------- /flask-consul/views/ldap.py: -------------------------------------------------------------------------------- 1 | """ 2 | ldap信息填写 3 | """ 4 | from flask import Blueprint 5 | from flask_restful import reqparse, Resource, Api 6 | import sys 7 | 8 | from units.json_response import JsonResponse 9 | from units.ldap.ldap_consul import Ldap_Consul 10 | 11 | sys.path.append("..") 12 | from units import token_auth, consul_kv,myaes 13 | from itsdangerous import TimedJSONWebSignatureSerializer 14 | 15 | secret_key = consul_kv.get_value('ConsulManager/assets/secret/skey')['sk'] 16 | s = TimedJSONWebSignatureSerializer(secret_key,expires_in=28800) 17 | 18 | blueprint = Blueprint('ldap',__name__) 19 | api = Api(blueprint) 20 | parser = reqparse.RequestParser() 21 | 22 | parser.add_argument('ldap_url',type=str) 23 | parser.add_argument('password',type=str) 24 | parser.add_argument('port',type=str) 25 | parser.add_argument('rule',type=str) 26 | parser.add_argument('ldapusr',type=str) 27 | parser.add_argument('allow',type=str) 28 | 29 | 30 | class LdapView(Resource): 31 | """ 32 | 封装了公共返回格式 33 | {"code": code,"success": success, "message": msg, "data": data} 34 | """ 35 | decorators = [token_auth.auth.login_required] 36 | def post(self,): 37 | args = parser.parse_args() 38 | Ldap_Consul.set_consul_args(**args) 39 | return JsonResponse(data="", code=20000, success=True, msg="添加统一认证成功") 40 | 41 | def get(self): 42 | ldap_info = consul_kv.get_value('ConsulManager/ldap/report') 43 | if ldap_info: 44 | ldap_info["password"] = myaes.decrypt(ldap_info["password"]) 45 | else: 46 | ldap_info = {'port': '389', 'allow': '*'} 47 | return {'code': 20000, 'ldap_info': ldap_info} 48 | 49 | def delete(self): 50 | consul_kv.del_key('ConsulManager/ldap/report') 51 | return {'code': 20000, 'data': 'DLAP登录配置已清除!'} 52 | 53 | api.add_resource(LdapView, '/api/ldap/config') 54 | -------------------------------------------------------------------------------- /flask-consul/views/prom/cloud_metrics.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint,Response 2 | from flask_restful import reqparse, Resource, Api 3 | from config import vendors,regions 4 | from units import token_auth,consul_kv 5 | from units.prom import mysql_huawei,mysql_ali,mysql_tencent,redis_huawei,redis_ali,redis_tencent 6 | import json 7 | blueprint = Blueprint('cloud_metrics',__name__) 8 | api = Api(blueprint) 9 | 10 | class RdsExporter(Resource): 11 | def get(self,vendor,account,region): 12 | if vendor == 'huaweicloud': 13 | prom_metric_list = mysql_huawei.exporter(vendor,account,region) 14 | elif vendor == 'alicloud': 15 | prom_metric_list = mysql_ali.exporter(vendor,account,region) 16 | elif vendor == 'tencent_cloud': 17 | prom_metric_list = mysql_tencent.exporter(vendor,account,region) 18 | return Response('\n'.join(prom_metric_list).encode('utf-8'),mimetype="text/plain") 19 | 20 | class RedisExporter(Resource): 21 | def get(self,vendor,account,region): 22 | if vendor == 'huaweicloud': 23 | prom_metric_list = redis_huawei.exporter(vendor,account,region) 24 | elif vendor == 'alicloud': 25 | prom_metric_list = redis_ali.exporter(vendor,account,region) 26 | elif vendor == 'tencent_cloud': 27 | prom_metric_list = redis_tencent.exporter(vendor,account,region) 28 | return Response('\n'.join(prom_metric_list).encode('utf-8'),mimetype="text/plain") 29 | api.add_resource(RdsExporter, '/api/cloud_mysql_metrics///') 30 | api.add_resource(RedisExporter, '/api/cloud_redis_metrics///') 31 | -------------------------------------------------------------------------------- /install/docker-compose/all_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export PATH=$PATH:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin 3 | tsspath="/opt/tensuns" 4 | uuid=`uuidgen` 5 | adminpwd=`uuidgen|awk -F- '{print $1}'` 6 | mkdir -p $tsspath/consul/config 7 | cat < $tsspath/consul/config/consul.hcl 8 | log_level = "error" 9 | data_dir = "/consul/data" 10 | client_addr = "0.0.0.0" 11 | ui_config{ 12 | enabled = true 13 | } 14 | ports = { 15 | grpc = -1 16 | https = -1 17 | dns = -1 18 | grpc_tls = -1 19 | serf_wan = -1 20 | } 21 | peering { 22 | enabled = false 23 | } 24 | connect { 25 | enabled = false 26 | } 27 | server = true 28 | bootstrap_expect=1 29 | acl = { 30 | enabled = true 31 | default_policy = "deny" 32 | enable_token_persistence = true 33 | tokens { 34 | initial_management = "$uuid" 35 | agent = "$uuid" 36 | } 37 | } 38 | EOF 39 | chmod 777 -R $tsspath/consul/config 40 | cat < $tsspath/docker-compose.yaml 41 | version: '3.6' 42 | services: 43 | consul: 44 | image: swr.cn-south-1.myhuaweicloud.com/starsl.cn/consul:latest 45 | container_name: consul 46 | hostname: consul 47 | restart: always 48 | ports: 49 | - "8500:8500" 50 | volumes: 51 | - $tsspath/consul/data:/consul/data 52 | - $tsspath/consul/config:/consul/config 53 | - /usr/share/zoneinfo/PRC:/etc/localtime 54 | command: "agent" 55 | networks: 56 | - TenSunS 57 | 58 | flask-consul: 59 | image: swr.cn-south-1.myhuaweicloud.com/starsl.cn/flask-consul:latest 60 | container_name: flask-consul 61 | hostname: flask-consul 62 | restart: always 63 | volumes: 64 | - /usr/share/zoneinfo/PRC:/etc/localtime 65 | environment: 66 | consul_token: $uuid 67 | consul_url: http://consul:8500/v1 68 | admin_passwd: $adminpwd 69 | log_level: INFO 70 | depends_on: 71 | - consul 72 | networks: 73 | - TenSunS 74 | 75 | nginx-consul: 76 | image: swr.cn-south-1.myhuaweicloud.com/starsl.cn/nginx-consul:latest 77 | container_name: nginx-consul 78 | hostname: nginx-consul 79 | restart: always 80 | ports: 81 | - "1026:1026" 82 | volumes: 83 | - /usr/share/zoneinfo/PRC:/etc/localtime 84 | depends_on: 85 | - flask-consul 86 | networks: 87 | - TenSunS 88 | 89 | networks: 90 | TenSunS: 91 | name: TenSunS 92 | driver: bridge 93 | ipam: 94 | driver: default 95 | EOF 96 | 97 | echo -e "\n\033[31;1m正在启动后羿运维平台...\033[0m" 98 | cd $tsspath && docker-compose up -d 99 | echo -e "\n后羿运维平台默认的admin密码是:\033[31;1m$adminpwd\033[0m\n修改密码请编辑 $tsspath/docker-compose.yaml 查找并修改变量 admin_passwd 的值\n" 100 | echo -e "请使用浏览器访问 http://{你的IP}:1026 并登录使用\n" 101 | echo -e "\033[31;1mhttp://`ip route get 1.2.3.4 | awk '{print $NF}'|head -1`:1026\033[0m\n" 102 | -------------------------------------------------------------------------------- /install/docker-compose/consul_install_only.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | uuid=`uuidgen` 3 | mkdir -p /opt/consul/config 4 | cat < /opt/consul/config/consul.hcl 5 | log_level = "error" 6 | data_dir = "/consul/data" 7 | client_addr = "0.0.0.0" 8 | ui_config{ 9 | enabled = true 10 | } 11 | ports = { 12 | grpc = -1 13 | https = -1 14 | dns = -1 15 | grpc_tls = -1 16 | serf_wan = -1 17 | } 18 | peering { 19 | enabled = false 20 | } 21 | connect { 22 | enabled = false 23 | } 24 | server = true 25 | bootstrap_expect=1 26 | acl = { 27 | enabled = true 28 | default_policy = "deny" 29 | enable_token_persistence = true 30 | tokens { 31 | initial_management = "$uuid" 32 | agent = "$uuid" 33 | } 34 | } 35 | EOF 36 | chmod 777 -R /opt/consul/config 37 | cat < /opt/consul/docker-compose.yaml 38 | version: '3.6' 39 | services: 40 | consul: 41 | image: swr.cn-south-1.myhuaweicloud.com/starsl.cn/consul:latest 42 | hostname: consul 43 | container_name: consul 44 | restart: always 45 | ports: 46 | - "8500:8500" 47 | volumes: 48 | - /opt/consul/data:/consul/data 49 | - /opt/consul/config:/consul/config 50 | - /usr/share/zoneinfo/PRC:/etc/localtime 51 | command: "agent" 52 | networks: 53 | - TenSunS 54 | 55 | networks: 56 | TenSunS: 57 | name: TenSunS 58 | driver: bridge 59 | ipam: 60 | driver: default 61 | EOF 62 | 63 | echo "请进入/opt/consul目录执行 docker-compose up -d 启动consul" 64 | echo "consul的管理员token是: $uuid" 65 | -------------------------------------------------------------------------------- /install/docker-compose/tensuns_install_only.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | tsspath="/opt/tensuns" 3 | mkdir -p $tsspath 4 | 5 | cat < $tsspath/docker-compose.yaml 6 | version: '3.6' 7 | services: 8 | flask-consul: 9 | image: swr.cn-south-1.myhuaweicloud.com/starsl.cn/flask-consul:latest 10 | container_name: flask-consul 11 | hostname: flask-consul 12 | restart: always 13 | volumes: 14 | - /usr/share/zoneinfo/PRC:/etc/localtime 15 | environment: 16 | consul_token: xxxxx-xxxxx-xxxxx 17 | consul_url: http://x.x.x.x:8500/v1 18 | admin_passwd: xxxxxxxx 19 | log_level: INFO 20 | networks: 21 | - TenSunS 22 | 23 | nginx-consul: 24 | image: swr.cn-south-1.myhuaweicloud.com/starsl.cn/nginx-consul:latest 25 | container_name: nginx-consul 26 | hostname: nginx-consul 27 | restart: always 28 | ports: 29 | - "1026:1026" 30 | volumes: 31 | - /usr/share/zoneinfo/PRC:/etc/localtime 32 | depends_on: 33 | - flask-consul 34 | networks: 35 | - TenSunS 36 | 37 | networks: 38 | TenSunS: 39 | name: TenSunS 40 | driver: bridge 41 | ipam: 42 | driver: default 43 | EOF 44 | 45 | 46 | echo -e "\n编辑:$tsspath/docker-compose.yaml,修改3个环境变量:\n\033[31;1mconsul_token\033[0m:consul的登录token(安装consul时生成的UUID)\n\033[31;1mconsul_url\033[0m:consul的URL(http开头,/v1要保留)\n\033[31;1madmin_passwd\033[0m:登录后羿运维平台admin用户的密码\n" 47 | echo "启动:cd $tsspath && docker-compose up -d" 48 | echo -e "\n请使用浏览器访问 http://{你的IP}:1026 并登录使用\n" 49 | echo -e "\033[31;1mhttp://`ip route get 1.2.3.4 | awk '{print $NF}'|head -1`:1026\033[0m\n" 50 | -------------------------------------------------------------------------------- /install/k8s/readme.md: -------------------------------------------------------------------------------- 1 | ## 2023-10-8前使用该install.sh脚本在K8S部署consul的同学请注意. 2 | 1. **完成第3步操作前不要删除consul的pod,也不要重启consul,否则会造成所有Consul KV数据丢失** 3 | 2. 使用kubectl进入conusl的容器`kubectl exec -i -t -n tensuns consul-0 -c consul -- sh -c "sh"` 4 | 3. 备份consul的KV存储数据(确保你部署的consul使用了持久化存储,以下命令会备份到持久化存储映射的目录下:`/consul/data/`.) 5 | ``` 6 | consul kv export --http-addr=http://127.0.0.1:8500 -token=$(cat /consul/config/consul.hcl|grep agent|awk -F\" '{print $2}') '' > /consul/data/consul_kv_bak.json 7 | ``` 8 | 4. 修改consul的Stateful Sets的yaml文件, 找到`image`字段下面, 增加`args`部分: 9 | ``` 10 | args: 11 | - agent 12 | ``` 13 | - 增加完成后应该是如下效果: 14 | ``` 15 | ... 16 | containers: 17 | - name: consul 18 | image: swr.cn-south-1.myhuaweicloud.com/starsl.cn/consul:latest 19 | args: 20 | - agent 21 | ports: 22 | - name: http 23 | containerPort: 8500 24 | protocol: TCP 25 | ... 26 | ``` 27 | 5. 保存后重启consul的Stateful Sets 28 | 6. 启动完成后再次使用第2步进入consul容器 29 | 7. 恢复consul的KV存储数据 30 | ``` 31 | consul kv import --http-addr=http://127.0.0.1:8500 -token=$(cat /consul/config/consul.hcl|grep agent|awk -F\" '{print $2}') @/consul/data/consul_kv_bak.json 32 | ``` 33 | 8. 完成,可再次重启consul后,验证consul kv数据是否丢失(能正常登录TenSunS说明KV数据正常). 34 | -------------------------------------------------------------------------------- /screenshot/README.md: -------------------------------------------------------------------------------- 1 | ## 截图 2 | ### Consul Web Manager 界面 3 | ![](https://github.com/starsliao/TenSunS/blob/main/screenshot/consul1.PNG?raw=true) 4 | ![](https://github.com/starsliao/TenSunS/blob/main/screenshot/consul2.PNG?raw=true) 5 | ![](https://github.com/starsliao/TenSunS/blob/main/screenshot/consul3.PNG?raw=true) 6 | ![](https://github.com/starsliao/TenSunS/blob/main/screenshot/consul4.PNG?raw=true) 7 | ### ECS Manager 界面 8 | ![](https://github.com/starsliao/TenSunS/blob/main/screenshot/ecs1.PNG?raw=true) 9 | ![](https://github.com/starsliao/TenSunS/blob/main/screenshot/ecs2.PNG?raw=true) 10 | ![](https://github.com/starsliao/TenSunS/blob/main/screenshot/ecs3.PNG?raw=true) 11 | ![](https://github.com/starsliao/TenSunS/blob/main/screenshot/ecs4.PNG?raw=true) 12 | ### Blackbox Manager 界面 13 | ![](https://github.com/starsliao/TenSunS/blob/main/screenshot/blackbox1.PNG?raw=true) 14 | ![](https://github.com/starsliao/TenSunS/blob/main/screenshot/blackbox2.PNG?raw=true) 15 | ![](https://github.com/starsliao/TenSunS/blob/main/screenshot/blackbox3.PNG?raw=true) 16 | ![](https://github.com/starsliao/TenSunS/blob/main/screenshot/blackbox4.PNG?raw=true) 17 | ### Node Exporter Dashboard 截图 18 | ![](https://raw.githubusercontent.com/starsliao/TenSunS/main/vue-consul/public/node-exporter.png) 19 | ### Blackbox Exporter Dashboard 截图 20 | ![](https://raw.githubusercontent.com/starsliao/TenSunS/main/vue-consul/public/blackbox.png) 21 | -------------------------------------------------------------------------------- /screenshot/blackbox1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/screenshot/blackbox1.PNG -------------------------------------------------------------------------------- /screenshot/blackbox2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/screenshot/blackbox2.PNG -------------------------------------------------------------------------------- /screenshot/blackbox3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/screenshot/blackbox3.PNG -------------------------------------------------------------------------------- /screenshot/blackbox4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/screenshot/blackbox4.PNG -------------------------------------------------------------------------------- /screenshot/bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/screenshot/bug.png -------------------------------------------------------------------------------- /screenshot/consul1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/screenshot/consul1.PNG -------------------------------------------------------------------------------- /screenshot/consul2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/screenshot/consul2.PNG -------------------------------------------------------------------------------- /screenshot/consul3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/screenshot/consul3.PNG -------------------------------------------------------------------------------- /screenshot/consul4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/screenshot/consul4.PNG -------------------------------------------------------------------------------- /screenshot/ecs1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/screenshot/ecs1.PNG -------------------------------------------------------------------------------- /screenshot/ecs2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/screenshot/ecs2.PNG -------------------------------------------------------------------------------- /screenshot/ecs3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/screenshot/ecs3.PNG -------------------------------------------------------------------------------- /screenshot/ecs4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/screenshot/ecs4.PNG -------------------------------------------------------------------------------- /screenshot/tensuns-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/screenshot/tensuns-arch.png -------------------------------------------------------------------------------- /thanks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/thanks.png -------------------------------------------------------------------------------- /tools/blackbox-input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import requests,json 3 | consul_token = 'xxxxxxxxxx' #Consul SecretID 4 | consul_url = 'http://x.x.x.x:8500/v1' 5 | 6 | with open('blackbox-instance.list', 'r') as file: 7 | lines = file.readlines() 8 | for line in lines: 9 | if line.startswith('#'): 10 | continue 11 | module,company,project,env,name,instance = line.split() 12 | headers = {'X-Consul-Token': consul_token} 13 | data = { 14 | "id": f"{module}/{company}/{project}/{env}@{name}", 15 | "name": 'blackbox_exporter', 16 | "tags": [module], 17 | "Meta": {'module':module,'company':company,'project':project,'env':env,'name':name,'instance':instance} 18 | } 19 | 20 | reg = requests.put(f"{consul_url}/agent/service/register", headers=headers, data=json.dumps(data)) 21 | if reg.status_code == 200: 22 | print({"code": 20000,"data": "增加成功!"}) 23 | else: 24 | print({"code": 50000,"data": f'{reg.status_code}:{reg.text}'}) 25 | -------------------------------------------------------------------------------- /tools/blackbox-instance.list: -------------------------------------------------------------------------------- 1 | # 监控类型和Blackbox配置中的module名必须保持一致 2 | # 每行需要有6个字段,空格分隔: 3 | # 监控类型 公司/部门 项目 环境 名称 实例url 4 | # 以下为例子,#开头的行不会被导入,需要导入的行不要以#开头: 5 | # http_2xx 阿里巴巴 电商 prod 淘宝 https://www.taobao.com 6 | -------------------------------------------------------------------------------- /tools/del_consul_svc.sh: -------------------------------------------------------------------------------- 1 | # Assuming localhost, substitute with actual Consul server address. 2 | export CONSUL_HTTP_TOKEN=234dbcac-25df-42d3-965a-af4193474a56 3 | CONSUL_HOST="http://172.26.32.83:8500" 4 | 5 | # Get all instances of the service 6 | SERVICE_INSTANCES=$(curl -s -H "X-Consul-Token: $CONSUL_HTTP_TOKEN" "${CONSUL_HOST}/v1/catalog/service/alicloud_dreame_app_mongodb") 7 | 8 | # Extract the service IDs and deregister each 9 | echo "${SERVICE_INSTANCES}" | jq -r '.[].ServiceID' | while read SERVICE_ID; do 10 | echo "Deregistering service instance: $SERVICE_ID" 11 | curl -s -X PUT -H "X-Consul-Token: $CONSUL_HTTP_TOKEN" "${CONSUL_HOST}/v1/agent/service/deregister/${SERVICE_ID}" 12 | done -------------------------------------------------------------------------------- /tools/docker-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | eth=$(ls /sys/class/net/ | grep -v "`ls /sys/devices/virtual/net/`") 3 | ip=$(ip addr | grep $eth | awk '/^[0-9]+: / {}; /inet.*global/ {print gensub(/(.*)\/(.*)/, "\\1", "g", $2)}' | head -1) 4 | cd flask-consul 5 | docker build -t flask-consul:latest . 6 | cd ../vue-consul 7 | docker build -t nginx-consul:latest . 8 | 9 | echo -e "\n\n自行编译的版本,注意修改docker-compose.yml中的镜像地址为本地仓库后再启动。" 10 | echo -e "\n\nBlackbox-Manager:\nhttp://${ip}:1026" 11 | -------------------------------------------------------------------------------- /tools/docker-push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ver=0.26 3 | docker login --username=youremail registry.cn-shenzhen.aliyuncs.com 4 | 5 | docker tag nginx-consul:latest registry.cn-shenzhen.aliyuncs.com/starsl/nginx-consul:latest 6 | docker tag nginx-consul:latest registry.cn-shenzhen.aliyuncs.com/starsl/nginx-consul:${ver} 7 | 8 | docker tag flask-consul:latest registry.cn-shenzhen.aliyuncs.com/starsl/flask-consul:latest 9 | docker tag flask-consul:latest registry.cn-shenzhen.aliyuncs.com/starsl/flask-consul:${ver} 10 | 11 | docker push registry.cn-shenzhen.aliyuncs.com/starsl/nginx-consul:latest 12 | docker push registry.cn-shenzhen.aliyuncs.com/starsl/nginx-consul:${ver} 13 | docker push registry.cn-shenzhen.aliyuncs.com/starsl/flask-consul:latest 14 | docker push registry.cn-shenzhen.aliyuncs.com/starsl/flask-consul:${ver} 15 | -------------------------------------------------------------------------------- /tools/selfnode-input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import requests,json 3 | consul_token = 'xxxxxxxxxx' #Consul SecretID 4 | consul_url = 'http://x.x.x.x:8500/v1' 5 | 6 | with open('selfnode-instance.list', 'r') as file: 7 | lines = file.readlines() 8 | for line in lines: 9 | if line.startswith('#'): 10 | continue 11 | vendor,account,region,group,name,instance,os = line.split() 12 | headers = {'X-Consul-Token': consul_token} 13 | sid = f"{vendor}/{account}/{region}/{group}@{name}" 14 | ip = instance.split(':')[0] 15 | port = instance.split(':')[1] 16 | data = { 17 | "id": sid, 18 | "name": 'selfnode_exporter', 19 | 'Address': ip, 20 | 'port': int(port), 21 | "tags": [vendor,os], 22 | "Meta": {'vendor':vendor,'account':account,'region':region,'group':group, 23 | 'name':name,'instance':instance,'os':os}, 24 | "check": {"tcp": instance,"interval": "60s"} 25 | } 26 | 27 | reg = requests.put(f"{consul_url}/agent/service/register", headers=headers, data=json.dumps(data)) 28 | if reg.status_code == 200: 29 | print({"code": 20000,"data": "增加成功!"}) 30 | else: 31 | print({"code": 50000,"data": f'{reg.status_code}:{reg.text}'}) 32 | -------------------------------------------------------------------------------- /tools/selfnode-instance.list: -------------------------------------------------------------------------------- 1 | # 每行需要有7个字段,空格分隔: 2 | # 机房/公司 租户/部门 区域/项目 分组/环境 名称 实例(ip:端口) 系统(linux/windows) 3 | # 以下为例子,#开头的行不会被导入,需要导入的行不要以#开头: 4 | # openstack 运维 深圳 prod 监控主机 10.0.0.26:9100 linux 5 | -------------------------------------------------------------------------------- /tools/当前已支持Web导入: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/tools/当前已支持Web导入 -------------------------------------------------------------------------------- /vue-consul/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /vue-consul/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /vue-consul/.env.development: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'development' 3 | 4 | # base api 5 | VUE_APP_BASE_API = '/dev-api' 6 | -------------------------------------------------------------------------------- /vue-consul/.env.production: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'production' 3 | 4 | # base api 5 | VUE_APP_BASE_API = '/prod-api' 6 | 7 | -------------------------------------------------------------------------------- /vue-consul/.env.staging: -------------------------------------------------------------------------------- 1 | NODE_ENV = production 2 | 3 | # just a flag 4 | ENV = 'staging' 5 | 6 | # base api 7 | VUE_APP_BASE_API = '/stage-api' 8 | 9 | -------------------------------------------------------------------------------- /vue-consul/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | src/assets 3 | public 4 | dist 5 | -------------------------------------------------------------------------------- /vue-consul/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | package-lock.json 8 | tests/**/coverage/ 9 | 10 | # Editor directories and files 11 | .idea 12 | .vscode 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | -------------------------------------------------------------------------------- /vue-consul/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 10 3 | script: npm run test 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /vue-consul/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.cn-shenzhen.aliyuncs.com/starsl/node:14-alpine AS builder 2 | ADD . /vue 3 | WORKDIR /vue 4 | RUN npm install --registry=https://registry.npmmirror.com && npm run build:prod 5 | 6 | FROM registry.cn-shenzhen.aliyuncs.com/starsl/nginx:1.24.0-alpine 7 | WORKDIR /www 8 | COPY --from=builder /vue/http-ops.conf /etc/nginx/conf.d/default.conf 9 | COPY --from=builder /vue/dist dist/ 10 | -------------------------------------------------------------------------------- /vue-consul/Dockerfile-arm64: -------------------------------------------------------------------------------- 1 | FROM node:14-buster AS builder 2 | ADD . /vue 3 | WORKDIR /vue 4 | RUN npm install --registry=https://registry.npm.taobao.org && npm run build:prod 5 | 6 | FROM llody/nginx:1.22.0-base 7 | WORKDIR /www 8 | COPY --from=builder /vue/http-ops.conf /etc/nginx/conf.d/default.conf 9 | COPY --from=builder /vue/dist dist/ 10 | -------------------------------------------------------------------------------- /vue-consul/Dockerfile.tensuns: -------------------------------------------------------------------------------- 1 | FROM node:14-alpine AS builder 2 | ADD . /vue 3 | WORKDIR /vue 4 | RUN mv -f src/views/login/index.vue.tensuns src/views/login/index.vue && \ 5 | mv -f src/settings.js.tensuns src/settings.js && \ 6 | npm install --registry=https://registry.npm.taobao.org && npm run build:prod 7 | 8 | FROM nginx:stable-alpine 9 | WORKDIR /www 10 | COPY --from=builder /vue/http-ops.conf /etc/nginx/conf.d/default.conf 11 | COPY --from=builder /vue/dist dist/ 12 | -------------------------------------------------------------------------------- /vue-consul/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-present PanJiaChen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vue-consul/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app 4 | '@vue/cli-plugin-babel/preset' 5 | ], 6 | 'env': { 7 | 'development': { 8 | // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require(). 9 | // This plugin can significantly increase the speed of hot updates, when you have a large number of pages. 10 | // https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html 11 | 'plugins': ['dynamic-import-node'] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /vue-consul/build/index.js: -------------------------------------------------------------------------------- 1 | const { run } = require('runjs') 2 | const chalk = require('chalk') 3 | const config = require('../vue.config.js') 4 | const rawArgv = process.argv.slice(2) 5 | const args = rawArgv.join(' ') 6 | 7 | if (process.env.npm_config_preview || rawArgv.includes('--preview')) { 8 | const report = rawArgv.includes('--report') 9 | 10 | run(`vue-cli-service build ${args}`) 11 | 12 | const port = 9526 13 | const publicPath = config.publicPath 14 | 15 | var connect = require('connect') 16 | var serveStatic = require('serve-static') 17 | const app = connect() 18 | 19 | app.use( 20 | publicPath, 21 | serveStatic('./dist', { 22 | index: ['index.html', '/'] 23 | }) 24 | ) 25 | 26 | app.listen(port, function () { 27 | console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) 28 | if (report) { 29 | console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) 30 | } 31 | 32 | }) 33 | } else { 34 | run(`vue-cli-service build ${args}`) 35 | } 36 | -------------------------------------------------------------------------------- /vue-consul/http-ops.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 1026; 3 | location /api/ { 4 | proxy_pass http://flask-consul:2026; 5 | } 6 | location / { 7 | gzip_static on; 8 | root /www/dist; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /vue-consul/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 6 | 'jest-transform-stub', 7 | '^.+\\.jsx?$': 'babel-jest' 8 | }, 9 | moduleNameMapper: { 10 | '^@/(.*)$': '/src/$1' 11 | }, 12 | snapshotSerializers: ['jest-serializer-vue'], 13 | testMatch: [ 14 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 15 | ], 16 | collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'], 17 | coverageDirectory: '/tests/unit/coverage', 18 | // 'collectCoverage': true, 19 | 'coverageReporters': [ 20 | 'lcov', 21 | 'text-summary' 22 | ], 23 | testURL: 'http://localhost/' 24 | } 25 | -------------------------------------------------------------------------------- /vue-consul/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /vue-consul/mock/index.js: -------------------------------------------------------------------------------- 1 | const Mock = require('mockjs') 2 | const { param2Obj } = require('./utils') 3 | 4 | const user = require('./user') 5 | const article = require('./article') 6 | const table = require('./table') 7 | 8 | const mocks = [ 9 | ...user, 10 | ...article, 11 | ...table 12 | ] 13 | 14 | // for front mock 15 | // please use it cautiously, it will redefine XMLHttpRequest, 16 | // which will cause many of your third-party libraries to be invalidated(like progress event). 17 | function mockXHR() { 18 | // mock patch 19 | // https://github.com/nuysoft/Mock/issues/300 20 | Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send 21 | Mock.XHR.prototype.send = function() { 22 | if (this.custom.xhr) { 23 | this.custom.xhr.withCredentials = this.withCredentials || false 24 | 25 | if (this.responseType) { 26 | this.custom.xhr.responseType = this.responseType 27 | } 28 | } 29 | this.proxy_send(...arguments) 30 | } 31 | 32 | function XHR2ExpressReqWrap(respond) { 33 | return function(options) { 34 | let result = null 35 | if (respond instanceof Function) { 36 | const { body, type, url } = options 37 | // https://expressjs.com/en/4x/api.html#req 38 | result = respond({ 39 | method: type, 40 | body: JSON.parse(body), 41 | query: param2Obj(url) 42 | }) 43 | } else { 44 | result = respond 45 | } 46 | return Mock.mock(result) 47 | } 48 | } 49 | 50 | for (const i of mocks) { 51 | Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response)) 52 | } 53 | } 54 | 55 | module.exports = { 56 | mocks, 57 | mockXHR 58 | } 59 | 60 | -------------------------------------------------------------------------------- /vue-consul/mock/mock-server.js: -------------------------------------------------------------------------------- 1 | const chokidar = require('chokidar') 2 | const bodyParser = require('body-parser') 3 | const chalk = require('chalk') 4 | const path = require('path') 5 | const Mock = require('mockjs') 6 | 7 | const mockDir = path.join(process.cwd(), 'mock') 8 | 9 | function registerRoutes(app) { 10 | let mockLastIndex 11 | const { mocks } = require('./index.js') 12 | const mocksForServer = mocks.map(route => { 13 | return responseFake(route.url, route.type, route.response) 14 | }) 15 | for (const mock of mocksForServer) { 16 | app[mock.type](mock.url, mock.response) 17 | mockLastIndex = app._router.stack.length 18 | } 19 | const mockRoutesLength = Object.keys(mocksForServer).length 20 | return { 21 | mockRoutesLength: mockRoutesLength, 22 | mockStartIndex: mockLastIndex - mockRoutesLength 23 | } 24 | } 25 | 26 | function unregisterRoutes() { 27 | Object.keys(require.cache).forEach(i => { 28 | if (i.includes(mockDir)) { 29 | delete require.cache[require.resolve(i)] 30 | } 31 | }) 32 | } 33 | 34 | // for mock server 35 | const responseFake = (url, type, respond) => { 36 | return { 37 | url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`), 38 | type: type || 'get', 39 | response(req, res) { 40 | console.log('request invoke:' + req.path) 41 | res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond)) 42 | } 43 | } 44 | } 45 | 46 | module.exports = app => { 47 | // parse app.body 48 | // https://expressjs.com/en/4x/api.html#req.body 49 | app.use(bodyParser.json()) 50 | app.use(bodyParser.urlencoded({ 51 | extended: true 52 | })) 53 | 54 | const mockRoutes = registerRoutes(app) 55 | var mockRoutesLength = mockRoutes.mockRoutesLength 56 | var mockStartIndex = mockRoutes.mockStartIndex 57 | 58 | // watch files, hot reload mock server 59 | chokidar.watch(mockDir, { 60 | ignored: /mock-server/, 61 | ignoreInitial: true 62 | }).on('all', (event, path) => { 63 | if (event === 'change' || event === 'add') { 64 | try { 65 | // remove mock routes stack 66 | app._router.stack.splice(mockStartIndex, mockRoutesLength) 67 | 68 | // clear routes cache 69 | unregisterRoutes() 70 | 71 | const mockRoutes = registerRoutes(app) 72 | mockRoutesLength = mockRoutes.mockRoutesLength 73 | mockStartIndex = mockRoutes.mockStartIndex 74 | 75 | console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`)) 76 | } catch (error) { 77 | console.log(chalk.redBright(error)) 78 | } 79 | } 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /vue-consul/mock/table.js: -------------------------------------------------------------------------------- 1 | const Mock = require('mockjs') 2 | 3 | const data = Mock.mock({ 4 | 'items|30': [{ 5 | id: '@id', 6 | title: '@sentence(10, 20)', 7 | 'status|1': ['published', 'draft', 'deleted'], 8 | author: 'name', 9 | display_time: '@datetime', 10 | pageviews: '@integer(300, 5000)' 11 | }] 12 | }) 13 | 14 | module.exports = [ 15 | { 16 | url: '/vue-admin-template/table/list', 17 | type: 'get', 18 | response: config => { 19 | const items = data.items 20 | return { 21 | code: 20000, 22 | data: { 23 | total: items.length, 24 | items: items 25 | } 26 | } 27 | } 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /vue-consul/mock/user.js: -------------------------------------------------------------------------------- 1 | 2 | const tokens = { 3 | admin: { 4 | token: 'admin-token' 5 | }, 6 | editor: { 7 | token: 'editor-token' 8 | } 9 | } 10 | 11 | const users = { 12 | 'admin-token': { 13 | roles: ['admin'], 14 | introduction: 'I am a super administrator', 15 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', 16 | name: 'Super Admin' 17 | }, 18 | 'editor-token': { 19 | roles: ['editor'], 20 | introduction: 'I am an editor', 21 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', 22 | name: 'Normal Editor' 23 | } 24 | } 25 | 26 | module.exports = [ 27 | // user login 28 | { 29 | url: '/vue-admin-template/user/login', 30 | type: 'post', 31 | response: config => { 32 | const { username } = config.body 33 | const token = tokens[username] 34 | 35 | // mock error 36 | if (!token) { 37 | return { 38 | code: 60204, 39 | message: 'Account and password are incorrect.' 40 | } 41 | } 42 | 43 | return { 44 | code: 20000, 45 | data: token 46 | } 47 | } 48 | }, 49 | 50 | // get user info 51 | { 52 | url: '/vue-admin-template/user/info\.*', 53 | type: 'get', 54 | response: config => { 55 | const { token } = config.query 56 | const info = users[token] 57 | 58 | // mock error 59 | if (!info) { 60 | return { 61 | code: 50008, 62 | message: 'Login failed, unable to get user details.' 63 | } 64 | } 65 | 66 | return { 67 | code: 20000, 68 | data: info 69 | } 70 | } 71 | }, 72 | 73 | // user logout 74 | { 75 | url: '/vue-admin-template/user/logout', 76 | type: 'post', 77 | response: _ => { 78 | return { 79 | code: 20000, 80 | data: 'success' 81 | } 82 | } 83 | } 84 | ] 85 | -------------------------------------------------------------------------------- /vue-consul/mock/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} url 3 | * @returns {Object} 4 | */ 5 | function param2Obj(url) { 6 | const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') 7 | if (!search) { 8 | return {} 9 | } 10 | const obj = {} 11 | const searchArr = search.split('&') 12 | searchArr.forEach(v => { 13 | const index = v.indexOf('=') 14 | if (index !== -1) { 15 | const name = v.substring(0, index) 16 | const val = v.substring(index + 1, v.length) 17 | obj[name] = val 18 | } 19 | }) 20 | return obj 21 | } 22 | 23 | module.exports = { 24 | param2Obj 25 | } 26 | -------------------------------------------------------------------------------- /vue-consul/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-admin-template", 3 | "version": "4.4.0", 4 | "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint", 5 | "author": "Pan ", 6 | "scripts": { 7 | "dev": "vue-cli-service serve", 8 | "build:prod": "vue-cli-service build", 9 | "build:stage": "vue-cli-service build --mode staging", 10 | "preview": "node build/index.js --preview", 11 | "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml", 12 | "lint": "eslint --fix --ext .js,.vue src", 13 | "test:unit": "jest --clearCache && vue-cli-service test:unit", 14 | "test:ci": "npm run lint && npm run test:unit" 15 | }, 16 | "dependencies": { 17 | "axios": "0.27.2", 18 | "core-js": "3.23.3", 19 | "element-ui": "2.15.9", 20 | "file-saver": "2.0.5", 21 | "js-cookie": "2.2.1", 22 | "normalize.css": "8.0.1", 23 | "nprogress": "0.2.0", 24 | "path-to-regexp": "2.4.0", 25 | "vue": "2.7.2", 26 | "vue-clipboard2": "^0.3.3", 27 | "vue-highlightjs": "^1.3.3", 28 | "vue-router": "3.5.4", 29 | "vuex": "3.6.2", 30 | "xlsx": "0.17.5" 31 | }, 32 | "devDependencies": { 33 | "@vue/cli-plugin-babel": "4.4.4", 34 | "@vue/cli-plugin-eslint": "4.4.4", 35 | "@vue/cli-plugin-unit-jest": "4.4.4", 36 | "@vue/cli-service": "4.4.4", 37 | "@vue/test-utils": "1.3.0", 38 | "autoprefixer": "9.5.1", 39 | "babel-eslint": "10.1.0", 40 | "babel-jest": "28.1.2", 41 | "babel-plugin-dynamic-import-node": "2.3.3", 42 | "chalk": "2.4.2", 43 | "compression-webpack-plugin": "^6.1.1", 44 | "connect": "3.6.6", 45 | "eslint": "6.7.2", 46 | "eslint-plugin-vue": "6.2.2", 47 | "html-webpack-plugin": "3.2.0", 48 | "mockjs": "1.0.1-beta3", 49 | "runjs": "4.3.2", 50 | "sass": "1.53.0", 51 | "sass-loader": "8.0.2", 52 | "script-ext-html-webpack-plugin": "2.1.3", 53 | "serve-static": "1.13.2", 54 | "svg-sprite-loader": "4.3.0", 55 | "svgo": "1.3.2", 56 | "vue-template-compiler": "2.7.2" 57 | }, 58 | "browserslist": [ 59 | "> 1%", 60 | "last 2 versions" 61 | ], 62 | "engines": { 63 | "node": ">=8.9", 64 | "npm": ">= 3.0.0" 65 | }, 66 | "license": "MIT" 67 | } 68 | -------------------------------------------------------------------------------- /vue-consul/postcss.config.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | 'plugins': { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | 'autoprefixer': {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /vue-consul/public/blackbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/vue-consul/public/blackbox.png -------------------------------------------------------------------------------- /vue-consul/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/vue-consul/public/favicon.ico -------------------------------------------------------------------------------- /vue-consul/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= webpackConfig.name %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /vue-consul/public/mysql1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/vue-consul/public/mysql1.png -------------------------------------------------------------------------------- /vue-consul/public/mysql2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/vue-consul/public/mysql2.png -------------------------------------------------------------------------------- /vue-consul/public/node1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/vue-consul/public/node1.png -------------------------------------------------------------------------------- /vue-consul/public/node2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/vue-consul/public/node2.png -------------------------------------------------------------------------------- /vue-consul/public/redis1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/vue-consul/public/redis1.png -------------------------------------------------------------------------------- /vue-consul/public/sl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/vue-consul/public/sl.png -------------------------------------------------------------------------------- /vue-consul/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /vue-consul/src/api/article.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function fetchList(query) { 4 | return request({ 5 | url: '/vue-admin-template/article/list', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | export function fetchArticle(id) { 12 | return request({ 13 | url: '/vue-admin-template/article/detail', 14 | method: 'get', 15 | params: { id } 16 | }) 17 | } 18 | 19 | export function fetchPv(pv) { 20 | return request({ 21 | url: '/vue-admin-template/article/pv', 22 | method: 'get', 23 | params: { pv } 24 | }) 25 | } 26 | 27 | export function createArticle(data) { 28 | return request({ 29 | url: '/vue-admin-template/article/create', 30 | method: 'post', 31 | data 32 | }) 33 | } 34 | 35 | export function updateArticle(data) { 36 | return request({ 37 | url: '/vue-admin-template/article/update', 38 | method: 'post', 39 | data 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /vue-consul/src/api/avd.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function getAvdList() { 4 | return request({ 5 | url: '/api/avd/list', 6 | method: 'get' 7 | }) 8 | } 9 | export function getAvdConfig() { 10 | return request({ 11 | url: '/api/avd/config', 12 | method: 'get' 13 | }) 14 | } 15 | 16 | export function postAvdJob(avd_config_dict) { 17 | return request({ 18 | url: '/api/avd/config', 19 | method: 'post', 20 | data: { avd_config_dict } 21 | }) 22 | } 23 | 24 | export function postAvdRun() { 25 | return request({ 26 | url: '/api/avd/run', 27 | method: 'post' 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /vue-consul/src/api/blackbox.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function getAllList(module, company, project, env) { 4 | return request({ 5 | url: '/api/blackbox/alllist', 6 | method: 'get', 7 | params: { module, company, project, env } 8 | }) 9 | } 10 | 11 | export function getAllInfo() { 12 | return request({ 13 | url: '/api/blackbox/service', 14 | method: 'get' 15 | }) 16 | } 17 | export function addService(data) { 18 | return request({ 19 | url: '/api/blackbox/service', 20 | method: 'post', 21 | data 22 | }) 23 | } 24 | export function updateService(del_dict, up_dict) { 25 | return request({ 26 | url: '/api/blackbox/service', 27 | method: 'put', 28 | data: { del_dict, up_dict } 29 | }) 30 | } 31 | export function delService(data) { 32 | return request({ 33 | url: '/api/blackbox/service', 34 | method: 'delete', 35 | data 36 | }) 37 | } 38 | export function getRules() { 39 | return request({ 40 | url: '/api/blackboxcfg/rules', 41 | method: 'get' 42 | }) 43 | } 44 | export function getPconfig() { 45 | return request({ 46 | url: '/api/blackboxcfg/pconfig', 47 | method: 'get' 48 | }) 49 | } 50 | export function getBconfig() { 51 | return request({ 52 | url: '/api/blackboxcfg/bconfig', 53 | method: 'get' 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /vue-consul/src/api/consul.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function getHosts() { 4 | return request({ 5 | url: '/api/consul/hosts', 6 | method: 'get' 7 | }) 8 | } 9 | 10 | export function getServices() { 11 | return request({ 12 | url: '/api/consul/services', 13 | method: 'get' 14 | }) 15 | } 16 | 17 | export function getServicesName() { 18 | return request({ 19 | url: '/api/consul/services_name', 20 | method: 'get' 21 | }) 22 | } 23 | 24 | export function getInstances(service_name) { 25 | return request({ 26 | url: '/api/consul/instances', 27 | method: 'get', 28 | params: { service_name } 29 | }) 30 | } 31 | 32 | export function delSid(sid) { 33 | return request({ 34 | url: '/api/consul/sid', 35 | method: 'delete', 36 | params: { sid } 37 | }) 38 | } 39 | 40 | export function addSid(instance_dict) { 41 | return request({ 42 | url: '/api/consul/sid', 43 | method: 'post', 44 | data: { instance_dict } 45 | }) 46 | } 47 | 48 | export function updateSid(sid, instance_dict) { 49 | return request({ 50 | url: '/api/consul/sid', 51 | method: 'put', 52 | data: { sid, instance_dict } 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /vue-consul/src/api/edit.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function getCloud() { 4 | return request({ 5 | url: '/api/edit/cloud', 6 | method: 'get' 7 | }) 8 | } 9 | export function findGroup(vendor, account, region) { 10 | return request({ 11 | url: '/api/edit/find', 12 | method: 'get', 13 | params: { vendor, account, region } 14 | }) 15 | } 16 | export function PostEditJob(editJob) { 17 | return request({ 18 | url: '/api/edit/commit', 19 | method: 'post', 20 | data: { editJob } 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /vue-consul/src/api/exp.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function getExpList(query_dict) { 4 | return request({ 5 | url: '/api/exp/list', 6 | method: 'get', 7 | params: { query_dict } 8 | }) 9 | } 10 | export function getExpConfig() { 11 | return request({ 12 | url: '/api/exp/config', 13 | method: 'get' 14 | }) 15 | } 16 | 17 | export function postExpJob(exp_config_dict) { 18 | return request({ 19 | url: '/api/exp/config', 20 | method: 'post', 21 | data: { exp_config_dict } 22 | }) 23 | } 24 | 25 | export function postExpIsnotify(isnotify_dict) { 26 | return request({ 27 | url: '/api/exp/isnotify', 28 | method: 'post', 29 | data: { isnotify_dict } 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /vue-consul/src/api/jms.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function getJmsList(query_dict) { 4 | return request({ 5 | url: '/api/jms/list', 6 | method: 'get', 7 | params: { query_dict } 8 | }) 9 | } 10 | export function getJmsConfig() { 11 | return request({ 12 | url: '/api/jms/config', 13 | method: 'get' 14 | }) 15 | } 16 | 17 | export function postJmsConfig(jms_config) { 18 | return request({ 19 | url: '/api/jms/config', 20 | method: 'post', 21 | data: { jms_config } 22 | }) 23 | } 24 | 25 | export function postJmsSwitch(switch_dict) { 26 | return request({ 27 | url: '/api/jms/switch', 28 | method: 'post', 29 | data: { switch_dict } 30 | }) 31 | } 32 | 33 | export function postJmsSync(jms_sync) { 34 | return request({ 35 | url: '/api/jms/sync', 36 | method: 'post', 37 | timeout: 600 * 1000, 38 | data: { jms_sync } 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /vue-consul/src/api/ldap.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function setldap(data) { 4 | return request({ 5 | url: '/api/ldap/config', 6 | method: 'post', 7 | data: data 8 | }) 9 | } 10 | export function getLdap() { 11 | return request({ 12 | url: '/api/ldap/config', 13 | method: 'get' 14 | }) 15 | } 16 | export function delLdap() { 17 | return request({ 18 | url: '/api/ldap/config', 19 | method: 'delete' 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /vue-consul/src/api/login.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function logo() { 4 | return request({ 5 | url: '/api/login/logo', 6 | method: 'get' 7 | }) 8 | } 9 | 10 | export function postnologo(height) { 11 | return request({ 12 | url: '/api/login/nologo', 13 | method: 'post', 14 | data: { height } 15 | }) 16 | } 17 | 18 | export function rebig() { 19 | return request({ 20 | url: '/api/login/rebig', 21 | method: 'post' 22 | }) 23 | } 24 | 25 | export function resmall() { 26 | return request({ 27 | url: '/api/login/resmall', 28 | method: 'post' 29 | }) 30 | } 31 | export function rebgimg() { 32 | return request({ 33 | url: '/api/login/rebgimg', 34 | method: 'post' 35 | }) 36 | } 37 | export function retitle() { 38 | return request({ 39 | url: '/api/login/retitle', 40 | method: 'post' 41 | }) 42 | } 43 | 44 | export function getbgimg() { 45 | return request({ 46 | url: '/api/login/bgimg', 47 | method: 'get' 48 | }) 49 | } 50 | 51 | export function getitle() { 52 | return request({ 53 | url: '/api/login/title', 54 | method: 'get' 55 | }) 56 | } 57 | 58 | export function postitle(title) { 59 | return request({ 60 | url: '/api/login/title', 61 | method: 'post', 62 | data: { title } 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /vue-consul/src/api/node-exporter.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function getAllJobs(query_dict) { 4 | return request({ 5 | url: '/api/jobs', 6 | method: 'get', 7 | params: { query_dict } 8 | }) 9 | } 10 | 11 | export function PostJob(job_dict) { 12 | return request({ 13 | url: '/api/jobs', 14 | method: 'post', 15 | data: { job_dict } 16 | }) 17 | } 18 | 19 | export function DelJob(job_id) { 20 | return request({ 21 | url: '/api/jobs', 22 | method: 'delete', 23 | params: { job_id } 24 | }) 25 | } 26 | 27 | export function getGroup(job_id) { 28 | return request({ 29 | url: '/api/nodes/group', 30 | method: 'get', 31 | params: { job_id } 32 | }) 33 | } 34 | 35 | export function getResList(job_id) { 36 | return request({ 37 | url: '/api/nodes/res', 38 | method: 'get', 39 | params: { job_id } 40 | }) 41 | } 42 | 43 | export function getJobEcs() { 44 | return request({ 45 | url: '/api/nodes/jobecs', 46 | method: 'get' 47 | }) 48 | } 49 | export function getServicesList() { 50 | return request({ 51 | url: '/api/nodes/ecs_services', 52 | method: 'get' 53 | }) 54 | } 55 | export function getConfig(services_dict) { 56 | return request({ 57 | url: '/api/nodes/config', 58 | method: 'post', 59 | data: { services_dict } 60 | }) 61 | } 62 | export function getRules() { 63 | return request({ 64 | url: '/api/nodes/rules', 65 | method: 'get' 66 | }) 67 | } 68 | 69 | export function postCstEcs(cst_ecs_dict) { 70 | return request({ 71 | url: '/api/nodes/cstecs', 72 | method: 'post', 73 | data: { cst_ecs_dict } 74 | }) 75 | } 76 | 77 | export function getCstEcsConfig(iid) { 78 | return request({ 79 | url: '/api/nodes/cstecsconf', 80 | method: 'get', 81 | params: { iid } 82 | }) 83 | } 84 | 85 | export function getCstEcsList(jobecs_name, checked) { 86 | return request({ 87 | url: '/api/nodes/cstecslist', 88 | method: 'get', 89 | params: { jobecs_name, checked } 90 | }) 91 | } 92 | -------------------------------------------------------------------------------- /vue-consul/src/api/rds.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function getResList(job_id) { 4 | return request({ 5 | url: '/api/nodes/res', 6 | method: 'get', 7 | params: { job_id } 8 | }) 9 | } 10 | 11 | export function getJobRds() { 12 | return request({ 13 | url: '/api/rds/jobrds', 14 | method: 'get' 15 | }) 16 | } 17 | 18 | export function getRdsServicesList() { 19 | return request({ 20 | url: '/api/rds/rds_services', 21 | method: 'get' 22 | }) 23 | } 24 | 25 | export function getRdsConfig(services_dict) { 26 | return request({ 27 | url: '/api/rds/rdspconfig', 28 | method: 'post', 29 | data: { services_dict } 30 | }) 31 | } 32 | export function getRdsRules() { 33 | return request({ 34 | url: '/api/rds/rdsrules', 35 | method: 'get' 36 | }) 37 | } 38 | 39 | export function postCstRds(cst_rds_dict) { 40 | return request({ 41 | url: '/api/rds/cstrds', 42 | method: 'post', 43 | data: { cst_rds_dict } 44 | }) 45 | } 46 | 47 | export function getCstRdsConfig(iid) { 48 | return request({ 49 | url: '/api/rds/cstrdsconf', 50 | method: 'get', 51 | params: { iid } 52 | }) 53 | } 54 | 55 | export function getCstRdsList(jobrds_name, checked) { 56 | return request({ 57 | url: '/api/rds/cstrdslist', 58 | method: 'get', 59 | params: { jobrds_name, checked } 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /vue-consul/src/api/redis.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function getResList(job_id) { 4 | return request({ 5 | url: '/api/nodes/res', 6 | method: 'get', 7 | params: { job_id } 8 | }) 9 | } 10 | 11 | export function getJobRedis() { 12 | return request({ 13 | url: '/api/redis/jobredis', 14 | method: 'get' 15 | }) 16 | } 17 | 18 | export function getRedisServicesList() { 19 | return request({ 20 | url: '/api/redis/redis_services', 21 | method: 'get' 22 | }) 23 | } 24 | 25 | export function getRedisConfig(services_dict) { 26 | return request({ 27 | url: '/api/redis/redispconfig', 28 | method: 'post', 29 | data: { services_dict } 30 | }) 31 | } 32 | export function getRedisRules() { 33 | return request({ 34 | url: '/api/redis/redisrules', 35 | method: 'get' 36 | }) 37 | } 38 | 39 | export function postCstRedis(cst_redis_dict) { 40 | return request({ 41 | url: '/api/redis/cstredis', 42 | method: 'post', 43 | data: { cst_redis_dict } 44 | }) 45 | } 46 | 47 | export function getCstRedisConfig(iid) { 48 | return request({ 49 | url: '/api/redis/cstredisconf', 50 | method: 'get', 51 | params: { iid } 52 | }) 53 | } 54 | 55 | export function getCstRedisList(jobredis_name, checked) { 56 | return request({ 57 | url: '/api/redis/cstredislist', 58 | method: 'get', 59 | params: { jobredis_name, checked } 60 | }) 61 | } 62 | -------------------------------------------------------------------------------- /vue-consul/src/api/selfnode.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function getAllList(vendor, account, region, group) { 4 | return request({ 5 | url: '/api/selfnode/alllist', 6 | method: 'get', 7 | params: { vendor, account, region, group } 8 | }) 9 | } 10 | 11 | export function getAllInfo() { 12 | return request({ 13 | url: '/api/selfnode/service', 14 | method: 'get' 15 | }) 16 | } 17 | export function addService(data) { 18 | return request({ 19 | url: '/api/selfnode/service', 20 | method: 'post', 21 | data 22 | }) 23 | } 24 | export function updateService(del_dict, up_dict) { 25 | return request({ 26 | url: '/api/selfnode/service', 27 | method: 'put', 28 | data: { del_dict, up_dict } 29 | }) 30 | } 31 | export function delService(data) { 32 | return request({ 33 | url: '/api/selfnode/service', 34 | method: 'delete', 35 | data 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /vue-consul/src/api/selfrds.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function getAllList(vendor, account, region, group) { 4 | return request({ 5 | url: '/api/selfrds/alllist', 6 | method: 'get', 7 | params: { vendor, account, region, group } 8 | }) 9 | } 10 | 11 | export function getAllInfo() { 12 | return request({ 13 | url: '/api/selfrds/service', 14 | method: 'get' 15 | }) 16 | } 17 | export function addService(data) { 18 | return request({ 19 | url: '/api/selfrds/service', 20 | method: 'post', 21 | data 22 | }) 23 | } 24 | export function updateService(del_dict, up_dict) { 25 | return request({ 26 | url: '/api/selfrds/service', 27 | method: 'put', 28 | data: { del_dict, up_dict } 29 | }) 30 | } 31 | export function delService(data) { 32 | return request({ 33 | url: '/api/selfrds/service', 34 | method: 'delete', 35 | data 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /vue-consul/src/api/selfredis.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function getAllList(vendor, account, region, group) { 4 | return request({ 5 | url: '/api/selfredis/alllist', 6 | method: 'get', 7 | params: { vendor, account, region, group } 8 | }) 9 | } 10 | 11 | export function getAllInfo() { 12 | return request({ 13 | url: '/api/selfredis/service', 14 | method: 'get' 15 | }) 16 | } 17 | export function addService(data) { 18 | return request({ 19 | url: '/api/selfredis/service', 20 | method: 'post', 21 | data 22 | }) 23 | } 24 | export function updateService(del_dict, up_dict) { 25 | return request({ 26 | url: '/api/selfredis/service', 27 | method: 'put', 28 | data: { del_dict, up_dict } 29 | }) 30 | } 31 | export function delService(data) { 32 | return request({ 33 | url: '/api/selfredis/service', 34 | method: 'delete', 35 | data 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /vue-consul/src/api/table.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getList(params) { 4 | return request({ 5 | url: '/vue-admin-template/table/list', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /vue-consul/src/api/user-ops.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function login(data) { 4 | return request({ 5 | url: '/api/user/login', 6 | method: 'post', 7 | data 8 | }) 9 | } 10 | 11 | export function getInfo(token) { 12 | return request({ 13 | url: '/api/user/info', 14 | method: 'get', 15 | params: { token } 16 | }) 17 | } 18 | 19 | export function logout() { 20 | return request({ 21 | url: '/api/user/logout', 22 | method: 'post' 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /vue-consul/src/api/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request-ops' 2 | 3 | export function login(data) { 4 | return request({ 5 | url: '/vue-admin-template/user/login', 6 | method: 'post', 7 | data 8 | }) 9 | } 10 | 11 | export function getInfo(token) { 12 | return request({ 13 | url: '/vue-admin-template/user/info', 14 | method: 'get', 15 | params: { token } 16 | }) 17 | } 18 | 19 | export function logout() { 20 | return request({ 21 | url: '/vue-admin-template/user/logout', 22 | method: 'post' 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /vue-consul/src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/vue-consul/src/assets/404_images/404.png -------------------------------------------------------------------------------- /vue-consul/src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/vue-consul/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /vue-consul/src/assets/login_images/SLH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/vue-consul/src/assets/login_images/SLH.png -------------------------------------------------------------------------------- /vue-consul/src/assets/login_images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/vue-consul/src/assets/login_images/bg.png -------------------------------------------------------------------------------- /vue-consul/src/assets/login_images/tensuns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starsliao/TenSunS/0d8568904d8e3375167e88805bf826d18ca4dcf7/vue-consul/src/assets/login_images/tensuns.png -------------------------------------------------------------------------------- /vue-consul/src/components/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 65 | 66 | 79 | -------------------------------------------------------------------------------- /vue-consul/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | 33 | 45 | -------------------------------------------------------------------------------- /vue-consul/src/components/Pagination/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 92 | 93 | 102 | -------------------------------------------------------------------------------- /vue-consul/src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 47 | 48 | 63 | -------------------------------------------------------------------------------- /vue-consul/src/directive/waves/index.js: -------------------------------------------------------------------------------- 1 | import waves from './waves' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('waves', waves) 5 | } 6 | 7 | if (window.Vue) { 8 | window.waves = waves 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | waves.install = install 13 | export default waves 14 | -------------------------------------------------------------------------------- /vue-consul/src/directive/waves/waves.css: -------------------------------------------------------------------------------- 1 | .waves-ripple { 2 | position: absolute; 3 | border-radius: 100%; 4 | background-color: rgba(0, 0, 0, 0.15); 5 | background-clip: padding-box; 6 | pointer-events: none; 7 | -webkit-user-select: none; 8 | -moz-user-select: none; 9 | -ms-user-select: none; 10 | user-select: none; 11 | -webkit-transform: scale(0); 12 | -ms-transform: scale(0); 13 | transform: scale(0); 14 | opacity: 1; 15 | } 16 | 17 | .waves-ripple.z-active { 18 | opacity: 0; 19 | -webkit-transform: scale(2); 20 | -ms-transform: scale(2); 21 | transform: scale(2); 22 | -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; 23 | transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; 24 | transition: opacity 1.2s ease-out, transform 0.6s ease-out; 25 | transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out; 26 | } -------------------------------------------------------------------------------- /vue-consul/src/directive/waves/waves.js: -------------------------------------------------------------------------------- 1 | import './waves.css' 2 | 3 | const context = '@@wavesContext' 4 | 5 | function handleClick(el, binding) { 6 | function handle(e) { 7 | const customOpts = Object.assign({}, binding.value) 8 | const opts = Object.assign({ 9 | ele: el, // 波纹作用元素 10 | type: 'hit', // hit 点击位置扩散 center中心点扩展 11 | color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色 12 | }, 13 | customOpts 14 | ) 15 | const target = opts.ele 16 | if (target) { 17 | target.style.position = 'relative' 18 | target.style.overflow = 'hidden' 19 | const rect = target.getBoundingClientRect() 20 | let ripple = target.querySelector('.waves-ripple') 21 | if (!ripple) { 22 | ripple = document.createElement('span') 23 | ripple.className = 'waves-ripple' 24 | ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px' 25 | target.appendChild(ripple) 26 | } else { 27 | ripple.className = 'waves-ripple' 28 | } 29 | switch (opts.type) { 30 | case 'center': 31 | ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px' 32 | ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px' 33 | break 34 | default: 35 | ripple.style.top = 36 | (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop || 37 | document.body.scrollTop) + 'px' 38 | ripple.style.left = 39 | (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft || 40 | document.body.scrollLeft) + 'px' 41 | } 42 | ripple.style.backgroundColor = opts.color 43 | ripple.className = 'waves-ripple z-active' 44 | return false 45 | } 46 | } 47 | 48 | if (!el[context]) { 49 | el[context] = { 50 | removeHandle: handle 51 | } 52 | } else { 53 | el[context].removeHandle = handle 54 | } 55 | 56 | return handle 57 | } 58 | 59 | export default { 60 | bind(el, binding) { 61 | el.addEventListener('click', handleClick(el, binding), false) 62 | }, 63 | update(el, binding) { 64 | el.removeEventListener('click', el[context].removeHandle, false) 65 | el.addEventListener('click', handleClick(el, binding), false) 66 | }, 67 | unbind(el) { 68 | el.removeEventListener('click', el[context].removeHandle, false) 69 | el[context] = null 70 | delete el[context] 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /vue-consul/src/filters/index.js: -------------------------------------------------------------------------------- 1 | // import parseTime, formatTime and set to filter 2 | export { parseTime, formatTime } from '@/utils' 3 | 4 | /** 5 | * Show plural label if time is plural number 6 | * @param {number} time 7 | * @param {string} label 8 | * @return {string} 9 | */ 10 | function pluralize(time, label) { 11 | if (time === 1) { 12 | return time + label 13 | } 14 | return time + label + 's' 15 | } 16 | 17 | /** 18 | * @param {number} time 19 | */ 20 | export function timeAgo(time) { 21 | const between = Date.now() / 1000 - Number(time) 22 | if (between < 3600) { 23 | return pluralize(~~(between / 60), ' minute') 24 | } else if (between < 86400) { 25 | return pluralize(~~(between / 3600), ' hour') 26 | } else { 27 | return pluralize(~~(between / 86400), ' day') 28 | } 29 | } 30 | 31 | /** 32 | * Number formatting 33 | * like 10000 => 10k 34 | * @param {number} num 35 | * @param {number} digits 36 | */ 37 | export function numberFormatter(num, digits) { 38 | const si = [ 39 | { value: 1E18, symbol: 'E' }, 40 | { value: 1E15, symbol: 'P' }, 41 | { value: 1E12, symbol: 'T' }, 42 | { value: 1E9, symbol: 'G' }, 43 | { value: 1E6, symbol: 'M' }, 44 | { value: 1E3, symbol: 'k' } 45 | ] 46 | for (let i = 0; i < si.length; i++) { 47 | if (num >= si[i].value) { 48 | return (num / si[i].value).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol 49 | } 50 | } 51 | return num.toString() 52 | } 53 | 54 | /** 55 | * 10000 => "10,000" 56 | * @param {number} num 57 | */ 58 | export function toThousandFilter(num) { 59 | return (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ',')) 60 | } 61 | 62 | /** 63 | * Upper case first char 64 | * @param {String} string 65 | */ 66 | export function uppercaseFirst(string) { 67 | return string.charAt(0).toUpperCase() + string.slice(1) 68 | } 69 | -------------------------------------------------------------------------------- /vue-consul/src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon'// svg component 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const req = require.context('./svg', false, /\.svg$/) 8 | const requireAll = requireContext => requireContext.keys().map(requireContext) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /vue-consul/src/icons/svg/dashboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-consul/src/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-consul/src/icons/svg/eye-open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-consul/src/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-consul/src/icons/svg/form.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-consul/src/icons/svg/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-consul/src/icons/svg/nested.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-consul/src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-consul/src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-consul/src/icons/svg/tree.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-consul/src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue-consul/src/icons/svgo.yml: -------------------------------------------------------------------------------- 1 | # replace default config 2 | 3 | # multipass: true 4 | # full: true 5 | 6 | plugins: 7 | 8 | # - name 9 | # 10 | # or: 11 | # - name: false 12 | # - name: true 13 | # 14 | # or: 15 | # - name: 16 | # param1: 1 17 | # param2: 2 18 | 19 | - removeAttrs: 20 | attrs: 21 | - 'fill' 22 | - 'fill-rule' 23 | -------------------------------------------------------------------------------- /vue-consul/src/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 32 | 33 | 41 | -------------------------------------------------------------------------------- /vue-consul/src/layout/components/Sidebar/FixiOSBug.js: -------------------------------------------------------------------------------- 1 | export default { 2 | computed: { 3 | device() { 4 | return this.$store.state.app.device 5 | } 6 | }, 7 | mounted() { 8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug 9 | // https://github.com/PanJiaChen/vue-element-admin/issues/1135 10 | this.fixBugIniOS() 11 | }, 12 | methods: { 13 | fixBugIniOS() { 14 | const $subMenu = this.$refs.subMenu 15 | if ($subMenu) { 16 | const handleMouseleave = $subMenu.handleMouseleave 17 | $subMenu.handleMouseleave = (e) => { 18 | if (this.device === 'mobile') { 19 | return 20 | } 21 | handleMouseleave(e) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vue-consul/src/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 42 | -------------------------------------------------------------------------------- /vue-consul/src/layout/components/Sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 44 | -------------------------------------------------------------------------------- /vue-consul/src/layout/components/Sidebar/Logo.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 33 | 34 | 83 | -------------------------------------------------------------------------------- /vue-consul/src/layout/components/Sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 96 | -------------------------------------------------------------------------------- /vue-consul/src/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 57 | -------------------------------------------------------------------------------- /vue-consul/src/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar' 2 | export { default as Sidebar } from './Sidebar' 3 | export { default as AppMain } from './AppMain' 4 | -------------------------------------------------------------------------------- /vue-consul/src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 52 | 53 | 94 | -------------------------------------------------------------------------------- /vue-consul/src/layout/mixin/ResizeHandler.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | const { body } = document 4 | const WIDTH = 992 // refer to Bootstrap's responsive design 5 | 6 | export default { 7 | watch: { 8 | $route(route) { 9 | if (this.device === 'mobile' && this.sidebar.opened) { 10 | store.dispatch('app/closeSideBar', { withoutAnimation: false }) 11 | } 12 | } 13 | }, 14 | beforeMount() { 15 | window.addEventListener('resize', this.$_resizeHandler) 16 | }, 17 | beforeDestroy() { 18 | window.removeEventListener('resize', this.$_resizeHandler) 19 | }, 20 | mounted() { 21 | const isMobile = this.$_isMobile() 22 | if (isMobile) { 23 | store.dispatch('app/toggleDevice', 'mobile') 24 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 25 | } 26 | }, 27 | methods: { 28 | // use $_ for mixins properties 29 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential 30 | $_isMobile() { 31 | const rect = body.getBoundingClientRect() 32 | return rect.width - 1 < WIDTH 33 | }, 34 | $_resizeHandler() { 35 | if (!document.hidden) { 36 | const isMobile = this.$_isMobile() 37 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') 38 | 39 | if (isMobile) { 40 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /vue-consul/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import 'normalize.css/normalize.css' // A modern alternative to CSS resets 4 | 5 | import ElementUI from 'element-ui' 6 | import 'element-ui/lib/theme-chalk/index.css' 7 | import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n 8 | 9 | import '@/styles/index.scss' // global css 10 | 11 | import App from './App' 12 | import store from './store' 13 | import router from './router' 14 | 15 | import '@/icons' // icon 16 | import '@/permission' // permission control 17 | import * as filters from './filters' // global filters 18 | 19 | import VueHighlightJS from 'vue-highlightjs' 20 | import 'highlight.js/styles/xt256.css' 21 | Vue.use(VueHighlightJS) 22 | import VueClipboard from 'vue-clipboard2' 23 | Vue.use(VueClipboard) 24 | 25 | /** 26 | * If you don't want to use mock-server 27 | * you want to use MockJs for mock api 28 | * you can execute: mockXHR() 29 | * 30 | * Currently MockJs will be used in the production environment, 31 | * please remove it before going online ! ! ! 32 | */ 33 | if (process.env.NODE_ENV === 'production') { 34 | const { mockXHR } = require('../mock') 35 | mockXHR() 36 | } 37 | 38 | // set ElementUI lang to EN 39 | Vue.use(ElementUI, { locale }) 40 | // 如果想要中文版 element-ui,按如下方式声明 41 | // Vue.use(ElementUI) 42 | 43 | // register global utility filters 44 | Object.keys(filters).forEach(key => { 45 | Vue.filter(key, filters[key]) 46 | }) 47 | 48 | Vue.config.productionTip = false 49 | Vue.prototype.VER = 'v1.2.1' 50 | 51 | new Vue({ 52 | el: '#app', 53 | router, 54 | store, 55 | render: h => h(App) 56 | }) 57 | -------------------------------------------------------------------------------- /vue-consul/src/permission.js: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import store from './store' 3 | import { Message } from 'element-ui' 4 | import NProgress from 'nprogress' // progress bar 5 | import 'nprogress/nprogress.css' // progress bar style 6 | import { getToken } from '@/utils/auth' // get token from cookie 7 | import getPageTitle from '@/utils/get-page-title' 8 | 9 | NProgress.configure({ showSpinner: false }) // NProgress Configuration 10 | 11 | const whiteList = ['/login'] // no redirect whitelist 12 | 13 | router.beforeEach(async(to, from, next) => { 14 | // start progress bar 15 | NProgress.start() 16 | 17 | // set page title 18 | document.title = getPageTitle(to.meta.title) 19 | 20 | // determine whether the user has logged in 21 | const hasToken = getToken() 22 | 23 | if (hasToken) { 24 | if (to.path === '/login') { 25 | // if is logged in, redirect to the home page 26 | next({ path: '/' }) 27 | NProgress.done() 28 | } else { 29 | const hasGetUserInfo = store.getters.name 30 | if (hasGetUserInfo) { 31 | next() 32 | } else { 33 | try { 34 | // get user info 35 | await store.dispatch('user/getInfo') 36 | 37 | next() 38 | } catch (error) { 39 | // remove token and go to login page to re-login 40 | await store.dispatch('user/resetToken') 41 | Message.error(error || 'Has Error') 42 | next(`/login?redirect=${to.path}`) 43 | NProgress.done() 44 | } 45 | } 46 | } 47 | } else { 48 | /* has no token*/ 49 | 50 | if (whiteList.indexOf(to.path) !== -1) { 51 | // in the free login whitelist, go directly 52 | next() 53 | } else { 54 | // other pages that do not have permission to access are redirected to the login page. 55 | next(`/login?redirect=${to.path}`) 56 | NProgress.done() 57 | } 58 | } 59 | }) 60 | 61 | router.afterEach(() => { 62 | // finish progress bar 63 | NProgress.done() 64 | }) 65 | -------------------------------------------------------------------------------- /vue-consul/src/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | title: 'TenSunS', 4 | 5 | /** 6 | * @type {boolean} true | false 7 | * @description Whether fix the header 8 | */ 9 | fixedHeader: false, 10 | 11 | /** 12 | * @type {boolean} true | false 13 | * @description Whether show the logo in sidebar 14 | */ 15 | sidebarLogo: false 16 | } 17 | -------------------------------------------------------------------------------- /vue-consul/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | device: state => state.app.device, 4 | token: state => state.user.token, 5 | avatar: state => state.user.avatar, 6 | name: state => state.user.name 7 | } 8 | export default getters 9 | -------------------------------------------------------------------------------- /vue-consul/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import getters from './getters' 4 | import app from './modules/app' 5 | import settings from './modules/settings' 6 | import user from './modules/user' 7 | 8 | Vue.use(Vuex) 9 | 10 | const store = new Vuex.Store({ 11 | modules: { 12 | app, 13 | settings, 14 | user 15 | }, 16 | getters 17 | }) 18 | 19 | export default store 20 | -------------------------------------------------------------------------------- /vue-consul/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const state = { 4 | sidebar: { 5 | opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, 6 | withoutAnimation: false 7 | }, 8 | device: 'desktop' 9 | } 10 | 11 | const mutations = { 12 | TOGGLE_SIDEBAR: state => { 13 | state.sidebar.opened = !state.sidebar.opened 14 | state.sidebar.withoutAnimation = false 15 | if (state.sidebar.opened) { 16 | Cookies.set('sidebarStatus', 1) 17 | } else { 18 | Cookies.set('sidebarStatus', 0) 19 | } 20 | }, 21 | CLOSE_SIDEBAR: (state, withoutAnimation) => { 22 | Cookies.set('sidebarStatus', 0) 23 | state.sidebar.opened = false 24 | state.sidebar.withoutAnimation = withoutAnimation 25 | }, 26 | TOGGLE_DEVICE: (state, device) => { 27 | state.device = device 28 | } 29 | } 30 | 31 | const actions = { 32 | toggleSideBar({ commit }) { 33 | commit('TOGGLE_SIDEBAR') 34 | }, 35 | closeSideBar({ commit }, { withoutAnimation }) { 36 | commit('CLOSE_SIDEBAR', withoutAnimation) 37 | }, 38 | toggleDevice({ commit }, device) { 39 | commit('TOGGLE_DEVICE', device) 40 | } 41 | } 42 | 43 | export default { 44 | namespaced: true, 45 | state, 46 | mutations, 47 | actions 48 | } 49 | -------------------------------------------------------------------------------- /vue-consul/src/store/modules/settings.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | 3 | const { showSettings, fixedHeader, sidebarLogo } = defaultSettings 4 | 5 | const state = { 6 | showSettings: showSettings, 7 | fixedHeader: fixedHeader, 8 | sidebarLogo: sidebarLogo 9 | } 10 | 11 | const mutations = { 12 | CHANGE_SETTING: (state, { key, value }) => { 13 | // eslint-disable-next-line no-prototype-builtins 14 | if (state.hasOwnProperty(key)) { 15 | state[key] = value 16 | } 17 | } 18 | } 19 | 20 | const actions = { 21 | changeSetting({ commit }, data) { 22 | commit('CHANGE_SETTING', data) 23 | } 24 | } 25 | 26 | export default { 27 | namespaced: true, 28 | state, 29 | mutations, 30 | actions 31 | } 32 | 33 | -------------------------------------------------------------------------------- /vue-consul/src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { login, logout, getInfo } from '@/api/user-ops' 2 | import { getToken, setToken, removeToken } from '@/utils/auth' 3 | import { resetRouter } from '@/router' 4 | 5 | const getDefaultState = () => { 6 | return { 7 | token: getToken(), 8 | name: '', 9 | avatar: '' 10 | } 11 | } 12 | 13 | const state = getDefaultState() 14 | 15 | const mutations = { 16 | RESET_STATE: (state) => { 17 | Object.assign(state, getDefaultState()) 18 | }, 19 | SET_TOKEN: (state, token) => { 20 | state.token = token 21 | }, 22 | SET_NAME: (state, name) => { 23 | state.name = name 24 | }, 25 | SET_AVATAR: (state, avatar) => { 26 | state.avatar = avatar 27 | } 28 | } 29 | 30 | const actions = { 31 | // user login 32 | login({ commit }, userInfo) { 33 | const { username, password, Ldapchecked } = userInfo 34 | return new Promise((resolve, reject) => { 35 | login({ username: username.trim(), password: password, ldap: Ldapchecked }).then(response => { 36 | const { data } = response 37 | commit('SET_TOKEN', data.token) 38 | setToken(data.token) 39 | resolve() 40 | }).catch(error => { 41 | reject(error) 42 | }) 43 | }) 44 | }, 45 | 46 | // get user info 47 | getInfo({ commit, state }) { 48 | return new Promise((resolve, reject) => { 49 | getInfo(state.token).then(response => { 50 | const { data } = response 51 | 52 | if (!data) { 53 | return reject('Verification failed, please Login again.') 54 | } 55 | 56 | const { name, avatar } = data 57 | 58 | commit('SET_NAME', name) 59 | commit('SET_AVATAR', avatar) 60 | resolve(data) 61 | }).catch(error => { 62 | reject(error) 63 | }) 64 | }) 65 | }, 66 | 67 | // user logout 68 | logout({ commit, state }) { 69 | return new Promise((resolve, reject) => { 70 | logout(state.token).then(() => { 71 | removeToken() // must remove token first 72 | resetRouter() 73 | commit('RESET_STATE') 74 | resolve() 75 | }).catch(error => { 76 | reject(error) 77 | }) 78 | }) 79 | }, 80 | 81 | // remove token 82 | resetToken({ commit }) { 83 | return new Promise(resolve => { 84 | removeToken() // must remove token first 85 | commit('RESET_STATE') 86 | resolve() 87 | }) 88 | } 89 | } 90 | 91 | export default { 92 | namespaced: true, 93 | state, 94 | mutations, 95 | actions 96 | } 97 | 98 | -------------------------------------------------------------------------------- /vue-consul/src/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | // cover some element-ui styles 2 | 3 | .el-breadcrumb__inner, 4 | .el-breadcrumb__inner a { 5 | font-weight: 400 !important; 6 | } 7 | 8 | .el-upload { 9 | input[type="file"] { 10 | display: none !important; 11 | } 12 | } 13 | 14 | .el-upload__input { 15 | display: none; 16 | } 17 | 18 | 19 | // to fixed https://github.com/ElemeFE/element/issues/2461 20 | .el-dialog { 21 | transform: none; 22 | left: 0; 23 | position: relative; 24 | margin: 0 auto; 25 | } 26 | 27 | // refine element ui upload 28 | .upload-container { 29 | .el-upload { 30 | width: 100%; 31 | 32 | .el-upload-dragger { 33 | width: 100%; 34 | height: 200px; 35 | } 36 | } 37 | } 38 | 39 | // dropdown 40 | .el-dropdown-menu { 41 | a { 42 | display: block 43 | } 44 | } 45 | 46 | // to fix el-date-picker css style 47 | .el-range-separator { 48 | box-sizing: content-box; 49 | } 50 | -------------------------------------------------------------------------------- /vue-consul/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | @import './mixin.scss'; 3 | @import './transition.scss'; 4 | @import './element-ui.scss'; 5 | @import './sidebar.scss'; 6 | 7 | body { 8 | height: 100%; 9 | -moz-osx-font-smoothing: grayscale; 10 | -webkit-font-smoothing: antialiased; 11 | text-rendering: optimizeLegibility; 12 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 13 | } 14 | 15 | label { 16 | font-weight: 700; 17 | } 18 | 19 | html { 20 | height: 100%; 21 | box-sizing: border-box; 22 | } 23 | 24 | #app { 25 | height: 100%; 26 | } 27 | 28 | *, 29 | *:before, 30 | *:after { 31 | box-sizing: inherit; 32 | } 33 | 34 | a:focus, 35 | a:active { 36 | outline: none; 37 | } 38 | 39 | a, 40 | a:focus, 41 | a:hover { 42 | cursor: pointer; 43 | color: inherit; 44 | text-decoration: none; 45 | } 46 | 47 | div:focus { 48 | outline: none; 49 | } 50 | 51 | .clearfix { 52 | &:after { 53 | visibility: hidden; 54 | display: block; 55 | font-size: 0; 56 | content: " "; 57 | clear: both; 58 | height: 0; 59 | } 60 | } 61 | 62 | // main-container global css 63 | .app-container { 64 | padding: 20px; 65 | } 66 | -------------------------------------------------------------------------------- /vue-consul/src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | @mixin scrollBar { 10 | &::-webkit-scrollbar-track-piece { 11 | background: #d3dce6; 12 | } 13 | 14 | &::-webkit-scrollbar { 15 | width: 6px; 16 | } 17 | 18 | &::-webkit-scrollbar-thumb { 19 | background: #99a9bf; 20 | border-radius: 20px; 21 | } 22 | } 23 | 24 | @mixin relative { 25 | position: relative; 26 | width: 100%; 27 | height: 100%; 28 | } 29 | -------------------------------------------------------------------------------- /vue-consul/src/styles/transition.scss: -------------------------------------------------------------------------------- 1 | // global transition css 2 | 3 | /* fade */ 4 | .fade-enter-active, 5 | .fade-leave-active { 6 | transition: opacity 0.28s; 7 | } 8 | 9 | .fade-enter, 10 | .fade-leave-active { 11 | opacity: 0; 12 | } 13 | 14 | /* fade-transform */ 15 | .fade-transform-leave-active, 16 | .fade-transform-enter-active { 17 | transition: all .5s; 18 | } 19 | 20 | .fade-transform-enter { 21 | opacity: 0; 22 | transform: translateX(-30px); 23 | } 24 | 25 | .fade-transform-leave-to { 26 | opacity: 0; 27 | transform: translateX(30px); 28 | } 29 | 30 | /* breadcrumb transition */ 31 | .breadcrumb-enter-active, 32 | .breadcrumb-leave-active { 33 | transition: all .5s; 34 | } 35 | 36 | .breadcrumb-enter, 37 | .breadcrumb-leave-active { 38 | opacity: 0; 39 | transform: translateX(20px); 40 | } 41 | 42 | .breadcrumb-move { 43 | transition: all .5s; 44 | } 45 | 46 | .breadcrumb-leave-active { 47 | position: absolute; 48 | } 49 | -------------------------------------------------------------------------------- /vue-consul/src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | // sidebar 2 | $menuText:#bfcbd9; 3 | $menuActiveText:#409EFF; 4 | $subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951 5 | 6 | $menuBg:#304156; 7 | $menuHover:#263445; 8 | 9 | $subMenuBg:#1f2d3d; 10 | $subMenuHover:#001528; 11 | 12 | $sideBarWidth: 210px; 13 | 14 | // the :export directive is the magic sauce for webpack 15 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 16 | :export { 17 | menuText: $menuText; 18 | menuActiveText: $menuActiveText; 19 | subMenuActiveText: $subMenuActiveText; 20 | menuBg: $menuBg; 21 | menuHover: $menuHover; 22 | subMenuBg: $subMenuBg; 23 | subMenuHover: $subMenuHover; 24 | sideBarWidth: $sideBarWidth; 25 | } 26 | -------------------------------------------------------------------------------- /vue-consul/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'vue_admin_template_token' 4 | 5 | export function getToken() { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken() { 14 | return Cookies.remove(TokenKey) 15 | } 16 | -------------------------------------------------------------------------------- /vue-consul/src/utils/get-page-title.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | 3 | const title = defaultSettings.title || 'Vue Admin Template' 4 | 5 | export default function getPageTitle(pageTitle) { 6 | if (pageTitle) { 7 | return `${pageTitle} - ${title}` 8 | } 9 | return `${title}` 10 | } 11 | -------------------------------------------------------------------------------- /vue-consul/src/utils/request-ops.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { MessageBox, Message } from 'element-ui' 3 | import store from '@/store' 4 | import { getToken } from '@/utils/auth' 5 | 6 | // create an axios instance 7 | const service = axios.create({ 8 | // baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url 9 | // withCredentials: true, // send cookies when cross-domain requests 10 | baseURL: '', 11 | // 非nginx调试,填写后端的信息。 12 | // baseURL: 'http://192.168.1.120:2026', 13 | timeout: 10000 // request timeout 14 | }) 15 | 16 | // request interceptor 17 | service.interceptors.request.use( 18 | config => { 19 | // do something before request is sent 20 | 21 | if (store.getters.token) { 22 | // let each request carry token 23 | // ['X-Token'] is a custom headers key 24 | // please modify it according to the actual situation 25 | config.headers['Authorization'] = getToken() 26 | } 27 | return config 28 | }, 29 | error => { 30 | // do something with request error 31 | console.log(error) // for debug 32 | return Promise.reject(error) 33 | } 34 | ) 35 | 36 | // response interceptor 37 | service.interceptors.response.use( 38 | /** 39 | * If you want to get http information such as headers or status 40 | * Please return response => response 41 | */ 42 | 43 | /** 44 | * Determine the request status by custom code 45 | * Here is just an example 46 | * You can also judge the status by HTTP Status Code 47 | */ 48 | response => { 49 | const res = response.data 50 | 51 | // if the custom code is not 20000, it is judged as an error. 52 | if (res.code !== 20000) { 53 | Message({ 54 | message: res.data || 'Error', 55 | type: 'error', 56 | duration: 5 * 1000 57 | }) 58 | 59 | // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired; 60 | if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 61 | // to re-login 62 | MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', { 63 | confirmButtonText: 'Re-Login', 64 | cancelButtonText: 'Cancel', 65 | type: 'warning' 66 | }).then(() => { 67 | store.dispatch('user/resetToken').then(() => { 68 | location.reload() 69 | }) 70 | }) 71 | } 72 | return Promise.reject(new Error(res.message || 'Error')) 73 | } else { 74 | return res 75 | } 76 | }, 77 | error => { 78 | console.log('err' + error) // for debug 79 | Message({ 80 | message: error.message, 81 | type: 'error', 82 | duration: 5 * 1000 83 | }) 84 | return Promise.reject(error) 85 | } 86 | ) 87 | 88 | export default service 89 | -------------------------------------------------------------------------------- /vue-consul/src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { MessageBox, Message } from 'element-ui' 3 | import store from '@/store' 4 | import { getToken } from '@/utils/auth' 5 | 6 | // create an axios instance 7 | const service = axios.create({ 8 | baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url 9 | // withCredentials: true, // send cookies when cross-domain requests 10 | timeout: 5000 // request timeout 11 | }) 12 | 13 | // request interceptor 14 | service.interceptors.request.use( 15 | config => { 16 | // do something before request is sent 17 | 18 | if (store.getters.token) { 19 | // let each request carry token 20 | // ['X-Token'] is a custom headers key 21 | // please modify it according to the actual situation 22 | config.headers['X-Token'] = getToken() 23 | } 24 | return config 25 | }, 26 | error => { 27 | // do something with request error 28 | console.log(error) // for debug 29 | return Promise.reject(error) 30 | } 31 | ) 32 | 33 | // response interceptor 34 | service.interceptors.response.use( 35 | /** 36 | * If you want to get http information such as headers or status 37 | * Please return response => response 38 | */ 39 | 40 | /** 41 | * Determine the request status by custom code 42 | * Here is just an example 43 | * You can also judge the status by HTTP Status Code 44 | */ 45 | response => { 46 | const res = response.data 47 | 48 | // if the custom code is not 20000, it is judged as an error. 49 | if (res.code !== 20000) { 50 | Message({ 51 | message: res.message || 'Error', 52 | type: 'error', 53 | duration: 5 * 1000 54 | }) 55 | 56 | // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired; 57 | if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 58 | // to re-login 59 | MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', { 60 | confirmButtonText: 'Re-Login', 61 | cancelButtonText: 'Cancel', 62 | type: 'warning' 63 | }).then(() => { 64 | store.dispatch('user/resetToken').then(() => { 65 | location.reload() 66 | }) 67 | }) 68 | } 69 | return Promise.reject(new Error(res.message || 'Error')) 70 | } else { 71 | return res 72 | } 73 | }, 74 | error => { 75 | console.log('err' + error) // for debug 76 | Message({ 77 | message: error.message, 78 | type: 'error', 79 | duration: 5 * 1000 80 | }) 81 | return Promise.reject(error) 82 | } 83 | ) 84 | 85 | export default service 86 | -------------------------------------------------------------------------------- /vue-consul/src/utils/scroll-to.js: -------------------------------------------------------------------------------- 1 | Math.easeInOutQuad = function(t, b, c, d) { 2 | t /= d / 2 3 | if (t < 1) { 4 | return c / 2 * t * t + b 5 | } 6 | t-- 7 | return -c / 2 * (t * (t - 2) - 1) + b 8 | } 9 | 10 | // requestAnimationFrame for Smart Animating http://goo.gl/sx5sts 11 | var requestAnimFrame = (function() { 12 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) } 13 | })() 14 | 15 | /** 16 | * Because it's so fucking difficult to detect the scrolling element, just move them all 17 | * @param {number} amount 18 | */ 19 | function move(amount) { 20 | document.documentElement.scrollTop = amount 21 | document.body.parentNode.scrollTop = amount 22 | document.body.scrollTop = amount 23 | } 24 | 25 | function position() { 26 | return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop 27 | } 28 | 29 | /** 30 | * @param {number} to 31 | * @param {number} duration 32 | * @param {Function} callback 33 | */ 34 | export function scrollTo(to, duration, callback) { 35 | const start = position() 36 | const change = to - start 37 | const increment = 20 38 | let currentTime = 0 39 | duration = (typeof (duration) === 'undefined') ? 500 : duration 40 | var animateScroll = function() { 41 | // increment the time 42 | currentTime += increment 43 | // find the value with the quadratic in-out easing function 44 | var val = Math.easeInOutQuad(currentTime, start, change, duration) 45 | // move the document.body 46 | move(val) 47 | // do the animation unless its over 48 | if (currentTime < duration) { 49 | requestAnimFrame(animateScroll) 50 | } else { 51 | if (callback && typeof (callback) === 'function') { 52 | // the animation is done so lets callback 53 | callback() 54 | } 55 | } 56 | } 57 | animateScroll() 58 | } 59 | -------------------------------------------------------------------------------- /vue-consul/src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by PanJiaChen on 16/11/18. 3 | */ 4 | 5 | /** 6 | * @param {string} path 7 | * @returns {Boolean} 8 | */ 9 | export function isExternal(path) { 10 | return /^(https?:|mailto:|tel:)/.test(path) 11 | } 12 | 13 | /** 14 | * @param {string} str 15 | * @returns {Boolean} 16 | */ 17 | export function validUsername(str) { 18 | const valid_map = ['admin', 'editor'] 19 | return valid_map.indexOf(str.trim()) >= 0 20 | } 21 | -------------------------------------------------------------------------------- /vue-consul/src/views/blackbox/bconfig.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 42 | 52 | -------------------------------------------------------------------------------- /vue-consul/src/views/blackbox/grafana.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /vue-consul/src/views/blackbox/pconfig.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 42 | 52 | -------------------------------------------------------------------------------- /vue-consul/src/views/blackbox/rules.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 42 | 52 | -------------------------------------------------------------------------------- /vue-consul/src/views/ldap/user.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /vue-consul/src/views/node-exporter/grafana.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /vue-consul/src/views/node-exporter/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /vue-consul/src/views/node-exporter/pconfig.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 66 | 76 | -------------------------------------------------------------------------------- /vue-consul/src/views/node-exporter/rules.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 42 | 52 | -------------------------------------------------------------------------------- /vue-consul/src/views/rds/grafana.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /vue-consul/src/views/rds/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /vue-consul/src/views/rds/rules.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 42 | 52 | -------------------------------------------------------------------------------- /vue-consul/src/views/redis/grafana.vue: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /vue-consul/src/views/redis/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /vue-consul/src/views/redis/rules.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 42 | 52 | -------------------------------------------------------------------------------- /vue-consul/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vue-consul/tests/unit/components/Breadcrumb.spec.js: -------------------------------------------------------------------------------- 1 | import { mount, createLocalVue } from '@vue/test-utils' 2 | import VueRouter from 'vue-router' 3 | import ElementUI from 'element-ui' 4 | import Breadcrumb from '@/components/Breadcrumb/index.vue' 5 | 6 | const localVue = createLocalVue() 7 | localVue.use(VueRouter) 8 | localVue.use(ElementUI) 9 | 10 | const routes = [ 11 | { 12 | path: '/', 13 | name: 'home', 14 | children: [{ 15 | path: 'dashboard', 16 | name: 'dashboard' 17 | }] 18 | }, 19 | { 20 | path: '/menu', 21 | name: 'menu', 22 | children: [{ 23 | path: 'menu1', 24 | name: 'menu1', 25 | meta: { title: 'menu1' }, 26 | children: [{ 27 | path: 'menu1-1', 28 | name: 'menu1-1', 29 | meta: { title: 'menu1-1' } 30 | }, 31 | { 32 | path: 'menu1-2', 33 | name: 'menu1-2', 34 | redirect: 'noredirect', 35 | meta: { title: 'menu1-2' }, 36 | children: [{ 37 | path: 'menu1-2-1', 38 | name: 'menu1-2-1', 39 | meta: { title: 'menu1-2-1' } 40 | }, 41 | { 42 | path: 'menu1-2-2', 43 | name: 'menu1-2-2' 44 | }] 45 | }] 46 | }] 47 | }] 48 | 49 | const router = new VueRouter({ 50 | routes 51 | }) 52 | 53 | describe('Breadcrumb.vue', () => { 54 | const wrapper = mount(Breadcrumb, { 55 | localVue, 56 | router 57 | }) 58 | it('dashboard', () => { 59 | router.push('/dashboard') 60 | const len = wrapper.findAll('.el-breadcrumb__inner').length 61 | expect(len).toBe(1) 62 | }) 63 | it('normal route', () => { 64 | router.push('/menu/menu1') 65 | const len = wrapper.findAll('.el-breadcrumb__inner').length 66 | expect(len).toBe(2) 67 | }) 68 | it('nested route', () => { 69 | router.push('/menu/menu1/menu1-2/menu1-2-1') 70 | const len = wrapper.findAll('.el-breadcrumb__inner').length 71 | expect(len).toBe(4) 72 | }) 73 | it('no meta.title', () => { 74 | router.push('/menu/menu1/menu1-2/menu1-2-2') 75 | const len = wrapper.findAll('.el-breadcrumb__inner').length 76 | expect(len).toBe(3) 77 | }) 78 | // it('click link', () => { 79 | // router.push('/menu/menu1/menu1-2/menu1-2-2') 80 | // const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner') 81 | // const second = breadcrumbArray.at(1) 82 | // console.log(breadcrumbArray) 83 | // const href = second.find('a').attributes().href 84 | // expect(href).toBe('#/menu/menu1') 85 | // }) 86 | // it('noRedirect', () => { 87 | // router.push('/menu/menu1/menu1-2/menu1-2-1') 88 | // const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner') 89 | // const redirectBreadcrumb = breadcrumbArray.at(2) 90 | // expect(redirectBreadcrumb.contains('a')).toBe(false) 91 | // }) 92 | it('last breadcrumb', () => { 93 | router.push('/menu/menu1/menu1-2/menu1-2-1') 94 | const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner') 95 | const redirectBreadcrumb = breadcrumbArray.at(3) 96 | expect(redirectBreadcrumb.contains('a')).toBe(false) 97 | }) 98 | }) 99 | -------------------------------------------------------------------------------- /vue-consul/tests/unit/components/Hamburger.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import Hamburger from '@/components/Hamburger/index.vue' 3 | describe('Hamburger.vue', () => { 4 | it('toggle click', () => { 5 | const wrapper = shallowMount(Hamburger) 6 | const mockFn = jest.fn() 7 | wrapper.vm.$on('toggleClick', mockFn) 8 | wrapper.find('.hamburger').trigger('click') 9 | expect(mockFn).toBeCalled() 10 | }) 11 | it('prop isActive', () => { 12 | const wrapper = shallowMount(Hamburger) 13 | wrapper.setProps({ isActive: true }) 14 | expect(wrapper.contains('.is-active')).toBe(true) 15 | wrapper.setProps({ isActive: false }) 16 | expect(wrapper.contains('.is-active')).toBe(false) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /vue-consul/tests/unit/components/SvgIcon.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import SvgIcon from '@/components/SvgIcon/index.vue' 3 | describe('SvgIcon.vue', () => { 4 | it('iconClass', () => { 5 | const wrapper = shallowMount(SvgIcon, { 6 | propsData: { 7 | iconClass: 'test' 8 | } 9 | }) 10 | expect(wrapper.find('use').attributes().href).toBe('#icon-test') 11 | }) 12 | it('className', () => { 13 | const wrapper = shallowMount(SvgIcon, { 14 | propsData: { 15 | iconClass: 'test' 16 | } 17 | }) 18 | expect(wrapper.classes().length).toBe(1) 19 | wrapper.setProps({ className: 'test' }) 20 | expect(wrapper.classes().includes('test')).toBe(true) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /vue-consul/tests/unit/utils/formatTime.spec.js: -------------------------------------------------------------------------------- 1 | import { formatTime } from '@/utils/index.js' 2 | 3 | describe('Utils:formatTime', () => { 4 | const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01" 5 | const retrofit = 5 * 1000 6 | 7 | it('ten digits timestamp', () => { 8 | expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分') 9 | }) 10 | it('test now', () => { 11 | expect(formatTime(+new Date() - 1)).toBe('刚刚') 12 | }) 13 | it('less two minute', () => { 14 | expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前') 15 | }) 16 | it('less two hour', () => { 17 | expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前') 18 | }) 19 | it('less one day', () => { 20 | expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前') 21 | }) 22 | it('more than one day', () => { 23 | expect(formatTime(d)).toBe('7月13日17时54分') 24 | }) 25 | it('format', () => { 26 | expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54') 27 | expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13') 28 | expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54') 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /vue-consul/tests/unit/utils/param2Obj.spec.js: -------------------------------------------------------------------------------- 1 | import { param2Obj } from '@/utils/index.js' 2 | describe('Utils:param2Obj', () => { 3 | const url = 'https://github.com/PanJiaChen/vue-element-admin?name=bill&age=29&sex=1&field=dGVzdA==&key=%E6%B5%8B%E8%AF%95' 4 | 5 | it('param2Obj test', () => { 6 | expect(param2Obj(url)).toEqual({ 7 | name: 'bill', 8 | age: '29', 9 | sex: '1', 10 | field: window.btoa('test'), 11 | key: '测试' 12 | }) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /vue-consul/tests/unit/utils/parseTime.spec.js: -------------------------------------------------------------------------------- 1 | import { parseTime } from '@/utils/index.js' 2 | 3 | describe('Utils:parseTime', () => { 4 | const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01" 5 | it('timestamp', () => { 6 | expect(parseTime(d)).toBe('2018-07-13 17:54:01') 7 | }) 8 | it('timestamp string', () => { 9 | expect(parseTime((d + ''))).toBe('2018-07-13 17:54:01') 10 | }) 11 | it('ten digits timestamp', () => { 12 | expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01') 13 | }) 14 | it('new Date', () => { 15 | expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01') 16 | }) 17 | it('format', () => { 18 | expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54') 19 | expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13') 20 | expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54') 21 | }) 22 | it('get the day of the week', () => { 23 | expect(parseTime(d, '{a}')).toBe('五') // 星期五 24 | }) 25 | it('get the day of the week', () => { 26 | expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日 27 | }) 28 | it('empty argument', () => { 29 | expect(parseTime()).toBeNull() 30 | }) 31 | 32 | it('null', () => { 33 | expect(parseTime(null)).toBeNull() 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /vue-consul/tests/unit/utils/validate.spec.js: -------------------------------------------------------------------------------- 1 | import { validUsername, isExternal } from '@/utils/validate.js' 2 | 3 | describe('Utils:validate', () => { 4 | it('validUsername', () => { 5 | expect(validUsername('admin')).toBe(true) 6 | expect(validUsername('editor')).toBe(true) 7 | expect(validUsername('xxxx')).toBe(false) 8 | }) 9 | it('isExternal', () => { 10 | expect(isExternal('https://github.com/PanJiaChen/vue-element-admin')).toBe(true) 11 | expect(isExternal('http://github.com/PanJiaChen/vue-element-admin')).toBe(true) 12 | expect(isExternal('github.com/PanJiaChen/vue-element-admin')).toBe(false) 13 | expect(isExternal('/dashboard')).toBe(false) 14 | expect(isExternal('./dashboard')).toBe(false) 15 | expect(isExternal('dashboard')).toBe(false) 16 | }) 17 | }) 18 | --------------------------------------------------------------------------------