Compare commits

..

1180 Commits

Author SHA1 Message Date
刘祥超
619a2817ce 检查版本更新时增加当前版本参数 2023-03-12 21:24:08 +08:00
刘祥超
12a33ee9fc 自动安装的MySQL binlog过期时间改成7天 2023-03-12 20:58:13 +08:00
刘祥超
26302ca930 重启edge-admin时确保同目录下的edge-api也能重启 2023-03-11 22:02:29 +08:00
刘祥超
7e85555ba7 安装过程中可以选择自动在本机安装MySQL 2023-03-11 18:52:40 +08:00
刘祥超
2546676f6a 删除页脚捐助作者链接 2023-03-11 14:44:19 +08:00
刘祥超
93b7edf5c4 安装过程中节点主机地址允许填写域名 2023-03-10 17:00:53 +08:00
刘祥超
571e432263 WAF cc防护增加“检查请求来源指纹”选项 2023-03-10 10:34:50 +08:00
刘祥超
580b158567 优化tips弹窗 2023-03-09 17:40:49 +08:00
刘祥超
395aa665d7 可以在运行日志页面中删除使用关键词匹配的运行日志 2023-03-09 16:20:09 +08:00
刘祥超
a80e54e0b3 优化<url-patterns-box>组件 2023-03-09 12:09:28 +08:00
刘祥超
8ccd41b551 优化路由规则标签、菜单、删除UAM组件 2023-03-09 11:42:15 +08:00
刘祥超
9d6a3a8a0d 优化<url-patterns-box>组件 2023-03-07 17:18:13 +08:00
刘祥超
a9d1b4b863 集群服务设置增加“记录找不到网站日志”选项 2023-03-07 10:30:11 +08:00
刘祥超
459d664a60 更新go.mod 2023-03-06 21:51:10 +08:00
刘祥超
5f10b0156c 5秒盾增加例外URL和限制URL 2023-03-06 21:48:29 +08:00
刘祥超
348c07f847 优化网站服务WAF设置界面 2023-03-06 16:40:42 +08:00
刘祥超
934b1894c4 使用edge-admin upgrade升级时可以通过--url参数指定升级包URL 2023-03-05 19:52:48 +08:00
刘祥超
a660f4af93 上传components.js 2023-03-05 19:51:46 +08:00
刘祥超
12c0d39b13 优化缓存条件设置界面 2023-03-05 16:48:01 +08:00
刘祥超
bc6de68006 远程升级API节点限制版本号必须不小于0.6.4 2023-03-05 15:32:27 +08:00
刘祥超
52bb753594 实现API节点远程升级 2023-03-05 12:05:18 +08:00
刘祥超
ed6b763d06 远程升级API节点(部分实现) 2023-03-04 21:04:30 +08:00
刘祥超
07e421afea 更新components.js 2023-03-04 09:34:17 +08:00
刘祥超
71352841bf 修复无法启动安装的Bug 2023-03-04 09:34:10 +08:00
刘祥超
91e8fcbb24 使用edge-admin upgrade升级系统时URL请求增加User-Agent 2023-03-03 10:44:04 +08:00
刘祥超
5b67a85624 WAF阻止动作也增加最大封禁时长 2023-03-01 19:18:22 +08:00
刘祥超
b49efa0d5a WAF拦截动作可以设置最大封禁时间,从而实现封禁时间随机 2023-03-01 18:59:42 +08:00
刘祥超
abeb585a0d 优化代码 2023-03-01 16:58:38 +08:00
刘祥超
cf621f1cc9 WAF支持忽略全局WAF规则 2023-03-01 16:46:49 +08:00
刘祥超
389a494e00 优化界面 2023-03-01 15:30:32 +08:00
刘祥超
a9c55dc23b 在节点列表中IP地址中显示对应的专属集群 2023-03-01 15:23:36 +08:00
刘祥超
3d35b7e71b 节点IP地址可以设置专属集群 2023-03-01 11:38:21 +08:00
刘祥超
6f78146711 可以修改单个用户的带宽算法 2023-02-27 10:46:48 +08:00
刘祥超
8afa47c351 删除不必要的文件 2023-02-22 19:25:38 +08:00
刘祥超
20838cfc3e 增加<js-page>组件 2023-02-22 17:34:32 +08:00
刘祥超
d00acd6d2f 上传日期相关公共函数 2023-02-18 20:45:05 +08:00
刘祥超
0b58a36779 优化界面 2023-02-17 10:30:25 +08:00
刘祥超
54bc98e9c1 优化组件显示 2023-02-13 09:44:10 +08:00
刘祥超
836daf2ad9 Dockerfile版本号修改为0.6.3 2023-02-10 18:17:57 +08:00
刘祥超
9a8cd9bd87 优化缓存请求条件界面 2023-02-10 11:15:06 +08:00
刘祥超
c555d91503 文件扩展名相关变量使用${requestPathLowerExtension} 2023-02-10 10:44:29 +08:00
刘祥超
e0e7c1bcc4 修复表单能够上传的数据过小问题 2023-02-07 11:40:28 +08:00
刘祥超
4bf733beec 在管理员登录后才验证OTP 2023-02-04 16:44:33 +08:00
刘祥超
e93f23c943 使用数据库存储SESSION 2023-02-04 15:18:26 +08:00
刘祥超
5384f4d9f2 优化WAF规则大小写敏感显示 2023-02-02 16:14:07 +08:00
刘祥超
3c0a97c3cc 修复防盗链设置中域名无法修改的Bug 2023-02-01 18:16:41 +08:00
刘祥超
129db6cf4e 修复无法显示IPv6最近日志的Bug 2023-02-01 10:36:30 +08:00
刘祥超
c6bfa5652f 版本号修改为0.6.4 2023-01-14 17:19:03 +08:00
刘祥超
780472d83e 更新components.js 2023-01-14 17:18:34 +08:00
刘祥超
0d02e3f15a 优化WAF创建规则界面中参数选项显示 2023-01-13 18:51:14 +08:00
刘祥超
bf82f22d0f WAF规则中如果对比值为空,则显示空字样 2023-01-13 15:58:35 +08:00
刘祥超
e18f182ce6 版本号修改为0.6.3 2023-01-11 15:45:03 +08:00
刘祥超
761c26b587 Update Dockerfile 2023-01-10 21:32:20 +08:00
刘祥超
5e62769dcf 修改版本为0.6.2 2023-01-10 21:17:20 +08:00
刘祥超
86b8a718a0 在Dockerfile中增加local repository提示 2023-01-10 18:31:28 +08:00
刘祥超
a729cfc31d 更新Dockerfile文件 2023-01-10 10:53:48 +08:00
刘祥超
96cfda852a Dockerfile版本号更新到0.6.0 2023-01-09 20:45:04 +08:00
刘祥超
0423d9246c 更新components.js 2023-01-09 18:12:45 +08:00
刘祥超
985798757f 修复<values-box>组件内容中如果出现数字的显示问题 2023-01-09 18:08:24 +08:00
刘祥超
72876f6749 版本修改为0.6.1 2023-01-09 16:06:13 +08:00
刘祥超
03d6e223d8 更新components.js 2023-01-09 09:31:19 +08:00
刘祥超
62d9f2ed97 优化文字提示 2023-01-08 19:38:50 +08:00
刘祥超
a550a44a52 集群服务设置增加自动读超时选项 2023-01-07 20:04:42 +08:00
刘祥超
b19d586949 更新components.js 2023-01-07 19:34:48 +08:00
刘祥超
bbfa3ee57f 优化任务Badge检查频率 2023-01-07 19:34:40 +08:00
刘祥超
af409dd3b8 优化WAF规则显示样式 2023-01-06 20:06:30 +08:00
刘祥超
3db79ca149 更新components.js 2023-01-06 19:17:31 +08:00
刘祥超
e880420494 WAF规则使用中文显示运算符 2023-01-06 19:12:53 +08:00
刘祥超
28ec17b8fe 优化请求条件操作符描述 2023-01-06 16:05:06 +08:00
刘祥超
8026a40807 修改一处错别字 2023-01-05 10:27:59 +08:00
刘祥超
068c6d406a useragent改为userAgent 2023-01-02 18:20:42 +08:00
刘祥超
57470e4ef0 集群服务设置中增加性能设置 2023-01-01 19:26:59 +08:00
刘祥超
ca8e1537f5 修复文字错误 2023-01-01 18:33:05 +08:00
刘祥超
d67b818398 华为云可以设置终端节点(endpoint) 2023-01-01 18:28:37 +08:00
刘祥超
f5f46424bb 集群/节点阈值切换监控项时同时切换参数描述 2022-12-31 18:33:42 +08:00
刘祥超
1e259717ce 优化代码 2022-12-31 17:31:17 +08:00
刘祥超
91ece99a9c 优化证书加载速度 2022-12-31 17:21:48 +08:00
刘祥超
d30ebdb369 优化证书数量很多时的页面加载速度 2022-12-31 17:12:49 +08:00
刘祥超
ade8522b69 实现UA名单功能 2022-12-30 20:48:38 +08:00
刘祥超
159b308f31 内容压缩增加默认内容长度限制 2022-12-30 14:37:50 +08:00
刘祥超
837bf25f7b 内容压缩支持例外扩展名 2022-12-30 12:04:17 +08:00
刘祥超
8301d3669b 默认情况下内容压缩不支持Partial Content 2022-12-30 11:43:47 +08:00
刘祥超
cc752e8d80 优化文字提示 2022-12-29 17:19:53 +08:00
刘祥超
d20e6bd42f 增加CORS自适应跨域 2022-12-29 17:16:07 +08:00
刘祥超
bfee9fe233 优化文字提示 2022-12-27 18:55:18 +08:00
刘祥超
9c962b09f1 调整文字提示 2022-12-22 11:43:31 +08:00
刘祥超
46edefead7 服务没有设置所属用户时可以设置一个用户 2022-12-21 16:13:39 +08:00
刘祥超
725cfc8a2b 优化代码 2022-12-15 16:18:10 +08:00
刘祥超
1ce48b9ef4 安全设置中检查搜索引擎和爬虫时不区分大小写 2022-12-13 18:22:11 +08:00
刘祥超
37cc28f225 优化界面 2022-12-13 18:21:31 +08:00
刘祥超
5ebe3bb8e0 实时访问日志有弹窗打开时,暂时不更新数据 2022-12-10 19:01:57 +08:00
刘祥超
aa01512f89 优化<more-options-indicator>组件 2022-12-10 15:57:39 +08:00
刘祥超
37ff2b886a 初步完成用户电子邮箱绑定(激活) 2022-12-08 20:25:20 +08:00
刘祥超
ce18212756 自定义线路增加区域之间关系设置 2022-12-06 22:32:32 +08:00
刘祥超
08f50a274a 修复HTTPS之HSTS设置无法手动输入有效时间的Bug 2022-12-06 14:38:59 +08:00
刘祥超
892ee0013a 修复点击修改WAF规则时未保存时也会生效的Bug 2022-12-06 10:46:46 +08:00
刘祥超
e9a47041fd 刷新预热页面增加功能说明 2022-12-06 09:52:57 +08:00
刘祥超
d419fa06e8 优化文字提示 2022-12-03 14:29:30 +08:00
刘祥超
8b961a890c 优化文字提示 2022-12-01 14:40:17 +08:00
刘祥超
db32915114 更新components.js 2022-11-29 15:40:37 +08:00
刘祥超
2ffdb10cce 版本号更新为0.6.0 2022-11-29 15:40:23 +08:00
刘祥超
507fd7e5d4 更新Dockerfile中的版本号 2022-11-29 15:40:11 +08:00
刘祥超
7df599b5a9 DNS线路选择器增加代号搜索 2022-11-28 19:00:14 +08:00
刘祥超
9987334f55 版本号修改为0.5.10 2022-11-28 18:59:40 +08:00
刘祥超
d8c3365384 节点时间相差30秒钟以上才提示 2022-11-28 15:57:41 +08:00
刘祥超
2e284b5af9 版本号修改为0.5.9 2022-11-28 15:57:18 +08:00
刘祥超
89ddd4e6a3 更新依赖库 2022-11-28 11:39:38 +08:00
刘祥超
36524ea481 修复一处测试用例package引用错误 2022-11-28 11:39:08 +08:00
刘祥超
35cf693610 更新Dockerfile 2022-11-28 11:34:02 +08:00
刘祥超
42148a66bd 改进文字提示 2022-11-27 22:00:35 +08:00
刘祥超
96878715bf 优化界面 2022-11-26 19:04:55 +08:00
刘祥超
1a5f3342e7 优化文字提示 2022-11-26 15:28:01 +08:00
刘祥超
3613d13a2b 优化文字说明 2022-11-25 19:05:11 +08:00
刘祥超
7786140d85 优化代码 2022-11-24 17:20:08 +08:00
刘祥超
3a23b57f1b 优化智能DNS访问日志组件 2022-11-24 10:24:04 +08:00
刘祥超
5a6e6fba69 节点详情显示API连接状况 2022-11-22 11:27:48 +08:00
刘祥超
910b3a6162 在节点详情中显示API节点地址 2022-11-21 21:07:51 +08:00
刘祥超
0dc19bed45 节点可以单独设置所使用的API节点地址 2022-11-21 19:54:54 +08:00
刘祥超
a7bdb64301 记录名第一段允许通配符 2022-11-20 20:08:03 +08:00
刘祥超
4739072a85 Update go.mod 2022-11-18 17:37:25 +08:00
刘祥超
07bdae2488 安装时检测本地数据库时增加更多的候选密码 2022-11-18 17:07:12 +08:00
刘祥超
b84035d821 优化代码 2022-11-18 17:02:38 +08:00
刘祥超
dc0a7b9dae 优化文字提示 2022-11-18 15:45:13 +08:00
刘祥超
2937bd8de0 提交components.js 2022-11-17 17:34:21 +08:00
刘祥超
37315ef4d9 优化代码 2022-11-17 10:41:56 +08:00
刘祥超
1986fece07 优化代码 2022-11-17 10:40:57 +08:00
刘祥超
16b1657f35 优化WAF规则相关界面 2022-11-16 15:43:08 +08:00
刘祥超
c115c62cd9 去掉服务CNAME末尾的点符号,防止误解 2022-11-16 14:44:07 +08:00
刘祥超
d2df7f8d5b 节点列表页带宽使用bps显示 2022-11-16 12:02:56 +08:00
刘祥超
6cb79864e6 边缘节点支持设置多个缓存目录 2022-11-15 20:35:45 +08:00
刘祥超
982d28c7b4 docker build增加--no-cache参数 2022-11-14 15:01:13 +08:00
刘祥超
0f57516fdc Dockerfile: 使用alpine:latest代替alpine:edge 2022-11-14 14:49:19 +08:00
刘祥超
75a89defcb 构建Dockerfile时从官网下载最新版本安装包 2022-11-14 10:01:22 +08:00
刘祥超
22a6c52060 增加Docker镜像构建脚本 2022-11-13 18:46:30 +08:00
刘祥超
37607e4a41 安装过程显示更详细内容 2022-11-11 21:47:36 +08:00
刘祥超
5936155998 优化文字提示 2022-11-11 17:46:10 +08:00
刘祥超
8d76de935f 优化文字提示 2022-11-10 15:07:15 +08:00
刘祥超
9baa530064 优化文字提示 2022-11-09 18:20:18 +08:00
刘祥超
103414b338 优化<checkbox>组件 2022-11-09 17:50:22 +08:00
刘祥超
72fe68ebfe 访问日志搜索method:XXX和requestMethod:XXX方法 2022-11-09 11:58:39 +08:00
刘祥超
cfed31958b 优化文字提示 2022-11-08 09:22:09 +08:00
刘祥超
3d5fca2d36 调整默认压缩的mime types 2022-11-08 09:18:13 +08:00
刘祥超
a5710286ec 优化代码 2022-11-06 20:34:19 +08:00
刘祥超
d0ce0c6c58 优化界面 2022-11-06 15:16:21 +08:00
刘祥超
779e2cf0f2 域名跳转增加忽略跳转前端口选项 2022-11-04 20:59:20 +08:00
刘祥超
2108474777 删除不必要的文件 2022-11-04 15:19:30 +08:00
刘祥超
e25e0f1747 删除不必要的文件 2022-11-04 15:01:36 +08:00
刘祥超
e8e74b639c 优化datepicker组件 2022-11-04 14:35:32 +08:00
刘祥超
a14fcd1e50 添加和修改API节点时,检查HTTP API端口是否和GRPC端口冲突 2022-11-04 12:04:07 +08:00
刘祥超
485c0e0891 修复系统用户和平台用户access key无法禁用和删除的问题 2022-11-04 11:06:45 +08:00
刘祥超
00a19e9d43 时钟同步增加是否检查chrony选项 2022-11-03 14:58:32 +08:00
刘祥超
67d0dc0783 版本修改为0.5.8 2022-11-02 15:10:47 +08:00
刘祥超
3718c35842 提交components.js 2022-11-02 15:10:39 +08:00
刘祥超
567ffc80b6 优化<domains-box>组件 2022-11-02 09:58:59 +08:00
刘祥超
5d15a08ac8 防盗链使用<domains-box>组件 2022-11-02 09:47:17 +08:00
刘祥超
2b84037346 修改一处注释 2022-11-01 21:16:48 +08:00
刘祥超
536f11e617 修复datetime组件时间戳生成错误 2022-10-31 11:08:36 +08:00
刘祥超
5d367a384e 修改版本为0.5.7 2022-10-31 10:39:10 +08:00
刘祥超
04933e6bf0 重置配置的同时也重置本地API节点的配置 2022-10-30 20:06:59 +08:00
刘祥超
12abe9aa69 优化界面显示 2022-10-30 20:06:38 +08:00
刘祥超
cba642a4bc 多处域名列表支持批量输入 2022-10-28 17:59:46 +08:00
刘祥超
9fce0ac0aa 优化API节点界面 2022-10-27 19:45:03 +08:00
刘祥超
681812b619 优化申请证书界面 2022-10-27 19:44:50 +08:00
刘祥超
6d0be57698 更新components.js 2022-10-27 11:19:10 +08:00
刘祥超
1d521602e1 节点SSH登录自动使用集群设置 2022-10-26 19:23:50 +08:00
刘祥超
2f67e7937a 集群全局服务配置中增加多个访问日志相关选项 2022-10-26 17:49:25 +08:00
刘祥超
e0078a42dc URL跳转中增加域名跳转、端口跳转 2022-10-26 16:06:50 +08:00
刘祥超
3ec875d49d 节点设置中增加“通过IP名单”选项 2022-10-26 10:41:42 +08:00
刘祥超
35028d1310 开源版本Dashboard不再显示用户节点相关信息 2022-10-26 09:51:09 +08:00
刘祥超
7ba3d7c4bb 修复窄屏显示时有些内容无法隐藏的问题 2022-10-26 09:30:41 +08:00
刘祥超
c05e64098c 更新components.js 2022-10-25 14:36:40 +08:00
刘祥超
e82ee56a2c IP名单支持模糊查询 2022-10-25 10:15:17 +08:00
刘祥超
5681b61aea 在选择区域弹出框中可以不选区域直接确定返回 2022-10-25 09:43:31 +08:00
刘祥超
d6ce7eab25 修复域名解析--集群中单节点多IP时无法修改IP的Bug 2022-10-24 16:33:56 +08:00
刘祥超
bf597fe41c 添加、修改、删除HTTP Header时增加通用Header提示 2022-10-24 15:42:18 +08:00
刘祥超
6a920f964f 集群服务设置--访问日志中可以设置是否只记录通用Header 2022-10-24 14:39:32 +08:00
刘祥超
977b66ba4e 防盗链功能增加禁止的来源域名 2022-10-24 10:20:44 +08:00
刘祥超
4659c29358 优化界面显示 2022-10-23 11:44:09 +08:00
刘祥超
e3bc95b275 管理安全设置中域名列表支持通配符 2022-10-21 18:36:33 +08:00
刘祥超
bf51255e13 优化区域列表界面 2022-10-20 15:17:03 +08:00
刘祥超
915fe6837b 区域设置中增加节点快速设置区域的功能 2022-10-20 15:11:26 +08:00
刘祥超
d1237215c0 更新components.js 2022-10-20 11:07:07 +08:00
刘祥超
8ba5cfdfa6 优化代码 2022-10-19 19:55:51 +08:00
刘祥超
0d1097425d 优化界面和文字 2022-10-18 10:31:20 +08:00
刘祥超
0789a9ecc8 优化数字格式化 2022-10-16 09:36:36 +08:00
刘祥超
400f764b74 优化数字格式化 2022-10-15 19:33:07 +08:00
刘祥超
46e036e3a4 删除代码 2022-10-15 16:58:45 +08:00
刘祥超
17e6264af8 删除不需要的代码 2022-10-14 09:57:24 +08:00
刘祥超
468b6ae125 添加源站的时候也提示8443和8080对应的协议 2022-10-07 09:01:22 +08:00
刘祥超
e5f5ee4f6a 提交components.js 2022-10-03 21:11:02 +08:00
刘祥超
3695082ec2 服务列表页带宽使用比特代替字节 2022-10-03 19:25:20 +08:00
刘祥超
f384d86014 版本修改为0.5.6 2022-10-01 08:49:00 +08:00
刘祥超
34bf5028c3 删除不必要的代码 2022-10-01 07:16:37 +08:00
刘祥超
8b727aa939 提交components.js 2022-09-30 18:36:46 +08:00
刘祥超
9432600de6 优化网站服务列表页在海量域名时的加载速度 2022-09-30 13:48:16 +08:00
刘祥超
bb790ec687 文件下载链接找不到对应元素时不提示错误 2022-09-30 09:39:59 +08:00
刘祥超
8bad658d7c 版本调整为v0.5.5 2022-09-28 18:57:47 +08:00
刘祥超
a66cc9c08a 删除不必要的代码 2022-09-28 17:38:11 +08:00
刘祥超
0dc24fb342 systemd服务增加BEGIN INIT INFO 2022-09-28 08:17:32 +08:00
刘祥超
b965ac6232 版本修改为0.5.4 2022-09-27 08:05:50 +08:00
刘祥超
08d61013c8 删除多于的文件 2022-09-27 08:04:35 +08:00
刘祥超
7796f65814 将版本修改为0.5.4 2022-09-26 15:16:53 +08:00
刘祥超
02a3afb9ad 版本修改为0.5.3.1 2022-09-26 13:02:35 +08:00
刘祥超
15a11c384b 优化代码 2022-09-25 20:34:43 +08:00
刘祥超
57b8496d89 更新components.js 2022-09-25 10:34:12 +08:00
刘祥超
7bbe44f5d2 优化代码 2022-09-25 10:33:10 +08:00
刘祥超
ac11ab0431 缓存条件增加”忽略URI参数“选项 2022-09-24 15:13:35 +08:00
刘祥超
12e352964c 默认支持206 Partial Content 2022-09-24 14:40:34 +08:00
刘祥超
7daefc4384 优化代码 2022-09-24 14:06:41 +08:00
刘祥超
29541becd0 添加Header的时候自动去除Header后多余的冒号 2022-09-23 19:00:51 +08:00
刘祥超
ef6dc82f88 优化代码 2022-09-23 11:09:21 +08:00
刘祥超
6d66d93180 智能DNS访问日志增加只记录失败查询选项 2022-09-22 18:41:43 +08:00
刘祥超
3ded17b920 优化防盗链选项说明文字 2022-09-22 17:55:58 +08:00
刘祥超
d35651f570 增加防盗链功能 2022-09-22 16:33:35 +08:00
刘祥超
235300d034 修改反向代理等文字 2022-09-22 15:27:23 +08:00
刘祥超
2f7ebf5166 优化代码 2022-09-21 13:49:02 +08:00
刘祥超
4819cc87c6 删除不必要的文件 2022-09-20 18:22:13 +08:00
刘祥超
c5da383d1e 更新components.js 2022-09-19 09:50:03 +08:00
刘祥超
2da17329fa 创建节点时尝试自动从节点名称中读取IP 2022-09-18 14:57:37 +08:00
刘祥超
70d84acf76 在节点手动安装页显示节点安装文件下载链接 2022-09-18 12:38:32 +08:00
刘祥超
fef99a0023 优化代码/DNS域名增加分页 2022-09-18 10:22:58 +08:00
刘祥超
76d0935e8f 可以设置是否自动安装nftables 2022-09-17 21:04:45 +08:00
刘祥超
42dfe5ada9 缓存的URL类型文字从“前缀”改成“目录” 2022-09-17 19:26:27 +08:00
刘祥超
ef136c25e1 域名解析域名和记录名中支持中文 2022-09-17 16:50:25 +08:00
刘祥超
4251635a8a 集群增加是否远程启动选项 2022-09-17 15:11:40 +08:00
刘祥超
13f00104dd 健康检查设置域名时检查域名是否存在 2022-09-17 11:38:04 +08:00
刘祥超
b0b82b29c1 集群设置--DNS设置页显示DNS账号名 2022-09-17 10:59:18 +08:00
刘祥超
e0d43ad3d9 节点即使离线后仍然在运行状态中显示版本、主程序位置等信息 2022-09-17 10:25:40 +08:00
刘祥超
ae15115af7 创建集群时增加“只允许绑定的域名访问”选项 2022-09-16 19:33:57 +08:00
刘祥超
a2890c6cb0 集群设置中增加服务设置 2022-09-16 18:41:04 +08:00
刘祥超
2b00ece07f 访问日志中增加源站状态码 2022-09-16 10:07:21 +08:00
刘祥超
2e3c34571f 删除不必要的文件 2022-09-15 19:03:30 +08:00
刘祥超
b8b10b5176 集群增加自动同步时钟选项 2022-09-15 15:57:53 +08:00
刘祥超
e6e62bbd24 域名和记录名中可以使用大写 2022-09-14 19:48:23 +08:00
刘祥超
05e9bbf1d6 添加域名窗口中提示可以添加泛域名 2022-09-14 09:24:27 +08:00
刘祥超
bf94c2395f 可以隐藏CDN功能菜单 2022-09-13 19:54:31 +08:00
刘祥超
4a749ca345 优化GRPC参数 2022-09-13 10:48:54 +08:00
刘祥超
80ffe6c9a3 实现DNS域名验证 2022-09-10 16:13:31 +08:00
刘祥超
5414b5a8f9 优化提示文字 2022-09-09 10:33:16 +08:00
刘祥超
e3ba85a326 修改管理界面设置中的时区时同时也会应用到API节点 2022-09-09 10:27:10 +08:00
刘祥超
154fa69dbb 优化安装时跟端口相关的提示 2022-09-08 19:45:23 +08:00
刘祥超
76c44973e9 域名解析增加EdgeDNS API 2022-09-08 19:36:37 +08:00
刘祥超
26fdc82cc1 创建集群的时候可以设置DNS记录的默认TTL 2022-09-08 11:02:19 +08:00
刘祥超
dcc587182c 优化文字提示 2022-09-08 11:00:44 +08:00
刘祥超
7cee1ff5ff 访问日志里以标签的形式显示中文域名 2022-09-07 17:02:40 +08:00
刘祥超
78b8e3bf0b API节点在启动时提示“API节点正在启动,请耐心等待完成” 2022-09-07 15:57:06 +08:00
刘祥超
bd4c014c12 增加edge-admin upgrade命令 2022-09-06 21:19:37 +08:00
刘祥超
daa263cd68 将版本修改为0.5.3 2022-09-06 09:24:02 +08:00
刘祥超
e2965d39af 将版本修改为0.5.2.1 2022-09-05 16:04:51 +08:00
刘祥超
8279d15ebb 将版本修改为0.5.3 2022-09-05 11:02:17 +08:00
刘祥超
9731fa35d8 生成components.js 2022-09-04 09:31:50 +08:00
刘祥超
53be4db22b 缓存条件--简单条件增加参数匹配 2022-09-03 19:25:08 +08:00
刘祥超
0202fa2ca3 优化缓存条件设置交互 2022-09-03 19:15:30 +08:00
刘祥超
d5262b5474 简化缓存条件设置 2022-09-03 18:14:34 +08:00
刘祥超
cb2e1d54c2 优化文字提示 2022-09-02 16:34:37 +08:00
刘祥超
6392297a27 修复因URL中含有搜索引擎关键词而导致页面403的问题 2022-09-02 09:26:50 +08:00
刘祥超
6001d4eba3 更新components.js和一处绿色色值 2022-09-01 09:07:28 +08:00
刘祥超
79588e4bbc DDoS防护增加秒级连接速率限制 2022-08-31 10:00:55 +08:00
刘祥超
2b21c38382 访问控制修改为访问鉴权 2022-08-30 17:04:02 +08:00
刘祥超
481d25845e 修复CSRF可能导致访问控制自动保存失败的问题 2022-08-30 12:08:42 +08:00
刘祥超
e0f8bfe283 优化路由规则中访问控制功能 2022-08-30 11:43:49 +08:00
刘祥超
d2ecb01358 修复服务访问控制页面可能空白的问题 2022-08-30 11:42:40 +08:00
刘祥超
316e793b1e 优化访问控制,将“认证”两字改为“鉴权” 2022-08-30 11:22:54 +08:00
刘祥超
6df6809ab3 优化代码 2022-08-29 09:20:46 +08:00
刘祥超
c71f892601 创建用户的时候,可以设置开通默认功能还是全部功能 2022-08-28 20:21:57 +08:00
刘祥超
d0b5a16ce7 更新components.js 2022-08-28 20:01:29 +08:00
刘祥超
84638d3228 优化代码 2022-08-28 15:51:55 +08:00
刘祥超
bb21a2aa5f 优化代码 2022-08-28 11:07:21 +08:00
刘祥超
c37c948129 优化表头排序图标上文字提示 2022-08-27 18:45:07 +08:00
刘祥超
5155476dd7 对缓存策略中的句柄和sendfile增加设置警告 2022-08-27 18:39:36 +08:00
刘祥超
7ef4f60309 服务列表带宽使用新的算法 2022-08-27 18:37:44 +08:00
刘祥超
b0865cbbdc 可以修改服务的CNAME 2022-08-26 19:51:07 +08:00
刘祥超
990c1070e2 DDoS防护增加单IP TCP新连接速率黑名单 2022-08-26 11:32:00 +08:00
刘祥超
65bdd413eb Ln节点可以指定访问IP 2022-08-25 20:36:51 +08:00
刘祥超
60fa35eb73 集群DNS设置中增加”包含Ln节点“选项 2022-08-25 19:18:34 +08:00
刘祥超
32cfd5c233 节点运行日志可以按照节点ID设置为已读 2022-08-25 18:27:15 +08:00
刘祥超
7852495527 优化代码 2022-08-25 16:02:26 +08:00
刘祥超
3679a78f47 WAF增加Javascript cookie验证 2022-08-25 15:35:16 +08:00
刘祥超
6b21568408 优化文字提示 2022-08-25 12:02:48 +08:00
刘祥超
04a5aa41d7 浏览访问日志时自动用点符号标记有数据的分表 2022-08-25 11:36:18 +08:00
刘祥超
fea1a2199c WAF cc2阈值设置太低时提示用户 2022-08-25 09:26:06 +08:00
刘祥超
9c8492efb9 增加测试用例 2022-08-25 09:25:52 +08:00
刘祥超
20b89a8ddd 相关区域名称可以显示别名 2022-08-23 19:15:44 +08:00
刘祥超
5a8e281fb1 优化代码 2022-08-23 14:55:06 +08:00
刘祥超
a9bb413199 NS节点基本的DDoS防护 2022-08-22 15:12:10 +08:00
刘祥超
428d8ab1b1 折叠访问日志选项 2022-08-22 11:16:01 +08:00
刘祥超
c2675bcdb6 优化代码 2022-08-21 20:47:29 +08:00
刘祥超
1fe228e4c0 连接API时,自动将本地的API节点地址转换为回路地址 2022-08-20 20:33:28 +08:00
刘祥超
c5a6497f10 优化代码 2022-08-20 20:31:00 +08:00
刘祥超
f25a82585f 访问数据看板时自动初始化左侧菜单Badge 2022-08-18 09:33:14 +08:00
刘祥超
01209b66ac 对运行日志和IP名单进行操作时,及时更新左侧菜单Badge 2022-08-18 09:28:09 +08:00
刘祥超
4e19817d6f 版本修改为0.5.2 2022-08-17 18:59:37 +08:00
刘祥超
a48adff8ac 版本修改为0.5.1 2022-08-15 19:38:31 +08:00
刘祥超
ab3b32fda1 优化代码 2022-08-14 20:03:31 +08:00
刘祥超
cafab78ab4 新版IP库管理阶段性提交(未完成) 2022-08-13 23:55:35 +08:00
刘祥超
2d497dee7d 自动纠正API节点相关地址填写的常见错误 2022-08-11 18:33:46 +08:00
刘祥超
280ecd9aea 优化代码 2022-08-11 15:54:09 +08:00
刘祥超
3a980a3bc0 删除不必要的文件 2022-08-11 15:28:24 +08:00
刘祥超
556a5bdd4e 优化代码 2022-08-11 15:25:58 +08:00
刘祥超
eca26a345f generate components.js 2022-08-09 21:04:30 +08:00
刘祥超
eacff7232d 更新components.js 2022-08-08 16:26:12 +08:00
刘祥超
3c071db207 优化文字 2022-08-08 16:16:48 +08:00
刘祥超
6609c56063 页面支持DNS.LA(商业版本可用) 2022-08-07 21:18:21 +08:00
刘祥超
f7ae3de914 修改版本号为0.5.0 2022-08-07 19:02:28 +08:00
刘祥超
d82bc4e77e 缓存条件增加If-None-Match和If-Modified-Since是否回源选项 2022-08-07 16:36:41 +08:00
刘祥超
31e1df0afd 增加刷新管理界面配置函数 2022-08-07 14:56:20 +08:00
刘祥超
700e9236f9 优化代码 2022-08-06 20:28:51 +08:00
刘祥超
ee1e62aff0 优化代码 2022-08-05 21:05:49 +08:00
刘祥超
aa0c38d66f 优化代码 2022-08-05 14:38:56 +08:00
刘祥超
4e4d9e33f0 修复运行日志菜单未读数字可能不会消失的问题 2022-08-04 18:48:50 +08:00
刘祥超
f78daa98bc 删除证书之前检查是否正在被NS集群使用 2022-08-04 16:26:34 +08:00
刘祥超
dad8802fb0 优化代码 2022-08-04 11:51:34 +08:00
刘祥超
529b7041e7 优化代码 2022-08-03 21:15:12 +08:00
刘祥超
5de1eecf92 TCP源站也支持专属域名项 2022-08-03 15:28:55 +08:00
刘祥超
08ee301f89 优化代码 2022-08-03 12:20:24 +08:00
刘祥超
4154904b21 优化代码 2022-08-03 10:44:48 +08:00
刘祥超
79282809e3 优化分页样式 2022-07-31 19:56:32 +08:00
刘祥超
108c2533c2 添加域名时自动将域名转换为小写 2022-07-30 16:17:05 +08:00
刘祥超
fd7309cd17 优化路由规则界面 2022-07-30 10:43:53 +08:00
刘祥超
c5f871edf6 路由规则支持请求限制 2022-07-29 11:38:31 +08:00
刘祥超
3f2d2b238d EdgeDNS:访问日志增加集群和记录类型筛选 2022-07-27 20:20:04 +08:00
刘祥超
e6a30b99d3 优化代码 2022-07-27 16:55:19 +08:00
刘祥超
08ce2b7799 修改版本号为0.4.11 2022-07-26 08:57:04 +08:00
刘祥超
dbe7336f32 改进文字提示 2022-07-26 08:56:37 +08:00
刘祥超
aac953f483 改进文字 2022-07-26 08:00:42 +08:00
刘祥超
49bc469430 优化文字提示 2022-07-24 16:18:13 +08:00
刘祥超
f3ac8a5cc5 用户增加OTP认证设置 2022-07-24 16:14:38 +08:00
刘祥超
847d08a9bb 网站服务列表增加用户筛选 2022-07-24 14:26:14 +08:00
刘祥超
0563a363c2 用户列表中显示实名审核状态 2022-07-24 11:57:46 +08:00
刘祥超
f9dc0d6b54 实现基础的实名认证功能(商业版本专有,开源版本只显示认证状态) 2022-07-24 09:57:26 +08:00
刘祥超
40ef3604aa 增加本地API节点需要升级提示 2022-07-21 19:22:18 +08:00
刘祥超
970604dc73 API节点状态中增加主程序位置信息 2022-07-21 15:24:07 +08:00
刘祥超
d6617f214d 边缘节点详情中包含主程序位置 2022-07-21 15:07:59 +08:00
刘祥超
860fccbd4c API RPC配置增加disableUpdate,可以停用自动更新API节点 2022-07-21 14:13:23 +08:00
刘祥超
b3adb839e0 修改版本为v0.4.10 2022-07-20 18:14:18 +08:00
刘祥超
5e9654c3bc 改进文字提示 2022-07-18 20:40:48 +08:00
刘祥超
43c6bff964 准备发布 2022-07-17 21:19:45 +08:00
刘祥超
aef84189a4 优化代码/自动去除域名中的http://和https://等,防止误填 2022-07-17 17:12:44 +08:00
刘祥超
04f8bfc975 价格增加总价格设定 2022-07-17 10:53:03 +08:00
刘祥超
d139e93160 改进文字提示 2022-07-16 19:21:37 +08:00
刘祥超
1fcc0694ca 改进文字提示 2022-07-16 19:03:56 +08:00
刘祥超
7ba7858076 WAF策略增加记录区域封禁日志选项 2022-07-16 18:45:39 +08:00
刘祥超
b1cbc433ed WAF策略增加记录请求Body选项 2022-07-16 17:04:56 +08:00
刘祥超
8beaf97306 cc2增加忽略常见文件扩展名选项 2022-07-15 12:04:24 +08:00
刘祥超
e626364f45 日志详情中增加源站信息 2022-07-14 11:59:09 +08:00
刘祥超
ebaec51f67 修复全局封锁名单不能创建IP的Bug/创建和修改IP可以直接选择过期时间 2022-07-14 10:19:45 +08:00
刘祥超
36b90451af 集群DNS设置增加允许通过CNAME访问网站服务选项/集群DNS设置可以设置不使用主域名 2022-07-14 09:48:13 +08:00
刘祥超
e0b1c2a6a4 去除开源版本中不必要的菜单 2022-07-12 14:07:25 +08:00
刘祥超
42c6f4264e 删除不必要的文件 2022-07-12 14:05:24 +08:00
刘祥超
78641e7052 删除不必要的文件 2022-07-12 13:57:17 +08:00
刘祥超
6bf0118d20 修复开源版本无法编译的问题 2022-07-12 13:55:00 +08:00
刘祥超
0415cd3719 优化代码 2022-07-11 14:42:38 +08:00
刘祥超
a3412b2f95 域名解析支持DNS.COM(商业版) 2022-07-11 11:52:03 +08:00
刘祥超
6a4b3b026f 修复开源版本无法访问“刷新预热”菜单的Bug 2022-07-10 20:47:48 +08:00
刘祥超
f213976a5d 支持ClouDNS(商业版) 2022-07-10 19:35:13 +08:00
刘祥超
3605f71f70 安全设置中增加禁止搜索引擎、禁止爬虫、允许访问的域名等选项 2022-07-07 19:58:30 +08:00
刘祥超
dc08847a7d 在robots.txt中移除GoEdge标识 2022-07-07 14:42:47 +08:00
刘祥超
a34204e25e 可以在管理界面修改用户平台数据看板相关设置 2022-07-07 12:39:23 +08:00
刘祥超
25d73ac0a2 Go版本号改为从v1.18开始 2022-07-07 09:16:29 +08:00
刘祥超
f67c0c0e75 优化文字 2022-07-05 20:07:37 +08:00
刘祥超
08c8255d59 优化集群设置菜单 2022-07-04 10:31:25 +08:00
刘祥超
157efaa02e 增加UAM(5秒盾)集群设置 2022-07-03 22:09:27 +08:00
刘祥超
a56a29495e 反向代理设置中增加移除回源主机名端口功能 2022-06-30 12:11:17 +08:00
刘祥超
c454cd75b3 实现源站端口跟随功能 2022-06-29 21:56:44 +08:00
刘祥超
633684f576 优化编译脚本 2022-06-29 17:00:05 +08:00
刘祥超
de50b5e0a1 优化编译脚本 2022-06-29 14:50:51 +08:00
刘祥超
855f287e39 DNS自定义线路中增加CIDR、区域以及排除功能 2022-06-28 20:26:06 +08:00
刘祥超
f018dee75e 修复弹窗中没有正确设置favicon的Bug 2022-06-28 20:25:19 +08:00
刘祥超
7d3b218e24 支持ZSTD压缩 2022-06-27 22:40:12 +08:00
刘祥超
536382ce34 改进文字提示 2022-06-27 15:17:54 +08:00
刘祥超
f6f003d524 TLS源站支持填写回源主机名 2022-06-27 15:10:45 +08:00
刘祥超
8126de4048 改进文字 2022-06-25 19:31:23 +08:00
刘祥超
4c41df85a0 优化网站服务菜单 2022-06-20 15:59:55 +08:00
刘祥超
2c9f78bb9e 版本改为v0.4.9 2022-06-20 15:59:26 +08:00
刘祥超
23899e196e 完善一些文字细节 2022-06-19 20:19:58 +08:00
刘祥超
ecbc87265e 优化交互 2022-06-16 19:32:40 +08:00
刘祥超
9f8731d668 删除不必要的文件 2022-06-16 16:33:50 +08:00
刘祥超
12d545138a 删除不必要的文件 2022-06-16 16:26:44 +08:00
刘祥超
0c054581ac 删除不必要的文件 2022-06-16 16:25:37 +08:00
刘祥超
c8c2f83763 删除不必要的文件 2022-06-16 15:35:14 +08:00
刘祥超
9056d591c3 DNS线路可以批量添加IP范围 2022-06-15 20:24:52 +08:00
刘祥超
191f58074e 取消IP库上传入口,防止用户误操作 2022-06-15 19:51:54 +08:00
刘祥超
92d3af3d2b 修改源站时校验端口号 2022-06-15 19:42:03 +08:00
刘祥超
2ddc4d62a2 添加源站时校验端口号 2022-06-15 19:40:07 +08:00
刘祥超
81a2967683 删除不必要的文件 2022-06-15 16:32:11 +08:00
刘祥超
29e3c55df0 优化界面 2022-06-15 15:52:30 +08:00
刘祥超
27e3cdffb1 自动更新缓存任务执行状况/优化缓存相关文字提示 2022-06-15 15:47:43 +08:00
刘祥超
a1ad56aebb 优化缓存任务Key状态显示 2022-06-15 15:17:54 +08:00
刘祥超
2573b3b827 边缘节点时间和API节点时间相差超过3秒时才会在节点详情提示。 2022-06-15 11:51:03 +08:00
刘祥超
bcd9a8fcd2 优化界面提示 2022-06-14 20:00:00 +08:00
刘祥超
8dbc83fd27 增加管理平台所在服务器磁盘空间过小提醒 2022-06-14 19:55:38 +08:00
刘祥超
275280d24e WAF规则中国家/地区、省份、城市、ISP增加候选项检索和选择 2022-06-14 17:38:50 +08:00
刘祥超
c82fa4709d 优化代码 2022-06-12 20:59:55 +08:00
刘祥超
c8e826014b 访问日志查询增加requestPath:/hello、proto:HTTP/1.1、scheme:http等语法 2022-06-12 20:36:05 +08:00
刘祥超
75b2e93678 优化界面 2022-06-09 19:45:09 +08:00
刘祥超
b0573df9d8 优化界面 2022-06-09 12:23:02 +08:00
刘祥超
e6c14590f1 启用服务HTTP/HTTPS设置时如果没有设置端口,则自动添加80/443 2022-06-08 20:11:38 +08:00
刘祥超
248ac43f28 优化代码 2022-06-08 19:55:57 +08:00
刘祥超
9361a27dca API连接错误提示更加详细,以便于快速发现问题 2022-06-08 15:17:00 +08:00
刘祥超
700836903f 安装时如果数据库地址填写的是公网IP,则提示会影响系统运行性能 2022-06-08 11:00:03 +08:00
刘祥超
039ce26f58 在节点详情中提示边缘节点和API节点时间差 2022-06-06 20:13:04 +08:00
刘祥超
95bfb7f0b6 可以设置用户每天执行缓存任务的额度 2022-06-05 21:15:08 +08:00
刘祥超
9a181556ca 发送远程指令时包括从节点 2022-06-05 17:18:26 +08:00
刘祥超
9554b6f3ec 增加刷新、预热缓存任务管理 2022-06-05 17:12:54 +08:00
刘祥超
45089437ef 优化界面 2022-05-25 11:45:56 +08:00
刘祥超
f47a3b0586 优化界面/修改用户集群不影响套餐服务 2022-05-25 11:44:05 +08:00
刘祥超
1a19c7520a 优化界面 2022-05-23 14:54:27 +08:00
刘祥超
6266af66f7 新创建WAF时增加默认选项 2022-05-21 20:00:39 +08:00
刘祥超
d3cdc24ebf WAF策略中增加验证码相关定制设置 2022-05-20 22:07:04 +08:00
刘祥超
f37d2fc4d7 健康检查增加是否记录访问日志选项 2022-05-19 17:14:19 +08:00
刘祥超
6cd182b858 实现基础的DDoS防护 2022-05-18 21:02:47 +08:00
刘祥超
d46bf37726 调整左侧主菜单顺序 2022-05-12 11:04:37 +08:00
刘祥超
363295e0ea 增加edge-admin [dev|prod]两个命令 2022-05-11 21:39:31 +08:00
刘祥超
611d5daf27 升级部分库 2022-05-09 15:51:14 +08:00
刘祥超
0c1e42078e 集群节点列表可以使用“未分组”筛选 2022-05-08 19:32:55 +08:00
刘祥超
c9acafd3ad fix typo 2022-05-08 19:32:05 +08:00
刘祥超
75dd760148 阿里云DNS增加区域ID 2022-05-07 20:59:38 +08:00
刘祥超
28bfbbfa68 DNS服务商增加厂家筛选 2022-05-07 20:41:20 +08:00
刘祥超
36326697d7 创建网站服务时强制填写域名/优化源站未填写时交互 2022-05-07 20:26:40 +08:00
刘祥超
34b7cfec6f 域名设置增加说明文字 2022-05-07 20:01:29 +08:00
刘祥超
f65b725b27 修复缓存条件设置时界面可能不会自动刷新的问题 2022-05-05 11:25:23 +08:00
刘祥超
d7bceb8f2d 路由规则可以单独设置UAM(仅企业版可用) 2022-05-04 20:31:58 +08:00
刘祥超
549f418b69 节点增加DNS解析库类型设置 2022-05-04 16:41:28 +08:00
刘祥超
8a95f49f8f 改进文字提示 2022-04-29 08:28:07 +08:00
刘祥超
2c269a87a6 syn flood封禁时间从4位延长到8位 2022-04-27 16:09:15 +08:00
刘祥超
c17a27b7bf 版本修改为0.4.8 2022-04-25 11:11:52 +08:00
刘祥超
3eaecf2ca0 修复节点列表中百分号显示 2022-04-25 09:24:38 +08:00
刘祥超
07182639f3 优化一个界面显示 2022-04-24 20:13:21 +08:00
刘祥超
046628eda4 管理界面设置中“是否显示底部开源信息”选项变更时即使更新界面底部显示 2022-04-24 18:05:25 +08:00
刘祥超
8f0054b2b0 更新components.js 2022-04-24 15:24:15 +08:00
刘祥超
33f86a7730 健康检查默认只做基础的请求 2022-04-23 13:26:34 +08:00
刘祥超
2487c69136 多个API节点时选择一个作为主节点 2022-04-23 12:32:13 +08:00
刘祥超
492d24f28a 修改一个搜索示例(从http改为https) 2022-04-23 09:09:00 +08:00
刘祥超
9a355da75c 集群概要信息中增加系统服务状态 2022-04-22 22:04:36 +08:00
刘祥超
8bae065572 手动健康检查增加超时时间为60s 2022-04-22 20:56:49 +08:00
刘祥超
efebb5a869 删除不需要的文件 2022-04-22 16:52:17 +08:00
刘祥超
32cc745a85 删除不需要的文件 2022-04-22 16:49:34 +08:00
刘祥超
42bfe3ffac 删除不必要的文件 2022-04-22 16:05:37 +08:00
刘祥超
de3a5f87cf 删除不需要的文件 2022-04-22 15:58:15 +08:00
刘祥超
bd1add6dea 优化代码 2022-04-22 15:58:07 +08:00
刘祥超
d275cc6a8b 修改一处颜色显示 2022-04-22 15:15:24 +08:00
刘祥超
556b5bd2e9 优化代码 2022-04-22 10:38:03 +08:00
刘祥超
e26cf2a19d 优化代码 2022-04-22 09:58:25 +08:00
刘祥超
94f5f1faf6 节点列表页CPU和内容用量增加百分号% 2022-04-22 09:22:15 +08:00
刘祥超
fb3c58bd60 修复IPBox只能显示昨日日志的Bug 2022-04-22 09:15:08 +08:00
刘祥超
e50c918e4d 优化界面显示 2022-04-22 09:14:48 +08:00
刘祥超
eb3ff3369c 增加WAF日志配置/WAF策略中之日志中增加分表查询 2022-04-21 19:45:25 +08:00
刘祥超
a07a08991f 更新components.js 2022-04-21 18:38:47 +08:00
刘祥超
6fcc8f3401 访问日志列表显示WAF相关信息 2022-04-21 16:20:03 +08:00
刘祥超
36b38c6da4 IP列表增加名单类型筛选 2022-04-21 15:09:24 +08:00
刘祥超
7f2f86b7ea 通过IP看访问日志时优先查询IP被加入名单的时间 2022-04-21 09:42:25 +08:00
刘祥超
68514bc7ed 修复访问日志耗时可能显示NaN的Bug 2022-04-21 09:41:59 +08:00
刘祥超
f0dfab536c 修复一个日志提示标签错误 2022-04-19 19:55:03 +08:00
刘祥超
3b75c9999e 自动将端口加入到本地防火墙 2022-04-19 19:51:38 +08:00
刘祥超
084ddf4f5d 更新components.js 2022-04-19 19:51:29 +08:00
刘祥超
6770b1fa72 域名解析增加GoDaddy DNS(目前仅商业版可用) 2022-04-19 16:33:51 +08:00
刘祥超
118d39ed83 修复sendfile功能显示错误 2022-04-19 14:19:06 +08:00
刘祥超
c24df4f876 证书在上传时检查有效期 2022-04-19 11:14:40 +08:00
刘祥超
0ae9c25d6f IP名单增加区域和ISP显示 2022-04-19 10:57:47 +08:00
刘祥超
f99b2a9def 优化界面文字 2022-04-18 21:05:01 +08:00
刘祥超
caf9eeae2e 集群DNS子域名可以随机生成 2022-04-18 18:19:30 +08:00
刘祥超
6afecb5708 单个服务切换集群时可以选择是否保留节点上的配置/总是可以切换集群,不再受所属用户的影响 2022-04-18 17:18:39 +08:00
刘祥超
739d32e2e0 修复访问日志分表为-1时无法切换的问题 2022-04-18 14:32:20 +08:00
刘祥超
4e601298b0 优化界面 2022-04-17 20:49:40 +08:00
刘祥超
74cffced3f 优化访问日志弹窗中的发送字节 2022-04-17 20:22:34 +08:00
刘祥超
f8cc76be35 服务访问日志也支持分表查询 2022-04-17 17:08:39 +08:00
刘祥超
dced2dd418 修复编译脚本无法生成components.js的问题 2022-04-17 16:40:29 +08:00
刘祥超
418fe97c67 Update components.src.js 2022-04-17 16:26:53 +08:00
刘祥超
1440f9b721 优化IPBox访问日志查询 2022-04-17 16:23:52 +08:00
刘祥超
9504c086ea 访问日志可以使用分表查询 2022-04-17 16:18:43 +08:00
刘祥超
3a15d6475c 优化看板界面 2022-04-16 22:23:58 +08:00
刘祥超
18f3a00c0f 修复看板中统计数据可能不显示的问题 2022-04-16 22:22:18 +08:00
刘祥超
5138bbe947 服务列表增加下行带宽 2022-04-15 12:15:09 +08:00
刘祥超
a309feb516 修复服务列表页面中待修复的日志数量显示错误的Bug 2022-04-15 09:58:30 +08:00
刘祥超
e9c172c261 服务列表--选择分组--增加"[未分组]"选项 2022-04-14 16:59:45 +08:00
刘祥超
6ac6842a8d 优化域名设置交互 2022-04-14 16:58:57 +08:00
刘祥超
71dc53ac1f 优化界面/将“反向代理”菜单改为“源站”,将“Web设置”改为“静态分发” 2022-04-14 15:58:39 +08:00
刘祥超
4450fa1379 优化SSH地址自动填充 2022-04-10 16:56:31 +08:00
刘祥超
af0f9489c7 优化本地日志 2022-04-10 15:58:54 +08:00
刘祥超
75b2b2bdb2 修改SSH时自动填入SSH主机地址/节点设置--SSH设置增加连接测试 2022-04-09 21:44:34 +08:00
刘祥超
2d8224fd12 服务设置菜单和路由规则菜单支持拦截函数 2022-04-08 22:09:45 +08:00
刘祥超
c3aea3ba72 支持使用uglifyjs压缩js组件文件 2022-04-08 21:24:54 +08:00
刘祥超
ad1ff29ed2 Update components.js 2022-04-08 20:49:33 +08:00
刘祥超
9887bd99c7 缓存条件增加暂停/恢复功能;缓存条件修改后自动保存 2022-04-08 20:49:31 +08:00
刘祥超
9af9d0192d 判断修改节点级别权限 2022-04-07 21:37:13 +08:00
刘祥超
62933cf637 优化界面 2022-04-07 19:53:10 +08:00
刘祥超
ee0837571d 增加当日统计接口 2022-04-07 19:46:45 +08:00
刘祥超
c2b12419c7 优化代码 2022-04-07 18:53:56 +08:00
刘祥超
991aed7e8c Update components.js 2022-04-07 18:34:34 +08:00
刘祥超
528d5fc5a9 增加节点列表 2022-04-07 18:31:21 +08:00
刘祥超
93569227f3 服务分组开启访问日志设置时,在服务访问日志设置界面有对应提示 2022-04-07 16:19:44 +08:00
刘祥超
46812f9e42 优化界面/删除不需要的文件 2022-04-07 10:21:38 +08:00
刘祥超
61f043319d 对访问日志中的Cookie进行排序显示 2022-04-05 15:58:35 +08:00
刘祥超
77140d01a0 优化访问日志Header显示 2022-04-05 15:43:49 +08:00
刘祥超
beff326001 访问日志中的响应Header和请求Header排序后显示 2022-04-05 15:38:22 +08:00
刘祥超
f2841b7328 优化界面 2022-04-05 15:23:48 +08:00
刘祥超
2ba63b2484 优化界面 2022-04-05 11:33:29 +08:00
刘祥超
f054241a73 优化界面 2022-04-04 19:47:57 +08:00
刘祥超
b5007b9195 缓存文件实现Sendfile 2022-04-04 19:46:12 +08:00
刘祥超
9deea64097 商业版支持L2节点 2022-04-04 12:08:18 +08:00
刘祥超
105777da21 优化界面 2022-04-02 16:33:08 +08:00
刘祥超
e13743dfa8 优化界面 2022-04-02 16:28:22 +08:00
刘祥超
35bdd3a41a 优化界面 2022-04-02 11:09:04 +08:00
刘祥超
464d757800 README增加功能介绍 2022-04-02 10:56:14 +08:00
刘祥超
8076fcd148 集群可以单独设置WebP策略 2022-04-01 16:42:08 +08:00
刘祥超
53b1f07601 优化证书上传和修改界面 2022-04-01 11:12:42 +08:00
刘祥超
2626d78835 只有满足缓存条件的图片内容才会被转换 2022-03-31 16:22:14 +08:00
刘祥超
3442ddfae2 优化代码 2022-03-31 15:21:02 +08:00
刘祥超
5ed863c5ec 特殊页面改为自定义页面 2022-03-31 14:53:07 +08:00
刘祥超
00f6387182 更新README中链接 2022-03-30 22:27:05 +08:00
刘祥超
ca70e8f0c8 全站防护改为5秒盾(商业版可用) 2022-03-30 11:37:55 +08:00
刘祥超
081e95b293 可以用域名搜索DNS账号 2022-03-30 11:15:32 +08:00
刘祥超
1282e610ba DNSPod区域改为中国站和国际站 2022-03-30 10:59:42 +08:00
刘祥超
287c52cef4 IP列表可以使用级别筛选 2022-03-30 09:41:14 +08:00
刘祥超
dd421fec80 支持DNSPod国际版 2022-03-30 09:12:16 +08:00
刘祥超
9b10bdaf0d Update components.js 2022-03-29 21:25:33 +08:00
刘祥超
4b916829a4 商业版增加UAM功能 2022-03-29 21:25:30 +08:00
刘祥超
b33e9d9187 修复服务统计--流量统计--即时的tooltip错误 2022-03-29 10:29:49 +08:00
刘祥超
9d0d323b50 数据库清理界面显示行数 2022-03-28 17:31:37 +08:00
刘祥超
2872569ce0 可以自行设定指标数据保留时间 2022-03-28 09:37:48 +08:00
刘祥超
cd2212b754 优化界面 2022-03-27 17:23:02 +08:00
刘祥超
eb6525b8d9 优化看板打开速度/删除一些不必要的文件 2022-03-27 16:39:20 +08:00
刘祥超
10319a1e3d 优化界面 2022-03-26 22:10:34 +08:00
刘祥超
6bcfea9372 支持路由定义请求脚本 2022-03-26 22:04:26 +08:00
刘祥超
4699d1c2ee 管理界面设置和用户界面设置可以修改时区 2022-03-26 10:23:03 +08:00
刘祥超
68f0b2efc3 反向代理要求必须添加源站 2022-03-25 14:42:28 +08:00
刘祥超
b2af8e196b 优化界面 2022-03-25 14:10:40 +08:00
刘祥超
2e626a915f 优化文字提示 2022-03-25 09:32:46 +08:00
刘祥超
0127050d89 修复无法修复单个日志的Bug 2022-03-25 09:32:35 +08:00
刘祥超
a25938022f 可以修复单页或者全部服务日志 2022-03-23 17:31:53 +08:00
刘祥超
c47b2973b0 优化文字 2022-03-23 16:33:20 +08:00
刘祥超
efd3934470 优化界面 2022-03-23 16:29:56 +08:00
刘祥超
fb7da4e07e 版本号改为0.4.7 2022-03-23 14:45:10 +08:00
刘祥超
d7d209f694 更改版本为0.4.6 2022-03-23 10:03:25 +08:00
刘祥超
83a88299c2 Update components.js 2022-03-21 08:23:34 +08:00
刘祥超
bfae3b86cc Age改为在缓存中的已存活时间 2022-03-20 21:18:51 +08:00
刘祥超
027ee4d336 GRPC通讯支持gzip压缩 2022-03-20 11:28:38 +08:00
刘祥超
b07a65c879 更新相关库 2022-03-20 10:46:09 +08:00
刘祥超
284b2762f5 优化界面 2022-03-18 20:21:00 +08:00
刘祥超
5ba6e3b332 改进文字 2022-03-18 17:02:36 +08:00
刘祥超
8b0e3d960a Update components.js 2022-03-17 19:59:58 +08:00
刘祥超
fba2953974 优化界面 2022-03-17 19:57:41 +08:00
刘祥超
a36b843eff 优化指标统计图表数字展示 2022-03-17 16:01:47 +08:00
刘祥超
0735bc2d8a 源站支持自定义回源主机名 2022-03-17 15:48:08 +08:00
刘祥超
69d6fd645b 修复访问日志按小时搜索无法上翻页的Bug 2022-03-17 12:31:45 +08:00
刘祥超
039b11d434 增加置顶集群功能 2022-03-17 11:12:24 +08:00
刘祥超
485581f680 优化界面 2022-03-17 10:36:30 +08:00
刘祥超
a0c2006e24 Update components.js 2022-03-16 22:48:04 +08:00
刘祥超
7a288c360d IPSet支持IPv6 2022-03-16 20:48:35 +08:00
刘祥超
597d39f651 优化文字 2022-03-16 17:07:46 +08:00
刘祥超
7d27f64b8a 节点可以单独设置缓存目录 2022-03-16 15:24:11 +08:00
刘祥超
bd280bdc53 改进文字 2022-03-14 19:25:19 +08:00
刘祥超
018429866e 优化文字提示 2022-03-14 16:24:09 +08:00
刘祥超
78ad6526b8 缓存策略可以使用存储类型筛选 2022-03-14 15:42:10 +08:00
刘祥超
8c6e960db7 实现回源跟随功能 2022-03-14 15:07:49 +08:00
刘祥超
46d38ff8c0 格式化部分图表中的数字 2022-03-14 12:04:46 +08:00
刘祥超
db6fe469ad 访问日志慢的时候增加指定域名查询建议 2022-03-11 20:35:42 +08:00
刘祥超
5a7630bcd1 增加证书OCSP错误日志管理 2022-03-11 20:27:45 +08:00
刘祥超
099b57169d 上传js 2022-03-10 21:32:56 +08:00
刘祥超
e22dfd3314 优化界面 2022-03-10 15:47:25 +08:00
刘祥超
be71992930 增加OCSP Stapling功能 2022-03-10 11:55:09 +08:00
刘祥超
37433178cb 支持使用小时筛选访问日志 2022-03-09 11:00:49 +08:00
刘祥超
14499a4564 增加对访问日志自动分表配置 2022-03-09 10:01:52 +08:00
刘祥超
0355777f59 优化文字 2022-03-06 19:48:50 +08:00
刘祥超
a55834c78d 优化界面 2022-03-04 17:00:01 +08:00
刘祥超
79bbb47459 更新TeaGo 2022-03-04 15:01:26 +08:00
刘祥超
d3bcf0605b 升级相关依赖库 2022-03-04 12:35:28 +08:00
刘祥超
19a02f3a40 实现基础的区间内容缓存(206 partial content) 2022-03-03 19:32:11 +08:00
刘祥超
db20e9308a 修复选择集群弹窗页面可能只显示前6个集群的Bug 2022-02-28 14:41:55 +08:00
刘祥超
b3f9f28554 优化界面显示 2022-02-27 21:20:49 +08:00
刘祥超
ea5bd3f346 可以在管理界面设置里设置默认每页显示数 2022-02-24 20:52:47 +08:00
刘祥超
5c13797639 缓存可以设置是否使用系统默认设置 2022-02-24 20:39:09 +08:00
刘祥超
f147905532 增加是否同步写入压缩缓存设置 2022-02-24 20:12:15 +08:00
刘祥超
a73314c12a 修改版本为0.4.5 2022-02-24 19:25:12 +08:00
刘祥超
619934a275 修复访问日志XSS漏洞 2022-02-23 17:34:54 +08:00
刘祥超
5fe1384e55 修改版本为0.4.4 2022-02-23 14:48:33 +08:00
刘祥超
9af2c4a18a 修改版本为v0.4.3 2022-02-21 18:32:03 +08:00
刘祥超
8f4c56b24a 更改版本为v0.4.2 2022-02-21 17:52:36 +08:00
刘祥超
5eb0f65934 更新截图 2022-02-21 11:53:51 +08:00
刘祥超
4ed4f165ac URL跳转可以设置是否保留参数 2022-02-20 09:17:30 +08:00
刘祥超
eb8419fda0 Update components.js 2022-02-17 17:24:23 +08:00
刘祥超
b4a7ee0e89 域名支持--(连续的连字符) 2022-02-17 17:24:14 +08:00
刘祥超
83ad1bc529 优化界面 2022-02-15 14:54:45 +08:00
刘祥超
56948d3035 优化菜单中badge显示 2022-02-14 08:58:31 +08:00
刘祥超
e71e5ad57e 支持默认价格设置 2022-01-23 20:16:02 +08:00
刘祥超
e06d1fa5b0 实现带宽计费套餐 2022-01-23 19:16:22 +08:00
刘祥超
460439f6bd 修复服务访问日志不能使用集群、节点筛选的Bug 2022-01-20 16:28:43 +08:00
刘祥超
4b4072a47e 优化界面 2022-01-20 15:53:46 +08:00
刘祥超
65d19d92e9 增加API方法调用耗时统计 2022-01-19 16:53:38 +08:00
刘祥超
79e52d1c6e 优化demo模式进入命令 2022-01-19 15:58:54 +08:00
刘祥超
59963ee7b9 修复检查更新配置不起作用的Bug 2022-01-18 19:29:42 +08:00
刘祥超
88273b1c9b 修改版本为v0.4.1 2022-01-17 10:53:28 +08:00
刘祥超
bc1eb994f9 Update components.js 2022-01-16 20:04:11 +08:00
刘祥超
136f0fd4bd 源站支持客户端证书 2022-01-16 19:51:26 +08:00
刘祥超
021dc13ce9 CAPTCHA增加多个选项 2022-01-16 16:54:20 +08:00
刘祥超
925b71489d Update components.js 2022-01-14 10:46:02 +08:00
刘祥超
580ce9f8f5 可以在IP名单、访问日志中跳到对应的WAF规则集 2022-01-14 10:45:58 +08:00
刘祥超
2b3d8c062d 优化界面 2022-01-13 15:05:57 +08:00
刘祥超
41858e091a Update components.js 2022-01-13 10:03:54 +08:00
刘祥超
5b7eaf08ae 实现open file cache 2022-01-12 21:09:11 +08:00
刘祥超
7362722adb 优化代码 2022-01-11 16:02:27 +08:00
刘祥超
8198ea8819 可以使用集群搜索WAF策略、缓存策略 2022-01-11 15:46:47 +08:00
刘祥超
cb615f4cbc Update components.js 2022-01-11 15:28:50 +08:00
刘祥超
6623fd8362 优化交互 2022-01-11 15:26:43 +08:00
刘祥超
7f8be85116 运行日志可以使用集群、节点筛选 2022-01-11 14:59:19 +08:00
刘祥超
c61381441c 访问日志可以使用集群和节点搜索 2022-01-11 12:04:03 +08:00
刘祥超
b3cecdfea2 实现自动SYN Flood防护 2022-01-10 19:54:29 +08:00
刘祥超
66f582df58 WAF规则增加备注信息/其他界面优化 2022-01-10 10:28:23 +08:00
刘祥超
7f6f7e11ce 生成components.js 2022-01-09 20:13:18 +08:00
刘祥超
0bdda313da WAF策略增加是否使用本地防火墙设置 2022-01-09 17:05:51 +08:00
刘祥超
d5e851cff7 优化界面 2022-01-09 10:47:03 +08:00
刘祥超
92f1ec13f9 优化界面 2022-01-09 10:43:37 +08:00
刘祥超
5376006754 优化文字提示 2022-01-08 16:49:46 +08:00
刘祥超
361979411e IP名单增加未读数、按未读筛选等操作 2022-01-08 16:48:45 +08:00
刘祥超
3981308083 优化代码 2022-01-06 11:13:36 +08:00
刘祥超
facd5e14cc 改进文字 2022-01-05 20:13:22 +08:00
刘祥超
ecce92c528 Update components.js 2022-01-05 15:54:22 +08:00
刘祥超
7696940989 用户列表可以使用待审核、关键词搜索 2022-01-05 11:12:28 +08:00
刘祥超
941ff46c2c 实现用户注册/审核功能 2022-01-05 10:45:28 +08:00
刘祥超
94036073de 优化请求脚本配置交互 2022-01-03 21:48:48 +08:00
刘祥超
13216f481c 尝试自动在firewalld中开放端口 2022-01-03 16:28:27 +08:00
刘祥超
eb35df8720 改进服务访问日志、设置页在手机下的显示 2022-01-03 14:50:11 +08:00
刘祥超
bf500fe1a4 增加格式化数字函数 2022-01-03 12:04:09 +08:00
刘祥超
c173e86e62 创建服务时默认选中统计 2022-01-03 11:27:01 +08:00
刘祥超
d4cb148272 WAF动作中各个超时/有效秒数最大值从10位改成9位 2022-01-03 11:17:33 +08:00
刘祥超
ffc3f8544e 节点运行日志增加标签筛选 2022-01-03 11:08:59 +08:00
刘祥超
b326bfe63a 优化文字 2021-12-31 19:45:14 +08:00
刘祥超
12f3f47ef9 实现初版边缘脚本 2021-12-31 15:20:59 +08:00
刘祥超
45734747c1 修改版本为0.4.0 2021-12-31 15:19:44 +08:00
刘祥超
37933d814c 修改版本号0.3.8 2021-12-31 11:38:55 +08:00
刘祥超
5b26287264 修复路由规则中不能设置响应Header的Bug 2021-12-28 20:12:29 +08:00
刘祥超
bac20b1d1f 自动检查更新被取消时,同时重置已发现的最新版本信息 2021-12-22 10:02:06 +08:00
刘祥超
904c641992 删除不必要的文件 2021-12-22 10:01:35 +08:00
刘祥超
36004bfd94 优化代码 2021-12-21 15:41:43 +08:00
刘祥超
0fcba7e90c 增加自动检查系统更新设置 2021-12-21 15:18:11 +08:00
刘祥超
2e9182933e 修改版本号为0.4.0 2021-12-20 20:01:55 +08:00
刘祥超
e3674fa2c1 优化文字 2021-12-20 09:35:36 +08:00
刘祥超
a14cbe1319 访问日志限制字段 2021-12-20 09:35:25 +08:00
刘祥超
c03c35de88 访问日志限制字段 2021-12-19 20:39:18 +08:00
刘祥超
dc6c649af8 优化文字提示等 2021-12-19 18:56:09 +08:00
刘祥超
b0d2bdb0ba 修复HSTS无法设置有效期的Bug 2021-12-18 17:23:55 +08:00
刘祥超
9239dd9a8b 调整单次通知更新任务的数量为500(原来200) 2021-12-17 14:25:40 +08:00
刘祥超
07a368a0bd 修改相关域名、文字等 2021-12-17 14:17:48 +08:00
刘祥超
b8d6d1c249 访问日志中缓存状态增加STALE 2021-12-17 11:55:12 +08:00
刘祥超
0f1d6a1ad2 实现stale cache配置 2021-12-16 17:27:09 +08:00
刘祥超
37c6928ffc 节点IP显示原始IP(如果已经切换到备用IP的话) 2021-12-16 15:18:21 +08:00
刘祥超
7478b6dbe0 优化代码 2021-12-15 20:46:42 +08:00
刘祥超
e3cd6e1441 增加在线检查最新版本功能 2021-12-15 20:13:10 +08:00
刘祥超
8081d968b6 Update components.js 2021-12-15 09:56:18 +08:00
刘祥超
a226eee6ef HTTP Header:实现请求方法、域名、状态码等限制,实现内容替换功能 2021-12-14 21:26:32 +08:00
刘祥超
8b5a21e593 访问日志查询过慢的时候,提示建议增加新的日志节点 2021-12-14 15:50:21 +08:00
刘祥超
2c71f6c6be 实现访问日志队列 2021-12-14 12:45:09 +08:00
刘祥超
1a50b01edf 优化代码 2021-12-13 10:53:49 +08:00
刘祥超
05f08eeb4c 优化坐标轴单位 2021-12-12 21:13:55 +08:00
刘祥超
fdc3ebb5c5 WAF添加规则:调整界面/增加正则表达式测试功能 2021-12-12 20:56:25 +08:00
刘祥超
8f06bccd48 WAF策略:可以修改分组代号/导入时可以根据名称合并/导出时可以导出停用的分组 2021-12-12 20:24:15 +08:00
刘祥超
16b4eb67d4 Update components.js 2021-12-12 17:14:46 +08:00
刘祥超
ffa545cb41 服务分组可以设置请求限制 2021-12-12 17:07:16 +08:00
刘祥超
f67454d51c 路由规则增加专属域名设置 2021-12-12 16:38:52 +08:00
刘祥超
8ae54c56db 增加排除扩展名条件 2021-12-12 16:14:56 +08:00
刘祥超
9a17adcc6f 请求条件增加不区分大小写选项 2021-12-12 16:11:25 +08:00
刘祥超
4dd6903025 优化界面 2021-12-12 11:58:16 +08:00
刘祥超
085770d0ad 实现请求连接数等限制/容量组件增加EB支持 2021-12-12 11:46:09 +08:00
刘祥超
fbaba7c37d 修复公共黑名单/白名单无法搜索的Bug 2021-12-10 11:12:49 +08:00
刘祥超
d5cea208d2 支持设置单节点最大线程数、单节点TCP最大连接数 2021-12-09 18:49:35 +08:00
刘祥超
36397adca4 修复服务统计图表颜色显示问题/Expires设置增加提示文字 2021-12-08 21:40:01 +08:00
刘祥超
4971e25d44 WAF看板最新拦截记录增加区域信息 2021-12-08 20:28:26 +08:00
刘祥超
150357441d 修复域名为空时点击审核页面为空的Bug 2021-12-08 20:20:10 +08:00
刘祥超
c4ee663285 重新生成components.js 2021-12-08 19:13:54 +08:00
刘祥超
8c95b4a9b9 多处访问日志增加单页显示条数选择 2021-12-08 19:13:34 +08:00
刘祥超
32ba919851 增加部分访问日志条数10->20 2021-12-08 17:49:30 +08:00
刘祥超
eb37345e85 可以在缓存条件里设置Expires Header 2021-12-08 17:41:12 +08:00
刘祥超
681e454917 修复编译时components.js可能没有更新的Bug 2021-12-08 10:03:27 +08:00
刘祥超
7c7b82dee4 优化地图标签 2021-12-08 09:37:46 +08:00
刘祥超
2ae47af8f0 增加批量增加节点IP接口 2021-12-07 18:22:46 +08:00
刘祥超
b72d91d0d4 访问日志实现记录和显示requestBody 2021-12-07 15:03:48 +08:00
刘祥超
56010e7203 缓存默认支持所有请求方法 2021-12-07 10:47:46 +08:00
刘祥超
f658698f7b 缓存支持请求方法设置 2021-12-07 10:45:49 +08:00
刘祥超
b708b9c6df 将缓存默认key改为${scheme}://${host}${requestPath}${isArgs}${args} 2021-12-07 10:04:50 +08:00
刘祥超
c2908e17fa SSH认证支持sudo 2021-12-06 19:24:30 +08:00
刘祥超
302daab824 服务看板增加区域地图 2021-12-06 09:16:23 +08:00
刘祥超
ec49f238d6 优化界面 2021-12-06 08:55:47 +08:00
刘祥超
3e43a5d866 上传components.js/优化地图样式 2021-12-05 20:59:07 +08:00
刘祥超
a99d5e68e9 商业版WAF看板增加地图 2021-12-05 19:38:14 +08:00
刘祥超
28514276ec 商业版首页增加地图/调低各个图表的高度,以便同时可以显示更多的图表 2021-12-05 18:59:20 +08:00
刘祥超
b611427c17 创建新服务时默认开启配置可以选择统计 2021-12-03 14:55:14 +08:00
刘祥超
ce7a4ead04 首页看板显示未审核的服务数、本周流量、昨日流量 2021-12-03 14:54:15 +08:00
刘祥超
5fe15a85fd 优化IPBox交互/优化TOA文字提示 2021-12-03 11:00:20 +08:00
刘祥超
ae412909f6 优化服务设置界面顶部菜单 2021-12-02 17:41:51 +08:00
刘祥超
c6ed579797 IPBox把IP加入黑名单可以选择过期时间/可以从已经添加的名单中删除/已经添加的名单中显示过期时间 2021-12-02 17:11:44 +08:00
刘祥超
b2a525268e WAF规则集中增加是否忽略局域网IP选项 2021-12-02 16:09:15 +08:00
刘祥超
eb78b4881c 多个提示页面增加请求ID 2021-12-02 14:45:51 +08:00
刘祥超
4ce6e5a9f6 访问日志弹窗中加入请求ID/优化Header添加/修改文字提示 2021-12-02 11:49:36 +08:00
刘祥超
15d7e75555 优化URL跳转文字提示 2021-12-02 10:39:22 +08:00
刘祥超
6453cc6ccc 缓存配置增加是否支持Cache-Control: max-age=... 2021-12-02 10:18:22 +08:00
刘祥超
d882a2eb63 缓存配置增加Age Header配置 2021-12-02 09:54:31 +08:00
刘祥超
065ac4aa25 增加generate.sh脚本用来生成文件 2021-12-02 09:46:46 +08:00
刘祥超
dea54fc55e 增加是否记录499选项 2021-12-01 21:13:15 +08:00
刘祥超
8f425bd9c7 当用户提交待审核域名时,给管理员发送消息 2021-12-01 17:19:50 +08:00
刘祥超
9d909d73b8 审核中服务增加提交审核时间/已通过域名标绿 2021-12-01 17:06:05 +08:00
刘祥超
b417d50a28 优化节点日志:可以批量设置服务错误日志为已修复等 2021-11-30 16:43:44 +08:00
刘祥超
aa93a2f702 优化编译脚本 2021-11-30 11:05:58 +08:00
刘祥超
ba7125e773 优化文字 2021-11-30 11:05:24 +08:00
刘祥超
46a7eaa4bb 商业版认证增加申请页面 2021-11-30 10:02:31 +08:00
刘祥超
887439a6fe 优化代码 2021-11-29 20:35:47 +08:00
刘祥超
0e2b07d06d 优化提示文字 2021-11-29 16:41:32 +08:00
刘祥超
b7d4bde11b 修改部分菜单名 2021-11-29 12:00:23 +08:00
刘祥超
9707360948 完善套餐 2021-11-28 20:11:48 +08:00
刘祥超
103a8eb092 修改版本号为0.3.7 2021-11-28 14:28:53 +08:00
刘祥超
dcba4f9376 节点同步不在图标上提示IP名单相关更新 2021-11-27 17:06:36 +08:00
刘祥超
12aaa6fcb1 更新生成的components.js 2021-11-27 17:06:03 +08:00
刘祥超
d8393481a4 修改域名变更时是否需要审核的初始状态 2021-11-25 12:10:23 +08:00
刘祥超
70d3202a2c 服务增加是否合并URL中的多余分隔符选项 2021-11-24 14:49:42 +08:00
刘祥超
c7f1bbc03d 版本号改为0.3.6 2021-11-24 14:03:56 +08:00
刘祥超
61a55cb3f4 优化命名 2021-11-24 11:58:01 +08:00
刘祥超
6a484af775 将版本修改为0.3.5.2 2021-11-24 10:29:59 +08:00
刘祥超
3074d41cf2 改进源站专属域名的文字提示和交互 2021-11-22 18:46:08 +08:00
刘祥超
54199058e3 修复服务无法创建的Bug 2021-11-22 14:34:32 +08:00
刘祥超
87a533791b 版本改为0.3.5.1 2021-11-22 14:34:20 +08:00
刘祥超
c3109bb2c6 编译时生成components.js 2021-11-22 12:08:53 +08:00
刘祥超
031cb836d2 安装时等API节点启动完毕后才进行下一步,避免因为未启动完整而导致的错误 2021-11-21 19:26:05 +08:00
刘祥超
aa0a9134cb 迁移后确认API节点界面可以跳转到安装界面 2021-11-21 19:25:42 +08:00
刘祥超
749eac74fe 当迁移了管理平台后,自动跳转到确认API配置页 2021-11-21 15:57:13 +08:00
刘祥超
ac39908737 优化删除IP时成功消息提示时间 2021-11-21 09:45:11 +08:00
刘祥超
bb8f4bf488 增加批量删除IP名单中的IP的功能 2021-11-21 09:43:14 +08:00
刘祥超
db0d157a74 修复时间输入组件时间戳总是多一秒的Bug 2021-11-21 08:44:03 +08:00
刘祥超
bd8e1bbe71 优化RPC客户端锁 2021-11-20 19:17:52 +08:00
刘祥超
7aba898cf5 优化文字 2021-11-20 19:17:16 +08:00
刘祥超
baf039755f 实现迁移辅助功能(系统设置 -- 高级设置 -- 迁移) 2021-11-20 18:58:58 +08:00
刘祥超
70977f7d80 IP地址“健康检查失败”阈值改为“健康检查结果” 2021-11-18 14:47:48 +08:00
刘祥超
884342d6af 节点IP阈值增加节点健康检查失败 2021-11-18 14:30:46 +08:00
刘祥超
411b0fb4c2 修复看板--事件中无法单条已读操作的Bug 2021-11-18 08:52:29 +08:00
刘祥超
8053fb2399 IP名单中显示已过期标签 2021-11-17 21:18:33 +08:00
刘祥超
ed42dcab9c IP名单增加是否只显示自动拦截名单选项 2021-11-17 20:25:31 +08:00
刘祥超
866b5b0f2f IP名单中增加搜索按钮 2021-11-17 20:15:37 +08:00
刘祥超
5834a1a0fa 增加全局查看、检索IP功能 2021-11-17 19:50:52 +08:00
刘祥超
8c6d845603 将公用IP名单默认改成全局名单,自动应用于所有服务 2021-11-17 16:14:37 +08:00
刘祥超
667f363f3c WAF block动作默认封锁范围为global 2021-11-16 19:17:08 +08:00
刘祥超
e209ff38d9 IP名单中显示IP创建时相关的服务、WAF策略等信息 2021-11-16 16:11:29 +08:00
刘祥超
ea915993b6 节点运行日志中显示相关服务链接 2021-11-15 16:51:39 +08:00
刘祥超
72d0468c6a IP名单中的IP增加添加日期显示 2021-11-15 11:31:54 +08:00
刘祥超
35ae13b1c3 优化缓存配置界面 2021-11-14 16:21:04 +08:00
刘祥超
1d9460f565 优化RPC连接 2021-11-10 22:22:27 +08:00
刘祥超
e9a3ed71b4 将带宽限制改为流量限制 2021-11-09 17:36:38 +08:00
刘祥超
e344e5b7e6 支持套餐相关操作 2021-11-09 15:36:18 +08:00
刘祥超
e814064403 编译时删除.js.map文件 2021-11-09 14:19:42 +08:00
刘祥超
f5aeb5cbcd 支持购买套餐/续费套餐/用户账户操作等 2021-11-08 20:52:02 +08:00
刘祥超
f41164b892 删除不需要的文件 2021-11-07 09:11:30 +08:00
刘祥超
03073c8364 删除不需要的文件 2021-11-07 08:42:11 +08:00
刘祥超
a359bff531 安装时自动检查服务器上安装的MySQL 2021-11-06 18:35:22 +08:00
刘祥超
3789ac6433 域名解析中可以删除和恢复某个域名 2021-11-06 16:23:38 +08:00
刘祥超
9f53f59f18 SSH登录支持Passphrase 2021-11-06 15:31:07 +08:00
刘祥超
521bd746e3 当证书被API节点或者用户节点使用时不允许删除/规范命名 2021-11-05 17:56:17 +08:00
刘祥超
3d8e43a42b 修改文字 2021-11-05 17:13:43 +08:00
刘祥超
9452e1852d 规范API命名 2021-11-05 15:34:48 +08:00
刘祥超
3920d24af6 只有一个可用的API节点时不允许删除 2021-11-05 15:29:37 +08:00
刘祥超
3af11e6ba8 修改版本号为0.3.5 2021-11-05 14:59:10 +08:00
刘祥超
7bcde46d49 修复缓存条件可能无法保存的Bug 2021-11-04 11:23:29 +08:00
刘祥超
0b73041718 支持info指令查询PID、版本号等信息 2021-11-04 11:13:54 +08:00
刘祥超
dcdc0cb8c1 修复无法设置缓存条件的Bug 2021-11-04 11:12:59 +08:00
刘祥超
ad6ac1aad6 优化安装程序 2021-11-01 21:09:52 +08:00
刘祥超
249dc6accd 修改版本为0.3.4 2021-11-01 10:45:41 +08:00
刘祥超
939e5999ca 优化细节 2021-10-30 22:33:56 +08:00
刘祥超
1f91e57d56 增加套餐相关代码 2021-10-29 14:02:01 +08:00
刘祥超
81e749dc60 支持gif转webp 2021-10-29 12:21:52 +08:00
刘祥超
241b2afda8 Update .gitignore 2021-10-26 20:43:59 +08:00
刘祥超
94cc29f227 删除不必要的文件 2021-10-26 20:43:47 +08:00
刘祥超
6d6659eee1 WAF增加显示网页动作 2021-10-25 19:40:28 +08:00
刘祥超
5220be0775 优化代码 2021-10-25 19:01:56 +08:00
刘祥超
07ebbf0863 WAF模板中有新的规则时,可以在界面上收到提醒并点击加入 2021-10-25 12:02:03 +08:00
刘祥超
b60c767fc5 创建网站服务时增加缓存、WAF、从上级代理中读取IP等选项 2021-10-25 09:06:23 +08:00
刘祥超
371c3b78c3 DNS记录名支持下划线 2021-10-25 09:05:26 +08:00
刘祥超
6a3aa219d5 优化HTTP客户端IP配置交互 2021-10-22 14:40:39 +08:00
刘祥超
df586ddfdd 优化代码 2021-10-22 13:57:04 +08:00
刘祥超
f3b2bbfec0 删除不需要的文件 2021-10-22 13:20:02 +08:00
刘祥超
3ea2114798 IP名单列表可以搜索关键词 2021-10-22 12:38:52 +08:00
刘祥超
809cf70e0e 可以在IP名单中搜索IP 2021-10-22 12:19:02 +08:00
刘祥超
390619535f 实现单个服务的带宽限制(商业版) 2021-10-21 17:10:08 +08:00
刘祥超
3392ac1fa8 优化交互 2021-10-20 09:59:08 +08:00
刘祥超
b09d94abbe 网站服务--访问日志增加服务链接 2021-10-19 16:32:08 +08:00
刘祥超
03ac01d21f 健康检查支持UserAgent和是否基础请求设置 2021-10-19 16:31:45 +08:00
刘祥超
12b1d785e5 增加防盗链规则参数 2021-10-19 11:38:56 +08:00
刘祥超
71c58e9d2e WAF阻止动作增加封锁范围 2021-10-18 20:09:06 +08:00
刘祥超
13c2997a52 内容压缩支持对已压缩内容重新压缩 2021-10-18 16:49:19 +08:00
刘祥超
47b840cac9 优化文字提示 2021-10-18 16:49:04 +08:00
刘祥超
3f7f243f50 默认的内容压缩算法从gzip改为brotli 2021-10-18 11:59:50 +08:00
刘祥超
1d1e83b18d 增加PURGE某个URL缓存功能 2021-10-17 20:22:57 +08:00
刘祥超
6f3a602c76 优化分页条数修改/在弹窗下不运行某些任务 2021-10-16 12:45:56 +08:00
刘祥超
afb7a4c6a7 修复选择集群弹窗无法修改分页条数的问题 2021-10-16 12:45:25 +08:00
刘祥超
d0c950d4ca 优化界面 2021-10-16 12:06:55 +08:00
刘祥超
63ee7d5211 支持任意域名通过CNAME访问服务(开启选项后)/可以重新生成服务CNAME 2021-10-16 12:03:21 +08:00
刘祥超
6b0d875745 优化界面显示 2021-10-16 10:29:14 +08:00
刘祥超
a47a9b9c0c 删除CodeMirror中没用的代码 2021-10-16 10:26:14 +08:00
刘祥超
27040a3e5c 节点运行日志增加本页已读 2021-10-15 13:05:02 +08:00
刘祥超
c7a8a40e22 运行日志显示未读的日志数量 2021-10-15 12:54:23 +08:00
刘祥超
408de6af63 优化文字提示 2021-10-15 09:42:55 +08:00
刘祥超
8a324afaa1 数据看板增加事件列表(商业版) 2021-10-14 17:29:30 +08:00
刘祥超
54bc4cede0 修复无法编译amd64以外架构的Bug 2021-10-13 18:06:38 +08:00
刘祥超
4cfbea80b0 支持PROXY Protocol 2021-10-12 20:18:29 +08:00
刘祥超
64cb8286bd 集群非上海时区的在列表里显示时区 2021-10-12 14:39:13 +08:00
刘祥超
3e92e0afc6 优化修改时区交互 2021-10-12 11:49:26 +08:00
刘祥超
8a91308280 可以在集群中指定节点时区 2021-10-12 11:43:53 +08:00
刘祥超
3da861d71e 选择线路的时候关键词可以搜索域名 2021-10-12 08:41:02 +08:00
刘祥超
3566e18e99 WebP压缩支持ico 2021-10-11 14:51:50 +08:00
刘祥超
6e608e627a WebP默认mimeTypes从image/*改为image/png等 2021-10-11 13:56:51 +08:00
刘祥超
65f7fb979b 修改版本为0.3.3 2021-10-11 13:56:20 +08:00
刘祥超
558b5e14f1 优化细节 2021-10-10 20:17:40 +08:00
刘祥超
a5ee2dd03b 页面底部增加GoEdge官网和文档链接 2021-10-10 16:38:45 +08:00
刘祥超
9e9fe78b8d 优化升级提示文字 2021-10-10 16:33:27 +08:00
刘祥超
265e126faf TCP、TLS、UDP支持端口范围 2021-10-10 16:30:21 +08:00
刘祥超
af440e5c5b 服务分组增加特殊页面设置 2021-10-10 10:52:58 +08:00
刘祥超
32b8c91113 特殊页面可以直接使用HTML 2021-10-10 10:35:14 +08:00
刘祥超
dbc60ccca4 证书上传时可以选择输入文本内容 2021-10-09 17:30:05 +08:00
刘祥超
d5b5af5d3a 数据看板-WAF看板增加节点拦截排行和域名拦截排行 2021-10-09 16:01:17 +08:00
刘祥超
8c1bd3bc4e 增加新的界面风格theme4, theme5 2021-10-09 11:50:02 +08:00
刘祥超
8f638186a3 在服务看板中可以切换到附近的服务 2021-10-08 14:36:57 +08:00
刘祥超
0b5a27e674 支持更多的分组全局设置功能 2021-10-07 16:47:14 +08:00
刘祥超
870f1aaaec WAF模式从pass改为bypass 2021-10-07 13:55:00 +08:00
刘祥超
23cb4dcbe5 服务支持自定义访客IP地址获取方式 2021-10-06 11:40:24 +08:00
刘祥超
4f125b4244 添加源站时自动去除专属域名中的末尾斜杠 2021-10-06 09:29:44 +08:00
刘祥超
2b9de7938f 在服务设置里也显示WAF策略的模式 2021-10-06 09:08:09 +08:00
刘祥超
75bb07184f ACME使用EAB申请的账号只能绑定一个用户 2021-10-03 14:43:29 +08:00
刘祥超
1fb491d2e1 ACME证书增加ZeroSSL支持 2021-10-03 13:09:49 +08:00
刘祥超
0bc8bdd841 支持自动转换图像文件为WebP 2021-10-01 16:24:42 +08:00
刘祥超
71d4e2626e 自建DNS改成智能DNS 2021-09-30 13:20:50 +08:00
刘祥超
0b26cbdd01 WAF策略增加观察模式和通过模式 2021-09-30 11:30:36 +08:00
刘祥超
ca72b3c18b 内容压缩支持brotli和deflate 2021-09-29 20:12:27 +08:00
刘祥超
8676f2711b 在WAF规则产生错误时给予提示 2021-09-27 10:11:37 +08:00
刘祥超
788a86bdcf 看板增加离线节点数字 2021-09-27 09:23:48 +08:00
刘祥超
0df6b4b220 优化安装界面--设置管理员账号的交互 2021-09-26 15:08:53 +08:00
刘祥超
f14dcd5c28 缓存条件增加最小内容尺寸配置 2021-09-26 15:01:52 +08:00
刘祥超
f86548e046 版本改为0.3.2 2021-09-26 10:09:51 +08:00
刘祥超
9dea11ab11 节点设置中不显示阈值设置 2021-09-25 19:57:22 +08:00
刘祥超
00749f806c 优化编译脚本 2021-09-25 19:57:10 +08:00
刘祥超
e4e3591413 修改文字 2021-09-24 12:03:31 +08:00
刘祥超
3f847d7c7f 区域监控增加检测时间、结果页增加级别筛选 2021-09-23 10:02:32 +08:00
刘祥超
67a10994a4 提示已经有服务分组配置时加入分组链接 2021-09-22 19:46:22 +08:00
刘祥超
4016296e0b 可以在分组中设置一些全局配置选项 2021-09-22 19:39:38 +08:00
刘祥超
92777366ec 域名解析任务列表中增加集群解析页面链接/在域名解析--集群详情中显示正在执行的任务 2021-09-21 10:56:29 +08:00
刘祥超
beb34f8264 可以设置集群的DNS记录TTL 2021-09-20 20:01:15 +08:00
刘祥超
f6f8c5c858 在集群中可以设置自动加入DNS的CNAME记录 2021-09-20 16:37:41 +08:00
刘祥超
a19c4dffcd 反向代理源站实现使用域名分组 2021-09-20 11:54:28 +08:00
刘祥超
a5754971f6 开源版本也显示域名排行、缓存流量趋势、攻击流量趋势 2021-09-19 16:10:52 +08:00
刘祥超
dbe6435809 实现连通性变化发送通知功能 2021-09-18 14:22:01 +08:00
刘祥超
7e5b980600 优化消息弹窗交互 2021-09-18 11:02:23 +08:00
刘祥超
a6c3b70ee7 修改生成的YAML配置中可能含有tab的Bug 2021-09-16 15:06:37 +08:00
刘祥超
0a23e7951a 增加商业版激活功能 2021-09-16 10:32:58 +08:00
刘祥超
e0e3fc8fef 看板提醒商业版过期日期 2021-09-16 09:21:44 +08:00
刘祥超
a8b947f5ca 修复日期控件初始化格式可能错误的问题 2021-09-15 19:40:42 +08:00
刘祥超
0f28df51f1 优化界面 2021-09-15 19:22:54 +08:00
刘祥超
6e36528f35 IP地址详情中增加区域监控雷达图、数据列表 2021-09-15 17:54:03 +08:00
刘祥超
a885fdbea7 修复IP地址不能修改在线状态的Bug 2021-09-15 11:46:50 +08:00
刘祥超
713de74abb 调整部分命名 2021-09-14 19:39:41 +08:00
刘祥超
8db6a33e15 IP阈值动作增加WebHook 2021-09-14 15:27:54 +08:00
刘祥超
ed2d577be0 IP阈值增加节点分组和集群相关统计项目 2021-09-14 11:36:08 +08:00
刘祥超
084eddecbf 优化节点设置交互 2021-09-13 16:47:34 +08:00
刘祥超
5e09ba302a IP地址支持手动上线和从备用IP中恢复 2021-09-13 13:45:39 +08:00
刘祥超
fa37b40435 实现节点自动切换到备用IP 2021-09-13 10:51:16 +08:00
刘祥超
c95dde5187 实现基础的IP地址阈值 2021-09-12 20:21:32 +08:00
刘祥超
4d5a4d501a 修复当集群没有绑定DNS域名时无法修改节点DNS信息的Bug 2021-09-11 20:12:06 +08:00
刘祥超
ce97f20826 实现基本的监控终端管理 2021-09-08 19:34:51 +08:00
刘祥超
ab9cd13abc 版本修改为0.3.1 2021-09-08 19:34:47 +08:00
刘祥超
7af5e828bb 在选择线路弹窗可以从域名中获取线路 2021-09-06 09:16:19 +08:00
刘祥超
98d53c49a0 修复打开网站服务菜单时无法点击系统设置的Bug 2021-09-06 08:53:51 +08:00
刘祥超
bb32849e47 安装时延长API节点初始化等待时间 2021-09-06 08:53:26 +08:00
刘祥超
13221faee0 实现区域监控节点运行日志和运行结果 2021-09-06 08:53:08 +08:00
刘祥超
abda14f543 实现基本的区域监控终端管理功能 2021-09-05 11:10:07 +08:00
刘祥超
3f0e459181 删除不需要的文件 2021-09-04 10:27:45 +08:00
刘祥超
3df61381a4 删除部分不需要的文件/阶段性提交 2021-09-04 10:17:14 +08:00
刘祥超
daf8ae2192 修复IP地址保存后跳转错误的问题 2021-09-04 08:27:24 +08:00
刘祥超
3bf374ea5a 优化URL跳转交互 2021-09-01 08:49:59 +08:00
刘祥超
94b3559eee 增加独立的IP地址管理功能 2021-08-31 17:24:30 +08:00
刘祥超
1c93b42681 企业认证信息中增加节点数显示 2021-08-30 18:57:35 +08:00
刘祥超
1d9d44ecc3 数据看板和节点看板都改成异步加载 2021-08-30 15:23:31 +08:00
刘祥超
19178f963e 服务看板改成异步 2021-08-30 11:26:23 +08:00
刘祥超
4dc54f95b6 优化数据库节点管理 2021-08-30 10:56:03 +08:00
刘祥超
0790403923 优化交互 2021-08-30 09:52:09 +08:00
刘祥超
c55008d302 看板在健康检查失败离线时提供手动上线按钮 2021-08-29 16:56:37 +08:00
刘祥超
df9df94bb8 节点选择集群时可以根据关键词搜索 2021-08-29 16:41:59 +08:00
刘祥超
502ce4e414 健康检查连续下线次数默认值从1次改为3次 2021-08-29 15:26:56 +08:00
刘祥超
f5f07c0e96 可以在节点列表中直接修改节点所属线路 2021-08-29 14:00:59 +08:00
刘祥超
9549424835 缓存策略里的默认缓存条件增加、修改或者删除后自动保存 2021-08-29 09:40:21 +08:00
刘祥超
062b9baeab 修复缓存条件状态码无法修改的问题 2021-08-29 09:22:02 +08:00
刘祥超
08d1fcaf03 服务 -- 缓存设置中增加清理和预热功能 2021-08-29 09:21:44 +08:00
刘祥超
ad7523a500 修改accessKeys package因为大小写而无法编译的问题 2021-08-26 14:02:53 +08:00
刘祥超
28aa0f3b76 删除不需要的文件 2021-08-26 14:01:48 +08:00
刘祥超
8247843936 添加IP到IP名单时,可以选择批量输入 2021-08-26 10:28:52 +08:00
刘祥超
09ff5a37a9 DNS服务商支持搜索 2021-08-25 18:46:57 +08:00
刘祥超
663ec7710c 集群支持使用域名搜索 2021-08-25 18:39:35 +08:00
刘祥超
0dad0b10e6 选择DNS线路时增加搜索/节点如果没有设置DNS线路就使用默认线路 2021-08-25 17:16:14 +08:00
刘祥超
8c352813c4 优化HTTP Header管理界面交互 2021-08-25 14:09:37 +08:00
刘祥超
745f5d9759 优化集群选择DNS设置交互 2021-08-25 11:55:06 +08:00
刘祥超
6536ace4f7 默认集群没有节点时,在看板提示添加 2021-08-25 11:40:57 +08:00
刘祥超
67e6e06e72 新安装检查数据库权限后删除测试表 2021-08-25 11:27:09 +08:00
刘祥超
d0bf85de17 创建集群时自动创建缓存策略和WAF策略/优化界面 2021-08-25 11:17:24 +08:00
刘祥超
14cb3bdc67 增加忽略相似消息周期设置 2021-08-24 20:45:07 +08:00
刘祥超
e9dadd5571 消息接收人可以设置接收消息时间段 2021-08-24 17:46:00 +08:00
刘祥超
d5eceb05ce 消息发送任务队列可以删除单个任务 2021-08-24 16:36:47 +08:00
刘祥超
b510c88f84 通知媒介可以设置发送频率 2021-08-24 15:46:37 +08:00
刘祥超
16e409e5f5 通知媒介增加任务队列查看功能 2021-08-24 14:22:15 +08:00
刘祥超
ad257fcd0e 优化js 2021-08-24 09:31:14 +08:00
刘祥超
e84087d6ec 修复指标table类型图表的js错误 2021-08-24 09:31:07 +08:00
刘祥超
218c2385f0 在服务日志中增加WAF日志筛选 2021-08-22 16:49:15 +08:00
刘祥超
4ef8c2bc77 全局访问日志增加WAF日志/优化交互/数据看板--WAF--最新拦截记录增加更多链接 2021-08-22 16:34:20 +08:00
刘祥超
d9bc1bdfca Dashboard可以提示API节点升级 2021-08-21 19:44:03 +08:00
刘祥超
a5c287e8d6 自建DNS支持递归查询 2021-08-21 16:46:49 +08:00
刘祥超
1e9f5c9c56 DNS访问日志显示匹配的线路 2021-08-20 11:27:06 +08:00
刘祥超
fbcf439f89 集群界面数据改成异步加载 2021-08-20 10:51:01 +08:00
刘祥超
766c3666d8 添加DNS账号时自动读取DNS服务商下域名 2021-08-19 14:26:25 +08:00
刘祥超
f08289351d IP名单批量导入IP支持CIDR 2021-08-19 10:50:15 +08:00
刘祥超
e426bba8b5 优化界面 2021-08-18 17:09:09 +08:00
刘祥超
4b200762a5 节点IP地址可以设置阈值 2021-08-18 16:19:07 +08:00
刘祥超
fd203ed436 节点IP增加是否启用、是否在线状态 2021-08-18 09:24:03 +08:00
刘祥超
04cea4dbd6 优化界面 2021-08-17 10:48:38 +08:00
刘祥超
a8f1056c3c 调整版本为0.3.0 2021-08-17 09:44:14 +08:00
刘祥超
c2576f4c62 优化界面 2021-08-16 14:44:32 +08:00
刘祥超
1da5d1094f WAF设置处增加文字提示 2021-08-16 10:03:44 +08:00
刘祥超
34e6122407 修复可能edgeTest无法升级的问题 2021-08-16 10:03:14 +08:00
刘祥超
ee61feb581 优化IPBox交互 2021-08-15 15:44:24 +08:00
刘祥超
7f04f1ed62 IPBox增加地域、ISP、添加到黑名单等功能 2021-08-15 15:42:05 +08:00
刘祥超
82646c3576 改进细节 2021-08-15 10:39:22 +08:00
刘祥超
b3f62240c4 节点选择认证时增加推荐 2021-08-14 21:33:48 +08:00
刘祥超
86a5992e8a 优化节点创建流程 2021-08-14 18:06:24 +08:00
刘祥超
82a731ed06 修复在MySQL8下安装提示无法创建edgeTest的问题 2021-08-13 19:37:56 +08:00
刘祥超
e650529efb 可以远程停止和启动DNS节点 2021-08-12 11:47:27 +08:00
刘祥超
1de57e124f DNS节点可以修改SSH登录相关信息 2021-08-12 11:04:28 +08:00
刘祥超
9e72dcc390 实现DNS节点远程安装 2021-08-11 20:59:58 +08:00
刘祥超
80019e2071 修改文字 2021-08-11 17:16:36 +08:00
刘祥超
ab7cdcd1b1 增加全局访问日志 2021-08-11 15:38:49 +08:00
刘祥超
7afe1e0a30 安全设置检查IP时同时也检查直接连接管理平台的上游IP 2021-08-11 10:01:23 +08:00
刘祥超
a39eb80214 安全设置中增加允许记住登录选项 2021-08-11 09:36:16 +08:00
刘祥超
10e2a08cd2 访问日志显示节点信息 2021-08-10 11:14:41 +08:00
刘祥超
6da4949c98 优化自建DNS交互 2021-08-10 10:47:12 +08:00
刘祥超
4d092f329b 自建DNS增加解析测试 2021-08-09 18:42:00 +08:00
刘祥超
69c1d35406 EdgeDNS支持内置线路 2021-08-09 13:56:11 +08:00
刘祥超
a256a7328e DNS节点增加在线状态通知 2021-08-08 10:29:57 +08:00
刘祥超
75c8658366 访问日志搜索增加域名和IP搜索 2021-08-07 22:04:30 +08:00
刘祥超
c3e68915a3 优化代码/支持IP名单的更多格式的导入、导出 2021-08-07 18:26:50 +08:00
刘祥超
d97f4da7fa 修复节点无法修改线路的Bug 2021-08-07 16:11:04 +08:00
刘祥超
06e81dbe37 调整版本为0.2.9 2021-08-07 15:41:21 +08:00
刘祥超
4a46aaa880 自建DNS增加全局配置 2021-08-05 16:08:18 +08:00
刘祥超
e766372a81 域名解析支持华为云解析DNS 2021-08-04 22:14:35 +08:00
刘祥超
c5ee9f095a 调整版本号 2021-08-04 15:36:30 +08:00
刘祥超
9734ed1cf8 调整版本号 2021-08-03 10:40:24 +08:00
刘祥超
548d122c64 修复在自动安装过程中不能填写SSH认证用户名的Bug 2021-08-03 06:29:01 +08:00
刘祥超
c5205ef7a9 优化界面 2021-08-02 14:51:15 +08:00
刘祥超
f71a27960b 优化界面 2021-08-01 14:54:27 +08:00
刘祥超
d747e656dc 优化界面 2021-08-01 11:13:17 +08:00
刘祥超
867215e2af 初步实现多集群共享节点 2021-07-31 22:23:07 +08:00
刘祥超
3614b9f3b7 修复服务设置 -- HTTP/HTTPS页面可能为空的Bug 2021-07-30 13:43:36 +08:00
刘祥超
cbfa4c85c1 优化文字 2021-07-29 17:40:16 +08:00
刘祥超
b9bef5042a 优化界面 2021-07-29 17:33:42 +08:00
刘祥超
46e523d005 实现基本的访问日志策略 2021-07-29 16:51:22 +08:00
刘祥超
fa2930dfc5 调整样式 2021-07-27 15:24:32 +08:00
刘祥超
086ab15e8a 优化交互 2021-07-27 11:49:04 +08:00
刘祥超
c2e3e784a2 优化一处规则显示 2021-07-27 11:26:52 +08:00
刘祥超
9dd01bd36e 网站服务显示服务错误的时候增加节点信息和链接 2021-07-27 10:48:42 +08:00
刘祥超
02d08b0762 图表中攻击流量类型改为Line Area 2021-07-27 09:51:47 +08:00
刘祥超
cbd312e67d 指标图表可以设置忽略空值和其他对象值 2021-07-26 16:44:22 +08:00
刘祥超
5084d9f87e 各个线图改成圆滑曲线 2021-07-26 14:32:24 +08:00
刘祥超
3c3408624d URL跳转模式默认改成匹配前缀 2021-07-26 12:57:41 +08:00
刘祥超
6e8403390b 自动跳转到HTTPS可以设置允许和排除的域名 2021-07-26 11:21:47 +08:00
刘祥超
ef9e5d4bbc 修复一个跳转到详情的链接错误 2021-07-26 10:09:07 +08:00
刘祥超
c0b49948d7 版本改为0.2.6 2021-07-26 08:27:06 +08:00
刘祥超
48b066fb83 改进文字 2021-07-25 20:34:31 +08:00
刘祥超
7d551d59db 缓存目录默认为/opt/cache 2021-07-25 20:19:58 +08:00
刘祥超
5a057857dc 点击访问日志的IP弹出该IP最近访问日志 2021-07-25 19:58:30 +08:00
刘祥超
0a9cea722c 修改部分文字 2021-07-25 19:42:07 +08:00
刘祥超
f7eb7b1ec6 将访问日志保留天数从30天改成14天 2021-07-25 19:41:38 +08:00
刘祥超
0eb57a734e 更新编译脚本 2021-07-25 16:28:48 +08:00
刘祥超
82ff9a2b06 区分社区版和商业版 2021-07-25 15:44:14 +08:00
刘祥超
051a48bc73 DNS支持TSIG 2021-07-25 15:08:11 +08:00
刘祥超
6b9d5cf7a0 DNS服务支持密钥管理/“域名服务”改为“自建DNS” 2021-07-25 09:44:29 +08:00
刘祥超
2e7d64d97b 更新相关库 2021-07-22 18:44:55 +08:00
刘祥超
dd6050ee0b 修复在手机端无法正常浏览图表的Bug 2021-07-22 10:45:36 +08:00
刘祥超
c954910728 修复企业版身份认证可能会导致异常退出的问题 2021-07-22 10:45:23 +08:00
刘祥超
0fe2bc4a54 修复在使用内存缓存时导致节点看板页为空的Bug 2021-07-22 08:25:42 +08:00
刘祥超
9f39cd615f Demo模式下不允许调用Sync方法 2021-07-22 08:25:14 +08:00
刘祥超
07bf21dbb4 修改DEMO链接地址 2021-07-21 22:18:08 +08:00
刘祥超
43e8c65473 增加DEMO连接 2021-07-21 22:14:40 +08:00
刘祥超
8a926eabbe 增加DEMO模式、修复监控节点、用户节点、认证节点无法查看运行日志的Bug 2021-07-21 22:14:33 +08:00
刘祥超
fcbed5f63e 修复DNS看板中Y坐标轴不正确的问题 2021-07-21 09:02:28 +08:00
刘祥超
8d203a1d7c 优化界面 2021-07-21 08:07:46 +08:00
刘祥超
d6efb2dd12 域名记录可以停用/启用 2021-07-20 19:02:45 +08:00
刘祥超
6ea3938f74 自动替换API节点时增加对新节点的测试 2021-07-20 18:10:22 +08:00
刘祥超
63e773ce96 增加恢复模式 2021-07-20 17:15:17 +08:00
刘祥超
60b8e1041c 在各个地方支持IPv6 2021-07-20 10:55:25 +08:00
刘祥超
fb263c926b 创建网站服务后自动开启访问日志 2021-07-20 08:48:55 +08:00
刘祥超
dbbae30f5a 创建网站服务后自动开启Websocket 2021-07-20 08:29:50 +08:00
刘祥超
7b73c86fa4 增加内置的统计指标 2021-07-19 20:38:19 +08:00
刘祥超
c16c6ea6a6 优化指标图表 2021-07-19 18:19:33 +08:00
刘祥超
4ecba0040a Dashboard增加指标图表 2021-07-19 17:58:02 +08:00
刘祥超
f65aabe897 指标中列出使用当前指标的集群 2021-07-19 15:48:36 +08:00
刘祥超
de2337afe7 实现公用的统计指标 2021-07-19 15:23:20 +08:00
刘祥超
3360f2fc08 实现新的CC 2021-07-19 10:48:53 +08:00
刘祥超
7d8f3a9d9b IP名单中可以通过IP查找访问日志 2021-07-18 17:09:31 +08:00
刘祥超
63884bc836 优化WAF动作 2021-07-18 15:52:50 +08:00
刘祥超
9c231a2b8c WAF支持更多动作 2021-07-14 22:45:52 +08:00
刘祥超
6a5c979d2a 修复一个指标图表无法显示时间的Bug 2021-07-14 22:44:13 +08:00
刘祥超
094734ae1f 安装时不检查API地址是否可以绑定 2021-07-14 15:40:23 +08:00
刘祥超
97b4d457fd IP测试时同时也检查绑定的IP名单 2021-07-13 15:50:09 +08:00
刘祥超
9669c0ddda 路径规则改成路由规则 2021-07-13 14:28:06 +08:00
刘祥超
daf8257a1f 优化看板 2021-07-13 11:04:18 +08:00
刘祥超
40f32c7cf9 访问日志增加更容易可视化的时间显示 2021-07-12 18:06:43 +08:00
刘祥超
b8295e5cfc 改进界面 2021-07-12 17:37:10 +08:00
刘祥超
aba489217a 修复<keyword>组件的安全问题 2021-07-12 17:35:33 +08:00
刘祥超
7775689dfa 实现数据看板--WAF 2021-07-12 16:56:56 +08:00
刘祥超
90397b2b6f 修改一处文字 2021-07-12 10:27:35 +08:00
刘祥超
32683a2b59 管理界面可以切换风格 2021-07-12 10:21:17 +08:00
刘祥超
9e36847397 实现数据看板-DNS 2021-07-11 21:44:03 +08:00
刘祥超
4c82804622 实现数据看板--用户 2021-07-11 18:05:49 +08:00
刘祥超
cb911c474e 数据看板使用字节代替比特 2021-07-11 11:09:41 +08:00
刘祥超
ce2537d69d 节点看板磁盘缓存用量增加剩余磁盘空间 2021-07-08 19:47:49 +08:00
刘祥超
38746e83c5 节点看板增加缓存目录用量 2021-07-08 19:43:39 +08:00
刘祥超
be8988d309 优化关键词显示 2021-07-08 16:23:51 +08:00
刘祥超
ed59994db8 实现服务看板(企业版) 2021-07-07 19:55:32 +08:00
刘祥超
11a8d3ba70 优化交互 2021-07-07 19:02:39 +08:00
刘祥超
adf91bf3d0 节点列表增加下行流量,节点列表可以按CPU、内存、下行流量排序 2021-07-07 15:17:41 +08:00
刘祥超
7729b88a00 取消节点的监控图表,使用节点看板代替 2021-07-06 20:09:47 +08:00
刘祥超
e93aecabb4 实现节点看板(仅对企业版开放) 2021-07-06 20:06:20 +08:00
刘祥超
3cf43b2cc1 优化API节点详情页 2021-07-06 15:31:09 +08:00
刘祥超
3cba8cf98d 优化ACME任务修改页面 2021-07-06 15:30:06 +08:00
刘祥超
e2ca94c93b 实现集群看板(暂时只对企业版开放) 2021-07-05 11:38:07 +08:00
刘祥超
1c3e25ba1a 指标增加一些易读的名称等信息 2021-07-03 18:05:55 +08:00
刘祥超
f297f4ec52 实现基本的图表管理 2021-07-03 15:44:49 +08:00
刘祥超
534f10b8a7 指标数据增加占比数据 2021-07-01 10:39:07 +08:00
刘祥超
a7aed3d49e 实现基础的统计指标 2021-06-30 19:59:59 +08:00
刘祥超
9a915a48b5 SSH认证--私钥认证方式增加用户名选项 2021-06-30 14:55:21 +08:00
刘祥超
81a3b299f0 优化交互 2021-06-28 21:09:22 +08:00
刘祥超
a4edae6692 修改版本为0.2.5 2021-06-28 10:31:56 +08:00
刘祥超
047f6c409f API节点相关操作增加一些提示 2021-06-28 09:38:26 +08:00
刘祥超
963be2bc63 阶段性提交 2021-06-27 21:59:06 +08:00
刘祥超
bd34a9bd37 服务列表可以搜索端口号 2021-06-25 11:04:45 +08:00
刘祥超
32b93b2bd9 安装时默认设置访问日志保留30天 2021-06-24 21:06:43 +08:00
刘祥超
e7c4507b0b 集群设置左侧菜单显示TOA设置状态 2021-06-24 18:03:33 +08:00
刘祥超
17ae34c44f ACME申请证书时可以设置回调URL 2021-06-24 09:58:44 +08:00
刘祥超
b466ea2fd1 ACME申请证书时可以设置回调URL 2021-06-24 09:27:11 +08:00
刘祥超
8e4ee54f03 实现公用的IP名单 2021-06-23 13:12:33 +08:00
刘祥超
c99547d9e3 变更版本 2021-06-21 14:44:13 +08:00
刘祥超
1d22125d6e 管理员也支持AccessKey 2021-06-20 19:23:02 +08:00
刘祥超
d4689571cb 优化界面 2021-06-19 22:20:15 +08:00
刘祥超
cb1cb62e30 访问控制支持基本认证和子请求认证 2021-06-19 21:35:38 +08:00
刘祥超
77ad860d35 阶段性提交 2021-06-17 21:17:43 +08:00
刘祥超
5082561dc9 调整版本为0.2.3 2021-06-17 18:46:07 +08:00
刘祥超
7d4c80eedb 调整版本为0.2.2 2021-06-17 18:08:14 +08:00
刘祥超
af4bd950b7 用户增加AccessKey管理 2021-06-16 10:48:45 +08:00
刘祥超
f85f9b0920 调整版本为0.2.1 2021-06-16 08:30:44 +08:00
刘祥超
89ea96c28e Update README.md 2021-06-15 14:32:04 +08:00
刘祥超
6f2e3f5f13 ReadMe增加对Gitee的感谢 2021-06-15 11:15:03 +08:00
刘祥超
ee91e8312f 优化界面 2021-06-15 10:32:40 +08:00
刘祥超
004a29846e 优化界面 2021-06-14 19:53:53 +08:00
刘祥超
01eb22a0fe WAF的出站规则支持请求相关参数 2021-06-12 10:53:59 +08:00
刘祥超
bb2d2c5c86 优化安装过程中的错误提示 2021-06-11 12:00:11 +08:00
刘祥超
e0c36e3c45 优化界面 2021-06-10 19:19:43 +08:00
刘祥超
9fa9f74b90 优化界面 2021-06-10 10:39:05 +08:00
刘祥超
7308748957 去除一处不必要的注释 2021-06-09 22:15:23 +08:00
刘祥超
748105eae1 路径规则、重写规则、URL跳转规则均支持匹配条件;URL跳转规则增加排序、是否启用功能 2021-06-09 21:45:26 +08:00
刘祥超
33f45dfc92 请求条件支持参数匹配 2021-06-09 17:14:31 +08:00
刘祥超
dda1e3054a 优化代码 2021-06-08 22:58:39 +08:00
刘祥超
a8030fa045 优化缓存条件设置,支持不缓存条件设置,支持条件排序等 2021-06-08 22:46:08 +08:00
刘祥超
9de66f080d 优化代码 2021-06-08 15:20:59 +08:00
刘祥超
7a57180ecd 请求统计增加即时、按天 2021-06-08 15:08:38 +08:00
刘祥超
48e1e0b631 增加服务流量统计 2021-06-08 11:22:44 +08:00
刘祥超
d5fe38ba8d 优化界面 2021-06-07 16:23:37 +08:00
刘祥超
cb8035452b 支持UDP代理 2021-06-07 15:43:43 +08:00
刘祥超
c2cab60940 边缘节点IP支持IPV6 2021-06-07 10:00:06 +08:00
刘祥超
58e7e1b283 WAF策略列表支持搜索 2021-06-07 08:58:54 +08:00
刘祥超
5d108b1b6f 优化界面 2021-06-07 08:44:06 +08:00
刘祥超
8fbbbd65c4 缓存策略列表支持搜索 2021-06-07 08:43:57 +08:00
刘祥超
7e0a96857c 创建服务的时候默认增加http和https端口绑定 2021-06-06 14:10:45 +08:00
刘祥超
139b0e037e 日志级别增加success 2021-06-06 13:40:47 +08:00
刘祥超
7cbfa2458b 优化交互 2021-06-06 09:22:59 +08:00
刘祥超
abc5162bca 优化添加端口、添加源站相关交互 2021-06-05 20:27:57 +08:00
刘祥超
d3cf71dad8 改进界面 2021-06-05 16:53:26 +08:00
刘祥超
0909164a06 域名服务集群创建时可以选择开启访问日志 2021-06-05 15:38:57 +08:00
刘祥超
6df50d97e9 改进RPC相关提示 2021-06-05 11:35:36 +08:00
刘祥超
98f8fb3bec 服务访问日志增加关键词搜索 2021-06-04 10:15:31 +08:00
刘祥超
0f5f175f08 DNS访问日志可以使用关键词搜索 2021-06-04 09:09:23 +08:00
刘祥超
16a2a6cb35 修改域名服务界面细节 2021-06-03 22:09:12 +08:00
刘祥超
da38fcaa0c 实现在域名解析中使用EdgeDNS 2021-06-02 18:14:00 +08:00
刘祥超
b6fd9aeb14 域名服务增加访问日志 2021-06-02 11:53:08 +08:00
刘祥超
201c3e2f97 优化域名服务相关功能 2021-06-01 16:42:25 +08:00
刘祥超
d834c2517a [域名服务]实现基本的线路配置 2021-05-30 16:31:12 +08:00
刘祥超
dc98081a92 更新go.mod 2021-05-30 11:48:30 +08:00
刘祥超
30e9753c80 实现基本的域名、记录管理 2021-05-27 17:09:53 +08:00
刘祥超
70cd06dc85 实现最基本的域名服务节点管理 2021-05-26 14:43:29 +08:00
刘祥超
194a919a73 延长缓存相关操作超时时间 2021-05-25 18:24:45 +08:00
刘祥超
b57b242232 优化API命名 2021-05-25 17:48:51 +08:00
刘祥超
cc76172a46 实现域名服务器集群管理 2021-05-25 15:47:40 +08:00
刘祥超
5858f98ec6 改进节点日志显示 2021-05-25 14:14:57 +08:00
刘祥超
941e000142 修改版本号为0.2.0 2021-05-25 12:14:53 +08:00
刘祥超
7dd58fd9dc 优化日志显示 2021-05-25 12:14:21 +08:00
刘祥超
2b560a225e 修改版本号为0.1.2 2021-05-24 16:51:57 +08:00
刘祥超
c768f22059 使用CodeMirror高亮各处的配置信息 2021-05-24 11:23:20 +08:00
刘祥超
f738ba9152 缓存支持默认条件 2021-05-24 09:02:55 +08:00
刘祥超
95c27119ac 缓存策略可以根据Key前缀进行批量删除 2021-05-23 22:49:53 +08:00
刘祥超
2b2b516788 优化集群批量操作结果显示 2021-05-23 22:36:52 +08:00
刘祥超
0bb63b5bb7 SSH认证可搜索 2021-05-23 21:12:52 +08:00
刘祥超
48012072d7 SSL/TLS选择证书增加关键词搜索 2021-05-23 20:54:17 +08:00
刘祥超
0053949bc0 在服务列表中显示需要修复的错误 2021-05-23 20:44:51 +08:00
刘祥超
88e7246366 将节点日志级别筛选中的警告从warn改成warning 2021-05-23 20:03:46 +08:00
刘祥超
fbedf3b605 添加端口时校验端口范围 2021-05-23 20:03:14 +08:00
刘祥超
d5659e627d URL跳转支持正则匹配 2021-05-23 17:01:54 +08:00
刘祥超
709d4dc805 增加Licenses目录 2021-05-20 15:39:21 +08:00
刘祥超
37f052e9a5 节点监控图表流量使用秒来计算 2021-05-20 10:38:46 +08:00
刘祥超
58c765e97f 实现日志消息聚合 2021-05-19 20:52:41 +08:00
刘祥超
da332c2756 增加全局的边缘节点日志 2021-05-19 19:03:03 +08:00
刘祥超
e9d329bf8b 变更版本 2021-05-19 19:02:38 +08:00
刘祥超
775b46cf8f 修改版本 2021-05-13 14:36:22 +08:00
刘祥超
c99eb54811 增加edge-admin reset命令,用来清理对应的配置 2021-05-13 14:27:17 +08:00
刘祥超
7917c68b6f 节点详情中增加缓存用量显示 2021-05-13 11:50:15 +08:00
刘祥超
75ce1490cc 节点可以单独设置缓存的磁盘、内存容量 2021-05-12 21:37:43 +08:00
刘祥超
6f1cd668b6 访问日志中增加缓存状态 2021-05-12 16:31:43 +08:00
刘祥超
5b7cb59997 缓存设置中增加自动添加X-Cache Header 2021-05-12 16:09:40 +08:00
刘祥超
67242ec80e 缓存匹配条件增加“排除URL正则匹配” 2021-05-12 16:05:18 +08:00
刘祥超
2af5e059eb 缓存条件增加多个匹配方式/优化缓存设置界面 2021-05-12 15:07:30 +08:00
刘祥超
1d992b0e97 数据看板显示升级提醒 2021-05-11 22:47:21 +08:00
刘祥超
0c650987d6 服务支持fastcgi;路径规则支持匹配后缀 2021-05-10 21:13:09 +08:00
刘祥超
e8af3960f8 在访问日志中显示ws和wss 2021-05-08 20:52:13 +08:00
刘祥超
1d95087abb Websocket默认不开启 2021-05-08 19:51:05 +08:00
刘祥超
c350fceef3 实现一些阈值设置细节 2021-05-05 19:51:13 +08:00
刘祥超
e6970abcb8 实现基础的阈值设置 2021-05-04 21:02:25 +08:00
刘祥超
9cf5f56e59 修复因为ClusterHelper而导致POST Action可能被阻止的情形 2021-05-03 15:31:59 +08:00
刘祥超
c2ebf81e6c 记录和显示最近常用的服务 2021-05-03 15:15:31 +08:00
刘祥超
19a2af8bfa 记录和显示最近常用的集群 2021-05-03 11:32:59 +08:00
刘祥超
df5c1262d2 增加连接数监控图表 2021-04-29 17:06:40 +08:00
刘祥超
26340110c1 实现基本的监控图表 2021-04-29 16:47:45 +08:00
刘祥超
452f03bb78 修改TB、PB、EB的有些计算错误 2021-04-29 15:04:42 +08:00
刘祥超
609794b2d8 修复TLS证书配置时无法删除证书的问题 2021-04-28 14:56:47 +08:00
刘祥超
554d0daf6d 财务管理只有企业版才默认打开 2021-04-28 14:56:19 +08:00
刘祥超
e4015282a5 缓存设置中增加“支持分片内容”选项,用来支持Chunked内容 2021-04-18 22:16:46 +08:00
刘祥超
a7453a4baa 修复ttlcache的一个bug 2021-04-18 21:50:26 +08:00
刘祥超
913a98b4de 变更版本号 2021-04-18 21:25:01 +08:00
刘祥超
89f391ca22 增加SSH认证连接测试功能 2021-04-18 21:19:50 +08:00
刘祥超
e585149a7e 修改文件缓存策略的文件目录最大容量和内存最大容量值 2021-04-18 19:05:35 +08:00
刘祥超
f9f7cd723a 改进界面 2021-04-18 16:30:58 +08:00
刘祥超
4a813f08cd 改进界面 2021-04-18 15:58:52 +08:00
刘祥超
e75757f2bc 改进安装界面中的小细节 2021-04-18 15:41:47 +08:00
刘祥超
91010325eb 优化集群没有节点时的提示 2021-04-18 15:17:49 +08:00
刘祥超
441c0bd628 优化界面 2021-04-15 17:11:38 +08:00
刘祥超
a91b97ff2b 域名解析增加CloudFlare DNS支持 2021-04-15 17:02:35 +08:00
刘祥超
fb775a0ee9 版本认证中增加公司/组织名 2021-04-14 20:02:08 +08:00
刘祥超
8ed02c8103 增加认证节点管理 2021-04-13 21:23:13 +08:00
刘祥超
42a306ff22 增加企业版认证相关API 2021-04-13 20:01:43 +08:00
刘祥超
a637783249 实现发送消息到媒介 2021-04-12 19:19:59 +08:00
刘祥超
7ffff890a8 URL跳转增加匹配前缀和是否保留RequestURI选项 2021-04-07 11:20:23 +08:00
刘祥超
dbfbdddb3a 实现监控节点管理 2021-04-06 16:32:23 +08:00
刘祥超
f85b3a40ea 实现基础的通知媒介管理 2021-04-05 20:48:13 +08:00
刘祥超
27421bbd46 优化错误提示界面 2021-04-05 08:18:17 +08:00
刘祥超
129d2ccef0 系统用户增加是否允许登录选项 2021-03-30 11:00:06 +08:00
刘祥超
a553bad1ef 优化界面细节 2021-03-29 17:47:10 +08:00
刘祥超
ae7020437b 不在界面上显示SSH认证的密码,提升安全性 2021-03-28 16:21:46 +08:00
刘祥超
74e6c7a87c “系统设置 -- 安全管理”里可以单独添加允许访问的IP 2021-03-28 15:50:28 +08:00
刘祥超
658c5ae6dd 优化左侧菜单显示 2021-03-28 14:47:21 +08:00
刘祥超
7a24a69223 反向代理可以整体设置源站默认超时时间等参数 2021-03-26 22:08:18 +08:00
刘祥超
de90acaae4 反向代理中源站增加最大连接数、连接超时时间等参数 2021-03-25 21:18:50 +08:00
刘祥超
51080f2669 申请证书任务可以筛选和搜索 2021-03-16 18:10:00 +08:00
刘祥超
fa5d97b74c 访问日志默认为关闭 2021-03-16 10:24:22 +08:00
刘祥超
42ba081cd1 修改README中的文档链接地址 2021-03-11 14:42:45 +08:00
刘祥超
404d733ba1 增加编译脚本 2021-03-11 14:41:09 +08:00
刘祥超
2f3398e251 优化界面 2021-03-09 14:45:16 +08:00
刘祥超
5c09e272f3 修改版本号 2021-03-09 14:44:47 +08:00
刘祥超
d045f0526f 文件缓存策略支持二级缓存(内存 | 文件) 2021-03-02 19:38:13 +08:00
刘祥超
0766ec9d5a WAF动作增加显示HTML内容 2021-02-26 16:32:07 +08:00
刘祥超
4511b48382 可以设置管理界面和用户界面的浏览器图标和Logo 2021-02-25 20:54:30 +08:00
刘祥超
151e58fbb9 优化界面 2021-02-25 19:07:58 +08:00
刘祥超
c833ac2e96 因为健康检查下线的节点可以手动恢复上线 2021-02-24 19:27:33 +08:00
刘祥超
99983330a3 集群健康检查URL中可以输入主机名 2021-02-24 16:20:04 +08:00
刘祥超
a14e81a28f 优化界面显示 2021-02-24 15:01:45 +08:00
刘祥超
fc714a15a3 相关统计图表中X坐标显示所有标签 2021-02-24 11:11:41 +08:00
刘祥超
8a8f9d2912 修复重新加载RPC连接时可能死锁的问题 2021-02-24 10:44:53 +08:00
刘祥超
58a0521605 自动更新API节点配置 2021-02-24 09:00:12 +08:00
刘祥超
e129b52f1c 更新版本号 2021-02-24 08:46:05 +08:00
刘祥超
5cefd900a4 终端统计中显示系统和浏览器的版本号 2021-02-07 09:23:55 +08:00
刘祥超
857dc70b4d 修复一个流量格式化错误 2021-02-07 09:16:58 +08:00
刘祥超
e3036025bc 修复反向代理配置组件addHeaders错误 2021-02-07 09:07:18 +08:00
刘祥超
76035dc3c2 创建或修改服务域名时检查域名是否重复 2021-02-06 21:52:00 +08:00
刘祥超
22c55d0b83 修改节点分组的节点数显示错误 2021-02-06 19:50:00 +08:00
刘祥超
9780937a1f 修改版本号 2021-02-06 19:49:36 +08:00
刘祥超
b4eb6e92e0 优化交互 2021-02-06 19:40:29 +08:00
1854 changed files with 151594 additions and 11665 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
*_plus.go
*-plus.sh
*_plus.html
*_plus.js
*@plus.js
*_plus.less
*_plus.css
*_plus.css.map

View File

@@ -1,15 +1,49 @@
# GoEdge目标
做一款人人用得起的CDN & WAF系统。
![架构](doc/screenshot.png)
![截图](doc/screenshot.png)
## 特性
* `免费` - 开源、免费、自由、开放
* `简单` - 架构简单清晰,安装简单,使用简单,运维简单
* `高扩展性` - 可以自由扩展新的节点,支持亿级数据
## 功能介绍
* 多用户
* 日志审计
* 集群管理
* HTTP/HTTPS/TCP/UDP等协议支持
* WAF
* 缓存
* DNS自动解析
* 多域名绑定
* 免费证书申请
* IP黑白名单
* 访问日志
* 统计
* 内容压缩
* Protocol Proxy协议
* 本地静态文件
* URL跳转
* 路由规则
* 重写规则
* 访问控制
* 字符编码
* 自定义页面
* 自定义HTTP Header
* Websocket
* WebP自动转换
* Fastcgi
* 请求限制
* 流量限制
## 在线演示
* [http://demo.goedge.cn](http://demo.goedge.cn)
## 文档
[点这里查看文档](https://github.com/TeaOSLab/EdgeDocs)
* [新手指南](https://goedge.cn/docs/QuickStart/Index.md)
* [完整文档](https://goedge.cn/docs)
* [开发者指南](https://goedge.cn/docs/Developer/Build.md)
## 架构
![架构](doc/architect-zh.jpg)
@@ -20,7 +54,7 @@
* [管理平台](https://github.com/TeaOSLab/EdgeAdmin)
## 联系我们
有什么问题和建议都可以加入QQ群 `659832182`
有什么问题和建议都可以加入QQ群 `659832182` 或者 [Telegram群](https://t.me/+5kVCMGxQhZxiODY9)
## 感谢
* 感谢[JetBrains公司](https://www.jetbrains.com/)提供免费的IDE开发Licence。
* 感谢 [Gitee](https://gitee.com/) 提供国内源代码托管平台

3
build/.gitignore vendored
View File

@@ -1 +1,2 @@
edge-api/
edge-api/
build-all-test.sh

21
build/build-all.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
ROOT=$(dirname $0)
# build all nodes
if [ -f $ROOT"/../../EdgeNode/build/build-all-plus.sh" ]; then
echo "=============================="
echo "build all edge-node"
echo "=============================="
cd $ROOT"/../../EdgeNode/build"
./build-all-plus.sh
cd -
fi
./build.sh linux amd64
./build.sh linux 386
./build.sh linux arm64
./build.sh linux mips64
./build.sh linux mips64le
./build.sh darwin amd64
./build.sh darwin arm64

View File

@@ -1,91 +0,0 @@
#!/usr/bin/env bash
function build() {
ROOT=$(dirname $0)
NAME="edge-admin"
DIST=$ROOT/"../dist/${NAME}"
OS=${1}
ARCH=${2}
if [ -z $OS ]; then
echo "usage: build.sh OS ARCH"
exit
fi
if [ -z $ARCH ]; then
echo "usage: build.sh OS ARCH"
exit
fi
VERSION=$(lookup-version $ROOT/../internal/const/const.go)
ZIP="${NAME}-${OS}-${ARCH}-v${VERSION}.zip"
# check edge-api
APINodeVersion=$(lookup-version $ROOT"/../../EdgeAPI/internal/const/const.go")
echo "building edge-api v${APINodeVersion} ..."
EDGE_API_BUILD_SCRIPT=$ROOT"/../../EdgeAPI/build/build.sh"
if [ ! -f $EDGE_API_BUILD_SCRIPT ]; then
echo "unable to find edge-api build script 'EdgeAPI/build/build.sh'"
exit
fi
cd $ROOT"/../../EdgeAPI/build"
echo "=============================="
./build.sh $OS $ARCH
echo "=============================="
cd -
# create dir & copy files
echo "copying ..."
if [ ! -d $DIST ]; then
mkdir $DIST
mkdir $DIST/bin
mkdir $DIST/configs
mkdir $DIST/logs
fi
cp -R $ROOT/../web $DIST/
rm -f $DIST/web/tmp/*
cp $ROOT/configs/server.template.yaml $DIST/configs/
EDGE_API_ZIP_FILE=$ROOT"/../../EdgeAPI/dist/edge-api-${OS}-${ARCH}-v${APINodeVersion}.zip"
cp $EDGE_API_ZIP_FILE $DIST/
cd $DIST/
unzip -q $(basename $EDGE_API_ZIP_FILE)
rm -f $(basename $EDGE_API_ZIP_FILE)
cd -
# build
echo "building "${NAME}" ..."
env GOOS=$OS GOARCH=$GOARCH go build -ldflags="-s -w" -tags demo -o $DIST/bin/${NAME} $ROOT/../cmd/edge-admin/main.go
# delete hidden files
find $DIST -name ".DS_Store" -delete
find $DIST -name ".gitignore" -delete
# zip
echo "zip files ..."
cd "${DIST}/../" || exit
if [ -f "${ZIP}" ]; then
rm -f "${ZIP}"
fi
zip -r -X -q "${ZIP}" ${NAME}/
rm -rf ${NAME}
cd - || exit
echo "[done]"
}
function lookup-version() {
FILE=$1
VERSION_DATA=$(cat $FILE)
re="Version[ ]+=[ ]+\"([0-9.]+)\""
if [[ $VERSION_DATA =~ $re ]]; then
VERSION=${BASH_REMATCH[1]}
echo $VERSION
else
echo "could not match version"
exit
fi
}
build $1 $2

View File

@@ -1,66 +1,112 @@
#!/usr/bin/env bash
function build() {
ROOT=$(dirname $0)
ROOT=$(dirname "$0")
JS_ROOT=$ROOT/../web/public/js
NAME="edge-admin"
DIST=$ROOT/"../dist/${NAME}"
OS=${1}
ARCH=${2}
TAG=${3}
if [ -z $OS ]; then
if [ -z "$OS" ]; then
echo "usage: build.sh OS ARCH"
exit
fi
if [ -z $ARCH ]; then
if [ -z "$ARCH" ]; then
echo "usage: build.sh OS ARCH"
exit
fi
if [ -z "$TAG" ]; then
TAG="community"
fi
VERSION=$(lookup-version $ROOT/../internal/const/const.go)
ZIP="${NAME}-${OS}-${ARCH}-v${VERSION}.zip"
# checking environment
echo "checking required commands ..."
commands=("zip" "unzip" "go" "find" "sed")
for cmd in "${commands[@]}"; do
if [ "$(which "${cmd}")" ]; then
echo "checking ${cmd}: ok"
else
echo "checking ${cmd}: not found"
return
fi
done
# check edge-api
APINodeVersion=$(lookup-version $ROOT"/../../EdgeAPI/internal/const/const.go")
VERSION=$(lookup-version "$ROOT"/../internal/const/const.go)
ZIP="${NAME}-${OS}-${ARCH}-${TAG}-v${VERSION}.zip"
# build edge-api
APINodeVersion=$(lookup-version "$ROOT""/../../EdgeAPI/internal/const/const.go")
echo "building edge-api v${APINodeVersion} ..."
EDGE_API_BUILD_SCRIPT=$ROOT"/../../EdgeAPI/build/build.sh"
if [ ! -f $EDGE_API_BUILD_SCRIPT ]; then
if [ ! -f "$EDGE_API_BUILD_SCRIPT" ]; then
echo "unable to find edge-api build script 'EdgeAPI/build/build.sh'"
exit
fi
cd $ROOT"/../../EdgeAPI/build"
cd "$ROOT""/../../EdgeAPI/build" || exit
echo "=============================="
./build.sh $OS $ARCH
./build.sh "$OS" "$ARCH" $TAG
echo "=============================="
cd -
cd - || exit
# generate files
echo "generating files ..."
go run -tags $TAG "$ROOT"/../cmd/edge-admin/main.go generate
if [ "$(which uglifyjs)" ]; then
echo "compress to component.js ..."
uglifyjs --compress --mangle -- "${JS_ROOT}"/components.src.js > "${JS_ROOT}"/components.js
else
echo "copy to component.js ..."
cp "${JS_ROOT}"/components.src.js "${JS_ROOT}"/components.js
fi
# create dir & copy files
echo "copying ..."
if [ ! -d $DIST ]; then
mkdir $DIST
mkdir $DIST/bin
mkdir $DIST/configs
mkdir $DIST/logs
if [ ! -d "$DIST" ]; then
mkdir "$DIST"
mkdir "$DIST"/bin
mkdir "$DIST"/configs
mkdir "$DIST"/logs
fi
cp -R $ROOT/../web $DIST/
rm -f $DIST/web/tmp/*
cp $ROOT/configs/server.template.yaml $DIST/configs/
cp -R "$ROOT"/../web "$DIST"/
rm -f "$DIST"/web/tmp/*
rm -rf "$DIST"/web/public/js/components
rm -f "$DIST"/web/public/js/components.src.js
cp "$ROOT"/configs/server.template.yaml "$DIST"/configs/
EDGE_API_ZIP_FILE=$ROOT"/../../EdgeAPI/dist/edge-api-${OS}-${ARCH}-v${APINodeVersion}.zip"
cp $EDGE_API_ZIP_FILE $DIST/
cd $DIST/
unzip -q $(basename $EDGE_API_ZIP_FILE)
rm -f $(basename $EDGE_API_ZIP_FILE)
cd -
# change _plus.[ext] to .[ext]
if [ "${TAG}" = "plus" ]; then
echo "converting filenames ..."
exts=("html" "js" "css")
for ext in "${exts[@]}"; do
pattern="*_plus."${ext}
find "$DIST"/web/views -type f -name "$pattern" | \
while read filename; do
mv "${filename}" "${filename/_plus."${ext}"/."${ext}"}"
done
done
fi
EDGE_API_ZIP_FILE=$ROOT"/../../EdgeAPI/dist/edge-api-${OS}-${ARCH}-${TAG}-v${APINodeVersion}.zip"
cp "$EDGE_API_ZIP_FILE" "$DIST"/
cd "$DIST"/ || exit
unzip -q "$(basename "$EDGE_API_ZIP_FILE")"
rm -f "$(basename "$EDGE_API_ZIP_FILE")"
cd - || exit
# build
echo "building "${NAME}" ..."
env GOOS=$OS GOARCH=$GOARCH go build -ldflags="-s -w" -o $DIST/bin/${NAME} $ROOT/../cmd/edge-admin/main.go
echo "building ${NAME} ..."
env GOOS="$OS" GOARCH="$ARCH" go build -trimpath -tags $TAG -ldflags="-s -w" -o "$DIST"/bin/${NAME} "$ROOT"/../cmd/edge-admin/main.go
# delete hidden files
find $DIST -name ".DS_Store" -delete
find $DIST -name ".gitignore" -delete
find "$DIST" -name ".DS_Store" -delete
find "$DIST" -name ".gitignore" -delete
find "$DIST" -name "*.less" -delete
find "$DIST" -name "*.css.map" -delete
find "$DIST" -name "*.js.map" -delete
# zip
echo "zip files ..."
@@ -77,15 +123,15 @@ function build() {
function lookup-version() {
FILE=$1
VERSION_DATA=$(cat $FILE)
VERSION_DATA=$(cat "$FILE")
re="Version[ ]+=[ ]+\"([0-9.]+)\""
if [[ $VERSION_DATA =~ $re ]]; then
VERSION=${BASH_REMATCH[1]}
echo $VERSION
echo "$VERSION"
else
echo "could not match version"
exit
fi
}
build $1 $2
build "$1" "$2" "$3"

View File

@@ -1,4 +1,5 @@
api.yaml
server.yaml
api_db.yaml
*.pem
*.pem
*.cache.json

16
build/generate.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
JS_ROOT=../web/public/js
echo "generate component.src.js ..."
go run -tags=community ../cmd/edge-admin/main.go generate
if [ `which uglifyjs` ]; then
echo "compress to component.js ..."
uglifyjs --compress --mangle -- ${JS_ROOT}/components.src.js > ${JS_ROOT}/components.js
else
echo "copy to component.js ..."
cp ${JS_ROOT}/components.src.js ${JS_ROOT}/components.js
fi
echo "ok"

1
build/licenses/README.md Normal file
View File

@@ -0,0 +1 @@
这个目录下我们列举了所有需要公开声明的第三方License如果有遗漏烦请告知 iwind.liu@gmail.com。再次感谢这些开源软件项目和贡献人员

View File

@@ -0,0 +1,30 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
As this is fork of the official Go code the same license applies.
Extensions of the original work are copyright (c) 2011 Miek Gieben

View File

@@ -1,19 +1,45 @@
package main
import (
"bytes"
"flag"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/apps"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/gen"
"github.com/TeaOSLab/EdgeAdmin/internal/nodes"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
_ "github.com/TeaOSLab/EdgeAdmin/internal/web"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/gosock/pkg/gosock"
"log"
"os"
"os/exec"
"time"
)
func main() {
app := apps.NewAppCmd().
var app = apps.NewAppCmd().
Version(teaconst.Version).
Product(teaconst.ProductName).
Usage(teaconst.ProcessName + " [-v|start|stop|restart|service|daemon]")
Usage(teaconst.ProcessName+" [-h|-v|start|stop|restart|service|daemon|reset|recover|demo|upgrade]").
Usage(teaconst.ProcessName+" [dev|prod]").
Option("-h", "show this help").
Option("-v", "show version").
Option("start", "start the service").
Option("stop", "stop the service").
Option("restart", "restart the service").
Option("service", "register service into systemd").
Option("daemon", "start the service with daemon").
Option("reset", "reset configs").
Option("recover", "enter recovery mode").
Option("demo", "switch to demo mode").
Option("dev", "switch to 'dev' mode").
Option("prod", "switch to 'prod' mode").
Option("upgrade [--url=URL]", "upgrade from official site or an url")
app.On("daemon", func() {
nodes.NewAdminNode().Daemon()
@@ -26,8 +52,133 @@ func main() {
}
fmt.Println("done")
})
app.On("reset", func() {
err := configs.ResetAPIConfig()
if err != nil {
fmt.Println("[ERROR]reset failed: " + err.Error())
return
}
// reset local api
var apiNodeExe = Tea.Root + "/edge-api/bin/edge-api"
_, err = os.Stat(apiNodeExe)
if err == nil {
var cmd = exec.Command(apiNodeExe, "reset")
var stderr = &bytes.Buffer{}
cmd.Stderr = stderr
err = cmd.Run()
if err != nil {
fmt.Println("reset api node failed: " + stderr.String())
}
}
fmt.Println("done")
})
app.On("recover", func() {
sock := gosock.NewTmpSock(teaconst.ProcessName)
if !sock.IsListening() {
fmt.Println("[ERROR]the service not started yet, you should start the service first")
return
}
_, err := sock.Send(&gosock.Command{Code: "recover"})
if err != nil {
fmt.Println("[ERROR]enter recovery mode failed: " + err.Error())
return
}
fmt.Println("enter recovery mode successfully")
})
app.On("demo", func() {
sock := gosock.NewTmpSock(teaconst.ProcessName)
if !sock.IsListening() {
fmt.Println("[ERROR]the service not started yet, you should start the service first")
return
}
reply, err := sock.Send(&gosock.Command{Code: "demo"})
if err != nil {
fmt.Println("[ERROR]change demo mode failed: " + err.Error())
return
}
var isDemo = maps.NewMap(reply.Params).GetBool("isDemo")
if isDemo {
fmt.Println("change demo mode to: on")
} else {
fmt.Println("change demo mode to: off")
}
})
app.On("generate", func() {
err := gen.Generate()
if err != nil {
fmt.Println("generate failed: " + err.Error())
return
}
})
app.On("dev", func() {
var env = "dev"
var sock = gosock.NewTmpSock(teaconst.ProcessName)
_, err := sock.Send(&gosock.Command{
Code: env,
Params: nil,
})
if err != nil {
fmt.Println("failed to switch to '" + env + "': " + err.Error())
} else {
fmt.Println("switch to '" + env + "' ok")
}
})
app.On("prod", func() {
var env = "prod"
var sock = gosock.NewTmpSock(teaconst.ProcessName)
_, err := sock.Send(&gosock.Command{
Code: env,
Params: nil,
})
if err != nil {
fmt.Println("failed to switch to '" + env + "': " + err.Error())
} else {
fmt.Println("switch to '" + env + "' ok")
}
})
app.On("upgrade", func() {
var downloadURL = ""
var flagSet = flag.NewFlagSet("", flag.ContinueOnError)
flagSet.StringVar(&downloadURL, "url", "", "new version download url")
_ = flagSet.Parse(os.Args[2:])
var manager = utils.NewUpgradeManager("admin", downloadURL)
log.Println("checking latest version ...")
var ticker = time.NewTicker(1 * time.Second)
go func() {
var lastProgress float32 = 0
var isStarted = false
for range ticker.C {
if manager.IsDownloading() {
if !isStarted {
log.Println("start downloading v" + manager.NewVersion() + " ...")
isStarted = true
}
var progress = manager.Progress()
if progress >= 0 {
if progress == 0 || progress == 1 || progress-lastProgress >= 0.1 {
lastProgress = progress
log.Println(fmt.Sprintf("%.2f%%", manager.Progress()*100))
}
}
} else {
break
}
}
}()
err := manager.Start()
if err != nil {
log.Println("upgrade failed: " + err.Error())
return
}
log.Println("finished!")
log.Println("restarting ...")
app.RunRestart()
})
app.Run(func() {
adminNode := nodes.NewAdminNode()
var adminNode = nodes.NewAdminNode()
adminNode.Run()
})
}

2
dist/.gitignore vendored
View File

@@ -1,2 +1,2 @@
*.zip
shield-admin
edge-admin

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 225 KiB

1
docker/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.zip

37
docker/Dockerfile Normal file
View File

@@ -0,0 +1,37 @@
FROM alpine:latest
LABEL maintainer="iwind.liu@gmail.com"
ENV TZ "Asia/Shanghai"
ENV VERSION 0.6.3
ENV ROOT_DIR /usr/local/goedge
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip
ENV TAR_URL "https://dl.goedge.cn/edge/v${VERSION}/edge-admin-linux-amd64-plus-v${VERSION}.zip"
#ENV TAR_URL "http://192.168.2.60:8080/edge-admin-linux-amd64-plus-v${VERSION}.zip" # your local repository
RUN apk add --no-cache tzdata
RUN apk add wget
RUN mkdir ${ROOT_DIR}; \
cd ${ROOT_DIR}; \
wget ${TAR_URL} -O ${TAR_FILE}; \
apk add unzip; \
unzip ${TAR_FILE}; \
rm -f ${TAR_FILE}
RUN apk add mysql mysql-client; \
sed -e "s/\[mysqld\]/\[mysqld\]\n\ndatadir=\/var\/lib\/mysql\nport=3306\ninnodb_flush_log_at_trx_commit=2\nmax_connections=256\nmax_prepared_stmt_count=65535\nbinlog_cache_size=1M\nbinlog_stmt_cache_size=1M\nthread_cache_size=32\nbinlog_expire_logs_seconds=1209600\n\n/" /etc/my.cnf > /tmp/my.cnf; \
cp /tmp/my.cnf /etc/my.cnf; \
sed -e "s/skip-networking/#skip-networking/" /etc/my.cnf.d/mariadb-server.cnf > /tmp/mariadb-server.cnf; \
cp /tmp/mariadb-server.cnf /etc/my.cnf.d/mariadb-server.cnf; \
mysql_install_db --user=mysql
RUN mysqld_safe --user=mysql & \
sleep 5; \
mysql -uroot -hlocalhost --execute="ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';"
RUN echo -e "#!/usr/bin/env sh\n\nmysqld_safe --user=mysql &\n/usr/local/goedge/edge-admin/bin/edge-admin\n" > ${ROOT_DIR}/run.sh; \
chmod u+x ${ROOT_DIR}/run.sh
EXPOSE 7788
EXPOSE 8001
EXPOSE 3306
ENTRYPOINT [ "/usr/local/goedge/run.sh" ]

5
docker/build.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
VERSION=latest
docker build --no-cache -t goedge/edge-admin:${VERSION} .

5
docker/run.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
VERSION=latest
docker run -d -p 7788:7788 -p 8001:8001 -p 3306:3306 --name edge-admin goedge/edge-admin:${VERSION}

36
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/TeaOSLab/EdgeAdmin
go 1.15
go 1.18
replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
@@ -8,14 +8,34 @@ require (
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
github.com/cespare/xxhash v1.1.0
github.com/go-sql-driver/mysql v1.5.0
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/iwind/TeaGo v0.0.0-20201206115018-cdd967bfb13d
github.com/miekg/dns v1.1.35
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
github.com/miekg/dns v1.1.43
github.com/shirou/gopsutil/v3 v3.22.5
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/tealeg/xlsx/v3 v3.2.3
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c // indirect
google.golang.org/grpc v1.32.0
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
golang.org/x/sys v0.5.0
google.golang.org/grpc v1.45.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/frankban/quicktest v1.11.3 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/rogpeppe/fastuuid v1.2.0 // indirect
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)

136
go.sum
View File

@@ -1,10 +1,12 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
@@ -12,24 +14,33 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.5.0 h1:Tb4jWdSpdjKzTUicPnY61PZxKbDoGa7ABbrReT3gQVY=
github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-redis/redis/v8 v8.0.0-beta.7/go.mod h1:FGJAWDWFht1sQ4qxyJHZZbVyvnVcKQN0E3u5/5lRz+g=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -41,37 +52,49 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iwind/TeaGo v0.0.0-20200923021120-f5d76441fe9e/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/TeaGo v0.0.0-20201206115018-cdd967bfb13d h1:J4ohNUwOqCQkY62LFwaygfGtHJj+87pnzr+RJxEYSBo=
github.com/iwind/TeaGo v0.0.0-20201206115018-cdd967bfb13d/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/TeaGo v0.0.0-20230207032553-d6dcde0cd518 h1:zuWjQ57zc67ZSTHpxNK95JYoa9Ph/JRSnapsTY/hlhQ=
github.com/iwind/TeaGo v0.0.0-20230207032553-d6dcde0cd518/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/TeaGo v0.0.0-20230303070415-9d0689db6456 h1:xv3AVaxuwjThkBDptAfsFSmuHQIrRrvt8BRaekWnsvs=
github.com/iwind/TeaGo v0.0.0-20230303070415-9d0689db6456/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470 h1:TuRxvKRv9PxKVijWOkUnZm5TeanQqWGUJyPx9u6cra4=
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -86,29 +109,39 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa/go.mod h1:Yjr3bdWaVWyME1kha7X0jsz3k2DgXNa1Pj3XGyUAbx8=
github.com/shirou/gopsutil/v3 v3.22.5 h1:atX36I/IXgFiB81687vSiBI5zrMsxcIBkP9cQMJQoJA=
github.com/shirou/gopsutil/v3 v3.22.5/go.mod h1:so9G9VzeHt/hsd0YwqprnjHnfARAUktauykSbr+y2gA=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tealeg/xlsx/v3 v3.2.3 h1:MXnVh+9Y8cUglowItTy2HL3Kv6z+q/0aNjeKuTsVqZQ=
github.com/tealeg/xlsx/v3 v3.2.3/go.mod h1:0hGmAEoZ48SS1ZAE6eqZJkJVXgOMY+8a33vjXa8S8HA=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4=
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
@@ -123,60 +156,81 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e h1:fNKDNuUyC4WH+inqDMpfXDdfvwfYILbsX+oskGZ8hxg=
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -185,22 +239,26 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -2,29 +2,35 @@ package apps
import (
"fmt"
"github.com/iwind/TeaGo/Tea"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/iwind/gosock/pkg/gosock"
"os"
"os/exec"
"runtime"
"strconv"
"syscall"
"time"
)
// App命令帮助
// AppCmd App命令帮助
type AppCmd struct {
product string
version string
usage string
usages []string
options []*CommandHelpOption
appendStrings []string
directives []*Directive
sock *gosock.Sock
}
func NewAppCmd() *AppCmd {
return &AppCmd{}
return &AppCmd{
sock: gosock.NewTmpSock(teaconst.ProcessName),
}
}
type CommandHelpOption struct {
@@ -32,25 +38,25 @@ type CommandHelpOption struct {
Description string
}
// 产品
// Product 产品
func (this *AppCmd) Product(product string) *AppCmd {
this.product = product
return this
}
// 版本
// Version 版本
func (this *AppCmd) Version(version string) *AppCmd {
this.version = version
return this
}
// 使用方法
// Usage 使用方法
func (this *AppCmd) Usage(usage string) *AppCmd {
this.usage = usage
this.usages = append(this.usages, usage)
return this
}
// 选项
// Option 选项
func (this *AppCmd) Option(code string, description string) *AppCmd {
this.options = append(this.options, &CommandHelpOption{
Code: code,
@@ -59,18 +65,20 @@ func (this *AppCmd) Option(code string, description string) *AppCmd {
return this
}
// 附加内容
// Append 附加内容
func (this *AppCmd) Append(appendString string) *AppCmd {
this.appendStrings = append(this.appendStrings, appendString)
return this
}
// 打印
// Print 打印
func (this *AppCmd) Print() {
fmt.Println(this.product + " v" + this.version)
usage := this.usage
fmt.Println("Usage:", "\n "+usage)
fmt.Println("Usage:")
for _, usage := range this.usages {
fmt.Println(" " + usage)
}
if len(this.options) > 0 {
fmt.Println("")
@@ -104,7 +112,7 @@ func (this *AppCmd) Print() {
}
}
// 添加指令
// On 添加指令
func (this *AppCmd) On(arg string, callback func()) {
this.directives = append(this.directives, &Directive{
Arg: arg,
@@ -112,10 +120,10 @@ func (this *AppCmd) On(arg string, callback func()) {
})
}
// 运行
// Run 运行
func (this *AppCmd) Run(main func()) {
// 获取参数
args := os.Args[1:]
var args = os.Args[1:]
if len(args) > 0 {
switch args[0] {
case "-v", "version", "-version", "--version":
@@ -131,7 +139,7 @@ func (this *AppCmd) Run(main func()) {
this.runStop()
return
case "restart":
this.runRestart()
this.RunRestart()
return
case "status":
this.runStatus()
@@ -151,11 +159,8 @@ func (this *AppCmd) Run(main func()) {
return
}
// 记录PID
_ = this.writePid()
// 日志
writer := new(LogWriter)
var writer = new(LogWriter)
writer.Init()
logs.SetWriter(writer)
@@ -165,7 +170,7 @@ func (this *AppCmd) Run(main func()) {
// 版本号
func (this *AppCmd) runVersion() {
fmt.Println(this.product+" v"+this.version, "(build: "+runtime.Version(), runtime.GOOS, runtime.GOARCH+")")
fmt.Println(this.product+" v"+this.version, "(build: "+runtime.Version(), runtime.GOOS, runtime.GOARCH, teaconst.Tag+")")
}
// 帮助
@@ -175,9 +180,9 @@ func (this *AppCmd) runHelp() {
// 启动
func (this *AppCmd) runStart() {
proc := this.checkPid()
if proc != nil {
fmt.Println(this.product+" already started, pid:", proc.Pid)
var pid = this.getPID()
if pid > 0 {
fmt.Println(this.product+" already started, pid:", pid)
return
}
@@ -193,22 +198,19 @@ func (this *AppCmd) runStart() {
// 停止
func (this *AppCmd) runStop() {
proc := this.checkPid()
if proc == nil {
var pid = this.getPID()
if pid == 0 {
fmt.Println(this.product + " not started yet")
return
}
// 停止进程
_ = proc.Signal(syscall.SIGQUIT)
_, _ = this.sock.Send(&gosock.Command{Code: "stop"})
// 在Windows上经常不能及时释放资源
_ = DeletePid(Tea.Root + "/bin/pid")
fmt.Println(this.product+" stopped ok, pid:", proc.Pid)
fmt.Println(this.product+" stopped ok, pid:", types.String(pid))
}
// 重启
func (this *AppCmd) runRestart() {
// RunRestart 重启
func (this *AppCmd) RunRestart() {
this.runStop()
time.Sleep(1 * time.Second)
this.runStart()
@@ -216,20 +218,24 @@ func (this *AppCmd) runRestart() {
// 状态
func (this *AppCmd) runStatus() {
proc := this.checkPid()
if proc == nil {
var pid = this.getPID()
if pid == 0 {
fmt.Println(this.product + " not started yet")
} else {
fmt.Println(this.product + " is running, pid: " + fmt.Sprintf("%d", proc.Pid))
return
}
fmt.Println(this.product + " is running, pid: " + types.String(pid))
}
// 检查PID
func (this *AppCmd) checkPid() *os.Process {
return CheckPid(Tea.Root + "/bin/pid")
}
// 获取当前的PID
func (this *AppCmd) getPID() int {
if !this.sock.IsListening() {
return 0
}
// 写入PID
func (this *AppCmd) writePid() error {
return WritePid(Tea.Root + "/bin/pid")
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
if err != nil {
return 0
}
return maps.NewMap(reply.Params).GetInt("pid")
}

View File

@@ -1,17 +0,0 @@
// +build !windows
package apps
import (
"os"
"syscall"
)
// lock file
func LockFile(fp *os.File) error {
return syscall.Flock(int(fp.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
}
func UnlockFile(fp *os.File) error {
return syscall.Flock(int(fp.Fd()), syscall.LOCK_UN)
}

View File

@@ -1,17 +0,0 @@
// +build windows
package apps
import (
"errors"
"os"
)
// lock file
func LockFile(fp *os.File) error {
return errors.New("not implemented on windows")
}
func UnlockFile(fp *os.File) error {
return errors.New("not implemented on windows")
}

View File

@@ -1,51 +1,108 @@
package apps
import (
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/utils/time"
timeutil "github.com/iwind/TeaGo/utils/time"
"log"
"os"
"runtime"
"strconv"
"strings"
)
type LogWriter struct {
fileAppender *files.Appender
fp *os.File
c chan string
}
func (this *LogWriter) Init() {
// 创建目录
dir := files.NewFile(Tea.LogDir())
var dir = files.NewFile(Tea.LogDir())
if !dir.Exists() {
err := dir.Mkdir()
if err != nil {
log.Println("[error]" + err.Error())
log.Println("[LOG]create log dir failed: " + err.Error())
}
}
logFile := files.NewFile(Tea.LogFile("run.log"))
// 打开要写入的日志文件
appender, err := logFile.Appender()
var logPath = Tea.LogFile("run.log")
fp, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
logs.Error(err)
log.Println("[LOG]open log file failed: " + err.Error())
} else {
this.fileAppender = appender
this.fp = fp
}
this.c = make(chan string, 1024)
// 异步写入文件
var maxFileSize = 2 * sizes.G // 文件最大尺寸,超出此尺寸则清空
if fp != nil {
goman.New(func() {
var totalSize int64 = 0
stat, err := fp.Stat()
if err == nil {
totalSize = stat.Size()
}
for message := range this.c {
totalSize += int64(len(message))
_, err := fp.WriteString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
if err != nil {
log.Println("[LOG]write log failed: " + err.Error())
} else {
// 如果太大则Truncate
if totalSize > maxFileSize {
_ = fp.Truncate(0)
totalSize = 0
}
}
}
})
}
}
func (this *LogWriter) Write(message string) {
log.Println(message)
backgroundEnv, _ := os.LookupEnv("EdgeBackground")
if backgroundEnv != "on" {
// 文件和行号
var file string
var line int
if Tea.IsTesting() {
var callDepth = 3
var ok bool
_, file, line, ok = runtime.Caller(callDepth)
if ok {
file = this.packagePath(file)
}
}
if this.fileAppender != nil {
_, err := this.fileAppender.AppendString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
if err != nil {
log.Println("[error]" + err.Error())
if len(file) > 0 {
log.Println(message + " (" + file + ":" + strconv.Itoa(line) + ")")
} else {
log.Println(message)
}
}
this.c <- message
}
func (this *LogWriter) Close() {
if this.fileAppender != nil {
_ = this.fileAppender.Close()
if this.fp != nil {
_ = this.fp.Close()
}
close(this.c)
}
func (this *LogWriter) packagePath(path string) string {
var pieces = strings.Split(path, "/")
if len(pieces) >= 2 {
return strings.Join(pieces[len(pieces)-2:], "/")
}
return path
}

View File

@@ -1,113 +0,0 @@
package apps
import (
"fmt"
"github.com/iwind/TeaGo/types"
"io/ioutil"
"os"
"runtime"
)
var pidFileList = []*os.File{}
// 检查Pid
func CheckPid(path string) *os.Process {
// windows上打开的文件是不能删除的
if runtime.GOOS == "windows" {
if os.Remove(path) == nil {
return nil
}
}
file, err := os.Open(path)
if err != nil {
return nil
}
defer func() {
_ = file.Close()
}()
// 是否能取得Lock
err = LockFile(file)
if err == nil {
_ = UnlockFile(file)
return nil
}
pidBytes, err := ioutil.ReadAll(file)
if err != nil {
return nil
}
pid := types.Int(string(pidBytes))
if pid <= 0 {
return nil
}
proc, _ := os.FindProcess(pid)
return proc
}
// 写入Pid
func WritePid(path string) error {
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
if err != nil {
return err
}
if runtime.GOOS != "windows" {
err = LockFile(fp)
if err != nil {
return err
}
}
pidFileList = append(pidFileList, fp) // hold the file pointers
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getpid()))
if err != nil {
return err
}
return nil
}
// 写入Ppid
func WritePpid(path string) error {
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
if err != nil {
return err
}
if runtime.GOOS != "windows" {
err = LockFile(fp)
if err != nil {
return err
}
}
pidFileList = append(pidFileList, fp) // hold the file pointers
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getppid()))
if err != nil {
return err
}
return nil
}
// 删除Pid
func DeletePid(path string) error {
_, err := os.Stat(path)
if err != nil {
if !os.IsNotExist(err) {
return nil
}
return err
}
for _, fp := range pidFileList {
_ = UnlockFile(fp)
_ = fp.Close()
}
return os.Remove(path)
}

View File

@@ -1,6 +1,7 @@
package configloaders
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
@@ -14,11 +15,14 @@ const (
AdminModuleCodeServer AdminModuleCode = "server" // 网站
AdminModuleCodeNode AdminModuleCode = "node" // 节点
AdminModuleCodeDNS AdminModuleCode = "dns" // DNS
AdminModuleCodeNS AdminModuleCode = "ns" // 域名服务
AdminModuleCodeAdmin AdminModuleCode = "admin" // 系统用户
AdminModuleCodeUser AdminModuleCode = "user" // 平台用户
AdminModuleCodeFinance AdminModuleCode = "finance" // 财务
AdminModuleCodePlan AdminModuleCode = "plan" // 套餐
AdminModuleCodeLog AdminModuleCode = "log" // 日志
AdminModuleCodeSetting AdminModuleCode = "setting" // 设置
AdminModuleCodeTicket AdminModuleCode = "ticket" // 工单
AdminModuleCodeCommon AdminModuleCode = "common" // 只要登录就可以访问的模块
)
@@ -42,6 +46,7 @@ func loadAdminModuleMapping() (map[int64]*AdminModuleList, error) {
list := &AdminModuleList{
IsSuper: m.IsSuper,
Fullname: m.Fullname,
Theme: m.Theme,
}
for _, pbModule := range m.Modules {
@@ -68,7 +73,7 @@ func NotifyAdminModuleMappingChange() error {
return err
}
// 检查用户是否存在
// CheckAdmin 检查用户是否存在
func CheckAdmin(adminId int64) bool {
locker.Lock()
defer locker.Unlock()
@@ -82,7 +87,7 @@ func CheckAdmin(adminId int64) bool {
return ok
}
// 检查模块是否允许访问
// AllowModule 检查模块是否允许访问
func AllowModule(adminId int64, module string) bool {
locker.Lock()
defer locker.Unlock()
@@ -103,7 +108,7 @@ func AllowModule(adminId int64, module string) bool {
return false
}
// 获取管理员第一个可访问模块
// FindFirstAdminModule 获取管理员第一个可访问模块
func FindFirstAdminModule(adminId int64) (module AdminModuleCode, ok bool) {
locker.Lock()
defer locker.Unlock()
@@ -118,7 +123,7 @@ func FindFirstAdminModule(adminId int64) (module AdminModuleCode, ok bool) {
return
}
// 查找某个管理员名称
// FindAdminFullname 查找某个管理员名称
func FindAdminFullname(adminId int64) string {
locker.Lock()
defer locker.Unlock()
@@ -130,9 +135,32 @@ func FindAdminFullname(adminId int64) string {
return ""
}
// 所有权限列表
// FindAdminTheme 查找某个管理员选择的风格
func FindAdminTheme(adminId int64) string {
locker.Lock()
defer locker.Unlock()
list, ok := sharedAdminModuleMapping[adminId]
if ok {
return list.Theme
}
return ""
}
// UpdateAdminTheme 设置某个管理员的风格
func UpdateAdminTheme(adminId int64, theme string) {
locker.Lock()
defer locker.Unlock()
list, ok := sharedAdminModuleMapping[adminId]
if ok {
list.Theme = theme
}
}
// AllModuleMaps 所有权限列表
func AllModuleMaps() []maps.Map {
return []maps.Map{
var m = []maps.Map{
{
"name": "看板",
"code": AdminModuleCodeDashboard,
@@ -153,6 +181,15 @@ func AllModuleMaps() []maps.Map {
"code": AdminModuleCodeDNS,
"url": "/dns",
},
}
if teaconst.IsPlus {
m = append(m, maps.Map{
"name": "智能DNS",
"code": AdminModuleCodeNS,
"url": "/ns",
})
}
m = append(m, []maps.Map{
{
"name": "平台用户",
"code": AdminModuleCodeUser,
@@ -168,6 +205,24 @@ func AllModuleMaps() []maps.Map {
"code": AdminModuleCodeFinance,
"url": "/finance",
},
}...)
if teaconst.IsPlus {
m = append(m, []maps.Map{
{
"name": "套餐管理",
"code": AdminModuleCodePlan,
"url": "/plans",
},
{
"name": "工单系统",
"code": AdminModuleCodeTicket,
"url": "/tickets",
},
}...)
}
m = append(m, []maps.Map{
{
"name": "日志审计",
"code": AdminModuleCodeLog,
@@ -178,5 +233,6 @@ func AllModuleMaps() []maps.Map {
"code": AdminModuleCodeSetting,
"url": "/settings",
},
}
}...)
return m
}

View File

@@ -6,6 +6,7 @@ type AdminModuleList struct {
IsSuper bool
Modules []*systemconfigs.AdminModule
Fullname string
Theme string
}
func (this *AdminModuleList) Allow(module string) bool {

View File

@@ -6,7 +6,7 @@ import (
)
func TestLoadAdminModuleMapping(t *testing.T) {
m, err := LoadAdminModuleMapping()
m, err := loadAdminModuleMapping()
if err != nil {
t.Fatal(err)
}

View File

@@ -3,18 +3,16 @@ package configloaders
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/logs"
"reflect"
"time"
)
var sharedAdminUIConfig *systemconfigs.AdminUIConfig = nil
const (
AdminUISettingName = "adminUIConfig"
)
func LoadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
locker.Lock()
defer locker.Unlock()
@@ -28,6 +26,15 @@ func LoadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
return &v, nil
}
func ReloadAdminUIConfig() error {
locker.Lock()
defer locker.Unlock()
sharedAdminUIConfig = nil
_, err := loadAdminUIConfig()
return err
}
func UpdateAdminUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
locker.Lock()
defer locker.Unlock()
@@ -41,7 +48,7 @@ func UpdateAdminUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
return err
}
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
Code: AdminUISettingName,
Code: systemconfigs.SettingCodeAdminUIConfig,
ValueJSON: valueJSON,
})
if err != nil {
@@ -49,10 +56,13 @@ func UpdateAdminUIConfig(uiConfig *systemconfigs.AdminUIConfig) error {
}
sharedAdminUIConfig = uiConfig
// timezone
updateTimeZone(uiConfig)
return nil
}
// 是否显示财务信息
// ShowFinance 是否显示财务信息
func ShowFinance() bool {
config, _ := LoadAdminUIConfig()
if config != nil && !config.ShowFinance {
@@ -70,7 +80,7 @@ func loadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
return nil, err
}
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
Code: AdminUISettingName,
Code: systemconfigs.SettingCodeAdminUIConfig,
})
if err != nil {
return nil, err
@@ -80,13 +90,17 @@ func loadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
return sharedAdminUIConfig, nil
}
config := &systemconfigs.AdminUIConfig{}
var config = &systemconfigs.AdminUIConfig{}
err = json.Unmarshal(resp.ValueJSON, config)
if err != nil {
logs.Println("[UI_MANAGER]" + err.Error())
sharedAdminUIConfig = defaultAdminUIConfig()
return sharedAdminUIConfig, nil
}
// timezone
updateTimeZone(config)
sharedAdminUIConfig = config
return sharedAdminUIConfig, nil
}
@@ -98,5 +112,17 @@ func defaultAdminUIConfig() *systemconfigs.AdminUIConfig {
ShowOpenSourceInfo: true,
ShowVersion: true,
ShowFinance: true,
DefaultPageSize: 10,
TimeZone: nodeconfigs.DefaultTimeZoneLocation,
}
}
// 修改时区
func updateTimeZone(config *systemconfigs.AdminUIConfig) {
if len(config.TimeZone) > 0 {
location, err := time.LoadLocation(config.TimeZone)
if err == nil && time.Local != location {
time.Local = location
}
}
}

View File

@@ -9,7 +9,7 @@ import (
func TestLoadUIConfig(t *testing.T) {
for i := 0; i < 10; i++ {
before := time.Now()
config, err := LoadUIConfig()
config, err := LoadAdminUIConfig()
if err != nil {
t.Fatal(err)
}
@@ -20,7 +20,7 @@ func TestLoadUIConfig(t *testing.T) {
func TestLoadUIConfig2(t *testing.T) {
for i := 0; i < 10; i++ {
config, err := LoadUIConfig()
config, err := LoadAdminUIConfig()
if err != nil {
t.Fatal(err)
}

View File

@@ -52,6 +52,10 @@ func UpdateSecurityConfig(securityConfig *systemconfigs.SecurityConfig) error {
if err != nil {
return err
}
err = securityConfig.Init()
if err != nil {
return err
}
sharedSecurityConfig = securityConfig
// 通知更新
@@ -86,6 +90,10 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
sharedSecurityConfig = defaultSecurityConfig()
return sharedSecurityConfig, nil
}
err = config.Init()
if err != nil {
return nil, err
}
sharedSecurityConfig = config
return sharedSecurityConfig, nil
}

View File

@@ -1,93 +0,0 @@
package configloaders
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/logs"
"reflect"
)
var sharedUserUIConfig *systemconfigs.UserUIConfig = nil
const (
UserUISettingName = "userUIConfig"
)
func LoadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
locker.Lock()
defer locker.Unlock()
config, err := loadUserUIConfig()
if err != nil {
return nil, err
}
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.UserUIConfig)
return &v, nil
}
func UpdateUserUIConfig(uiConfig *systemconfigs.UserUIConfig) error {
locker.Lock()
defer locker.Unlock()
var rpcClient, err = rpc.SharedRPC()
if err != nil {
return err
}
valueJSON, err := json.Marshal(uiConfig)
if err != nil {
return err
}
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
Code: UserUISettingName,
ValueJSON: valueJSON,
})
if err != nil {
return err
}
sharedUserUIConfig = uiConfig
return nil
}
func loadUserUIConfig() (*systemconfigs.UserUIConfig, error) {
if sharedUserUIConfig != nil {
return sharedUserUIConfig, nil
}
var rpcClient, err = rpc.SharedRPC()
if err != nil {
return nil, err
}
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
Code: UserUISettingName,
})
if err != nil {
return nil, err
}
if len(resp.ValueJSON) == 0 {
sharedUserUIConfig = defaultUserUIConfig()
return sharedUserUIConfig, nil
}
config := &systemconfigs.UserUIConfig{}
err = json.Unmarshal(resp.ValueJSON, config)
if err != nil {
logs.Println("[UI_MANAGER]" + err.Error())
sharedUserUIConfig = defaultUserUIConfig()
return sharedUserUIConfig, nil
}
sharedUserUIConfig = config
return sharedUserUIConfig, nil
}
func defaultUserUIConfig() *systemconfigs.UserUIConfig {
return &systemconfigs.UserUIConfig{
ProductName: "GoEdge",
UserSystemName: "GoEdge用户系统",
ShowOpenSourceInfo: true,
ShowVersion: true,
ShowFinance: true,
}
}

View File

@@ -2,23 +2,23 @@ package configs
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/go-yaml/yaml"
"github.com/iwind/TeaGo/Tea"
"io/ioutil"
"gopkg.in/yaml.v3"
"os"
"path/filepath"
)
// API配置
// APIConfig API配置
type APIConfig struct {
RPC struct {
Endpoints []string `yaml:"endpoints"`
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
} `yaml:"rpc"`
NodeId string `yaml:"nodeId"`
Secret string `yaml:"secret"`
}
// 加载API配置
// LoadAPIConfig 加载API配置
func LoadAPIConfig() (*APIConfig, error) {
// 候选文件
localFile := Tea.ConfigFile("api.yaml")
@@ -33,7 +33,7 @@ func LoadAPIConfig() (*APIConfig, error) {
var data []byte
var err error
for _, path := range paths {
data, err = ioutil.ReadFile(path)
data, err = os.ReadFile(path)
if err == nil {
if path == localFile {
isFromLocal = true
@@ -53,50 +53,117 @@ func LoadAPIConfig() (*APIConfig, error) {
if !isFromLocal {
// 恢复文件
_ = ioutil.WriteFile(localFile, data, 0666)
_ = os.WriteFile(localFile, data, 0666)
}
return config, nil
}
// 写入API配置
// ResetAPIConfig 重置配置
func ResetAPIConfig() error {
var filename = "api.yaml"
// 重置 configs/api.yaml
{
var configFile = Tea.ConfigFile(filename)
stat, err := os.Stat(configFile)
if err == nil && !stat.IsDir() {
err = os.Remove(configFile)
if err != nil {
return err
}
}
}
// 重置 ~/.edge-admin/api.yaml
homeDir, homeErr := os.UserHomeDir()
if homeErr == nil {
var configFile = homeDir + "/." + teaconst.ProcessName + "/" + filename
stat, err := os.Stat(configFile)
if err == nil && !stat.IsDir() {
err = os.Remove(configFile)
if err != nil {
return err
}
}
}
// 重置 /etc/edge-admin/api.yaml
{
var configFile = "/etc/" + teaconst.ProcessName + "/" + filename
stat, err := os.Stat(configFile)
if err == nil && !stat.IsDir() {
err = os.Remove(configFile)
if err != nil {
return err
}
}
}
return nil
}
// WriteFile 写入API配置
func (this *APIConfig) WriteFile(path string) error {
data, err := yaml.Marshal(this)
if err != nil {
return err
}
err = ioutil.WriteFile(path, data, 0666)
err = os.WriteFile(path, data, 0666)
if err != nil {
return err
}
// 写入 ~/ 和 /etc/ 目录,因为是备份需要,所以不需要提示错误信息
// 写入 ~/.edge-admin/
filename := filepath.Base(path)
// 这个用来判断用户是否为重装,所以比较重要
var filename = filepath.Base(path)
homeDir, homeErr := os.UserHomeDir()
if homeErr == nil {
dir := homeDir + "/." + teaconst.ProcessName
stat, err := os.Stat(dir)
if err == nil && stat.IsDir() {
_ = ioutil.WriteFile(dir+"/"+filename, data, 0666)
err = os.WriteFile(dir+"/"+filename, data, 0666)
if err != nil {
return err
}
} else if err != nil && os.IsNotExist(err) {
err = os.Mkdir(dir, 0777)
if err == nil {
_ = ioutil.WriteFile(dir+"/"+filename, data, 0666)
err = os.WriteFile(dir+"/"+filename, data, 0666)
if err != nil {
return err
}
}
}
}
// 写入 /etc/.edge-admin
// 写入 /etc/edge-admin
{
dir := "/etc/" + teaconst.ProcessName
var dir = "/etc/" + teaconst.ProcessName
stat, err := os.Stat(dir)
if err == nil && stat.IsDir() {
_ = ioutil.WriteFile(dir+"/"+filename, data, 0666)
_ = os.WriteFile(dir+"/"+filename, data, 0666)
} else if err != nil && os.IsNotExist(err) {
err = os.Mkdir(dir, 0777)
if err == nil {
_ = ioutil.WriteFile(dir+"/"+filename, data, 0666)
_ = os.WriteFile(dir+"/"+filename, data, 0666)
}
}
}
return err
return nil
}
// Clone 克隆当前配置
func (this *APIConfig) Clone() *APIConfig {
return &APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
}{},
NodeId: this.NodeId,
Secret: this.Secret,
}
}

View File

@@ -14,13 +14,7 @@ func TestLoadAPIConfig(t *testing.T) {
}
func TestAPIConfig_WriteFile(t *testing.T) {
config := &APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
}{},
NodeId: "1",
Secret: "2",
}
config := &APIConfig{}
err := config.WriteFile("/tmp/api_config.yaml")
if err != nil {
t.Fatal(err)

8
internal/const/build.go Normal file
View File

@@ -0,0 +1,8 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
package teaconst
const BuildCommunity = true
const BuildPlus = false
const Tag = "community"

View File

@@ -1,7 +1,9 @@
package teaconst
const (
Version = "0.0.10"
Version = "0.6.4"
APINodeVersion = "0.6.4"
ProductName = "Edge Admin"
ProcessName = "edge-admin"
@@ -16,4 +18,5 @@ const (
CookieSID = "edgesid"
SystemdServiceName = "edge-admin"
UpdatesURL = "https://goedge.cn/api/boot/versions?os=${os}&arch=${arch}&version=${version}"
)

View File

@@ -1,7 +0,0 @@
// +build demo
package teaconst
const (
IsDemo = true
)

View File

@@ -1,7 +0,0 @@
// +build !demo
package teaconst
const (
IsDemo = false
)

6
internal/const/plus.go Normal file
View File

@@ -0,0 +1,6 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
package teaconst
var IsPlus = false

13
internal/const/vars.go Normal file
View File

@@ -0,0 +1,13 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package teaconst
var (
IsRecoverMode = false
IsDemoMode = false
ErrorDemoOperation = "DEMO模式下无法进行创建、修改、删除等操作"
NewVersionCode = "" // 有新的版本
NewVersionDownloadURL = "" // 新版本下载地址
)

View File

@@ -10,7 +10,7 @@ import (
"time"
)
// 生成Token
// Generate 生成Token
func Generate() string {
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
@@ -23,7 +23,7 @@ func Generate() string {
return token
}
// 校验Token
// Validate 校验Token
func Validate(token string) (b bool) {
if len(token) == 0 {
return

140
internal/gen/generate.go Normal file
View File

@@ -0,0 +1,140 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package gen
import (
"bytes"
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/conds/condutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
"github.com/iwind/TeaGo/logs"
"io"
"os"
"path/filepath"
)
func Generate() error {
err := generateComponentsJSFile()
if err != nil {
return errors.New("generate 'components.src.js' failed: " + err.Error())
}
return nil
}
// 生成Javascript文件
func generateComponentsJSFile() error {
var buffer = bytes.NewBuffer([]byte{})
var webRoot string
if Tea.IsTesting() {
webRoot = Tea.Root + "/../web/public/js/components/"
} else {
webRoot = Tea.Root + "/web/public/js/components/"
}
f := files.NewFile(webRoot)
f.Range(func(file *files.File) {
if !file.IsFile() {
return
}
if file.Ext() != ".js" {
return
}
data, err := file.ReadAll()
if err != nil {
logs.Error(err)
return
}
buffer.Write(data)
buffer.Write([]byte{'\n', '\n'})
})
// 条件组件
typesJSON, err := json.Marshal(condutils.ReadAllAvailableCondTypes())
if err != nil {
logs.Println("ComponentsAction marshal request cond types failed: " + err.Error())
} else {
buffer.WriteString("window.REQUEST_COND_COMPONENTS = ")
buffer.Write(typesJSON)
buffer.Write([]byte{'\n', '\n'})
}
// 条件操作符
requestOperatorsJSON, err := json.Marshal(shared.AllRequestOperators())
if err != nil {
logs.Println("ComponentsAction marshal request operators failed: " + err.Error())
} else {
buffer.WriteString("window.REQUEST_COND_OPERATORS = ")
buffer.Write(requestOperatorsJSON)
buffer.Write([]byte{'\n', '\n'})
}
// 请求变量
requestVariablesJSON, err := json.Marshal(shared.DefaultRequestVariables())
if err != nil {
logs.Println("ComponentsAction marshal request variables failed: " + err.Error())
} else {
buffer.WriteString("window.REQUEST_VARIABLES = ")
buffer.Write(requestVariablesJSON)
buffer.Write([]byte{'\n', '\n'})
}
// 指标
metricHTTPKeysJSON, err := json.Marshal(serverconfigs.FindAllMetricKeyDefinitions(serverconfigs.MetricItemCategoryHTTP))
if err != nil {
logs.Println("ComponentsAction marshal metric http keys failed: " + err.Error())
} else {
buffer.WriteString("window.METRIC_HTTP_KEYS = ")
buffer.Write(metricHTTPKeysJSON)
buffer.Write([]byte{'\n', '\n'})
}
// IP地址阈值项目
ipAddrThresholdItemsJSON, err := json.Marshal(nodeconfigs.FindAllIPAddressThresholdItems())
if err != nil {
logs.Println("ComponentsAction marshal ip addr threshold items failed: " + err.Error())
} else {
buffer.WriteString("window.IP_ADDR_THRESHOLD_ITEMS = ")
buffer.Write(ipAddrThresholdItemsJSON)
buffer.Write([]byte{'\n', '\n'})
}
// IP地址阈值动作
ipAddrThresholdActionsJSON, err := json.Marshal(nodeconfigs.FindAllIPAddressThresholdActions())
if err != nil {
logs.Println("ComponentsAction marshal ip addr threshold actions failed: " + err.Error())
} else {
buffer.WriteString("window.IP_ADDR_THRESHOLD_ACTIONS = ")
buffer.Write(ipAddrThresholdActionsJSON)
buffer.Write([]byte{'\n', '\n'})
}
// WAF操作符
wafOperatorsJSON, err := json.Marshal(firewallconfigs.AllRuleOperators)
if err != nil {
logs.Println("ComponentsAction marshal waf rule operators failed: " + err.Error())
} else {
buffer.WriteString("window.WAF_RULE_OPERATORS = ")
buffer.Write(wafOperatorsJSON)
buffer.Write([]byte{'\n', '\n'})
}
fp, err := os.OpenFile(filepath.Clean(Tea.PublicFile("/js/components.src.js")), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
if err != nil {
return err
}
_, err = io.Copy(fp, buffer)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,13 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package gen
import "testing"
func TestGenerate(t *testing.T) {
err := Generate()
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}

View File

@@ -0,0 +1,12 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package goman
import "time"
type Instance struct {
Id uint64
CreatedTime time.Time
File string
Line int
}

81
internal/goman/lib.go Normal file
View File

@@ -0,0 +1,81 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package goman
import (
"runtime"
"sync"
"time"
)
var locker = &sync.Mutex{}
var instanceMap = map[uint64]*Instance{} // id => *Instance
var instanceId = uint64(0)
// New 新创建goroutine
func New(f func()) {
_, file, line, _ := runtime.Caller(1)
go func() {
locker.Lock()
instanceId++
var instance = &Instance{
Id: instanceId,
CreatedTime: time.Now(),
}
instance.File = file
instance.Line = line
instanceMap[instanceId] = instance
locker.Unlock()
// run function
f()
locker.Lock()
delete(instanceMap, instanceId)
locker.Unlock()
}()
}
// NewWithArgs 创建带有参数的goroutine
func NewWithArgs(f func(args ...interface{}), args ...interface{}) {
_, file, line, _ := runtime.Caller(1)
go func() {
locker.Lock()
instanceId++
var instance = &Instance{
Id: instanceId,
CreatedTime: time.Now(),
}
instance.File = file
instance.Line = line
instanceMap[instanceId] = instance
locker.Unlock()
// run function
f(args...)
locker.Lock()
delete(instanceMap, instanceId)
locker.Unlock()
}()
}
// List 列出所有正在运行goroutine
func List() []*Instance {
locker.Lock()
defer locker.Unlock()
var result = []*Instance{}
for _, instance := range instanceMap {
result = append(result, instance)
}
return result
}

View File

@@ -0,0 +1,28 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package goman
import (
"testing"
"time"
)
func TestNew(t *testing.T) {
New(func() {
t.Log("Hello")
t.Log(List())
})
time.Sleep(1 * time.Second)
t.Log(List())
time.Sleep(1 * time.Second)
}
func TestNewWithArgs(t *testing.T) {
NewWithArgs(func(args ...interface{}) {
t.Log(args[0], args[1])
}, 1, 2)
time.Sleep(1 * time.Second)
}

View File

@@ -1,23 +1,27 @@
package nodes
import (
"errors"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/errors"
"github.com/TeaOSLab/EdgeAdmin/internal/events"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/iwind/TeaGo"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/sessions"
"io/ioutil"
"github.com/iwind/TeaGo/types"
"github.com/iwind/gosock/pkg/gosock"
"gopkg.in/yaml.v3"
"log"
"net"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
)
@@ -25,6 +29,7 @@ import (
var SharedAdminNode *AdminNode = nil
type AdminNode struct {
sock *gosock.Sock
subPIDs []int
}
@@ -36,13 +41,13 @@ func (this *AdminNode) Run() {
SharedAdminNode = this
// 启动管理界面
secret := this.genSecret()
var secret = this.genSecret()
configs.Secret = secret
// 本地Sock
err := this.listenSock()
if err != nil {
logs.Println("NODE" + err.Error())
logs.Println("[NODE]", err.Error())
return
}
@@ -56,6 +61,9 @@ func (this *AdminNode) Run() {
return
}
// 添加端口到防火墙
this.addPortsToFirewall()
// 监听信号
sigQueue := make(chan os.Signal)
signal.Notify(sigQueue, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT)
@@ -78,22 +86,27 @@ func (this *AdminNode) Run() {
this.startAPINode()
// 启动Web服务
sessionManager, err := NewSessionManager()
if err != nil {
log.Fatal("start session failed: " + err.Error())
return
}
TeaGo.NewServer(false).
AccessLog(false).
EndAll().
Session(sessions.NewFileSessionManager(86400, secret), teaconst.CookieSID).
Session(sessionManager, teaconst.CookieSID).
ReadHeaderTimeout(3 * time.Second).
ReadTimeout(600 * time.Second).
ReadTimeout(1200 * time.Second).
Start()
}
// 实现守护进程
// Daemon 实现守护进程
func (this *AdminNode) Daemon() {
path := os.TempDir() + "/edge-admin.sock"
var sock = gosock.NewTmpSock(teaconst.ProcessName)
isDebug := lists.ContainsString(os.Args, "debug")
isDebug = true
for {
conn, err := net.DialTimeout("unix", path, 1*time.Second)
conn, err := sock.Dial()
if err != nil {
if isDebug {
log.Println("[DAEMON]starting ...")
@@ -132,7 +145,7 @@ func (this *AdminNode) Daemon() {
}
}
// 安装系统服务
// InstallSystemService 安装系统服务
func (this *AdminNode) InstallSystemService() error {
shortName := teaconst.SystemdServiceName
@@ -149,7 +162,7 @@ func (this *AdminNode) InstallSystemService() error {
return nil
}
// 添加子PID
// AddSubPID 添加子PID
func (this *AdminNode) AddSubPID(pid int) {
this.subPIDs = append(this.subPIDs, pid)
}
@@ -165,9 +178,9 @@ func (this *AdminNode) checkServer() error {
if os.IsNotExist(err) {
// 创建文件
templateFile := Tea.ConfigFile("server.template.yaml")
data, err := ioutil.ReadFile(templateFile)
data, err := os.ReadFile(templateFile)
if err == nil {
err = ioutil.WriteFile(configFile, data, 0666)
err = os.WriteFile(configFile, data, 0666)
if err != nil {
return errors.New("create config file failed: " + err.Error())
}
@@ -187,7 +200,7 @@ https:
cert: ""
key: ""
`
err = ioutil.WriteFile(configFile, []byte(templateYAML), 0666)
err = os.WriteFile(configFile, []byte(templateYAML), 0666)
if err != nil {
return errors.New("create config file failed: " + err.Error())
}
@@ -199,6 +212,44 @@ https:
return nil
}
// 添加端口到防火墙
func (this *AdminNode) addPortsToFirewall() {
var configFile = Tea.ConfigFile("server.yaml")
data, err := os.ReadFile(configFile)
if err != nil {
return
}
var config = &TeaGo.ServerConfig{}
err = yaml.Unmarshal(data, config)
if err != nil {
return
}
var ports = []int{}
if config.Http.On {
for _, listen := range config.Http.Listen {
_, portString, _ := net.SplitHostPort(listen)
var port = types.Int(portString)
if port > 0 && !lists.ContainsInt(ports, port) {
ports = append(ports, port)
}
}
}
if config.Https.On {
for _, listen := range config.Https.Listen {
_, portString, _ := net.SplitHostPort(listen)
var port = types.Int(portString)
if port > 0 && !lists.ContainsInt(ports, port) {
ports = append(ports, port)
}
}
}
utils.AddPortsToFirewall(ports)
}
// 启动API节点
func (this *AdminNode) startAPINode() {
configPath := Tea.Root + "/edge-api/configs/api.yaml"
@@ -217,9 +268,9 @@ func (this *AdminNode) startAPINode() {
for _, path := range paths {
_, err = os.Stat(path)
if err == nil {
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
if err == nil {
err = ioutil.WriteFile(configPath, data, 0666)
err = os.WriteFile(configPath, data, 0666)
if err == nil {
logs.Println("[NODE]recover 'edge-api/configs/api.yaml' from '" + path + "'")
canStart = true
@@ -243,9 +294,9 @@ func (this *AdminNode) startAPINode() {
for _, path := range paths {
_, err = os.Stat(path)
if err == nil {
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
if err == nil {
err = ioutil.WriteFile(dbPath, data, 0666)
err = os.WriteFile(dbPath, data, 0666)
if err == nil {
logs.Println("[NODE]recover 'edge-api/configs/db.yaml' from '" + path + "'")
break
@@ -270,48 +321,101 @@ func (this *AdminNode) startAPINode() {
// 生成Secret
func (this *AdminNode) genSecret() string {
tmpFile := os.TempDir() + "/edge-admin-secret.tmp"
data, err := ioutil.ReadFile(tmpFile)
data, err := os.ReadFile(tmpFile)
if err == nil && len(data) == 32 {
return string(data)
}
secret := rands.String(32)
_ = ioutil.WriteFile(tmpFile, []byte(secret), 0666)
_ = os.WriteFile(tmpFile, []byte(secret), 0666)
return secret
}
// 监听本地sock
func (this *AdminNode) listenSock() error {
path := os.TempDir() + "/edge-admin.sock"
this.sock = gosock.NewTmpSock(teaconst.ProcessName)
// 检查是否已经存
_, err := os.Stat(path)
if err == nil {
conn, err := net.Dial("unix", path)
if err != nil {
_ = os.Remove(path)
// 检查是否在运行
if this.sock.IsListening() {
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
if err == nil {
return errors.New("error: the process is already running, pid: " + maps.NewMap(reply.Params).GetString("pid"))
} else {
_ = conn.Close()
return errors.New("error: the process is already running")
}
}
// 新的监听任务
listener, err := net.Listen("unix", path)
if err != nil {
return err
}
events.On(events.EventQuit, func() {
logs.Println("NODE", "quit unix sock")
_ = listener.Close()
})
// 启动监听
go func() {
for {
_, err := listener.Accept()
if err != nil {
return
this.sock.OnCommand(func(cmd *gosock.Command) {
switch cmd.Code {
case "pid":
_ = cmd.Reply(&gosock.Command{
Code: "pid",
Params: map[string]interface{}{
"pid": os.Getpid(),
},
})
case "stop":
_ = cmd.ReplyOk()
// 关闭子进程
for _, pid := range this.subPIDs {
p, err := os.FindProcess(pid)
if err == nil && p != nil {
_ = p.Kill()
}
}
// 停止当前目录下的API节点
var apiSock = gosock.NewTmpSock("edge-api")
apiReply, err := apiSock.Send(&gosock.Command{Code: "info"})
if err == nil {
adminExe, _ := os.Executable()
if len(adminExe) > 0 && apiReply != nil && strings.HasPrefix(maps.NewMap(apiReply.Params).GetString("path"), filepath.Dir(filepath.Dir(adminExe))) {
_, _ = apiSock.Send(&gosock.Command{Code: "stop"})
}
}
// 退出主进程
events.Notify(events.EventQuit)
os.Exit(0)
case "recover":
teaconst.IsRecoverMode = true
_ = cmd.ReplyOk()
case "demo":
teaconst.IsDemoMode = !teaconst.IsDemoMode
_ = cmd.Reply(&gosock.Command{
Params: map[string]interface{}{"isDemo": teaconst.IsDemoMode},
})
case "info":
exePath, _ := os.Executable()
_ = cmd.Reply(&gosock.Command{
Code: "info",
Params: map[string]interface{}{
"pid": os.Getpid(),
"version": teaconst.Version,
"path": exePath,
},
})
case "dev": // 切换到dev
Tea.Env = Tea.EnvDev
_ = cmd.ReplyOk()
case "prod": // 切换到prod
Tea.Env = Tea.EnvProd
_ = cmd.ReplyOk()
}
})
err := this.sock.Listen()
if err != nil {
logs.Println("NODE", err.Error())
}
}()
events.On(events.EventQuit, func() {
logs.Println("NODE", "quit unix sock")
_ = this.sock.Close()
})
return nil
}

View File

@@ -0,0 +1,96 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package nodes
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
"strings"
)
type SessionManager struct {
life uint
}
func NewSessionManager() (*SessionManager, error) {
return &SessionManager{}, nil
}
func (this *SessionManager) Init(config *actions.SessionConfig) {
this.life = config.Life
}
func (this *SessionManager) Read(sid string) map[string]string {
// 忽略OTP
if strings.HasSuffix(sid, "_otp") {
return map[string]string{}
}
var result = map[string]string{}
rpcClient, err := rpc.SharedRPC()
if err != nil {
return map[string]string{}
}
resp, err := rpcClient.LoginSessionRPC().FindLoginSession(rpcClient.Context(0), &pb.FindLoginSessionRequest{Sid: sid})
if err != nil {
logs.Println("SESSION", "read '"+sid+"' failed: "+err.Error())
return result
}
var session = resp.LoginSession
if session == nil || len(session.ValuesJSON) == 0 {
return result
}
err = json.Unmarshal(session.ValuesJSON, &result)
if err != nil {
logs.Println("SESSION", "decode '"+sid+"' values failed: "+err.Error())
}
return result
}
func (this *SessionManager) WriteItem(sid string, key string, value string) bool {
// 忽略OTP
if strings.HasSuffix(sid, "_otp") {
return false
}
rpcClient, err := rpc.SharedRPC()
if err != nil {
return false
}
_, err = rpcClient.LoginSessionRPC().WriteLoginSessionValue(rpcClient.Context(0), &pb.WriteLoginSessionValueRequest{
Sid: sid,
Key: key,
Value: value,
})
if err != nil {
logs.Println("SESSION", "write sid:'"+sid+"' key:'"+key+"' failed: "+err.Error())
}
return true
}
func (this *SessionManager) Delete(sid string) bool {
// 忽略OTP
if strings.HasSuffix(sid, "_otp") {
return false
}
rpcClient, err := rpc.SharedRPC()
if err != nil {
return false
}
_, err = rpcClient.LoginSessionRPC().DeleteLoginSession(rpcClient.Context(0), &pb.DeleteLoginSessionRequest{Sid: sid})
if err != nil {
logs.Println("SESSION", "delete '"+sid+"' failed: "+err.Error())
}
return true
}

View File

@@ -1,7 +0,0 @@
package rpc
import "context"
type ContextInterface interface {
AdminContext() context.Context
}

View File

@@ -11,32 +11,37 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/encoding/gzip"
"google.golang.org/grpc/metadata"
"net"
"net/url"
"strings"
"sync"
"time"
)
// RPC客户端
// RPCClient RPC客户端
type RPCClient struct {
apiConfig *configs.APIConfig
conns []*grpc.ClientConn
locker sync.Mutex
locker sync.RWMutex
}
// 构造新的RPC客户端
func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
// NewRPCClient 构造新的RPC客户端
func NewRPCClient(apiConfig *configs.APIConfig, isPrimary bool) (*RPCClient, error) {
if apiConfig == nil {
return nil, errors.New("api config should not be nil")
}
client := &RPCClient{
var client = &RPCClient{
apiConfig: apiConfig,
}
@@ -46,11 +51,17 @@ func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
}
// 设置RPC
dao.SetRPC(client)
if isPrimary {
dao.SetRPC(client)
}
return client, nil
}
func (this *RPCClient) APITokenRPC() pb.APITokenServiceClient {
return pb.NewAPITokenServiceClient(this.pickConn())
}
func (this *RPCClient) AdminRPC() pb.AdminServiceClient {
return pb.NewAdminServiceClient(this.pickConn())
}
@@ -67,6 +78,10 @@ func (this *RPCClient) NodeGrantRPC() pb.NodeGrantServiceClient {
return pb.NewNodeGrantServiceClient(this.pickConn())
}
func (this *RPCClient) NodeLoginRPC() pb.NodeLoginServiceClient {
return pb.NewNodeLoginServiceClient(this.pickConn())
}
func (this *RPCClient) NodeClusterRPC() pb.NodeClusterServiceClient {
return pb.NewNodeClusterServiceClient(this.pickConn())
}
@@ -83,18 +98,34 @@ func (this *RPCClient) NodeRegionRPC() pb.NodeRegionServiceClient {
return pb.NewNodeRegionServiceClient(this.pickConn())
}
func (this *RPCClient) NodePriceItemRPC() pb.NodePriceItemServiceClient {
return pb.NewNodePriceItemServiceClient(this.pickConn())
}
func (this *RPCClient) NodeIPAddressRPC() pb.NodeIPAddressServiceClient {
return pb.NewNodeIPAddressServiceClient(this.pickConn())
}
func (this *RPCClient) NodeIPAddressLogRPC() pb.NodeIPAddressLogServiceClient {
return pb.NewNodeIPAddressLogServiceClient(this.pickConn())
}
func (this *RPCClient) NodeIPAddressThresholdRPC() pb.NodeIPAddressThresholdServiceClient {
return pb.NewNodeIPAddressThresholdServiceClient(this.pickConn())
}
func (this *RPCClient) NodeValueRPC() pb.NodeValueServiceClient {
return pb.NewNodeValueServiceClient(this.pickConn())
}
func (this *RPCClient) NodeThresholdRPC() pb.NodeThresholdServiceClient {
return pb.NewNodeThresholdServiceClient(this.pickConn())
}
func (this *RPCClient) ServerRPC() pb.ServerServiceClient {
return pb.NewServerServiceClient(this.pickConn())
}
func (this *RPCClient) ServerBandwidthStatRPC() pb.ServerBandwidthStatServiceClient {
return pb.NewServerBandwidthStatServiceClient(this.pickConn())
}
func (this *RPCClient) ServerClientSystemMonthlyStatRPC() pb.ServerClientSystemMonthlyStatServiceClient {
return pb.NewServerClientSystemMonthlyStatServiceClient(this.pickConn())
}
@@ -123,6 +154,10 @@ func (this *RPCClient) ServerHTTPFirewallDailyStatRPC() pb.ServerHTTPFirewallDai
return pb.NewServerHTTPFirewallDailyStatServiceClient(this.pickConn())
}
func (this *RPCClient) ServerDailyStatRPC() pb.ServerDailyStatServiceClient {
return pb.NewServerDailyStatServiceClient(this.pickConn())
}
func (this *RPCClient) ServerGroupRPC() pb.ServerGroupServiceClient {
return pb.NewServerGroupServiceClient(this.pickConn())
}
@@ -131,8 +166,8 @@ func (this *RPCClient) APINodeRPC() pb.APINodeServiceClient {
return pb.NewAPINodeServiceClient(this.pickConn())
}
func (this *RPCClient) UserNodeRPC() pb.UserNodeServiceClient {
return pb.NewUserNodeServiceClient(this.pickConn())
func (this *RPCClient) APIMethodStatRPC() pb.APIMethodStatServiceClient {
return pb.NewAPIMethodStatServiceClient(this.pickConn())
}
func (this *RPCClient) DBNodeRPC() pb.DBNodeServiceClient {
@@ -179,6 +214,14 @@ func (this *RPCClient) HTTPCachePolicyRPC() pb.HTTPCachePolicyServiceClient {
return pb.NewHTTPCachePolicyServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPCacheTaskRPC() pb.HTTPCacheTaskServiceClient {
return pb.NewHTTPCacheTaskServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPCacheTaskKeyRPC() pb.HTTPCacheTaskKeyServiceClient {
return pb.NewHTTPCacheTaskKeyServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPFirewallPolicyRPC() pb.HTTPFirewallPolicyServiceClient {
return pb.NewHTTPFirewallPolicyServiceClient(this.pickConn())
}
@@ -191,6 +234,10 @@ func (this *RPCClient) HTTPFirewallRuleSetRPC() pb.HTTPFirewallRuleSetServiceCli
return pb.NewHTTPFirewallRuleSetServiceClient(this.pickConn())
}
func (this *RPCClient) FirewallRPC() pb.FirewallServiceClient {
return pb.NewFirewallServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPLocationRPC() pb.HTTPLocationServiceClient {
return pb.NewHTTPLocationServiceClient(this.pickConn())
}
@@ -203,11 +250,19 @@ func (this *RPCClient) HTTPRewriteRuleRPC() pb.HTTPRewriteRuleServiceClient {
return pb.NewHTTPRewriteRuleServiceClient(this.pickConn())
}
// 访问日志
// HTTPAccessLogRPC 访问日志
func (this *RPCClient) HTTPAccessLogRPC() pb.HTTPAccessLogServiceClient {
return pb.NewHTTPAccessLogServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPFastcgiRPC() pb.HTTPFastcgiServiceClient {
return pb.NewHTTPFastcgiServiceClient(this.pickConn())
}
func (this *RPCClient) HTTPAuthPolicyRPC() pb.HTTPAuthPolicyServiceClient {
return pb.NewHTTPAuthPolicyServiceClient(this.pickConn())
}
func (this *RPCClient) SSLCertRPC() pb.SSLCertServiceClient {
return pb.NewSSLCertServiceClient(this.pickConn())
}
@@ -224,10 +279,46 @@ func (this *RPCClient) MessageRPC() pb.MessageServiceClient {
return pb.NewMessageServiceClient(this.pickConn())
}
func (this *RPCClient) MessageRecipientGroupRPC() pb.MessageRecipientGroupServiceClient {
return pb.NewMessageRecipientGroupServiceClient(this.pickConn())
}
func (this *RPCClient) MessageRecipientRPC() pb.MessageRecipientServiceClient {
return pb.NewMessageRecipientServiceClient(this.pickConn())
}
func (this *RPCClient) MessageMediaRPC() pb.MessageMediaServiceClient {
return pb.NewMessageMediaServiceClient(this.pickConn())
}
func (this *RPCClient) MessageMediaInstanceRPC() pb.MessageMediaInstanceServiceClient {
return pb.NewMessageMediaInstanceServiceClient(this.pickConn())
}
func (this *RPCClient) MessageTaskRPC() pb.MessageTaskServiceClient {
return pb.NewMessageTaskServiceClient(this.pickConn())
}
func (this *RPCClient) MessageTaskLogRPC() pb.MessageTaskLogServiceClient {
return pb.NewMessageTaskLogServiceClient(this.pickConn())
}
func (this *RPCClient) MessageReceiverRPC() pb.MessageReceiverServiceClient {
return pb.NewMessageReceiverServiceClient(this.pickConn())
}
func (this *RPCClient) IPLibraryRPC() pb.IPLibraryServiceClient {
return pb.NewIPLibraryServiceClient(this.pickConn())
}
func (this *RPCClient) IPLibraryFileRPC() pb.IPLibraryFileServiceClient {
return pb.NewIPLibraryFileServiceClient(this.pickConn())
}
func (this *RPCClient) IPLibraryArtifactRPC() pb.IPLibraryArtifactServiceClient {
return pb.NewIPLibraryArtifactServiceClient(this.pickConn())
}
func (this *RPCClient) IPListRPC() pb.IPListServiceClient {
return pb.NewIPListServiceClient(this.pickConn())
}
@@ -252,6 +343,18 @@ func (this *RPCClient) RegionProvinceRPC() pb.RegionProvinceServiceClient {
return pb.NewRegionProvinceServiceClient(this.pickConn())
}
func (this *RPCClient) RegionCityRPC() pb.RegionCityServiceClient {
return pb.NewRegionCityServiceClient(this.pickConn())
}
func (this *RPCClient) RegionTownRPC() pb.RegionTownServiceClient {
return pb.NewRegionTownServiceClient(this.pickConn())
}
func (this *RPCClient) RegionProviderRPC() pb.RegionProviderServiceClient {
return pb.NewRegionProviderServiceClient(this.pickConn())
}
func (this *RPCClient) LogRPC() pb.LogServiceClient {
return pb.NewLogServiceClient(this.pickConn())
}
@@ -280,26 +383,86 @@ func (this *RPCClient) ACMETaskRPC() pb.ACMETaskServiceClient {
return pb.NewACMETaskServiceClient(this.pickConn())
}
func (this *RPCClient) ACMEProviderRPC() pb.ACMEProviderServiceClient {
return pb.NewACMEProviderServiceClient(this.pickConn())
}
func (this *RPCClient) ACMEProviderAccountRPC() pb.ACMEProviderAccountServiceClient {
return pb.NewACMEProviderAccountServiceClient(this.pickConn())
}
func (this *RPCClient) UserRPC() pb.UserServiceClient {
return pb.NewUserServiceClient(this.pickConn())
}
func (this *RPCClient) UserBillRPC() pb.UserBillServiceClient {
return pb.NewUserBillServiceClient(this.pickConn())
func (this *RPCClient) UserAccessKeyRPC() pb.UserAccessKeyServiceClient {
return pb.NewUserAccessKeyServiceClient(this.pickConn())
}
func (this *RPCClient) UserIdentityRPC() pb.UserIdentityServiceClient {
return pb.NewUserIdentityServiceClient(this.pickConn())
}
func (this *RPCClient) LoginRPC() pb.LoginServiceClient {
return pb.NewLoginServiceClient(this.pickConn())
}
func (this *RPCClient) LoginSessionRPC() pb.LoginSessionServiceClient {
return pb.NewLoginSessionServiceClient(this.pickConn())
}
func (this *RPCClient) NodeTaskRPC() pb.NodeTaskServiceClient {
return pb.NewNodeTaskServiceClient(this.pickConn())
}
// 构造Admin上下文
func (this *RPCClient) AuthorityKeyRPC() pb.AuthorityKeyServiceClient {
return pb.NewAuthorityKeyServiceClient(this.pickConn())
}
func (this *RPCClient) AuthorityNodeRPC() pb.AuthorityNodeServiceClient {
return pb.NewAuthorityNodeServiceClient(this.pickConn())
}
func (this *RPCClient) LatestItemRPC() pb.LatestItemServiceClient {
return pb.NewLatestItemServiceClient(this.pickConn())
}
func (this *RPCClient) MetricItemRPC() pb.MetricItemServiceClient {
return pb.NewMetricItemServiceClient(this.pickConn())
}
func (this *RPCClient) MetricStatRPC() pb.MetricStatServiceClient {
return pb.NewMetricStatServiceClient(this.pickConn())
}
func (this *RPCClient) MetricChartRPC() pb.MetricChartServiceClient {
return pb.NewMetricChartServiceClient(this.pickConn())
}
func (this *RPCClient) NodeClusterMetricItemRPC() pb.NodeClusterMetricItemServiceClient {
return pb.NewNodeClusterMetricItemServiceClient(this.pickConn())
}
func (this *RPCClient) ServerStatBoardRPC() pb.ServerStatBoardServiceClient {
return pb.NewServerStatBoardServiceClient(this.pickConn())
}
func (this *RPCClient) ServerDomainHourlyStatRPC() pb.ServerDomainHourlyStatServiceClient {
return pb.NewServerDomainHourlyStatServiceClient(this.pickConn())
}
func (this *RPCClient) ServerStatBoardChartRPC() pb.ServerStatBoardChartServiceClient {
return pb.NewServerStatBoardChartServiceClient(this.pickConn())
}
func (this *RPCClient) TrafficDailyStatRPC() pb.TrafficDailyStatServiceClient {
return pb.NewTrafficDailyStatServiceClient(this.pickConn())
}
// Context 构造Admin上下文
func (this *RPCClient) Context(adminId int64) context.Context {
ctx := context.Background()
m := maps.Map{
var ctx = context.Background()
var m = maps.Map{
"timestamp": time.Now().Unix(),
"type": "admin",
"userId": adminId,
@@ -314,15 +477,15 @@ func (this *RPCClient) Context(adminId int64) context.Context {
utils.PrintError(err)
return context.Background()
}
token := base64.StdEncoding.EncodeToString(data)
var token = base64.StdEncoding.EncodeToString(data)
ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token)
return ctx
}
// 构造API上下文
// APIContext 构造API上下文
func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
ctx := context.Background()
m := maps.Map{
var ctx = context.Background()
var m = maps.Map{
"timestamp": time.Now().Unix(),
"type": "api",
"userId": apiNodeId,
@@ -337,27 +500,58 @@ func (this *RPCClient) APIContext(apiNodeId int64) context.Context {
utils.PrintError(err)
return context.Background()
}
token := base64.StdEncoding.EncodeToString(data)
var token = base64.StdEncoding.EncodeToString(data)
ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token)
return ctx
}
// UpdateConfig 修改配置
func (this *RPCClient) UpdateConfig(config *configs.APIConfig) error {
this.apiConfig = config
this.locker.Lock()
err := this.init()
this.locker.Unlock()
return err
}
// 初始化
func (this *RPCClient) init() error {
// 当前的IP地址
var localIPAddrs = this.localIPAddrs()
// 重新连接
conns := []*grpc.ClientConn{}
var conns = []*grpc.ClientConn{}
for _, endpoint := range this.apiConfig.RPC.Endpoints {
u, err := url.Parse(endpoint)
if err != nil {
return errors.New("parse endpoint failed: " + err.Error())
}
var apiHost = u.Host
// 如果本机,则将地址修改为回路地址
if lists.ContainsString(localIPAddrs, u.Hostname()) {
if strings.Contains(apiHost, "[") { // IPv6 [host]:port
apiHost = "[::1]"
} else {
apiHost = "127.0.0.1"
}
var port = u.Port()
if len(port) > 0 {
apiHost += ":" + port
}
}
var conn *grpc.ClientConn
var callOptions = grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128*1024*1024),
grpc.UseCompressor(gzip.Name))
if u.Scheme == "http" {
conn, err = grpc.Dial(u.Host, grpc.WithInsecure(), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128*1024*1024)))
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions)
} else if u.Scheme == "https" {
conn, err = grpc.Dial(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true,
})), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(128*1024*1024)))
})), callOptions)
} else {
return errors.New("parse endpoint failed: invalid scheme '" + u.Scheme + "'")
}
@@ -369,6 +563,8 @@ func (this *RPCClient) init() error {
if len(conns) == 0 {
return errors.New("[RPC]no available endpoints")
}
// 这里不需要加锁因为会和pickConn冲突
this.conns = conns
return nil
}
@@ -379,39 +575,71 @@ func (this *RPCClient) pickConn() *grpc.ClientConn {
defer this.locker.Unlock()
// 检查连接状态
if len(this.conns) > 0 {
availableConns := []*grpc.ClientConn{}
for _, state := range []connectivity.State{connectivity.Ready, connectivity.Idle, connectivity.Connecting} {
var countConns = len(this.conns)
if countConns > 0 {
if countConns == 1 {
return this.conns[0]
}
for _, state := range []connectivity.State{
connectivity.Ready,
connectivity.Idle,
connectivity.Connecting,
connectivity.TransientFailure,
} {
var availableConns = []*grpc.ClientConn{}
for _, conn := range this.conns {
if conn.GetState() == state {
availableConns = append(availableConns, conn)
}
}
if len(availableConns) > 0 {
break
return this.randConn(availableConns)
}
}
if len(availableConns) > 0 {
return availableConns[rands.Int(0, len(availableConns)-1)]
}
// 关闭
for _, conn := range this.conns {
_ = conn.Close()
}
}
// 重新初始化
err := this.init()
if err != nil {
// 错误提示已经在构造对象时打印过,所以这里不再重复打印
return nil
}
if len(this.conns) == 0 {
return nil
}
return this.conns[rands.Int(0, len(this.conns)-1)]
return this.randConn(this.conns)
}
// Close 关闭
func (this *RPCClient) Close() error {
this.locker.Lock()
defer this.locker.Unlock()
var lastErr error
for _, conn := range this.conns {
var err = conn.Close()
if err != nil {
lastErr = err
continue
}
}
return lastErr
}
func (this *RPCClient) localIPAddrs() []string {
localInterfaceAddrs, err := net.InterfaceAddrs()
var localIPAddrs = []string{}
if err == nil {
for _, addr := range localInterfaceAddrs {
var addrString = addr.String()
var index = strings.Index(addrString, "/")
if index > 0 {
localIPAddrs = append(localIPAddrs, addrString[:index])
}
}
}
return localIPAddrs
}
func (this *RPCClient) randConn(conns []*grpc.ClientConn) *grpc.ClientConn {
var l = len(conns)
if l == 0 {
return nil
}
if l == 1 {
return conns[0]
}
return conns[rands.Int(0, l-1)]
}

View File

@@ -18,7 +18,7 @@ func TestRPCClient_NodeRPC(t *testing.T) {
if err != nil {
t.Fatal(err)
}
rpc, err := NewRPCClient(config)
rpc, err := NewRPCClient(config, true)
if err != nil {
t.Fatal(err)
}
@@ -35,13 +35,14 @@ func TestRPCClient_NodeRPC(t *testing.T) {
func TestRPC_Dial_HTTP(t *testing.T) {
client, err := NewRPCClient(&configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
}{
Endpoints: []string{"127.0.0.1:8003"},
Endpoints: []string{"http://127.0.0.1:8004"},
},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
})
}, true)
if err != nil {
t.Fatal(err)
}
@@ -56,13 +57,14 @@ func TestRPC_Dial_HTTP(t *testing.T) {
func TestRPC_Dial_HTTP_2(t *testing.T) {
client, err := NewRPCClient(&configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
}{
Endpoints: []string{"http://127.0.0.1:8003"},
Endpoints: []string{"https://127.0.0.1:8003"},
},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
})
}, true)
if err != nil {
t.Fatal(err)
}
@@ -77,13 +79,14 @@ func TestRPC_Dial_HTTP_2(t *testing.T) {
func TestRPC_Dial_HTTPS(t *testing.T) {
client, err := NewRPCClient(&configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
}{
Endpoints: []string{"https://127.0.0.1:8004"},
},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
})
}, true)
if err != nil {
t.Fatal(err)
}
@@ -94,3 +97,53 @@ func TestRPC_Dial_HTTPS(t *testing.T) {
}
t.Log(resp.Node)
}
func BenchmarkNewRPCClient(b *testing.B) {
config, err := configs.LoadAPIConfig()
if err != nil {
b.Fatal(err)
}
rpc, err := NewRPCClient(config, true)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := rpc.AdminRPC().LoginAdmin(rpc.Context(0), &pb.LoginAdminRequest{
Username: "admin",
Password: stringutil.Md5("123456"),
})
if err != nil {
b.Fatal(err)
}
_ = resp
}
}
func BenchmarkNewRPCClient_2(b *testing.B) {
config, err := configs.LoadAPIConfig()
if err != nil {
b.Fatal(err)
}
rpc, err := NewRPCClient(config, true)
if err != nil {
b.Fatal(err)
}
var conn = rpc.AdminRPC()
b.ResetTimer()
for i := 0; i < b.N; i++ {
resp, err := conn.LoginAdmin(rpc.Context(0), &pb.LoginAdminRequest{
Username: "admin",
Password: stringutil.Md5("123456"),
})
if err != nil {
b.Fatal(err)
}
_ = resp
}
}

View File

@@ -20,7 +20,7 @@ func SharedRPC() (*RPCClient, error) {
if err != nil {
return nil, err
}
client, err := NewRPCClient(config)
client, err := NewRPCClient(config, true)
if err != nil {
return nil, err
}

View File

@@ -2,11 +2,13 @@ package setup
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"os"
)
var isConfigured bool
// 判断系统是否已经配置过
// IsConfigured 判断系统是否已经配置过
func IsConfigured() bool {
if isConfigured {
return true
@@ -16,3 +18,16 @@ func IsConfigured() bool {
isConfigured = err == nil
return isConfigured
}
// IsNewInstalled IsNew 检查是否新安装
func IsNewInstalled() bool {
homeDir, err := os.UserHomeDir()
if err != nil {
return false
}
_, err = os.Stat(homeDir + "/." + teaconst.ProcessName + "/api.yaml")
if err != nil {
return true
}
return false
}

View File

@@ -0,0 +1,132 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package tasks
import (
"encoding/json"
"errors"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/events"
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
stringutil "github.com/iwind/TeaGo/utils/string"
"io"
"net/http"
"runtime"
"strings"
"time"
)
func init() {
events.On(events.EventStart, func() {
var task = NewCheckUpdatesTask()
goman.New(func() {
task.Start()
})
})
}
type CheckUpdatesTask struct {
ticker *time.Ticker
}
func NewCheckUpdatesTask() *CheckUpdatesTask {
return &CheckUpdatesTask{}
}
func (this *CheckUpdatesTask) Start() {
this.ticker = time.NewTicker(12 * time.Hour)
for range this.ticker.C {
err := this.Loop()
if err != nil {
logs.Println("[TASK][CHECK_UPDATES_TASK]" + err.Error())
}
}
}
func (this *CheckUpdatesTask) Loop() error {
// 检查是否开启
rpcClient, err := rpc.SharedRPC()
if err != nil {
return err
}
valueResp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeCheckUpdates})
if err != nil {
return err
}
var valueJSON = valueResp.ValueJSON
var config = &systemconfigs.CheckUpdatesConfig{AutoCheck: false}
if len(valueJSON) > 0 {
err = json.Unmarshal(valueJSON, config)
if err != nil {
return errors.New("decode config failed: " + err.Error())
}
if !config.AutoCheck {
return nil
}
} else {
return nil
}
// 开始检查
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
// 目前支持Linux
if runtime.GOOS != "linux" {
return nil
}
var apiURL = teaconst.UpdatesURL
apiURL = strings.ReplaceAll(apiURL, "${os}", runtime.GOOS)
apiURL = strings.ReplaceAll(apiURL, "${arch}", runtime.GOARCH)
apiURL = strings.ReplaceAll(apiURL, "${version}", teaconst.Version)
resp, err := http.Get(apiURL)
if err != nil {
return errors.New("read api failed: " + err.Error())
}
defer func() {
_ = resp.Body.Close()
}()
data, err := io.ReadAll(resp.Body)
if err != nil {
return errors.New("read api failed: " + err.Error())
}
var apiResponse = &Response{}
err = json.Unmarshal(data, apiResponse)
if err != nil {
return errors.New("decode version data failed: " + err.Error())
}
if apiResponse.Code != 200 {
return errors.New("invalid response: " + apiResponse.Message)
}
var m = maps.NewMap(apiResponse.Data)
var dlHost = m.GetString("host")
var versions = m.GetSlice("versions")
if len(versions) > 0 {
for _, version := range versions {
var vMap = maps.NewMap(version)
if vMap.GetString("code") == "admin" {
var latestVersion = vMap.GetString("version")
if stringutil.VersionCompare(teaconst.Version, latestVersion) < 0 {
teaconst.NewVersionCode = latestVersion
teaconst.NewVersionDownloadURL = dlHost + vMap.GetString("url")
return nil
}
}
}
}
return nil
}

View File

@@ -0,0 +1,166 @@
package tasks
import (
"context"
"crypto/tls"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/events"
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"net/url"
"sort"
"strings"
"sync"
"time"
)
func init() {
events.On(events.EventStart, func() {
task := NewSyncAPINodesTask()
goman.New(func() {
task.Start()
})
})
}
// SyncAPINodesTask API节点同步任务
type SyncAPINodesTask struct {
}
func NewSyncAPINodesTask() *SyncAPINodesTask {
return &SyncAPINodesTask{}
}
func (this *SyncAPINodesTask) Start() {
ticker := time.NewTicker(5 * time.Minute)
if Tea.IsTesting() {
// 快速测试
ticker = time.NewTicker(1 * time.Minute)
}
for range ticker.C {
err := this.Loop()
if err != nil {
logs.Println("[TASK][SYNC_API_NODES]" + err.Error())
}
}
}
func (this *SyncAPINodesTask) Loop() error {
// 如果还没有安装直接返回
if !setup.IsConfigured() || teaconst.IsRecoverMode {
return nil
}
config, err := configs.LoadAPIConfig()
if err != nil {
return err
}
// 是否禁止自动升级
if config.RPC.DisableUpdate {
return nil
}
// 获取所有可用的节点
rpcClient, err := rpc.SharedRPC()
if err != nil {
return err
}
resp, err := rpcClient.APINodeRPC().FindAllEnabledAPINodes(rpcClient.Context(0), &pb.FindAllEnabledAPINodesRequest{})
if err != nil {
return err
}
var newEndpoints = []string{}
for _, node := range resp.ApiNodes {
if !node.IsOn {
continue
}
newEndpoints = append(newEndpoints, node.AccessAddrs...)
}
// 和现有的对比
if this.isSame(newEndpoints, config.RPC.Endpoints) {
return nil
}
// 测试是否有API节点可用
hasOk := this.testEndpoints(newEndpoints)
if !hasOk {
return nil
}
// 修改RPC对象配置
config.RPC.Endpoints = newEndpoints
err = rpcClient.UpdateConfig(config)
if err != nil {
return err
}
// 保存到文件
err = config.WriteFile(Tea.ConfigFile("api.yaml"))
if err != nil {
return err
}
return nil
}
func (this *SyncAPINodesTask) isSame(endpoints1 []string, endpoints2 []string) bool {
sort.Strings(endpoints1)
sort.Strings(endpoints2)
return strings.Join(endpoints1, "&") == strings.Join(endpoints2, "&")
}
func (this *SyncAPINodesTask) testEndpoints(endpoints []string) bool {
if len(endpoints) == 0 {
return false
}
var wg = sync.WaitGroup{}
wg.Add(len(endpoints))
var ok = false
for _, endpoint := range endpoints {
go func(endpoint string) {
defer wg.Done()
u, err := url.Parse(endpoint)
if err != nil {
return
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer func() {
cancel()
}()
var conn *grpc.ClientConn
if u.Scheme == "http" {
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
} else if u.Scheme == "https" {
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true,
})), grpc.WithBlock())
}
if err != nil {
return
}
_ = conn.Close()
ok = true
}(endpoint)
}
wg.Wait()
return ok
}

View File

@@ -0,0 +1,15 @@
package tasks
import (
_ "github.com/iwind/TeaGo/bootstrap"
"testing"
)
func TestSyncAPINodesTask_Loop(t *testing.T) {
task := NewSyncAPINodesTask()
err := task.Loop()
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}

View File

@@ -1,7 +1,9 @@
package tasks
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/events"
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/nodeutils"
@@ -16,11 +18,13 @@ import (
func init() {
events.On(events.EventStart, func() {
task := NewSyncClusterTask()
go task.Start()
goman.New(func() {
task.Start()
})
})
}
// 自动同步集群任务
// SyncClusterTask 自动同步集群任务
type SyncClusterTask struct {
}
@@ -40,7 +44,7 @@ func (this *SyncClusterTask) Start() {
func (this *SyncClusterTask) loop() error {
// 如果还没有安装直接返回
if !setup.IsConfigured() {
if !setup.IsConfigured() || teaconst.IsRecoverMode {
return nil
}
@@ -50,7 +54,7 @@ func (this *SyncClusterTask) loop() error {
}
ctx := rpcClient.Context(0)
tasksResp, err := rpcClient.NodeTaskRPC().FindNotifyingNodeTasks(ctx, &pb.FindNotifyingNodeTasksRequest{Size: 100})
tasksResp, err := rpcClient.NodeTaskRPC().FindNotifyingNodeTasks(ctx, &pb.FindNotifyingNodeTasksRequest{Size: 500})
if err != nil {
return err
}

View File

@@ -30,7 +30,7 @@ func (this *Piece) IncreaseInt64(key uint64, delta int64, expiredAt int64) (resu
this.locker.Lock()
item, ok := this.m[key]
if ok {
result := types.Int64(item.Value) + delta
result = types.Int64(item.Value) + delta
item.Value = result
item.expiredAt = expiredAt
} else {

View File

@@ -0,0 +1,30 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package apinodeutils
var SharedManager = NewManager()
type Manager struct {
upgraderMap map[int64]*Upgrader
}
func NewManager() *Manager {
return &Manager{
upgraderMap: map[int64]*Upgrader{},
}
}
func (this *Manager) AddUpgrader(upgrader *Upgrader) {
this.upgraderMap[upgrader.apiNodeId] = upgrader
}
func (this *Manager) FindUpgrader(apiNodeId int64) *Upgrader {
return this.upgraderMap[apiNodeId]
}
func (this *Manager) RemoveUpgrader(upgrader *Upgrader) {
if upgrader == nil {
return
}
delete(this.upgraderMap, upgrader.apiNodeId)
}

View File

@@ -0,0 +1,201 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package apinodeutils
import (
"compress/gzip"
"crypto/md5"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string"
"io"
"os"
"path/filepath"
"runtime"
)
type Progress struct {
Percent float64
}
type Upgrader struct {
progress *Progress
apiExe string
apiNodeId int64
}
func NewUpgrader(apiNodeId int64) *Upgrader {
return &Upgrader{
apiExe: apiExe(),
progress: &Progress{Percent: 0},
apiNodeId: apiNodeId,
}
}
func (this *Upgrader) Upgrade() error {
sharedClient, err := rpc.SharedRPC()
if err != nil {
return err
}
apiNodeResp, err := sharedClient.APINodeRPC().FindEnabledAPINode(sharedClient.Context(0), &pb.FindEnabledAPINodeRequest{ApiNodeId: this.apiNodeId})
if err != nil {
return err
}
var apiNode = apiNodeResp.ApiNode
if apiNode == nil {
return errors.New("could not find api node with id '" + types.String(this.apiNodeId) + "'")
}
apiConfig, err := configs.LoadAPIConfig()
if err != nil {
return err
}
var newAPIConfig = apiConfig.Clone()
newAPIConfig.RPC.Endpoints = apiNode.AccessAddrs
rpcClient, err := rpc.NewRPCClient(newAPIConfig, false)
if err != nil {
return err
}
versionResp, err := rpcClient.APINodeRPC().FindCurrentAPINodeVersion(sharedClient.Context(0), &pb.FindCurrentAPINodeVersionRequest{})
if err != nil {
return err
}
if !Tea.IsTesting() /** 开发环境下允许突破此限制方便测试 **/ &&
(stringutil.VersionCompare(versionResp.Version, "0.6.4" /** 从0.6.4开始支持 **/) < 0 || versionResp.Os != runtime.GOOS || versionResp.Arch != runtime.GOARCH) {
return errors.New("could not upgrade api node v" + versionResp.Version + "/" + versionResp.Os + "/" + versionResp.Arch)
}
// 检查本地文件版本
canUpgrade, reason := CanUpgrade(versionResp.Version, versionResp.Os, versionResp.Arch)
if !canUpgrade {
return errors.New(reason)
}
localVersion, err := localVersion()
if err != nil {
return errors.New("lookup version failed: " + err.Error())
}
// 检查要升级的文件
var gzFile = this.apiExe + "." + localVersion + ".gz"
gzReader, err := os.Open(gzFile)
if err != nil {
if !os.IsNotExist(err) {
return err
}
err = func() error {
// 压缩文件
exeReader, err := os.Open(this.apiExe)
if err != nil {
return err
}
defer func() {
_ = exeReader.Close()
}()
var tmpGzFile = gzFile + ".tmp"
gzFileWriter, err := os.OpenFile(tmpGzFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
return err
}
var gzWriter = gzip.NewWriter(gzFileWriter)
defer func() {
_ = gzWriter.Close()
_ = gzFileWriter.Close()
_ = os.Rename(tmpGzFile, gzFile)
}()
_, err = io.Copy(gzWriter, exeReader)
if err != nil {
return err
}
return nil
}()
if err != nil {
return err
}
gzReader, err = os.Open(gzFile)
if err != nil {
return err
}
}
defer func() {
_ = gzReader.Close()
}()
// 开始上传
var hash = md5.New()
var buf = make([]byte, 128*4096)
var isFirst = true
stat, err := gzReader.Stat()
if err != nil {
return err
}
var totalSize = stat.Size()
if totalSize == 0 {
_ = gzReader.Close()
_ = os.Remove(gzFile)
return errors.New("invalid gz file")
}
var uploadedSize int64 = 0
for {
n, err := gzReader.Read(buf)
if n > 0 {
// 计算Hash
hash.Write(buf[:n])
// 上传
_, uploadErr := rpcClient.APINodeRPC().UploadAPINodeFile(rpcClient.Context(0), &pb.UploadAPINodeFileRequest{
Filename: filepath.Base(this.apiExe),
Sum: "",
ChunkData: buf[:n],
IsFirstChunk: isFirst,
IsLastChunk: false,
})
if uploadErr != nil {
return uploadErr
}
// 进度
uploadedSize += int64(n)
this.progress = &Progress{Percent: float64(uploadedSize*100) / float64(totalSize)}
}
if isFirst {
isFirst = false
}
if err != nil {
if err != io.EOF {
return err
}
if err == io.EOF {
_, uploadErr := rpcClient.APINodeRPC().UploadAPINodeFile(rpcClient.Context(0), &pb.UploadAPINodeFileRequest{
Filename: filepath.Base(this.apiExe),
Sum: fmt.Sprintf("%x", hash.Sum(nil)),
ChunkData: buf[:n],
IsFirstChunk: isFirst,
IsLastChunk: true,
})
if uploadErr != nil {
return uploadErr
}
break
}
}
}
return nil
}
func (this *Upgrader) Progress() *Progress {
return this.progress
}

View File

@@ -0,0 +1,22 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package apinodeutils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/apinodeutils"
_ "github.com/iwind/TeaGo/bootstrap"
"runtime"
"testing"
)
func TestUpgrader_CanUpgrade(t *testing.T) {
t.Log(apinodeutils.CanUpgrade("0.6.3", runtime.GOOS, runtime.GOARCH))
}
func TestUpgrader_Upgrade(t *testing.T) {
var upgrader = apinodeutils.NewUpgrader(1)
err := upgrader.Upgrade()
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,80 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package apinodeutils
import (
"bytes"
"errors"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/iwind/TeaGo/Tea"
stringutil "github.com/iwind/TeaGo/utils/string"
"os"
"os/exec"
"regexp"
"runtime"
"strings"
)
func CanUpgrade(apiVersion string, osName string, arch string) (canUpgrade bool, reason string) {
if len(apiVersion) == 0 {
return false, "current api version should not be empty"
}
if stringutil.VersionCompare(apiVersion, "0.6.4") < 0 {
return false, "api node version must greater than or equal to 0.6.4"
}
if osName != runtime.GOOS {
return false, "os not match: " + osName
}
if arch != runtime.GOARCH {
return false, "arch not match: " + arch
}
stat, err := os.Stat(apiExe())
if err != nil {
return false, "stat error: " + err.Error()
}
if stat.IsDir() {
return false, "is directory"
}
localVersion, err := localVersion()
if err != nil {
return false, "lookup version failed: " + err.Error()
}
if localVersion != teaconst.APINodeVersion {
return false, "not newest api node"
}
if stringutil.VersionCompare(localVersion, apiVersion) <= 0 {
return false, "need not upgrade, local '" + localVersion + "' vs remote '" + apiVersion + "'"
}
return true, ""
}
func localVersion() (string, error) {
var cmd = exec.Command(apiExe(), "-V")
var output = &bytes.Buffer{}
cmd.Stdout = output
err := cmd.Run()
if err != nil {
return "", err
}
var localVersion = strings.TrimSpace(output.String())
// 检查版本号
var reg = regexp.MustCompile(`^[\d.]+$`)
if !reg.MatchString(localVersion) {
return "", errors.New("lookup version failed: " + localVersion)
}
return localVersion, nil
}
func apiExe() string {
return Tea.Root + "/edge-api/bin/edge-api"
}

View File

@@ -0,0 +1,12 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package dateutils
// SplitYmd 分隔Ymd格式的日期
// Ymd => Y-m-d
func SplitYmd(day string) string {
if len(day) != 8 {
return day
}
return day[:4] + "-" + day[4:6] + "-" + day[6:]
}

12
internal/utils/email.go Normal file
View File

@@ -0,0 +1,12 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
import "regexp"
var emailReg = regexp.MustCompile(`(?i)^[a-z\d]+([._+-]*[a-z\d]+)*@([a-z\d]+[a-z\d-]*[a-z\d]+\.)+[a-z\d]+$`)
// ValidateEmail 校验电子邮箱格式
func ValidateEmail(email string) bool {
return emailReg.MatchString(email)
}

View File

@@ -0,0 +1,22 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestValidateEmail(t *testing.T) {
var a = assert.NewAssertion(t)
a.IsTrue(utils.ValidateEmail("aaaa@gmail.com"))
a.IsTrue(utils.ValidateEmail("a.b@gmail.com"))
a.IsTrue(utils.ValidateEmail("a.b.c.d@gmail.com"))
a.IsTrue(utils.ValidateEmail("aaaa@gmail.com.cn"))
a.IsTrue(utils.ValidateEmail("hello.world.123@gmail.123.com"))
a.IsTrue(utils.ValidateEmail("10000@qq.com"))
a.IsFalse(utils.ValidateEmail("aaaa.@gmail.com"))
a.IsFalse(utils.ValidateEmail("aaaa@gmail"))
a.IsFalse(utils.ValidateEmail("aaaa@123"))
}

View File

@@ -0,0 +1,28 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
import (
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/types"
"os/exec"
"runtime"
)
func AddPortsToFirewall(ports []int) {
for _, port := range ports {
// Linux
if runtime.GOOS == "linux" {
// firewalld
firewallCmd, _ := exec.LookPath("firewall-cmd")
if len(firewallCmd) > 0 {
err := exec.Command(firewallCmd, "--add-port="+types.String(port)+"/tcp").Run()
if err == nil {
logs.Println("ADMIN_NODE", "add port '"+types.String(port)+"' to firewalld")
_ = exec.Command(firewallCmd, "--add-port="+types.String(port)+"/tcp", "--permanent").Run()
}
}
}
}
}

View File

@@ -1,14 +1,17 @@
package utils
import (
"bytes"
"encoding/binary"
"errors"
"github.com/iwind/TeaGo/types"
"math/big"
"net"
"regexp"
"strings"
)
// 将IP转换为整型
// IP2Long 将IP转换为整型
func IP2Long(ip string) uint64 {
s := net.ParseIP(ip)
if len(s) != 16 {
@@ -23,7 +26,7 @@ func IP2Long(ip string) uint64 {
return uint64(binary.BigEndian.Uint32(s.To4()))
}
// 判断是否为IPv4
// IsIPv4 判断是否为IPv4
func IsIPv4(ip string) bool {
if !regexp.MustCompile(`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$`).MatchString(ip) {
return false
@@ -34,10 +37,120 @@ func IsIPv4(ip string) bool {
return true
}
// 判断是否为IPv6
// IsIPv6 判断是否为IPv6
func IsIPv6(ip string) bool {
if !strings.Contains(ip, ":") {
return false
}
return len(net.ParseIP(ip)) == net.IPv6len
}
// ExtractIP 分解IP
// 只支持D段掩码的CIDR
// 最多只记录255个值
func ExtractIP(ipStrings string) ([]string, error) {
ipStrings = strings.ReplaceAll(ipStrings, " ", "")
// CIDR
if strings.Contains(ipStrings, "/") {
_, cidrNet, err := net.ParseCIDR(ipStrings)
if err != nil {
return nil, err
}
var index = strings.Index(ipStrings, "/")
var ipFrom = ipStrings[:index]
var bits = types.Int(ipStrings[index+1:])
if bits < 24 {
return nil, errors.New("CIDR bits should be greater than 24")
}
var ipv4 = net.ParseIP(ipFrom).To4()
if len(ipv4) == 0 {
return nil, errors.New("support IPv4 only")
}
var result = []string{}
ipv4[3] = 0 // 从0开始
for i := 0; i <= 255; i++ {
if cidrNet.Contains(ipv4) {
result = append(result, ipv4.String())
}
ipv4 = NextIP(ipv4)
}
return result, nil
}
// IP Range
if strings.Contains(ipStrings, "-") {
var index = strings.Index(ipStrings, "-")
var ipFromString = ipStrings[:index]
var ipToString = ipStrings[index+1:]
var ipFrom = net.ParseIP(ipFromString).To4()
if len(ipFrom) == 0 {
return nil, errors.New("invalid ip '" + ipFromString + "'")
}
var ipTo = net.ParseIP(ipToString).To4()
if len(ipTo) == 0 {
return nil, errors.New("invalid ip '" + ipToString + "'")
}
if bytes.Compare(ipFrom, ipTo) > 0 {
ipFrom, ipTo = ipTo, ipFrom
}
var result = []string{}
for i := 0; i < 255; i++ {
if bytes.Compare(ipFrom, ipTo) > 0 {
break
}
result = append(result, ipFrom.String())
ipFrom = NextIP(ipFrom)
}
return result, nil
}
return []string{ipStrings}, nil
}
// NextIP IP增加1
func NextIP(prevIP net.IP) net.IP {
var ip = make(net.IP, len(prevIP))
copy(ip, prevIP)
var index = len(ip) - 1
for {
if ip[index] == 255 {
ip[index] = 0
index--
if index < 0 {
break
}
} else {
ip[index]++
break
}
}
return ip
}
// IsLocalIP 判断是否为本地IP
// ip 是To4()或者To16()的结果
func IsLocalIP(ip net.IP) bool {
if ip == nil {
return false
}
if ip[0] == 127 ||
ip[0] == 10 ||
(ip[0] == 172 && ip[1]&0xf0 == 16) ||
(ip[0] == 192 && ip[1] == 168) {
return true
}
if ip.String() == "::1" {
return true
}
return false
}

View File

@@ -1,6 +1,7 @@
package utils
import (
"net"
"testing"
)
@@ -83,3 +84,27 @@ func TestIsIPv6(t *testing.T) {
}
}
}
func TestExtractIP(t *testing.T) {
t.Log(ExtractIP("192.168.1.100"))
}
func TestExtractIP_CIDR(t *testing.T) {
t.Log(ExtractIP("192.168.2.100/24"))
}
func TestExtractIP_Range(t *testing.T) {
t.Log(ExtractIP("192.168.2.100 - 192.168.4.2"))
}
func TestNextIP(t *testing.T) {
for _, ip := range []string{"192.168.1.1", "0.0.0.0", "255.255.255.255", "192.168.2.255", "192.168.255.255"} {
t.Log(ip+":", NextIP(net.ParseIP(ip).To4()))
}
}
func TestNextIP_Copy(t *testing.T) {
var ip = net.ParseIP("192.168.1.100")
var nextIP = NextIP(ip)
t.Log(ip, nextIP)
}

24
internal/utils/json.go Normal file
View File

@@ -0,0 +1,24 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package utils
import (
"encoding/json"
"reflect"
)
// JSONClone 使用JSON克隆对象
func JSONClone(v interface{}) (interface{}, error) {
data, err := json.Marshal(v)
if err != nil {
return nil, err
}
var nv = reflect.New(reflect.TypeOf(v).Elem()).Interface()
err = json.Unmarshal(data, nv)
if err != nil {
return nil, err
}
return nv, nil
}

View File

@@ -0,0 +1,25 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package utils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"testing"
)
func TestJSONClone(t *testing.T) {
type A struct {
B int `json:"b"`
C string `json:"c"`
}
var a = &A{B: 123, C: "456"}
for i := 0; i < 5; i++ {
c, err := utils.JSONClone(a)
if err != nil {
t.Fatal(err)
}
t.Logf("%p, %#v", c, c)
}
}

View File

@@ -1,10 +1,11 @@
package utils
import (
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/miekg/dns"
)
// 获取CNAME
// LookupCNAME 获取CNAME
func LookupCNAME(host string) (string, error) {
config, err := dns.ClientConfigFromFile("/etc/resolv.conf")
if err != nil {
@@ -16,13 +17,19 @@ func LookupCNAME(host string) (string, error) {
m.SetQuestion(host+".", dns.TypeCNAME)
m.RecursionDesired = true
r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port)
if err != nil {
return "", err
}
if len(r.Answer) == 0 {
return "", nil
}
return r.Answer[0].(*dns.CNAME).Target, nil
var lastErr error
for _, serverAddr := range config.Servers {
r, _, err := c.Exchange(m, configutils.QuoteIP(serverAddr)+":"+config.Port)
if err != nil {
lastErr = err
continue
}
if len(r.Answer) == 0 {
continue
}
return r.Answer[0].(*dns.CNAME).Target, nil
}
return "", lastErr
}

View File

@@ -0,0 +1,26 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
package nodelogutils
import (
"github.com/iwind/TeaGo/maps"
)
// FindCommonTags 查找常用的标签
func FindNodeCommonTags() []maps.Map {
return []maps.Map{
{
"name": "端口监听",
"code": "LISTENER",
},
{
"name": "WAF",
"code": "WAF",
},
{
"name": "访问日志",
"code": "ACCESS_LOG",
},
}
}

View File

@@ -2,7 +2,10 @@ package numberutils
import (
"fmt"
"github.com/iwind/TeaGo/types"
"regexp"
"strconv"
"strings"
)
func FormatInt64(value int64) string {
@@ -13,30 +16,132 @@ func FormatInt(value int) string {
return strconv.Itoa(value)
}
func Pow1024(n int) int64 {
if n <= 0 {
return 1
}
if n == 1 {
return 1024
}
return Pow1024(n-1) * 1024
}
func FormatBytes(bytes int64) string {
if bytes < 1024 {
if bytes < Pow1024(1) {
return FormatInt64(bytes) + "B"
} else if bytes < 1024*1024 {
return fmt.Sprintf("%.2fKB", float64(bytes)/1024)
} else if bytes < 1024*1024*1024 {
return fmt.Sprintf("%.2fMB", float64(bytes)/1024/1024)
} else if bytes < 1024*1024*1024*1024 {
return fmt.Sprintf("%.2fGB", float64(bytes)/1024/1024/1024)
} else if bytes < Pow1024(2) {
return TrimZeroSuffix(fmt.Sprintf("%.2fKB", float64(bytes)/float64(Pow1024(1))))
} else if bytes < Pow1024(3) {
return TrimZeroSuffix(fmt.Sprintf("%.2fMB", float64(bytes)/float64(Pow1024(2))))
} else if bytes < Pow1024(4) {
return TrimZeroSuffix(fmt.Sprintf("%.2fGB", float64(bytes)/float64(Pow1024(3))))
} else if bytes < Pow1024(5) {
return TrimZeroSuffix(fmt.Sprintf("%.2fTB", float64(bytes)/float64(Pow1024(4))))
} else if bytes < Pow1024(6) {
return TrimZeroSuffix(fmt.Sprintf("%.2fPB", float64(bytes)/float64(Pow1024(5))))
} else {
return fmt.Sprintf("%.2fPB", float64(bytes)/1024/1024/1024/1024)
return TrimZeroSuffix(fmt.Sprintf("%.2fEB", float64(bytes)/float64(Pow1024(6))))
}
}
func FormatBits(bits int64) string {
if bits < 1000 {
return FormatInt64(bits) + "B"
} else if bits < 1000*1000 {
return fmt.Sprintf("%.2fKB", float64(bits)/1000)
} else if bits < 1000*1000*1000 {
return fmt.Sprintf("%.2fMB", float64(bits)/1000/1000)
} else if bits < 1000*1000*1000*1000 {
return fmt.Sprintf("%.2fGB", float64(bits)/1000/10001000)
if bits < Pow1024(1) {
return FormatInt64(bits) + "bps"
} else if bits < Pow1024(2) {
return TrimZeroSuffix(fmt.Sprintf("%.4fKbps", float64(bits)/float64(Pow1024(1))))
} else if bits < Pow1024(3) {
return TrimZeroSuffix(fmt.Sprintf("%.4fMbps", float64(bits)/float64(Pow1024(2))))
} else if bits < Pow1024(4) {
return TrimZeroSuffix(fmt.Sprintf("%.4fGbps", float64(bits)/float64(Pow1024(3))))
} else if bits < Pow1024(5) {
return TrimZeroSuffix(fmt.Sprintf("%.4fTbps", float64(bits)/float64(Pow1024(4))))
} else if bits < Pow1024(6) {
return TrimZeroSuffix(fmt.Sprintf("%.4fPbps", float64(bits)/float64(Pow1024(5))))
} else {
return fmt.Sprintf("%.2fPB", float64(bits)/1000/1000/1000/1000)
return TrimZeroSuffix(fmt.Sprintf("%.4fEbps", float64(bits)/float64(Pow1024(6))))
}
}
func FormatCount(count int64) string {
if count < 1000 {
return types.String(count)
}
if count < 1000*1000 {
return fmt.Sprintf("%.1fK", float32(count)/1000)
}
if count < 1000*1000*1000 {
return fmt.Sprintf("%.1fM", float32(count)/1000/1000)
}
return fmt.Sprintf("%.1fB", float32(count)/1000/1000/1000)
}
func FormatFloat(f interface{}, decimal int) string {
if f == nil {
return ""
}
switch x := f.(type) {
case float32, float64:
var s = fmt.Sprintf("%."+types.String(decimal)+"f", x)
// 分隔
var dotIndex = strings.Index(s, ".")
if dotIndex > 0 {
var d = s[:dotIndex]
var f2 = s[dotIndex:]
f2 = strings.TrimRight(strings.TrimRight(f2, "0"), ".")
return formatDigit(d) + f2
}
return s
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
return formatDigit(types.String(x))
case string:
return x
}
return ""
}
func FormatFloat2(f interface{}) string {
return FormatFloat(f, 2)
}
var decimalReg = regexp.MustCompile(`^(\d+\.\d+)([a-zA-Z]+)?$`)
// TrimZeroSuffix 去除小数数字尾部多余的0
func TrimZeroSuffix(s string) string {
var matches = decimalReg.FindStringSubmatch(s)
if len(matches) < 3 {
return s
}
return strings.TrimRight(strings.TrimRight(matches[1], "0"), ".") + matches[2]
}
func formatDigit(d string) string {
if len(d) == 0 {
return d
}
var prefix = ""
if d[0] < '0' || d[0] > '9' {
prefix = d[:1]
d = d[1:]
}
var l = len(d)
if l > 3 {
var pieces = l / 3
var commIndex = l - pieces*3
var d2 = ""
if commIndex > 0 {
d2 = d[:commIndex] + ", "
}
for i := 0; i < pieces; i++ {
d2 += d[commIndex+i*3 : commIndex+i*3+3]
if i != pieces-1 {
d2 += ", "
}
}
return prefix + d2
}
return prefix + d
}

View File

@@ -0,0 +1,67 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package numberutils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"testing"
)
func TestFormatBytes(t *testing.T) {
t.Log(numberutils.FormatBytes(1))
t.Log(numberutils.FormatBytes(1000))
t.Log(numberutils.FormatBytes(1_000_000))
t.Log(numberutils.FormatBytes(1_000_000_000))
t.Log(numberutils.FormatBytes(1_000_000_000_000))
t.Log(numberutils.FormatBytes(1_000_000_000_000_000))
t.Log(numberutils.FormatBytes(1_000_000_000_000_000_000))
t.Log(numberutils.FormatBytes(9_000_000_000_000_000_000))
}
func TestFormatCount(t *testing.T) {
t.Log(numberutils.FormatCount(1))
t.Log(numberutils.FormatCount(1000))
t.Log(numberutils.FormatCount(1500))
t.Log(numberutils.FormatCount(1_000_000))
t.Log(numberutils.FormatCount(1_500_000))
t.Log(numberutils.FormatCount(1_000_000_000))
t.Log(numberutils.FormatCount(1_500_000_000))
}
func TestFormatFloat(t *testing.T) {
t.Log(numberutils.FormatFloat(1, 2))
t.Log(numberutils.FormatFloat(100.23456, 2))
t.Log(numberutils.FormatFloat(100.000023, 2))
t.Log(numberutils.FormatFloat(100.012, 2))
t.Log(numberutils.FormatFloat(123.012, 2))
t.Log(numberutils.FormatFloat(1234.012, 2))
t.Log(numberutils.FormatFloat(12345.012, 2))
t.Log(numberutils.FormatFloat(123456.012, 2))
t.Log(numberutils.FormatFloat(1234567.012, 2))
t.Log(numberutils.FormatFloat(12345678.012, 2))
t.Log(numberutils.FormatFloat(123456789.012, 2))
t.Log(numberutils.FormatFloat(1234567890.012, 2))
t.Log(numberutils.FormatFloat(123, 2))
t.Log(numberutils.FormatFloat(1234, 2))
t.Log(numberutils.FormatFloat(1234.00001, 4))
t.Log(numberutils.FormatFloat(1234.56700, 4))
t.Log(numberutils.FormatFloat(-1234.56700, 2))
t.Log(numberutils.FormatFloat(-221745.12, 2))
}
func TestTrimZeroSuffix(t *testing.T) {
for _, s := range []string{
"1",
"1.0000",
"1.10",
"100",
"100.0000",
"100.0",
"100.0123",
"100.0010",
"100.000KB",
"100.010MB",
} {
t.Log(s, "=>", numberutils.TrimZeroSuffix(s))
}
}

View File

@@ -1,4 +1,4 @@
// +build linux
//go:build linux
package utils
@@ -7,7 +7,6 @@ import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
"io/ioutil"
"os"
"os/exec"
"regexp"
@@ -16,7 +15,7 @@ import (
var systemdServiceFile = "/etc/systemd/system/edge-admin.service"
var initServiceFile = "/etc/init.d/" + teaconst.SystemdServiceName
// 安装服务
// Install 安装服务
func (this *ServiceManager) Install(exePath string, args []string) error {
if os.Getgid() != 0 {
return errors.New("only root users can install the service")
@@ -30,7 +29,7 @@ func (this *ServiceManager) Install(exePath string, args []string) error {
return this.installSystemdService(systemd, exePath, args)
}
// 启动服务
// Start 启动服务
func (this *ServiceManager) Start() error {
if os.Getgid() != 0 {
return errors.New("only root users can start the service")
@@ -47,7 +46,7 @@ func (this *ServiceManager) Start() error {
return exec.Command("service", teaconst.ProcessName, "start").Start()
}
// 删除服务
// Uninstall 删除服务
func (this *ServiceManager) Uninstall() error {
if os.Getgid() != 0 {
return errors.New("only root users can uninstall the service")
@@ -83,13 +82,13 @@ func (this *ServiceManager) installInitService(exePath string, args []string) er
return errors.New("'scripts/" + shortName + "' file not exists")
}
data, err := ioutil.ReadFile(scriptFile)
data, err := os.ReadFile(scriptFile)
if err != nil {
return err
}
data = regexp.MustCompile("INSTALL_DIR=.+").ReplaceAll(data, []byte("INSTALL_DIR="+Tea.Root))
err = ioutil.WriteFile(initServiceFile, data, 0777)
err = os.WriteFile(initServiceFile, data, 0777)
if err != nil {
return err
}
@@ -112,7 +111,8 @@ func (this *ServiceManager) installSystemdService(systemd, exePath string, args
shortName := teaconst.SystemdServiceName
longName := "GoEdge Admin" // TODO 将来可以修改
desc := `# Provides: ` + shortName + `
desc := `### BEGIN INIT INFO
# Provides: ` + shortName + `
# Required-Start: $all
# Required-Stop:
# Default-Start: 2 3 4 5
@@ -137,7 +137,7 @@ ExecReload=` + exePath + ` reload
WantedBy=multi-user.target`
// write file
err := ioutil.WriteFile(systemdServiceFile, []byte(desc), 0777)
err := os.WriteFile(systemdServiceFile, []byte(desc), 0777)
if err != nil {
return err
}

View File

@@ -1,4 +1,4 @@
// +build !linux,!windows
//go:build !linux && !windows
package utils

View File

@@ -1,4 +1,4 @@
// +build windows
//go:build windows
package utils

View File

@@ -0,0 +1,10 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package sizes
const (
K int64 = 1024
M = 1024 * K
G = 1024 * M
T = 1024 * G
)

View File

@@ -0,0 +1,17 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package sizes_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestSizes(t *testing.T) {
var a = assert.NewAssertion(t)
a.IsTrue(sizes.K == 1024)
a.IsTrue(sizes.M == 1024*1024)
a.IsTrue(sizes.G == 1024*1024*1024)
a.IsTrue(sizes.T == 1024*1024*1024*1024)
}

View File

@@ -5,7 +5,7 @@ import (
"strings"
)
// format address
// FormatAddress format address
func FormatAddress(addr string) string {
if strings.HasSuffix(addr, "unix:") {
return addr
@@ -17,7 +17,7 @@ func FormatAddress(addr string) string {
return addr
}
// 分割数字
// SplitNumbers 分割数字
func SplitNumbers(numbers string) (result []int64) {
if len(numbers) == 0 {
return

View File

@@ -0,0 +1,67 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
import (
"github.com/iwind/TeaGo/lists"
"strings"
)
func FilterNotEmpty(item string) bool {
return len(item) > 0
}
func MapAddPrefixFunc(prefix string) func(item string) string {
return func(item string) string {
if !strings.HasPrefix(item, prefix) {
return prefix + item
}
return item
}
}
type StringsStream struct {
s []string
}
func NewStringsStream(s []string) *StringsStream {
return &StringsStream{s: s}
}
func (this *StringsStream) Map(f ...func(item string) string) *StringsStream {
for index, item := range this.s {
for _, f1 := range f {
item = f1(item)
}
this.s[index] = item
}
return this
}
func (this *StringsStream) Filter(f ...func(item string) bool) *StringsStream {
for _, f1 := range f {
var newStrings = []string{}
for _, item := range this.s {
if f1(item) {
newStrings = append(newStrings, item)
}
}
this.s = newStrings
}
return this
}
func (this *StringsStream) Unique() *StringsStream {
var newStrings = []string{}
for _, item := range this.s {
if !lists.ContainsString(newStrings, item) {
newStrings = append(newStrings, item)
}
}
this.s = newStrings
return this
}
func (this *StringsStream) Result() []string {
return this.s
}

View File

@@ -0,0 +1,25 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"strings"
"testing"
)
func TestStringsStream_Filter(t *testing.T) {
var stream = utils.NewStringsStream([]string{"a", "b", "1", "2", "", "png", "a"})
stream.Filter(func(item string) bool {
return len(item) > 0
})
t.Log(stream.Result())
stream.Map(func(item string) string {
return "." + item
})
t.Log(stream.Result())
stream.Unique()
t.Log(stream.Result())
stream.Map(strings.ToUpper, strings.ToLower)
t.Log(stream.Result())
}

55
internal/utils/time.go Normal file
View File

@@ -0,0 +1,55 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
import (
"errors"
"fmt"
"github.com/iwind/TeaGo/types"
"regexp"
)
// RangeTimes 计算时间点
func RangeTimes(timeFrom string, timeTo string, everyMinutes int32) (result []string, err error) {
if everyMinutes <= 0 {
return nil, errors.New("invalid 'everyMinutes'")
}
var reg = regexp.MustCompile(`^\d{4}$`)
if !reg.MatchString(timeFrom) {
return nil, errors.New("invalid timeFrom '" + timeFrom + "'")
}
if !reg.MatchString(timeTo) {
return nil, errors.New("invalid timeTo '" + timeTo + "'")
}
if timeFrom > timeTo {
// swap
timeFrom, timeTo = timeTo, timeFrom
}
var everyMinutesInt = int(everyMinutes)
var fromHour = types.Int(timeFrom[:2])
var fromMinute = types.Int(timeFrom[2:])
var toHour = types.Int(timeTo[:2])
var toMinute = types.Int(timeTo[2:])
if fromMinute%everyMinutesInt == 0 {
result = append(result, timeFrom)
}
for {
fromMinute += everyMinutesInt
if fromMinute > 59 {
fromHour += fromMinute / 60
fromMinute = fromMinute % 60
}
if fromHour > toHour || (fromHour == toHour && fromMinute > toMinute) {
break
}
result = append(result, fmt.Sprintf("%02d%02d", fromHour, fromMinute))
}
return
}

View File

@@ -0,0 +1,298 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
)
type UpgradeFileWriter struct {
rawWriter io.Writer
written int64
}
func NewUpgradeFileWriter(rawWriter io.Writer) *UpgradeFileWriter {
return &UpgradeFileWriter{rawWriter: rawWriter}
}
func (this *UpgradeFileWriter) Write(p []byte) (n int, err error) {
n, err = this.rawWriter.Write(p)
this.written += int64(n)
return
}
func (this *UpgradeFileWriter) TotalWritten() int64 {
return this.written
}
type UpgradeManager struct {
client *http.Client
component string
newVersion string
contentLength int64
isDownloading bool
writer *UpgradeFileWriter
body io.ReadCloser
isCancelled bool
downloadURL string
}
func NewUpgradeManager(component string, downloadURL string) *UpgradeManager {
return &UpgradeManager{
component: component,
downloadURL: downloadURL,
client: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
CheckRedirect: nil,
Jar: nil,
Timeout: 30 * time.Minute,
},
}
}
func (this *UpgradeManager) Start() error {
if this.isDownloading {
return errors.New("another process is running")
}
this.isDownloading = true
defer func() {
this.client.CloseIdleConnections()
this.isDownloading = false
}()
// 检查unzip
unzipExe, _ := exec.LookPath("unzip")
if len(unzipExe) == 0 {
// TODO install unzip automatically or pack with a static 'unzip' file
return errors.New("can not find 'unzip' command")
}
// 检查cp
cpExe, _ := exec.LookPath("cp")
if len(cpExe) == 0 {
return errors.New("can not find 'cp' command")
}
// 检查新版本
var downloadURL = this.downloadURL
if len(downloadURL) == 0 {
var url = teaconst.UpdatesURL
var osName = runtime.GOOS
if Tea.IsTesting() && osName == "darwin" {
osName = "linux"
}
url = strings.ReplaceAll(url, "${os}", osName)
url = strings.ReplaceAll(url, "${arch}", runtime.GOARCH)
url = strings.ReplaceAll(url, "${version}", teaconst.Version)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return errors.New("create url request failed: " + err.Error())
}
req.Header.Set("User-Agent", "Edge-Admin/"+teaconst.Version)
resp, err := this.client.Do(req)
if err != nil {
return errors.New("read latest version failed: " + err.Error())
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
return errors.New("read latest version failed: invalid response code '" + types.String(resp.StatusCode) + "'")
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return errors.New("read latest version failed: " + err.Error())
}
var m = maps.Map{}
err = json.Unmarshal(data, &m)
if err != nil {
return errors.New("invalid response data: " + err.Error() + ", origin data: " + string(data))
}
var code = m.GetInt("code")
if code != 200 {
return errors.New(m.GetString("message"))
}
var dataMap = m.GetMap("data")
var downloadHost = dataMap.GetString("host")
var versions = dataMap.GetSlice("versions")
var downloadPath = ""
for _, component := range versions {
var componentMap = maps.NewMap(component)
if componentMap.Has("version") {
if componentMap.GetString("code") == this.component {
var version = componentMap.GetString("version")
if stringutil.VersionCompare(version, teaconst.Version) > 0 {
this.newVersion = version
downloadPath = componentMap.GetString("url")
break
}
}
}
}
if len(downloadPath) == 0 {
return errors.New("no latest version to download")
}
downloadURL = downloadHost + downloadPath
}
{
req, err := http.NewRequest(http.MethodGet, downloadURL, nil)
if err != nil {
return errors.New("create download request failed: " + err.Error())
}
req.Header.Set("User-Agent", "Edge-Admin/"+teaconst.Version)
resp, err := this.client.Do(req)
if err != nil {
return errors.New("download failed: " + downloadURL + ": " + err.Error())
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
return errors.New("download failed: " + downloadURL + ": invalid response code '" + types.String(resp.StatusCode) + "'")
}
this.contentLength = resp.ContentLength
this.body = resp.Body
// download to tmp
var tmpDir = os.TempDir()
var filename = filepath.Base(downloadURL)
var destFile = tmpDir + "/" + filename
_ = os.Remove(destFile)
fp, err := os.Create(destFile)
if err != nil {
return errors.New("create file failed: " + err.Error())
}
defer func() {
// 删除安装文件
_ = os.Remove(destFile)
}()
this.writer = NewUpgradeFileWriter(fp)
_, err = io.Copy(this.writer, resp.Body)
if err != nil {
_ = fp.Close()
if this.isCancelled {
return nil
}
return errors.New("download failed: " + err.Error())
}
_ = fp.Close()
// unzip
var unzipDir = tmpDir + "/edge-" + this.component + "-tmp"
stat, err := os.Stat(unzipDir)
if err == nil && stat.IsDir() {
err = os.RemoveAll(unzipDir)
if err != nil {
return errors.New("remove old dir '" + unzipDir + "' failed: " + err.Error())
}
}
var unzipCmd = exec.Command(unzipExe, "-q", "-o", destFile, "-d", unzipDir)
var unzipStderr = &bytes.Buffer{}
unzipCmd.Stderr = unzipStderr
err = unzipCmd.Run()
if err != nil {
return errors.New("unzip installation file failed: " + err.Error() + ": " + unzipStderr.String())
}
installationFiles, err := filepath.Glob(unzipDir + "/edge-" + this.component + "/*")
if err != nil {
return errors.New("lookup installation files failed: " + err.Error())
}
// cp to target dir
currentExe, err := os.Executable()
if err != nil {
return errors.New("reveal current executable file path failed: " + err.Error())
}
var targetDir = filepath.Dir(filepath.Dir(currentExe))
if !Tea.IsTesting() {
for _, installationFile := range installationFiles {
var cpCmd = exec.Command(cpExe, "-R", "-f", installationFile, targetDir)
var cpStderr = &bytes.Buffer{}
cpCmd.Stderr = cpStderr
err = cpCmd.Run()
if err != nil {
return errors.New("overwrite installation files failed: '" + cpCmd.String() + "': " + cpStderr.String())
}
}
}
// remove tmp
_ = os.RemoveAll(unzipDir)
}
return nil
}
func (this *UpgradeManager) IsDownloading() bool {
return this.isDownloading
}
func (this *UpgradeManager) Progress() float32 {
if this.contentLength <= 0 {
return -1
}
if this.writer == nil {
return -1
}
return float32(this.writer.TotalWritten()) / float32(this.contentLength)
}
func (this *UpgradeManager) NewVersion() string {
return this.newVersion
}
func (this *UpgradeManager) Cancel() error {
this.isCancelled = true
this.isDownloading = false
if this.body != nil {
_ = this.body.Close()
}
return nil
}

View File

@@ -0,0 +1,35 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"testing"
"time"
)
func TestNewUpgradeManager(t *testing.T) {
var manager = utils.NewUpgradeManager("admin")
var ticker = time.NewTicker(2 * time.Second)
go func() {
for range ticker.C {
if manager.IsDownloading() {
t.Logf("%.2f%%", manager.Progress()*100)
}
}
}()
/**go func() {
time.Sleep(5 * time.Second)
if manager.IsDownloading() {
t.Log("cancel downloading")
_ = manager.Cancel()
}
}()**/
err := manager.Start()
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,15 @@
package actionutils
import (
"context"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/iwind/TeaGo/maps"
)
type ActionInterface interface {
RPC() *rpc.RPCClient
AdminContext() context.Context
ViewData() maps.Map
}

View File

@@ -1,6 +1,6 @@
package actionutils
// 子菜单定义
// Menu 子菜单定义
type Menu struct {
Id string `json:"id"`
Name string `json:"name"`
@@ -11,14 +11,14 @@ type Menu struct {
CountNormalItems int `json:"countNormalItems"`
}
// 获取新对象
// NewMenu 获取新对象
func NewMenu() *Menu {
return &Menu{
Items: []*MenuItem{},
}
}
// 添加菜单项
// Add 添加菜单项
func (this *Menu) Add(name string, subName string, url string, isActive bool) *MenuItem {
item := &MenuItem{
Name: name,
@@ -36,7 +36,7 @@ func (this *Menu) Add(name string, subName string, url string, isActive bool) *M
return item
}
// 添加特殊菜单项,不计数
// AddSpecial 添加特殊菜单项,不计数
func (this *Menu) AddSpecial(name string, subName string, url string, isActive bool) *MenuItem {
item := this.Add(name, subName, url, isActive)
this.CountNormalItems--

View File

@@ -5,20 +5,20 @@ import (
"github.com/iwind/TeaGo/lists"
)
// 菜单分组
// MenuGroup 菜单分组
type MenuGroup struct {
Menus []*Menu `json:"menus"`
AlwaysMenu *Menu `json:"alwaysMenu"`
}
// 获取新菜单分组对象
// NewMenuGroup 获取新菜单分组对象
func NewMenuGroup() *MenuGroup {
return &MenuGroup{
Menus: []*Menu{},
}
}
// 查找菜单,如果找不到则自动创建
// FindMenu 查找菜单,如果找不到则自动创建
func (this *MenuGroup) FindMenu(menuId string, menuName string) *Menu {
for _, m := range this.Menus {
if m.Id == menuId {
@@ -33,7 +33,7 @@ func (this *MenuGroup) FindMenu(menuId string, menuName string) *Menu {
return menu
}
// 排序
// Sort 排序
func (this *MenuGroup) Sort() {
lists.Sort(this.Menus, func(i int, j int) bool {
menu1 := this.Menus[i]
@@ -42,7 +42,7 @@ func (this *MenuGroup) Sort() {
})
}
// 设置子菜单
// SetSubMenu 设置子菜单
func SetSubMenu(action actions.ActionWrapper, menu *MenuGroup) {
action.Object().Data["teaSubMenus"] = menu
}

View File

@@ -1,6 +1,6 @@
package actionutils
// 菜单项
// MenuItem 菜单项
type MenuItem struct {
Id string `json:"id"`
Name string `json:"name"`

View File

@@ -111,7 +111,7 @@ func (this *Page) AsHTML() string {
}
// 每页数
result = append(result, `<select class="ui dropdown" style="height:34px;padding-top:0;padding-bottom:0;margin-left:1em;color:#666" onchange="ChangePageSize(this.value)">
result = append(result, `<select class="ui dropdown" style="padding-top:0;padding-bottom:0;margin-left:1em;color:#666" onchange="ChangePageSize(this.value)">
<option value="10">[每页]</option>`+this.renderSizeOption(10)+
this.renderSizeOption(20)+
this.renderSizeOption(30)+
@@ -127,7 +127,7 @@ func (this *Page) AsHTML() string {
return `<div class="page">` + strings.Join(result, "") + `</div>`
}
// 判断是否为最后一页
// IsLastPage 判断是否为最后一页
func (this *Page) IsLastPage() bool {
return this.Current == this.Max
}

View File

@@ -4,12 +4,14 @@ import (
"context"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"net/http"
"strconv"
)
@@ -18,9 +20,11 @@ type ParentAction struct {
actions.ActionObject
rpcClient *rpc.RPCClient
ctx context.Context
}
// 可以调用自身的一个简便方法
// Parent 可以调用自身的一个简便方法
func (this *ParentAction) Parent() *ParentAction {
return this
}
@@ -45,14 +49,25 @@ func (this *ParentAction) ErrorText(err string) {
}
func (this *ParentAction) NotFound(name string, itemId int64) {
this.ErrorPage(errors.New(name + " id: '" + strconv.FormatInt(itemId, 10) + "' is not found"))
if itemId > 0 {
this.ErrorPage(errors.New(name + " id: '" + strconv.FormatInt(itemId, 10) + "' is not found"))
} else {
this.ErrorPage(errors.New(name + " is not found"))
}
}
func (this *ParentAction) NewPage(total int64, size ...int64) *Page {
if len(size) > 0 {
return NewActionPage(this, total, size[0])
}
return NewActionPage(this, total, 10)
var pageSize int64 = 10
adminConfig, err := configloaders.LoadAdminUIConfig()
if err == nil && adminConfig.DefaultPageSize > 0 {
pageSize = int64(adminConfig.DefaultPageSize)
}
return NewActionPage(this, total, pageSize)
}
func (this *ParentAction) Nav(mainMenu string, tab string, firstMenu string) {
@@ -97,7 +112,7 @@ func (this *ParentAction) CreateLogInfo(description string, args ...interface{})
this.CreateLog(oplogs.LevelInfo, description, args...)
}
// 获取RPC
// RPC 获取RPC
func (this *ParentAction) RPC() *rpc.RPCClient {
if this.rpcClient != nil {
return this.rpcClient
@@ -114,7 +129,8 @@ func (this *ParentAction) RPC() *rpc.RPCClient {
return rpcClient
}
// 获取Context
// AdminContext 获取Context
// 每个请求的context都必须是一个新的实例
func (this *ParentAction) AdminContext() context.Context {
if this.rpcClient == nil {
rpcClient, err := rpc.SharedRPC()
@@ -124,5 +140,11 @@ func (this *ParentAction) AdminContext() context.Context {
}
this.rpcClient = rpcClient
}
return this.rpcClient.Context(this.AdminId())
this.ctx = this.rpcClient.Context(this.AdminId())
return this.ctx
}
// ViewData 视图里可以使用的数据
func (this *ParentAction) ViewData() maps.Map {
return this.Data
}

View File

@@ -5,19 +5,19 @@ import (
"github.com/iwind/TeaGo/maps"
)
// Tabbar定义
// Tabbar Tabbar定义
type Tabbar struct {
items []maps.Map
}
// 获取新对象
// NewTabbar 获取新对象
func NewTabbar() *Tabbar {
return &Tabbar{
items: []maps.Map{},
}
}
// 添加菜单项
// Add 添加菜单项
func (this *Tabbar) Add(name string, subName string, url string, icon string, active bool) maps.Map {
m := maps.Map{
"name": name,
@@ -31,12 +31,12 @@ func (this *Tabbar) Add(name string, subName string, url string, icon string, ac
return m
}
// 取得所有的Items
// Items 取得所有的Items
func (this *Tabbar) Items() []maps.Map {
return this.items
}
// 设置子菜单
// SetTabbar 设置子菜单
func SetTabbar(action actions.ActionWrapper, tabbar *Tabbar) {
action.Object().Data["teaTabbar"] = tabbar.Items()
}

View File

@@ -1,39 +1,150 @@
package actionutils
import (
"encoding/json"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
rpcerrors "github.com/TeaOSLab/EdgeCommon/pkg/rpc/errors"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/gosock/pkg/gosock"
"net"
"net/http"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"time"
)
// 提示服务器错误信息
// Fail 提示服务器错误信息
func Fail(action actions.ActionWrapper, err error) {
if err != nil {
logs.Println("[" + reflect.TypeOf(action).String() + "]" + findStack(err.Error()))
}
action.Object().Fail(teaconst.ErrServer)
action.Object().Fail(teaconst.ErrServer + "" + err.Error() + "")
}
// 提示页面错误信息
// FailPage 提示页面错误信息
func FailPage(action actions.ActionWrapper, err error) {
if err != nil {
logs.Println("[" + reflect.TypeOf(action).String() + "]" + findStack(err.Error()))
if err == nil {
err = errors.New("unknown error")
}
logs.Println("[" + reflect.TypeOf(action).String() + "]" + findStack(err.Error()))
// 当前API终端地址
var apiEndpoints = []string{}
apiConfig, apiConfigErr := configs.LoadAPIConfig()
if apiConfigErr == nil && apiConfig != nil {
apiEndpoints = append(apiEndpoints, apiConfig.RPC.Endpoints...)
}
var isRPCConnError bool
err, isRPCConnError = rpcerrors.HumanError(err, apiEndpoints, Tea.ConfigFile("api.yaml"))
var apiNodeIsStarting = false
var apiNodeProgress = ""
if isRPCConnError {
// API节点是否正在启动
var sock = gosock.NewTmpSock("edge-api")
reply, err := sock.SendTimeout(&gosock.Command{
Code: "starting",
Params: nil,
}, 1*time.Second)
if err == nil && reply != nil {
var params = maps.NewMap(reply.Params)
if params.GetBool("isStarting") {
apiNodeIsStarting = true
var progressMap = params.GetMap("progress")
apiNodeProgress = progressMap.GetString("description")
}
}
}
action.Object().ResponseWriter.WriteHeader(http.StatusInternalServerError)
if len(action.Object().Request.Header.Get("X-Requested-With")) > 0 {
action.Object().WriteString(teaconst.ErrServer)
} else {
// 本地的一些错误提示
var isLocalAPI = false
if isRPCConnError {
host, _, hostErr := net.SplitHostPort(action.Object().Request.Host)
if hostErr == nil {
for _, endpoint := range apiEndpoints {
if strings.HasPrefix(endpoint, "http://"+host) || strings.HasPrefix(endpoint, "https://"+host) || strings.HasPrefix(endpoint, host) {
isLocalAPI = true
break
}
}
}
}
var issuesHTML = ""
if isLocalAPI {
// 读取本地API节点的issues
issuesData, issuesErr := os.ReadFile(Tea.Root + "/edge-api/logs/issues.log")
if issuesErr == nil {
var issueMaps = []maps.Map{}
issuesErr = json.Unmarshal(issuesData, &issueMaps)
if issuesErr == nil && len(issueMaps) > 0 {
var issueMap = issueMaps[0]
issuesHTML = "本地API节点启动错误" + issueMap.GetString("message") + ",处理建议:" + issueMap.GetString("suggestion")
}
}
}
var html = `<!DOCTYPE html>
<html>
<head>
<title>有系统错误需要处理</title>
<meta charset="UTF-8"/>
<style type="text/css">
hr { border-top: 1px #ccc solid; }
.red { color: red; }
</style>
</head>
<body>
<div style="background: #eee; border: 1px #ccc solid; padding: 10px; font-size: 12px; line-height: 1.8">
`
if apiNodeIsStarting { // API节点正在启动
html += "<div class=\"red\">API节点正在启动请耐心等待完成"
if len(apiNodeProgress) > 0 {
html += "" + apiNodeProgress
}
html += "</div>"
} else {
html += teaconst.ErrServer + `
<div>可以通过查看 <strong><em>$安装目录/logs/run.log</em></strong> 日志文件查看具体的错误提示。</div>
<hr/>
<div class="red">Error: ` + err.Error() + `</div>`
if len(issuesHTML) > 0 {
html += ` <hr/>
<div class="red">` + issuesHTML + `</div>`
}
}
action.Object().WriteString(html + `
</div>
</body>
</html>`)
}
action.Object().WriteString(teaconst.ErrServer)
}
// 判断动作的文件路径是否相当
// MatchPath 判断动作的文件路径是否相当
func MatchPath(action *actions.ActionObject, path string) bool {
return action.Request.URL.Path == path
}
// 查找父级Action
// FindParentAction 查找父级Action
func FindParentAction(actionPtr actions.ActionWrapper) *ParentAction {
parentActionValue := reflect.ValueOf(actionPtr).Elem().FieldByName("ParentAction")
if parentActionValue.IsValid() {

View File

@@ -0,0 +1,49 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accesskeys
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct {
AdminId int64
}) {
this.Data["adminId"] = params.AdminId
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
AdminId int64
Description string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("description", params.Description).
Require("请输入备注")
accessKeyIdResp, err := this.RPC().UserAccessKeyRPC().CreateUserAccessKey(this.AdminContext(), &pb.CreateUserAccessKeyRequest{
AdminId: params.AdminId,
Description: params.Description,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo("创建AccessKey %d", accessKeyIdResp.UserAccessKeyId)
this.Success()
}

View File

@@ -0,0 +1,24 @@
package accesskeys
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAction struct {
actionutils.ParentAction
}
func (this *DeleteAction) RunPost(params struct {
AccessKeyId int64
}) {
defer this.CreateLogInfo("删除AccessKey %d", params.AccessKeyId)
_, err := this.RPC().UserAccessKeyRPC().DeleteUserAccessKey(this.AdminContext(), &pb.DeleteUserAccessKeyRequest{UserAccessKeyId: params.AccessKeyId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,54 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accesskeys
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/admins/adminutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "accessKey")
}
func (this *IndexAction) RunGet(params struct {
AdminId int64
}) {
err := adminutils.InitAdmin(this.Parent(), params.AdminId)
if err != nil {
this.ErrorPage(err)
return
}
accessKeysResp, err := this.RPC().UserAccessKeyRPC().FindAllEnabledUserAccessKeys(this.AdminContext(), &pb.FindAllEnabledUserAccessKeysRequest{AdminId: params.AdminId})
if err != nil {
this.ErrorPage(err)
return
}
accessKeyMaps := []maps.Map{}
for _, accessKey := range accessKeysResp.UserAccessKeys {
var accessedTime string
if accessKey.AccessedAt > 0 {
accessedTime = timeutil.FormatTime("Y-m-d H:i:s", accessKey.AccessedAt)
}
accessKeyMaps = append(accessKeyMaps, maps.Map{
"id": accessKey.Id,
"isOn": accessKey.IsOn,
"uniqueId": accessKey.UniqueId,
"secret": accessKey.Secret,
"description": accessKey.Description,
"accessedTime": accessedTime,
})
}
this.Data["accessKeys"] = accessKeyMaps
this.Show()
}

View File

@@ -0,0 +1,28 @@
package accesskeys
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type UpdateIsOnAction struct {
actionutils.ParentAction
}
func (this *UpdateIsOnAction) RunPost(params struct {
AccessKeyId int64
IsOn bool
}) {
defer this.CreateLogInfo("设置AccessKey %d 启用状态", params.AccessKeyId)
_, err := this.RPC().UserAccessKeyRPC().UpdateUserAccessKeyIsOn(this.AdminContext(), &pb.UpdateUserAccessKeyIsOnRequest{
UserAccessKeyId: params.AccessKeyId,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -30,12 +30,22 @@ func (this *AdminAction) RunGet(params struct {
return
}
// AccessKey数量
countAccessKeyResp, err := this.RPC().UserAccessKeyRPC().CountAllEnabledUserAccessKeys(this.AdminContext(), &pb.CountAllEnabledUserAccessKeysRequest{AdminId: params.AdminId})
if err != nil {
this.ErrorPage(err)
return
}
countAccessKeys := countAccessKeyResp.Count
this.Data["admin"] = maps.Map{
"id": admin.Id,
"fullname": admin.Fullname,
"username": admin.Username,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"id": admin.Id,
"fullname": admin.Fullname,
"username": admin.Username,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"canLogin": admin.CanLogin,
"countAccessKeys": countAccessKeys,
}
// 权限

View File

@@ -0,0 +1,38 @@
package adminutils
import (
"errors"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
// InitAdmin 查找管理员基本信息
func InitAdmin(p *actionutils.ParentAction, adminId int64) error {
// 管理员信息
resp, err := p.RPC().AdminRPC().FindEnabledAdmin(p.AdminContext(), &pb.FindEnabledAdminRequest{AdminId: adminId})
if err != nil {
return err
}
if resp.Admin == nil {
return errors.New("not found admin")
}
var admin = resp.Admin
// AccessKey数量
countAccessKeysResp, err := p.RPC().UserAccessKeyRPC().CountAllEnabledUserAccessKeys(p.AdminContext(), &pb.CountAllEnabledUserAccessKeysRequest{
AdminId: adminId,
UserId: 0,
})
if err != nil {
return err
}
p.Data["admin"] = maps.Map{
"id": admin.Id,
"username": admin.Username,
"fullname": admin.Fullname,
"countAccessKeys": countAccessKeysResp.Count,
}
return nil
}

View File

@@ -31,6 +31,7 @@ func (this *CreatePopupAction) RunPost(params struct {
Pass2 string
ModuleCodes []string
IsSuper bool
CanLogin bool
// OTP
OtpOn bool
@@ -88,6 +89,7 @@ func (this *CreatePopupAction) RunPost(params struct {
Fullname: params.Fullname,
ModulesJSON: modulesJSON,
IsSuper: params.IsSuper,
CanLogin: params.CanLogin,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -42,6 +42,7 @@ func (this *IndexAction) RunGet(params struct{}) {
"fullname": admin.Fullname,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", admin.CreatedAt),
"otpLoginIsOn": admin.OtpLogin != nil && admin.OtpLogin.IsOn,
"canLogin": admin.CanLogin,
})
}
this.Data["admins"] = adminMaps

Some files were not shown because too many files have changed in this diff Show More