Compare commits

...

804 Commits

Author SHA1 Message Date
刘祥超
7e5802dcd1 优化没有网站时的提示 2024-01-21 21:17:42 +08:00
刘祥超
2b6d1c70a8 集群节点列表页增加停用/启用操作 2024-01-21 17:42:56 +08:00
刘祥超
faaef3b353 Dockerfile版本号修改为1.3.3 2024-01-21 16:58:01 +08:00
刘祥超
6ffa1d4a1d 版本号修改为1.3.3 2024-01-21 16:55:55 +08:00
刘祥超
10f0e0dcca 提交components.js 2024-01-21 16:55:31 +08:00
刘祥超
0afad2e192 增加创建本地节点命令 2024-01-21 16:55:22 +08:00
刘祥超
6a1e7266b9 优化缓存文件句柄缓存的文字提示 2024-01-21 14:31:05 +08:00
刘祥超
6ffc552bd8 WAF允许ALLOW动作增加有效范围 2024-01-20 21:23:59 +08:00
刘祥超
8d8245971a WAF策略增加显示页面动作默认设置 2024-01-20 16:17:28 +08:00
刘祥超
a2f730d57e 修复部分内置页面没有<head>标签的问题 2024-01-20 10:15:05 +08:00
刘祥超
bf282c41fd 优化创建策略规则集提示 2024-01-19 11:24:02 +08:00
刘祥超
3e3f7b2cc6 WAF动作默认设置为“显示网页(page)”,减少因错误规则导致的IP封禁 2024-01-19 11:13:01 +08:00
刘祥超
0580e063be 优化网站列表批量操作按钮,防止误操作 2024-01-19 10:56:31 +08:00
刘祥超
2924aad25e 修改版本号为1.3.2.2 2024-01-16 20:59:07 +08:00
刘祥超
39907299cd 提交components.js 2024-01-16 20:58:58 +08:00
刘祥超
77705895d5 优化添加规则界面 2024-01-16 20:42:06 +08:00
刘祥超
3fb85edf0b 提交components.js 2024-01-15 08:51:21 +08:00
刘祥超
0e7a968cac 版本号修改为1.3.2.1 2024-01-15 08:40:14 +08:00
刘祥超
195a9dc771 优化网站设置和路由设置中的左侧菜单 2024-01-14 20:33:07 +08:00
刘祥超
ca227a846a 优化代码 2024-01-13 19:31:52 +08:00
刘祥超
e1057fc76e 字符编码设置增加“强制替换”选项 2024-01-13 16:28:37 +08:00
刘祥超
b5168c3174 优化文字 2024-01-13 15:46:26 +08:00
刘祥超
4ced00de50 修改API节点时提示用户需要重启edge-api进程 2024-01-12 16:44:02 +08:00
刘祥超
750c929506 实现批量删除网站功能 2024-01-11 18:40:29 +08:00
刘祥超
8b46a5a5af 自定义页面跳转支持使用变量 2024-01-11 15:37:31 +08:00
刘祥超
10d606a101 增加带宽相关组件 2024-01-11 15:17:52 +08:00
刘祥超
45d36dadcb 修复“迁移”功能中无法远程修改API节点访问地址的问题 2024-01-09 10:39:09 +08:00
刘祥超
61484d5cb8 增加用户系统文章相关管理 2024-01-09 10:21:05 +08:00
刘祥超
b7775a99ef 优化添加缓存条件和不缓存条件交互 2023-12-28 15:50:19 +08:00
刘祥超
c45ba38ffa 修复参数匹配不区分大小写选项无法保存的问题 2023-12-28 15:31:40 +08:00
刘祥超
e97d9fb8a0 优化可执行文件程序 2023-12-26 09:09:21 +08:00
刘祥超
bb56ec9ec5 修改Dockerfile版本 2023-12-24 17:35:30 +08:00
刘祥超
6d8f659558 版本号修改为1.3.2 2023-12-24 11:14:31 +08:00
刘祥超
7e9047c8fe 优化IP名单管理 2023-12-24 11:05:38 +08:00
刘祥超
b5af560b67 IP名单中可以批量删除最多10000个IP 2023-12-24 10:53:03 +08:00
刘祥超
5622636870 优化请求脚本相关代码 2023-12-23 20:54:37 +08:00
刘祥超
372c276afc 优化英文菜单样式 2023-12-23 09:41:33 +08:00
刘祥超
250b5fdd10 取消“通用设置”菜单 2023-12-22 16:58:16 +08:00
刘祥超
d1fb0bf8b7 优化数据库节点相关文字提示 2023-12-21 17:18:04 +08:00
刘祥超
490c273d36 改进部分文字 2023-12-20 15:06:19 +08:00
刘祥超
0773dbaa28 在网站设置中显示流量相关限制状态 2023-12-19 14:56:05 +08:00
刘祥超
4d04964740 修复缓存条件简单和复杂切换时可能导致复杂条件失效的问题 2023-12-19 09:26:18 +08:00
刘祥超
537e6c9a5e 修复一处编译问题 2023-12-18 10:12:20 +08:00
刘祥超
082a6721f3 版本号修改为1.3.1.2 2023-12-18 08:51:16 +08:00
刘祥超
d515a20129 提升Go模板安全性 2023-12-14 19:57:02 +08:00
刘祥超
a66d3ef61a 更新相关库 2023-12-14 19:53:43 +08:00
刘祥超
3b05b1a933 优化修改网站域名时套餐相关检查 2023-12-13 19:02:12 +08:00
刘祥超
105479ce4b 网站所属用户没有套餐时提示用户 2023-12-13 18:53:08 +08:00
刘祥超
1a950e3aad 非商业版隐藏网站配置复制图标 2023-12-13 18:46:16 +08:00
刘祥超
79fa76e039 缓存设置中可以设置缓存主域名,用来复用多域名下的缓存 2023-12-13 18:33:48 +08:00
刘祥超
9db9a70a68 优化文字 2023-12-13 11:08:35 +08:00
刘祥超
632862c742 优化多语言相关代码 2023-12-13 10:29:50 +08:00
刘祥超
76d0783f6f 切换语言时保存语言设置 2023-12-12 22:39:42 +08:00
刘祥超
fc6a0e9813 切换语言时保存更长时间 2023-12-12 22:28:17 +08:00
刘祥超
038e289057 更新components.js 2023-12-12 22:28:04 +08:00
刘祥超
4b2a4af1e7 优化极验相关代码 2023-12-12 15:00:38 +08:00
刘祥超
0fb2555bea Update en-us.js 2023-12-12 12:11:13 +08:00
刘祥超
bd8a809065 优化英文版样式 2023-12-12 11:54:20 +08:00
刘祥超
fc6fb7dce2 部分菜单实现中英文切换 2023-12-12 11:47:41 +08:00
刘祥超
bacb94abe6 优化WebP相关提示和代码 2023-12-11 11:07:02 +08:00
刘祥超
eb04421412 WebP转换质量转移到WebP策略配置 2023-12-11 10:00:22 +08:00
刘祥超
2fbd5a785e 改进部分表单文字提示 2023-12-10 10:46:50 +08:00
刘祥超
d8ae1525be 管理系统安全设置增加“自定义客户端IP报头” 2023-12-10 10:46:35 +08:00
刘祥超
daaa165d25 优化集群设置--WAF策略选择窗口尺寸 2023-12-10 08:55:12 +08:00
刘祥超
27ea02be5e 优化WAF动作“显示网页”显示 2023-12-09 15:55:17 +08:00
刘祥超
8eac1ef307 优化文字提示 2023-12-08 16:00:48 +08:00
刘祥超
1181974ea5 WAF规则集列表中参数和操作符增加悬停提示 2023-12-08 12:01:36 +08:00
刘祥超
5f57fa0470 WAF规则集列表中参数显示中文 2023-12-08 11:20:32 +08:00
刘祥超
c0ad74ab7b WAF操作符对应的对比值可以不填写 2023-12-07 20:24:26 +08:00
刘祥超
c27905e1cc 优化WAF多行对比值的文字提示 2023-12-07 11:44:37 +08:00
刘祥超
cadb65a85f 业务域名CNAME检查DNS主机地址增加Google公共DNS 2023-12-06 09:42:15 +08:00
刘祥超
b3c152685a 优化检查域名解析文字提示 2023-12-05 11:33:32 +08:00
刘祥超
16ce0726f8 将部分MB、GB...改成MiB、GiB... 2023-12-03 11:41:04 +08:00
刘祥超
5be306e087 将部分MB、GB...改成MiB、GiB... 2023-12-03 11:30:02 +08:00
刘祥超
0d21fc27ab 增加“极验-行为验”验证码集成支持 2023-11-29 16:57:58 +08:00
刘祥超
7deaa678bc 创建集群时默认生成子域名 2023-11-27 11:28:01 +08:00
刘祥超
0d230a9237 <domain-box>组件自动分割单个域名 2023-11-27 10:31:41 +08:00
刘祥超
8453d754f1 添加域名时自动分割用逗号、顿号连接的域名列表 2023-11-27 10:20:54 +08:00
刘祥超
05f77f7a0d 修复go.mod 2023-11-24 10:21:07 +08:00
刘祥超
4334b6f148 修复若干测试用例 2023-11-24 09:13:18 +08:00
刘祥超
72955759d7 更新go.sum 2023-11-24 08:53:40 +08:00
刘祥超
18dc52996d 更新Dockerfile中版本 2023-11-23 17:40:28 +08:00
刘祥超
0b16d09330 版本号修改为1.3.1 2023-11-23 17:26:24 +08:00
刘祥超
027d81a8cd 优化SSH认证sudo设置 2023-11-23 16:12:41 +08:00
刘祥超
d833158784 DNSPod改名为腾讯云DNSPod/DNSPod 支持腾讯云API密钥 2023-11-23 15:14:34 +08:00
刘祥超
75024c9364 优化X-Cache报头设置文字提示 2023-11-22 19:02:18 +08:00
刘祥超
1dd5db3c42 优化第三方域名解析azure dns提示文字 2023-11-22 10:30:29 +08:00
刘祥超
faba80e315 GRPC增加Keepalive参数 2023-11-20 09:56:35 +08:00
刘祥超
1b7dc8eb96 优化下载升级新版本文字提示 2023-11-20 08:25:34 +08:00
刘祥超
7c0af45dd0 更新Dockerfile中版本号 2023-11-19 17:34:15 +08:00
刘祥超
daea270132 更新components.js 2023-11-19 17:33:56 +08:00
刘祥超
a42e3ac4a6 优化CAPTCHA最大失败次数文字提示 2023-11-19 15:43:37 +08:00
刘祥超
c94c526b43 提交components.js 2023-11-19 09:09:50 +08:00
刘祥超
e531787773 集群WAF策略处于非防御模式时在地区封禁页面提示用户 2023-11-18 15:25:14 +08:00
刘祥超
0eda84a99a 修复修改单个WAF动作时名称变为阻止的问题 2023-11-18 08:51:50 +08:00
刘祥超
89b791448a 优化DNS.LA域名解析提示文字 2023-11-17 18:13:54 +08:00
刘祥超
2239328b2e 优化GoDaddy域名解析提示文字 2023-11-17 18:01:50 +08:00
刘祥超
e440a46080 源站支持404内容自动重试其他源站 2023-11-15 19:05:04 +08:00
刘祥超
4a0fec6e35 优化WAF设置中人机识别验证方式 2023-11-15 17:19:29 +08:00
刘祥超
a9b46987bc 更新components.js 2023-11-15 15:13:09 +08:00
刘祥超
6771afead5 WAF人机识别实现点击验证和滑动解锁验证/单个网站可以设置默认的人机识别方式 2023-11-15 15:12:58 +08:00
刘祥超
553fca2981 版本号修改为1.3.0 2023-11-14 14:47:52 +08:00
刘祥超
8ef1e36b8b 优化界面 2023-11-13 17:47:24 +08:00
刘祥超
9e6c49b67c 调教components.js 2023-11-13 17:00:22 +08:00
刘祥超
170632ddbc 增加用户列表菜单 2023-11-13 11:53:30 +08:00
刘祥超
4a74488cac 自定义页面增加例外URL和限制URL设置 2023-11-13 10:45:13 +08:00
刘祥超
5fd6a08988 第三方域名解析支持微软Azure DNS(商业版) 2023-11-12 20:31:16 +08:00
刘祥超
c86b91ead6 自定义页面增加“跳转URL”功能 2023-11-10 16:36:03 +08:00
刘祥超
aeebb763c1 优化自定义页面组件界面 2023-11-10 14:59:53 +08:00
刘祥超
b05630e3e0 优化集群设置 -- 网站设置界面 2023-11-10 11:46:02 +08:00
刘祥超
51905a0ee3 URL跳转中增加例外域名和仅限域名 2023-11-10 11:05:05 +08:00
刘祥超
869ebd59d1 优化Ln节点域名解析选项提示 2023-11-09 17:02:15 +08:00
刘祥超
8a6aedca16 集群设置--DNS设置中增加”解析记录“页面 2023-11-09 16:24:52 +08:00
刘祥超
cf98c3453a 域名解析支持Amazon Route 53(商业版) 2023-11-08 21:17:32 +08:00
刘祥超
1b6f234f39 第三方域名解析增加火山引擎TrafficRoute 2023-11-06 16:21:19 +08:00
刘祥超
26c7f0f566 优化多行消息显示 2023-11-03 16:39:20 +08:00
刘祥超
617ad9b16e 申请ACME证书时防止重复提交 2023-11-03 16:26:20 +08:00
刘祥超
04a6792dbf 删除不需要的文件 2023-11-03 11:00:26 +08:00
刘祥超
f2bf8f2dbf 删除不需要的文件 2023-11-03 10:48:34 +08:00
刘祥超
3aba0abd24 优化硬盘不足提示算法 2023-11-03 09:06:33 +08:00
刘祥超
b56ccc7cef 优化数据看板 2023-10-30 16:44:01 +08:00
刘祥超
aeebf55e60 优化数据看板 2023-10-30 11:33:00 +08:00
刘祥超
9a9e0d25f3 优化数据看板图表排列 2023-10-30 11:30:51 +08:00
刘祥超
6b3033e240 删除不需要的文件 2023-10-27 09:12:19 +08:00
刘祥超
2859a59340 优化界面 2023-10-26 15:13:35 +08:00
刘祥超
45367dd579 将看板中的“服务”文字改为“网站” 2023-10-26 11:35:31 +08:00
刘祥超
fee3fd743f MySQL安装程序支持8.2 2023-10-26 08:33:35 +08:00
刘祥超
ba4cfea47c 优化文字提示 2023-10-25 15:22:13 +08:00
刘祥超
5c11f07f8e 优化域名解析文字提示 2023-10-17 15:53:58 +08:00
刘祥超
ffac080611 将版本号修改为1.2.11 2023-10-17 13:50:03 +08:00
刘祥超
f7a1e60735 更新架构图 2023-10-15 20:32:57 +08:00
刘祥超
f22651821c 更新架构图 2023-10-15 20:19:59 +08:00
刘祥超
b2a5a9ac9b 更新架构图片 2023-10-15 20:13:52 +08:00
刘祥超
245af2bd77 修改Dockfile中的版本号为1.2.10 2023-10-15 14:42:04 +08:00
刘祥超
9dd2b1d35c 删除不需要的代码 2023-10-15 14:34:37 +08:00
刘祥超
bdbfd3e055 提交components.js 2023-10-15 14:30:27 +08:00
刘祥超
1518c03d2b 将版本号修改为1.2.10 2023-10-15 13:34:44 +08:00
刘祥超
45920d4df2 WAF记录IP动作中IP名单允许留空 2023-10-15 09:34:07 +08:00
刘祥超
1c106ac6eb 优化消息通知相关代码 2023-10-14 17:15:45 +08:00
刘祥超
9f4a32b4ac macOS上检测磁盘是否不足时忽略/Developer/相关分区 2023-10-13 09:22:45 +08:00
刘祥超
2565b10491 优化集群设置DNS TTL设置文字提示 2023-10-12 10:29:48 +08:00
刘祥超
34d632a401 删除看板中不需要的版本判断 2023-10-11 17:52:02 +08:00
刘祥超
a9df27c0e1 删除不需要的文件 2023-10-11 17:49:59 +08:00
刘祥超
313011a168 支持批量复制WAF设置 2023-10-09 19:52:39 +08:00
刘祥超
5c595aad83 提交components.js 2023-10-09 17:30:23 +08:00
刘祥超
2c67eac63c 升级时如果unzip命令不存在,则使用自定义解压程序 2023-10-09 17:30:10 +08:00
刘祥超
d20c20cb33 申请证书任务列表区分管理员和用户 2023-10-09 16:18:27 +08:00
刘祥超
fa76f032be 证书列表区分管理员和用户证书 2023-10-09 15:53:02 +08:00
刘祥超
3f74910640 访问日志列表搜索增加请求来源查询语法:referer:example.com 2023-10-08 17:52:48 +08:00
刘祥超
22bd2a57e9 WAF规则对比值长度限制为4096个字符 2023-10-08 16:14:50 +08:00
刘祥超
e45a1d0367 集群设置中增加“自动调节系统参数”选项 2023-10-08 15:08:11 +08:00
刘祥超
ee7acdc6a0 优化<url-pattern-box>组件对输入问号的提示 2023-09-27 15:24:05 +08:00
刘祥超
32264f2700 优化重写规则中目标URL选项说明 2023-09-25 18:02:35 +08:00
刘祥超
249ea32973 网站访问日志未开启时,在访问日志列表显示提醒文字 2023-09-22 16:26:15 +08:00
刘祥超
9c31b69a58 优化“集群设置--网站设置”页面 2023-09-18 17:58:16 +08:00
刘祥超
6e985cf6cc 将全局设置的TCP相关设置移到“集群设置--网站设置”中 2023-09-18 16:55:40 +08:00
刘祥超
0f35b45704 将全局的通用设置--域名审核设置移到“集群设置--网站设置”中 2023-09-18 16:07:57 +08:00
刘祥超
ba9245e724 优化集群设置--网站设置界面 2023-09-18 10:43:56 +08:00
刘祥超
6e806f2822 优化缓存磁盘用量选项提示文字 2023-09-18 10:35:22 +08:00
刘祥超
7c6842a859 修改Dockfile的版本号 2023-09-18 07:40:17 +08:00
刘祥超
39f9fa0e6b 访客IP设置中支持多个请求报头 2023-09-17 19:14:34 +08:00
刘祥超
0c8ae8960d 优化节点升级提示文字 2023-09-17 09:54:14 +08:00
刘祥超
ffd6a20829 优化界面文字和细节 2023-09-14 09:18:36 +08:00
刘祥超
2ee3e2782c 版本号修改为1.2.9 2023-09-14 09:01:40 +08:00
刘祥超
61324c28c6 在WAF规则集动作中优化已删除IP名单提示 2023-09-13 17:25:03 +08:00
刘祥超
e9f70ecb90 增加删除IP名单任务 2023-09-13 17:14:33 +08:00
刘祥超
cc3b802575 优化文字提示 2023-09-13 11:52:49 +08:00
刘祥超
9b780ff4a3 提交components.js 2023-09-12 15:02:00 +08:00
刘祥超
deb1a33e78 WebP增加图片像素限制提示 2023-09-11 15:48:02 +08:00
刘祥超
5021350aa0 WAF策略中验证码动作页面模板中使用<form></form>包裹${body}时提示警告 2023-09-10 18:05:38 +08:00
刘祥超
41b3dab135 删除不需要的文件 2023-09-08 19:36:48 +08:00
刘祥超
ecf94170c8 优化访客IP地址设置 2023-09-07 18:01:52 +08:00
刘祥超
56acdf3ce3 集群设置 -- 网站设置-- 增加“处理未绑定域名方式”选项 2023-09-07 15:15:35 +08:00
刘祥超
5d6d970d49 优化<digit-input>组件 2023-09-07 11:46:37 +08:00
刘祥超
b501cf3c88 重新实现套餐相关功能 2023-09-06 16:31:05 +08:00
刘祥超
deac2baa18 优化源站选择证书提示 2023-09-02 17:07:01 +08:00
刘祥超
b1e3f54055 优化mysql安装程序 2023-09-01 11:57:28 +08:00
刘祥超
8ebf79ffbe 优化文字提示 2023-08-27 21:37:49 +08:00
刘祥超
8d7307cddf WebP支持的默认格式中去除image/gif 2023-08-25 18:04:38 +08:00
刘祥超
c547837ae3 优化节点DNS线路选择组件 2023-08-25 17:30:21 +08:00
刘祥超
b408996e72 修复单个节点同属多个集群时DNS线路设置自动复制的问题 2023-08-25 17:28:07 +08:00
刘祥超
6b5866524d 有消息提示时页面标题增加点符号提示 2023-08-25 16:08:55 +08:00
刘祥超
4994dfb488 网站设置增加是否支持${serverAddr}选项 2023-08-25 15:31:08 +08:00
刘祥超
5d5040651e 优化几个内置的页面版本,增加连接信息,方便诊断 2023-08-25 15:03:31 +08:00
刘祥超
5252188a68 优化IP列表中区域显示 2023-08-24 12:33:06 +08:00
刘祥超
3f664882d5 使用查询本地IP库代替API查询IP信息 2023-08-24 12:22:16 +08:00
刘祥超
15f2a2d517 修复 安全设置 -- 允许访问的国家和地区 中不能使用中国特殊区域的问题 2023-08-24 11:50:21 +08:00
刘祥超
6637b0fb8f 删除不需要的文件 2023-08-24 11:01:30 +08:00
刘祥超
a06eeb9129 优化文字提示 2023-08-24 11:01:06 +08:00
刘祥超
a24fce2c22 反向代理增加是否重试50X选项,默认为启用 2023-08-20 15:49:09 +08:00
刘祥超
ddbdb64fc4 优化缓存条件界面文字 2023-08-20 11:16:58 +08:00
刘祥超
38c6d545ec 添加域名时移除多余的端口号 2023-08-20 10:27:55 +08:00
刘祥超
fe880abbb0 优化添加域名表单提示 2023-08-20 10:22:35 +08:00
刘祥超
081ed76c39 优化api_admin.yaml生成 2023-08-15 10:34:20 +08:00
刘祥超
613a00686f 修复Dockfile一处注释错误 2023-08-14 14:29:52 +08:00
刘祥超
11739ee671 修改Dockfile中的版本号为1.2.8 2023-08-14 12:21:53 +08:00
刘祥超
d06951e3b5 提交components.js 2023-08-14 12:21:42 +08:00
刘祥超
63bbacd4d8 版本号修改为1.2.8 2023-08-14 12:21:34 +08:00
刘祥超
6ba130b62c 自动生成新的配置文件(api_admin.yaml) 2023-08-14 12:21:00 +08:00
刘祥超
5529d644ac 优化缓存条件;默认缓存有效期从2个小时改为1天 2023-08-14 10:42:36 +08:00
刘祥超
7e5e6fb5b4 请求条件中的“URL前缀”改为“URL目录前缀” 2023-08-14 10:17:06 +08:00
刘祥超
f5fb1911f7 缓存条件中的“URL前缀”改为“URL目录前缀” 2023-08-14 10:02:01 +08:00
刘祥超
e26e129a63 缓存条件中的“条件类型”改为“缓存对象” 2023-08-14 09:59:55 +08:00
刘祥超
280cedad8d Dockerfile指定平台为amd64 2023-08-14 09:25:03 +08:00
刘祥超
7c3d5aa69c 修改docker版本号为1.2.7 2023-08-14 09:09:02 +08:00
刘祥超
7224ba1123 提交components.js 2023-08-13 20:04:51 +08:00
刘祥超
0f7d5057fd 优化缓存策略--“清理“功能 2023-08-13 19:22:48 +08:00
刘祥超
43f2804ced WAF增加通配符匹配/不匹配操作符 2023-08-13 10:38:14 +08:00
刘祥超
5bb83555a4 优化WAF添加规则表单 2023-08-13 10:00:41 +08:00
刘祥超
af4d14364c 修改区域监控的api.yaml到api_reporter.yaml 2023-08-12 19:12:46 +08:00
刘祥超
105e717eb2 将cluster.yaml修改为api_cluster.yaml 2023-08-12 18:52:32 +08:00
刘祥超
697ec6f6e3 优化API配置格式化 2023-08-12 18:16:17 +08:00
刘祥超
e07c99f34d 修复安装界面中安装按钮被部分遮挡的问题 2023-08-12 18:09:25 +08:00
刘祥超
c047bd5896 安装界面中增加MySQL版本号需求说明 2023-08-12 17:59:32 +08:00
刘祥超
d97ac01d79 提交components.js 2023-08-12 17:58:09 +08:00
刘祥超
df19a04ad1 将api.yaml修改为api_admin.yaml 2023-08-12 17:58:00 +08:00
刘祥超
cbce8a9934 优化多个节点安装界面 2023-08-12 15:43:03 +08:00
刘祥超
7739a84828 将节点的api.yaml改为api_node.yaml,并简化配置内容 2023-08-12 15:29:28 +08:00
刘祥超
96f711b43f 优化错误处理相关代码 2023-08-11 16:41:43 +08:00
刘祥超
96b3ae5e7d 安装时API节点端口输入框增加新手提示 2023-08-11 15:36:40 +08:00
刘祥超
d0508e257a 提交components.js 2023-08-11 08:20:10 +08:00
刘祥超
ec8548f133 临时关闭页面内容默认类型从url改为html 2023-08-10 14:07:46 +08:00
刘祥超
f217b4190b 静态分发增加例外URL、限制URL、排除隐藏文件等选项 2023-08-10 11:26:53 +08:00
刘祥超
9a3b9ee8f8 访问日志中的“[服务]”改为“[网站]” 2023-08-10 10:34:48 +08:00
刘祥超
1c299c767b WAF策略可以自定义默认的区域/省份封禁提示 2023-08-10 10:30:38 +08:00
刘祥超
03b46f4c24 WAF策略中的地区/省份封禁也支持自定义提示HTML 2023-08-10 09:52:01 +08:00
刘祥超
0f40b6438a 讲默认的最多检查内容尺寸从1MB改为512K 2023-08-10 09:21:08 +08:00
刘祥超
4549d93e0d 改进文字 2023-08-10 09:12:25 +08:00
刘祥超
844cec1011 节点安装界面显示SSH地址,方便用户校对 2023-08-09 15:41:07 +08:00
刘祥超
0464c6ce43 将版本号修改为1.2.7 2023-08-09 14:24:09 +08:00
刘祥超
0cdaa9bb70 提交components.js 2023-08-09 14:23:57 +08:00
刘祥超
dade2eb4ff Update .golangci.yaml 2023-08-09 08:11:42 +08:00
刘祥超
42e1cd560e 添加golangci-lint配置 2023-08-08 18:32:58 +08:00
刘祥超
5db14ec84a 优化代码 2023-08-08 14:17:16 +08:00
刘祥超
22aca2e80c 集群设置 -- 缓存策略 可以直接点击修改 2023-08-07 17:39:10 +08:00
刘祥超
4f105f9327 优化缓存条件输入框 2023-08-07 17:16:55 +08:00
刘祥超
e2a218dd63 可以在缓存设置中搜索缓存条件 2023-08-07 17:08:38 +08:00
刘祥超
f9c0226fa0 缓存条件默认最大值设置从32MB改为128MB 2023-08-07 11:32:59 +08:00
刘祥超
fcccd69cee 如果用户设置的可缓存最大尺寸超出缓存策略设置,则提示用户 2023-08-07 11:32:36 +08:00
刘祥超
cc60c827da 缓存策略增加“缓存磁盘最小空余空间”选项 2023-08-06 18:09:01 +08:00
刘祥超
3c44e14386 缓存策略增加预热超时时间设置(默认20分钟) 2023-08-06 17:06:28 +08:00
刘祥超
7f765f4267 当网站所在集群没有指定根域名时,增加提示和设置链接 2023-08-06 11:22:52 +08:00
刘祥超
2262e6f3ca 优化网站设置DNS菜单位置 2023-08-06 11:22:02 +08:00
刘祥超
4349d165ac 优化文字 2023-08-06 10:27:55 +08:00
刘祥超
9d396af447 优化文字提示 2023-08-02 19:22:07 +08:00
刘祥超
a665457014 WAF策略增加“最多检查内容尺寸“选项 2023-08-02 16:58:45 +08:00
刘祥超
92ddd04b07 节点详情中显示磁盘预估写入速度 2023-08-02 14:48:23 +08:00
刘祥超
b5f93d6fbc 修复系统服务相关代码可能不执行的问题 2023-08-01 16:18:04 +08:00
刘祥超
374b75b9f1 启动时自动创建相关软链接 2023-08-01 10:46:56 +08:00
刘祥超
47b7d283a3 缓存条件增加“强制Range回源选项” 2023-07-31 17:31:42 +08:00
刘祥超
03a3b8b38f 将“区间”改为“分片” 2023-07-31 17:11:42 +08:00
刘祥超
d2a27f16d7 将“分片”改为“分段”,将“分区”改为“分片” 2023-07-31 16:59:12 +08:00
刘祥超
b9837f526b 缓存条件增加是否允许异步读取源站选项 2023-07-31 15:59:19 +08:00
刘祥超
2393f3a701 修复自定义页面可能无法保存的问题 2023-07-31 09:38:28 +08:00
刘祥超
b910339e6e Update Dockerfile 2023-07-28 09:26:27 +08:00
刘祥超
438445dab8 版本号更改为1.2.6 2023-07-28 09:26:05 +08:00
刘祥超
dc7e62c388 修复分组设置无法打开的问题 2023-07-28 09:25:04 +08:00
刘祥超
3ed8f9ca55 可以修改访问未绑定域名时的提示页面的状态码 2023-07-27 11:23:08 +08:00
刘祥超
0282b5e75f Update Dockerfile 2023-07-27 08:36:27 +08:00
刘祥超
f5f8218940 Update Dockerfile 2023-07-26 15:52:58 +08:00
刘祥超
a634046757 版本号更改为1.2.5 2023-07-26 15:30:19 +08:00
刘祥超
6a4d86e084 将版本号改为1.2.4 2023-07-26 10:15:40 +08:00
刘祥超
9f9b41c63d 更新Dockerfile中版本号 2023-07-26 10:15:32 +08:00
刘祥超
d813f6515b Update Dockerfile 2023-07-25 13:41:24 +08:00
刘祥超
730a445ef6 版本号修改为1.2.3 2023-07-25 13:16:31 +08:00
刘祥超
938947548b 优化节点同步任务界面 2023-07-25 10:21:50 +08:00
刘祥超
2d5085e652 删除TOA功能 2023-07-24 09:51:37 +08:00
刘祥超
e665e299f2 优化缓存策略相关界面 2023-07-20 17:06:45 +08:00
刘祥超
30b9c5eda5 默认自动检查版本更新 2023-07-20 09:38:09 +08:00
刘祥超
fbf29e774a 优化文字提示 2023-07-19 19:40:11 +08:00
刘祥超
5429971553 优化文字提示 2023-07-19 15:14:33 +08:00
刘祥超
df81bde6fd 优化安装界面 2023-07-19 14:22:48 +08:00
刘祥超
cb8b56ceb8 自动安装的mysql版本从8.0改为8.1 2023-07-19 11:18:25 +08:00
刘祥超
1886c9954b Update Dockerfile 2023-07-18 14:35:46 +08:00
刘祥超
c942503351 版本号更改为1.2.2 2023-07-18 14:33:22 +08:00
刘祥超
806fc42379 优化静态分发组件 2023-07-18 14:33:09 +08:00
刘祥超
06a49f0272 优化静态分发组件 2023-07-18 14:32:32 +08:00
刘祥超
a99bcdc437 路由规则找不到的时候提示用户 2023-07-17 15:30:19 +08:00
刘祥超
2d4378423b 优化静态分发组件 2023-07-17 11:54:41 +08:00
刘祥超
6aaf620f18 Update Dockerfile 2023-07-17 09:43:46 +08:00
刘祥超
8e38cb2149 修复无法读取鉴权方法列表的问题 2023-07-17 09:33:19 +08:00
刘祥超
49ea05c890 commit components.js 2023-07-16 19:11:32 +08:00
刘祥超
54b479fb3a 访问鉴权设置帮助中的链接区分管理员和用户 2023-07-16 15:12:55 +08:00
刘祥超
182fbd3e23 WAF-区域封禁增加提示内容设置 2023-07-14 11:00:50 +08:00
刘祥超
6819e33510 修改文字 2023-07-13 19:55:04 +08:00
刘祥超
d547c657a0 集群设置--网站设置增加“服务器旗标”设置 2023-07-12 17:39:04 +08:00
刘祥超
62172db59b 缓存条件扩展名输入框允许输入多个扩展名 2023-07-12 17:13:27 +08:00
刘祥超
fc507b69ab 修改示例域名为example.com 2023-07-12 14:43:52 +08:00
刘祥超
cfde05f5af 优化代码 2023-07-12 14:32:47 +08:00
刘祥超
15618ad03b 自动跳转到HTTPS中域名输入使用新组件 2023-07-12 09:10:52 +08:00
刘祥超
d54fec069c 更新sum和菜单 2023-07-11 19:43:41 +08:00
刘祥超
f3cbb9b9d1 优化部分组件界面 2023-07-10 17:42:00 +08:00
刘祥超
01b510f7f8 提交components.js 2023-07-09 21:27:17 +08:00
刘祥超
2c1463e071 版本号改为1.2.1 2023-07-09 17:37:57 +08:00
刘祥超
9f90ab1b6b 证书列表完善提示 2023-07-09 09:49:24 +08:00
刘祥超
ca5bf930d0 优化文字提示 2023-07-08 20:00:50 +08:00
刘祥超
7eb0ab342a 缓存策略移除“容纳Key数量”选项 2023-07-08 18:50:29 +08:00
刘祥超
f400916351 优化网站列表样式 2023-07-07 18:57:14 +08:00
刘祥超
858cf49966 网站列表增加QPS和攻击QPS信息 2023-07-07 18:52:00 +08:00
刘祥超
a5e97fc425 改进WAF策略的区域封禁 2023-07-07 15:28:29 +08:00
刘祥超
981d1c626b 优化自定义页面设置,页面URL不再支持填写本地文件 2023-07-07 11:48:36 +08:00
刘祥超
ca2b1a6612 优化WAF区域/省份封禁:增加仅允许、增加中国香港澳台、内地等特殊区域 2023-07-07 09:53:00 +08:00
刘祥超
fc749b6ef8 修改某些消息代号 2023-07-06 11:12:49 +08:00
刘祥超
7c4e5b1a57 优化网站设置菜单顺序 2023-07-05 16:51:00 +08:00
刘祥超
4e0e5e955c 优化硬盘空间不足的提示 2023-07-05 16:42:32 +08:00
刘祥超
a07568f412 限制自定义页面最大长度不能超过32K 2023-07-05 16:12:01 +08:00
刘祥超
092735680e 当集群设置不允许记录访问日志时,在网站的访问日志查看页面提醒 2023-07-05 15:55:47 +08:00
刘祥超
5d43125284 “集群设置 -- 网站设置”增加“允许记录访问日志”选项 2023-07-05 15:29:17 +08:00
刘祥超
05d51a5447 优化源站地址输入框 2023-07-05 09:54:21 +08:00
刘祥超
dd6b8b7157 优化HTTP报头界面 2023-07-03 15:21:46 +08:00
刘祥超
3218760f84 将部分“Header”文字改为“报头” 2023-07-03 11:57:07 +08:00
刘祥超
3fa2cdfe44 增加清空节点同步任务、清空DNS同步任务功能 2023-07-02 17:29:00 +08:00
刘祥超
60eef31490 出站规则集中WAF跳转到下一个规则分组可以选择出站规则分组 2023-07-02 16:24:01 +08:00
刘祥超
3e83e89c2b 迁移后确认后,不再自动更新管理系统API信息 2023-07-02 09:05:59 +08:00
刘祥超
949fd9092d 增加部分数据清理周期设置 2023-07-01 17:57:49 +08:00
刘祥超
4276f3436d 优化迁移后确认表单的文字提示 2023-07-01 15:09:19 +08:00
刘祥超
4735aa12a9 优化文字提示 2023-07-01 10:51:52 +08:00
刘祥超
5e38b1fbca 部分中文转换为多语言代号 2023-06-30 18:08:30 +08:00
刘祥超
65555e1fe3 部分中文转换为多语言代号 2023-06-28 19:07:42 +08:00
刘祥超
a5e53df998 部分中文转换为多语言代号 2023-06-28 16:18:52 +08:00
刘祥超
70452428ab 添加多语言最基础代码 2023-06-28 09:14:07 +08:00
刘祥超
a431c57e25 优化界面 2023-06-25 14:58:25 +08:00
刘祥超
a575afdb05 优化集群设置菜单显示 2023-06-24 17:22:08 +08:00
刘祥超
40d6c7f87b 源站支持HTTP/2 2023-06-23 11:43:36 +08:00
刘祥超
8feae0a80f 优化文字提示 2023-06-21 14:34:27 +08:00
刘祥超
0a5b91f0cf 优化部分文字 2023-06-21 11:51:25 +08:00
刘祥超
08aae4df39 Update Dockerfile 2023-06-19 09:33:16 +08:00
刘祥超
0ff8aecbea commit components.js 2023-06-18 19:51:47 +08:00
刘祥超
e6cae84764 优化文字提示 2023-06-18 16:19:16 +08:00
刘祥超
2d2d2931f1 commit components.js 2023-06-17 21:53:08 +08:00
刘祥超
69ad5e7d1f 安装mysql时预先安装 numactl-libs 2023-06-17 21:53:00 +08:00
刘祥超
97abc6aeb9 缓存条件类型增加“URL通配符” 2023-06-16 11:34:39 +08:00
刘祥超
8cf37dd37c 优化文字提示 2023-06-16 09:54:51 +08:00
刘祥超
e11d091ac5 优化错误提示 2023-06-16 08:17:16 +08:00
刘祥超
e952dfcedd 提交components.js 2023-06-15 15:25:01 +08:00
刘祥超
2565d5cab2 缓存条件增加"强制返回区间内容"选项 2023-06-15 15:15:11 +08:00
刘祥超
e4e66c74bc 更新Components.js 2023-06-13 20:52:46 +08:00
刘祥超
6607c41823 实现自动下载升级版本 2023-06-13 20:52:37 +08:00
刘祥超
43570f20b5 优化OSS源站相关代码 2023-06-13 15:40:40 +08:00
刘祥超
9ea7a93206 优化左侧主菜单显示 2023-06-12 19:50:53 +08:00
刘祥超
dec4922fe5 优化左侧主菜单显示 2023-06-12 19:46:37 +08:00
刘祥超
ef1f95b347 IP列表可以搜索单个IP 2023-06-12 18:29:11 +08:00
刘祥超
7317312f68 版本号改为1.2.0 2023-06-12 14:42:48 +08:00
刘祥超
2965e8df65 commit components.js 2023-06-12 11:16:56 +08:00
刘祥超
4fdcc7fae9 优化缓存条件添加表单 2023-06-10 17:58:03 +08:00
刘祥超
6be8f7539d 缓存条件类型支持“全站” 2023-06-10 17:35:45 +08:00
刘祥超
a6de65fe39 迁移后API地址确认页面提示更详细的API节点配置错误 2023-06-10 16:31:20 +08:00
刘祥超
fc5dae5a8e 优化源站列表界面 2023-06-09 17:46:15 +08:00
刘祥超
db69454d32 压缩utils.js 2023-06-09 17:29:14 +08:00
刘祥超
c824335b0e 增加备用音频 2023-06-09 17:28:49 +08:00
刘祥超
1f1ce11078 优化OSS相关代码 2023-06-08 17:50:22 +08:00
刘祥超
0ec1571d37 初步实现对象存储源站 2023-06-07 17:24:56 +08:00
刘祥超
407e8b52a5 允许在集群设置 -- “网站设置” 中设置节点IP访问显示的内容 2023-06-05 19:26:55 +08:00
刘祥超
1be13a151d 网站全局设置增加“强制Ln请求“选项 2023-06-05 17:05:42 +08:00
刘祥超
4d9d417d7a 增加<dns-resolvers-config-box>组件 2023-06-05 12:33:30 +08:00
刘祥超
aaff8f0c4a 将API设置URL从/api改为/settings/api 2023-06-05 09:41:52 +08:00
刘祥超
8c064daf3f “服务分组”改为“网站分组” 2023-06-04 10:30:57 +08:00
刘祥超
b0b3dae147 增加并发任务函数 2023-06-03 11:13:05 +08:00
刘祥超
2ea2be43d3 更新components.js 2023-06-03 09:09:18 +08:00
刘祥超
81f7364e93 改进文字提示 2023-06-02 16:33:24 +08:00
刘祥超
c530869530 优化代码 2023-06-01 18:07:46 +08:00
刘祥超
586ccd90ec 初步实现HTTP3 2023-06-01 17:44:39 +08:00
刘祥超
ece237c49f 增加不能登录的敏感区域 2023-06-01 15:56:45 +08:00
刘祥超
11da5ca98c 线图坐标增加xColorFunc参数 2023-05-29 13:56:32 +08:00
刘祥超
276a68bda6 Update Dockerfile 2023-05-29 09:08:59 +08:00
刘祥超
fba89d197a 优化交互 2023-05-28 20:24:58 +08:00
刘祥超
b3259e2489 commit components.js 2023-05-28 19:22:05 +08:00
刘祥超
880704dda0 优化WAF“跳转‘动作显示 2023-05-28 17:22:20 +08:00
刘祥超
d5e92e9c09 WAF增加“跳转”动作 2023-05-28 17:11:45 +08:00
刘祥超
df2f5692bd 版本号改为1.1.0 2023-05-28 16:06:05 +08:00
刘祥超
dacc99b8b6 优化文字 2023-05-28 15:17:33 +08:00
刘祥超
0e8ade3b61 手动执行健康检查时提示用户当前集群尚未部署网站 2023-05-28 15:06:53 +08:00
刘祥超
fe2d8f6261 优化界面 2023-05-28 14:41:38 +08:00
刘祥超
927381039b 优化界面 2023-05-28 11:31:59 +08:00
刘祥超
6b95947acb 集群列表页也增加“创建节点”链接 2023-05-27 19:55:06 +08:00
刘祥超
b05e53ce58 登录界面屏蔽个别敏感区域 2023-05-27 18:12:40 +08:00
刘祥超
a2546582f2 优化界面 2023-05-27 17:11:18 +08:00
刘祥超
a023b3401e 优化界面 2023-05-27 17:05:17 +08:00
刘祥超
07142e9872 删除EdgePlus 2023-05-27 16:46:30 +08:00
刘祥超
f5fea6c34d 创建节点表单显示剩余配额 2023-05-27 15:33:08 +08:00
刘祥超
5eb0fb3422 正则表达式填写多行时提示用户需要转换成竖杠(|)符号 2023-05-27 14:44:39 +08:00
刘祥超
fa915e7c35 部分“服务”文字改为“网站” 2023-05-26 11:55:21 +08:00
刘祥超
2b7905d31c 优化文字和界面 2023-05-26 10:52:45 +08:00
刘祥超
9cbbf37add 优化界面 2023-05-26 10:31:03 +08:00
刘祥超
642a0c3b0d 优化界面 2023-05-25 17:42:22 +08:00
刘祥超
ff47a19250 优化界面 2023-05-25 17:29:55 +08:00
刘祥超
1fc1a3fbe3 WAF国家/地区封禁、省份封禁增加例外URL、限制URL 2023-05-25 12:02:19 +08:00
刘祥超
996172bd69 改进界面 2023-05-25 10:59:18 +08:00
刘祥超
df038314ef commit components.js 2023-05-24 17:21:05 +08:00
刘祥超
67881c2e34 网站全局设置中增加“自动匹配证书”选项 2023-05-24 17:20:28 +08:00
刘祥超
9c691a17b2 优化集群菜单 2023-05-23 19:50:19 +08:00
刘祥超
54df86a4a2 优化文字提示 2023-05-23 19:15:35 +08:00
刘祥超
70aff759d7 优化文字显示 2023-05-23 11:25:25 +08:00
刘祥超
d903cc6e3b 优化源站列表专属域名显示 2023-05-23 11:25:13 +08:00
刘祥超
c520271ab5 实现自定义页面组件 2023-05-22 17:30:46 +08:00
刘祥超
f6936224d9 调整集群设置菜单位置 2023-05-22 09:49:56 +08:00
刘祥超
283984a6a6 HTTP Header中支持设置非标Header 2023-05-19 19:52:10 +08:00
刘祥超
c4fc09f72a 优化HTTP Header页面 2023-05-19 17:40:00 +08:00
刘祥超
efdbabfa04 优化HTTP Header页面文字提示 2023-05-19 17:34:54 +08:00
刘祥超
a43af333fd 优化文字提示 2023-05-19 16:51:49 +08:00
刘祥超
1f5894ff82 HTTP Header - CORS跨域设置增加多个选项 2023-05-19 16:32:28 +08:00
刘祥超
79b4054e31 在节点列表显示租期、是否为备用节点等信息 2023-05-19 11:11:34 +08:00
刘祥超
376d7d0c78 修改部分代码的联系方式 2023-05-19 11:09:57 +08:00
刘祥超
c8b85330ed 删除QQ群信息 2023-05-17 19:22:56 +08:00
刘祥超
c65dffee13 实现基础的智能调度 2023-05-17 18:41:27 +08:00
刘祥超
275320ad9b 如果管理系统前端有反向代理,则不要自动从HTTP跳转到HTTPS 2023-05-07 09:28:18 +08:00
刘祥超
f71cdf9065 防盗链增加”同时检查Origin选项“ 2023-05-02 17:05:35 +08:00
刘祥超
8e0a239de9 优化WAF CC2规则说明文字 2023-05-02 17:04:40 +08:00
刘祥超
ed2b95bc3f 集群设置中“服务设置”改为“网站设置” 2023-04-26 14:04:49 +08:00
刘祥超
2e23ee4c66 优化健康检查界面 2023-04-26 10:48:44 +08:00
刘祥超
58def52e30 数据库手动清理页面增加按表名和按占用空间排序/优化数据库相关界面 2023-04-26 09:49:28 +08:00
刘祥超
aea5ef3b68 Update Dockerfile 2023-04-24 10:29:31 +08:00
刘祥超
f2e14cf0a6 版本号修改为1.0.4 2023-04-24 10:17:05 +08:00
刘祥超
bc9b6d1595 为集群选择DNS域名时自动排序并不显示已下线域名 2023-04-24 09:38:57 +08:00
刘祥超
fd11b62390 更新components.js 2023-04-23 20:13:23 +08:00
刘祥超
8db00a5ab5 远程升级API节点时自动上传边缘节点安装文件 2023-04-23 19:48:07 +08:00
刘祥超
a6757e9374 申请ACME证书时可以指定平台用户 2023-04-23 15:01:43 +08:00
刘祥超
e0fe385404 证书申请任务可以使用用户筛选 2023-04-23 11:03:07 +08:00
刘祥超
f9f4258ec1 证书列表可以使用用户筛选 2023-04-23 10:44:37 +08:00
刘祥超
f939d563ad 选择证书时,如果证书列表为空,则提示可以搜索未指定用户证书 2023-04-22 12:04:04 +08:00
刘祥超
fad03add54 “网站服务”改为“网站”、“网站列表” 2023-04-21 17:51:25 +08:00
刘祥超
9ab6dab081 SESSION读取失败时不强制重新登录 2023-04-21 15:39:51 +08:00
刘祥超
5a55268830 修复在HTTP/1.x下开多个窗口访问非常慢的问题 2023-04-19 21:03:46 +08:00
刘祥超
fc3769239d 节点列表页CPU、内存使用比例增加百分号 2023-04-19 19:26:49 +08:00
刘祥超
c9fb3153eb 安全设置增加“检查客户端指纹"和"检查客户端区域"选项 2023-04-19 18:25:10 +08:00
刘祥超
a31f9ed9c5 节点列表负载数据如果是0,则显示0.00 2023-04-18 11:23:49 +08:00
刘祥超
2f75828ba4 优化滚动条在firefox上的显示 2023-04-11 16:33:22 +08:00
刘祥超
89679ec358 版本号改为1.1.0 2023-04-10 21:03:41 +08:00
刘祥超
98f77f52df Update Dockerfile 2023-04-10 09:20:59 +08:00
刘祥超
ca647d44bb 版本号修改为1.0.1 2023-04-10 09:17:37 +08:00
刘祥超
838b7dab5b Update Dockerfile 2023-04-10 08:48:09 +08:00
刘祥超
4f7fe247b4 更新components.js 2023-04-09 22:32:30 +08:00
刘祥超
cee20bd7d2 上传证书私钥时可以选择pem文件 2023-04-09 21:29:44 +08:00
刘祥超
f99ed0c4d9 优化grpc参数 2023-04-09 21:05:49 +08:00
刘祥超
4ae6523164 修复一个单词拼写错误 2023-04-09 20:09:12 +08:00
刘祥超
2ece764dcb 优化<combo-box>组件上下键盘键移动速度 2023-04-09 20:08:38 +08:00
刘祥超
f3bdd98af5 优化API升级处理提示文字 2023-04-09 17:29:29 +08:00
刘祥超
3c3c511c5b 版本号更改为1.0.0 2023-04-09 17:24:04 +08:00
刘祥超
45167b87e5 提交components.js 2023-04-09 17:23:51 +08:00
刘祥超
f7d5755744 提升Cookie安全性 2023-04-09 17:10:53 +08:00
刘祥超
e2adafd16b 服务配置菜单项增加配置代号 2023-04-09 15:59:37 +08:00
刘祥超
9d178b238d 审计日志列表增加级别筛选 2023-04-06 10:06:22 +08:00
刘祥超
431054a1be 自动检查管理员弱密码并提醒 2023-04-04 17:26:28 +08:00
刘祥超
5bf6428253 调整表单中注释颜色 2023-04-04 15:22:57 +08:00
刘祥超
6a4a03267b 优化文字提示 2023-04-03 10:03:48 +08:00
刘祥超
6598e16974 优化MySQL系统服务参数 2023-04-03 10:01:35 +08:00
刘祥超
b1943a4cec 优化MySQL系统服务参数 2023-04-02 18:05:54 +08:00
刘祥超
5f70d5afd3 优化文字提示 2023-04-02 10:17:56 +08:00
刘祥超
7dead36212 更新components.js 2023-04-02 10:17:47 +08:00
刘祥超
1f68a7830b 集群服务设置增加“支持低版本HTTP”选项 2023-04-01 09:51:44 +08:00
刘祥超
42c5b7a181 修复在未初始化缓存设置时添加缓存条件产生的panic错误 2023-03-31 17:42:33 +08:00
刘祥超
6596b47e54 证书即将到期提示 2023-03-28 16:52:21 +08:00
刘祥超
02e4a3e244 安装MySQL时指定 innodb_buffer_pool_size 为系统内存的一半 2023-03-28 11:42:19 +08:00
刘祥超
d9af90c76b 优化代码 2023-03-27 17:37:48 +08:00
刘祥超
a54405f24f 自动安装mysql时调整innodb_sort_buffer_size参数值 2023-03-27 17:22:13 +08:00
刘祥超
2b39c5d517 优化代码 2023-03-27 16:31:52 +08:00
刘祥超
a09d295948 优化<combo-box>组件 2023-03-27 11:28:06 +08:00
刘祥超
2c0b0be8c4 优化<combo-box>组件 2023-03-27 11:01:28 +08:00
刘祥超
b289877273 README中增加企业版链接 2023-03-26 20:50:45 +08:00
刘祥超
869d54b9a8 如果管理系统同时设置了HTTP和HTTPS端口,那么访问HTTP登录页时自动跳转到HTTPS地址 2023-03-26 15:54:14 +08:00
刘祥超
df48ae8316 优化缓存策略相关文字提示 2023-03-26 12:23:12 +08:00
刘祥超
e42cf2a420 创建服务和修改服务HTTPS设置时也支持批量上传证书 2023-03-26 12:00:41 +08:00
刘祥超
bd899b649d 创建服务时如果选择了所属用户,则证书列表中默认显示该用户的证书 2023-03-26 10:36:47 +08:00
刘祥超
2bc43ee2a5 实现自动匹配证书和批量选择证书功能 2023-03-25 20:50:27 +08:00
刘祥超
48e2907426 开源版本默认每次只能上传20个证书文件 2023-03-24 15:29:51 +08:00
刘祥超
4e33cce128 批量上传时如果证书文件和私钥文件不一致,也会提示错误 2023-03-24 15:13:28 +08:00
刘祥超
6658028f90 增加批量上传证书功能 2023-03-24 15:05:31 +08:00
刘祥超
3fe67bb179 优化系统设置--Web服务文字提示。 2023-03-24 10:53:40 +08:00
刘祥超
ce9a2d0cc3 证书内容输入框支持拖动文件上传 2023-03-24 10:17:05 +08:00
刘祥超
45b7c7af15 优化界面显示 2023-03-23 15:47:11 +08:00
刘祥超
3116aaeccb 节点列表节点IP超出4个后可以自动隐藏和显示 2023-03-23 14:50:12 +08:00
刘祥超
bfd5517c6c 优化创建网站服务界面 2023-03-22 17:52:22 +08:00
刘祥超
c83598a4f9 优化创建网站服务界面 2023-03-21 16:56:45 +08:00
刘祥超
3dc983871f 优化CNAME查询程序 2023-03-21 11:41:40 +08:00
刘祥超
ac6fcefffd 修改<columns-grid>组件文件名 2023-03-21 11:00:35 +08:00
刘祥超
094080cc9f 优化界面文字 2023-03-20 21:32:18 +08:00
刘祥超
d2dff968fb 自动安装MySQL时自动生成所需的动态库软链接 2023-03-20 21:32:10 +08:00
刘祥超
496f82a01f 日志数据库节点详情中密码使用星号(*)代替 2023-03-20 17:00:54 +08:00
刘祥超
9293c3e861 优化界面 2023-03-20 12:10:08 +08:00
刘祥超
da7d42edd5 修复<file-textarea>组件在firefox上无法复制内容的Bug 2023-03-20 09:43:21 +08:00
刘祥超
87b96d6526 SSH认证添加私钥时可以从私钥文件中直接拖入 2023-03-19 17:49:07 +08:00
刘祥超
a371821ff8 创建SSH认证私钥时校验私钥内容 2023-03-19 17:48:43 +08:00
刘祥超
748cb6eb8f 更新相关库 2023-03-19 17:46:28 +08:00
刘祥超
05bee642e9 配置初始化时增加context参数 2023-03-18 22:12:22 +08:00
刘祥超
081be04592 优化文字提示 2023-03-18 16:08:25 +08:00
刘祥超
f77518d086 生成节点DNS解析时区分节点是否已安装 2023-03-18 16:05:46 +08:00
刘祥超
35e6202b3c 创建节点时自动从节点名称中提取节点IP 2023-03-18 11:36:10 +08:00
刘祥超
1d84ea6ab9 更新components.js 2023-03-18 11:35:42 +08:00
刘祥超
df461d81d8 版本号更改为0.6.5 2023-03-17 15:53:01 +08:00
刘祥超
821d5aa595 优化节点列表连接数显示 2023-03-17 15:25:07 +08:00
刘祥超
c25bd71592 节点列表带宽保存两位小数数字 2023-03-17 15:20:53 +08:00
刘祥超
7e9224680e 当HTTP和HTTPS端口冲突时提示用户 2023-03-17 11:11:54 +08:00
刘祥超
bc89116d3d 编译脚本中暂时不删除.css.map和.js.map文件 2023-03-17 10:20:11 +08:00
刘祥超
aabe75ee13 优化文字提示 2023-03-17 10:19:38 +08:00
刘祥超
8903adc523 优化文字提示 2023-03-16 10:09:17 +08:00
刘祥超
61d8c8cd39 优化节点列表统计项宽度 2023-03-16 09:48:45 +08:00
刘祥超
2e1d991e0c 在开源版本看板增加检查是否为Plus的钩子 2023-03-16 09:37:16 +08:00
刘祥超
5a57167832 更新Dockerfile中的版本号 2023-03-16 09:27:36 +08:00
刘祥超
c3df181dcc 版本号改为0.6.4.2 2023-03-16 08:55:12 +08:00
刘祥超
699ea47ac5 更新components.js 2023-03-16 08:55:01 +08:00
刘祥超
fba69f3109 节点列表增加连接数列 2023-03-15 17:57:32 +08:00
刘祥超
063583dda1 调整节点任务和DNS同步任务弹窗高度 2023-03-15 16:24:14 +08:00
刘祥超
a31ca5cfb6 提高消息弹窗高度 2023-03-15 15:48:35 +08:00
刘祥超
c0a13305ad 管理系统增加两种背景风格 2023-03-15 15:35:11 +08:00
刘祥超
9d8cbf87dc URL跳转之域名跳转增加跳转后域名校验 2023-03-15 15:07:55 +08:00
刘祥超
009f7da26b 优化看板中的统计图表 2023-03-15 14:39:45 +08:00
刘祥超
f1d0359dc4 优化代码 2023-03-13 16:24:37 +08:00
刘祥超
473baa8c5b 版本号改为0.6.5 2023-03-13 16:16:26 +08:00
刘祥超
1a5dda6c72 Update Dockerfile 2023-03-13 12:12:44 +08:00
刘祥超
8a78dcb06e 版本号改为0.6.4.1 2023-03-13 10:36:56 +08:00
刘祥超
185768a80f 更新Dockerfile中的版本号 2023-03-13 09:15:23 +08:00
刘祥超
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
1265 changed files with 59631 additions and 16746 deletions

74
.golangci.yaml Normal file
View File

@@ -0,0 +1,74 @@
# https://golangci-lint.run/usage/configuration/
linters:
enable-all: true
disable:
- ifshort
- exhaustivestruct
- golint
- nosnakecase
- scopelint
- varcheck
- structcheck
- interfacer
- maligned
- deadcode
- dogsled
- wrapcheck
- wastedassign
- varnamelen
- testpackage
- thelper
- nilerr
- sqlclosecheck
- paralleltest
- nonamedreturns
- nlreturn
- nakedret
- ireturn
- interfacebloat
- gosmopolitan
- gomnd
- goerr113
- gochecknoglobals
- exhaustruct
- errorlint
- depguard
- exhaustive
- containedctx
- wsl
- cyclop
- dupword
- errchkjson
- contextcheck
- tagalign
- dupl
- forbidigo
- funlen
- goconst
- godox
- gosec
- lll
- nestif
- revive
- unparam
- stylecheck
- gocritic
- gofumpt
- gomoddirectives
- godot
- gofmt
- gocognit
- mirror
- gocyclo
- gochecknoinits
- gci
- maintidx
- prealloc
- goimports
- errname
- musttag
- forcetypeassert
- whitespace
- noctx
- reassign

View File

@@ -46,7 +46,7 @@
* [开发者指南](https://goedge.cn/docs/Developer/Build.md)
## 架构
![架构](doc/architect-zh.jpg)
![架构](doc/architect-zh.png)
其中的组件源码地址如下:
* [边缘节点](https://github.com/TeaOSLab/EdgeNode)
@@ -54,7 +54,10 @@
* [管理平台](https://github.com/TeaOSLab/EdgeAdmin)
## 联系我们
有什么问题和建议都可以加入QQ群 `659832182` 或者 [Telegram群](https://t.me/+5kVCMGxQhZxiODY9)
有什么问题和建议都可以加入 [Telegram群](https://t.me/+5kVCMGxQhZxiODY9)
## 企业版
* [GoEdge企业版](https://goedge.cn/commercial) - 功能更强大的CDN系统
## 感谢
* 感谢 [Gitee](https://gitee.com/) 提供国内源代码托管平台

View File

@@ -57,9 +57,11 @@ function build() {
if [ "$(which uglifyjs)" ]; then
echo "compress to component.js ..."
uglifyjs --compress --mangle -- "${JS_ROOT}"/components.src.js > "${JS_ROOT}"/components.js
uglifyjs --compress --mangle -- "${JS_ROOT}"/utils.js > "${JS_ROOT}"/utils.min.js
else
echo "copy to component.js ..."
cp "${JS_ROOT}"/components.src.js "${JS_ROOT}"/components.js
cp "${JS_ROOT}"/utils.js "${JS_ROOT}"/utils.min.js
fi
# create dir & copy files
@@ -105,8 +107,8 @@ function build() {
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
#find "$DIST" -name "*.css.map" -delete
#find "$DIST" -name "*.js.map" -delete
# zip
echo "zip files ..."

View File

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

View File

@@ -1,4 +0,0 @@
rpc:
endpoints: [ "http://127.0.0.1:8003" ]
nodeId: ""
secret: ""

View File

@@ -0,0 +1,3 @@
rpc.endpoints: [ "http://127.0.0.1:8003" ]
nodeId: ""
secret: ""

View File

@@ -2,15 +2,21 @@
JS_ROOT=../web/public/js
echo "generate component.src.js ..."
echo "generating component.src.js ..."
go run -tags=community ../cmd/edge-admin/main.go generate
if [ `which uglifyjs` ]; then
if [ "$(which uglifyjs)" ]; then
echo "compress to component.js ..."
uglifyjs --compress --mangle -- ${JS_ROOT}/components.src.js > ${JS_ROOT}/components.js
echo "compress to utils.min.js ..."
uglifyjs --compress --mangle -- ${JS_ROOT}/utils.js > ${JS_ROOT}/utils.min.js
else
echo "copy to component.js ..."
cp ${JS_ROOT}/components.src.js ${JS_ROOT}/components.js
echo "copy to utils.min.js ..."
cp ${JS_ROOT}/utils.js ${JS_ROOT}/utils.min.js
fi
echo "ok"

View File

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

View File

@@ -1,23 +1,33 @@
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/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
_ "github.com/TeaOSLab/EdgeCommon/pkg/langs/messages"
"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|reset|recover|demo]").
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").
@@ -30,7 +40,9 @@ func main() {
Option("recover", "enter recovery mode").
Option("demo", "switch to demo mode").
Option("dev", "switch to 'dev' mode").
Option("prod", "switch to 'prod' mode")
Option("prod", "switch to 'prod' mode").
Option("upgrade [--url=URL]", "upgrade from official site or an url").
Option("install-local-node", "install a local node")
app.On("daemon", func() {
nodes.NewAdminNode().Daemon()
@@ -49,10 +61,24 @@ func main() {
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)
var sock = gosock.NewTmpSock(teaconst.ProcessName)
if !sock.IsListening() {
fmt.Println("[ERROR]the service not started yet, you should start the service first")
return
@@ -65,7 +91,7 @@ func main() {
fmt.Println("enter recovery mode successfully")
})
app.On("demo", func() {
sock := gosock.NewTmpSock(teaconst.ProcessName)
var sock = gosock.NewTmpSock(teaconst.ProcessName)
if !sock.IsListening() {
fmt.Println("[ERROR]the service not started yet, you should start the service first")
return
@@ -115,8 +141,55 @@ func main() {
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.Printf("%.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.On("install-local-node", func() {
err := nodeutils.InstallLocalNode()
if err != nil {
fmt.Println("[ERROR]" + err.Error())
return
}
fmt.Println("success")
})
app.Run(func() {
adminNode := nodes.NewAdminNode()
var adminNode = nodes.NewAdminNode()
adminNode.Run()
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

BIN
doc/architect-zh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

1
docker/.gitignore vendored Normal file
View File

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

41
docker/Dockerfile Normal file
View File

@@ -0,0 +1,41 @@
FROM --platform=linux/amd64 alpine:latest
LABEL maintainer="goedge.cdn@gmail.com"
ENV TZ "Asia/Shanghai"
ENV VERSION 1.3.3
ENV ROOT_DIR /usr/local/goedge
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip
# remote official repository
ENV TAR_URL "https://dl.goedge.cn/edge/v${VERSION}/edge-admin-linux-amd64-plus-v${VERSION}.zip"
# your local repository
#ENV TAR_URL "http://192.168.2.61:8080/edge-admin-linux-amd64-plus-v${VERSION}.zip"
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}

32
go.mod
View File

@@ -8,37 +8,47 @@ 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/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475
github.com/iwind/TeaGo v0.0.0-20231214114820-1bfb0013bcf6
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
github.com/miekg/dns v1.1.43
github.com/quic-go/quic-go v0.40.0
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-20220412211240-33da011f77ad
golang.org/x/crypto v0.16.0
golang.org/x/sys v0.15.0
google.golang.org/grpc v1.45.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
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/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20231203200248-ad67f76aa53d // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/rogpeppe/fastuuid v1.2.0 // indirect
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
github.com/tdewolff/minify/v2 v2.12.7 // indirect
github.com/tdewolff/parse/v2 v2.6.6 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/text v0.3.7 // indirect
go.uber.org/mock v0.3.0 // indirect
golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.16.0 // indirect
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
google.golang.org/protobuf v1.27.1 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)

84
go.sum
View File

@@ -12,6 +12,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
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=
@@ -24,6 +25,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
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/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
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=
@@ -35,13 +38,17 @@ github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebP
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/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
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-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
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=
@@ -56,8 +63,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
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/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/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=
@@ -68,19 +76,23 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
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/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20231203200248-ad67f76aa53d h1:bM3Cipp7U7Sdn0DpYngVaLWFV9yonxQ1IB7ZPQ41OLw=
github.com/google/pprof v0.0.0-20231203200248-ad67f76aa53d/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
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-20220304043459-0dd944a5b475 h1:EseyfFaQOjWanGiby9KMw7PjDBMg/95tLDgIw/ns0Cw=
github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f h1:xo6XmXLtveKcwcZAXV6VMxkWNzy/2dStfHEnyowsGAE=
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/TeaGo v0.0.0-20231214114820-1bfb0013bcf6 h1:l8MW552d+gLzRd+MAtM/2X+80n/uS5nJAlBTYWHroRI=
github.com/iwind/TeaGo v0.0.0-20231214114820-1bfb0013bcf6/go.mod h1:Ng3xWekHSVy0E/6/jYqJ7Htydm/H+mWIl0AS+Eg3H2M=
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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
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=
@@ -89,23 +101,23 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
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/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
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.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
@@ -116,6 +128,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
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/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
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=
@@ -126,6 +144,7 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1
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/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -133,6 +152,13 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tdewolff/minify/v2 v2.12.7 h1:pBzz2tAfz5VghOXiQIsSta6srhmTeinQPjRDHWoumCA=
github.com/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws=
github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo=
github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
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=
@@ -143,12 +169,18 @@ github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPR
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=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
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=
golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=
golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -157,6 +189,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
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=
@@ -170,16 +204,16 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
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.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
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-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/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
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=
@@ -198,26 +232,27 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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=
@@ -250,8 +285,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
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=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -266,7 +302,7 @@ 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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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

@@ -1,6 +1,7 @@
package apps
import (
"errors"
"fmt"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/iwind/TeaGo/logs"
@@ -9,8 +10,10 @@ import (
"github.com/iwind/gosock/pkg/gosock"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
)
@@ -123,7 +126,7 @@ 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":
@@ -139,7 +142,7 @@ func (this *AppCmd) Run(main func()) {
this.runStop()
return
case "restart":
this.runRestart()
this.RunRestart()
return
case "status":
this.runStatus()
@@ -160,7 +163,7 @@ func (this *AppCmd) Run(main func()) {
}
// 日志
writer := new(LogWriter)
var writer = new(LogWriter)
writer.Init()
logs.SetWriter(writer)
@@ -186,13 +189,16 @@ func (this *AppCmd) runStart() {
return
}
cmd := exec.Command(os.Args[0])
var cmd = exec.Command(this.exe())
err := cmd.Start()
if err != nil {
fmt.Println(this.product+" start failed:", err.Error())
return
}
// create symbolic links
_ = this.createSymLinks()
fmt.Println(this.product+" started ok, pid:", cmd.Process.Pid)
}
@@ -209,8 +215,8 @@ func (this *AppCmd) runStop() {
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()
@@ -239,3 +245,58 @@ func (this *AppCmd) getPID() int {
}
return maps.NewMap(reply.Params).GetInt("pid")
}
func (this *AppCmd) exe() string {
var exe, _ = os.Executable()
if len(exe) == 0 {
exe = os.Args[0]
}
return exe
}
// 创建软链接
func (this *AppCmd) createSymLinks() error {
if runtime.GOOS != "linux" {
return nil
}
var exe, _ = os.Executable()
if len(exe) == 0 {
return nil
}
var errorList = []string{}
// bin
{
var target = "/usr/bin/" + teaconst.ProcessName
old, _ := filepath.EvalSymlinks(target)
if old != exe {
_ = os.Remove(target)
err := os.Symlink(exe, target)
if err != nil {
errorList = append(errorList, err.Error())
}
}
}
// log
{
var realPath = filepath.Dir(filepath.Dir(exe)) + "/logs/run.log"
var target = "/var/log/" + teaconst.ProcessName + ".log"
old, _ := filepath.EvalSymlinks(target)
if old != realPath {
_ = os.Remove(target)
err := os.Symlink(realPath, target)
if err != nil {
errorList = append(errorList, err.Error())
}
}
}
if len(errorList) > 0 {
return errors.New(strings.Join(errorList, "\n"))
}
return nil
}

View File

@@ -3,8 +3,11 @@ package configloaders
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
@@ -47,6 +50,7 @@ func loadAdminModuleMapping() (map[int64]*AdminModuleList, error) {
IsSuper: m.IsSuper,
Fullname: m.Fullname,
Theme: m.Theme,
Lang: m.Lang,
}
for _, pbModule := range m.Modules {
@@ -158,50 +162,91 @@ func UpdateAdminTheme(adminId int64, theme string) {
}
}
// FindAdminLang 查找某个管理员选择的语言
func FindAdminLang(adminId int64) string {
locker.Lock()
defer locker.Unlock()
list, ok := sharedAdminModuleMapping[adminId]
if ok {
return list.Lang
}
return ""
}
// UpdateAdminLang 修改某个管理员选择的语言
func UpdateAdminLang(adminId int64, langCode string) {
locker.Lock()
defer locker.Unlock()
list, ok := sharedAdminModuleMapping[adminId]
if ok {
list.Lang = langCode
}
}
func FindAdminLangForAction(actionPtr actions.ActionWrapper) (langCode langs.LangCode) {
locker.Lock()
defer locker.Unlock()
var adminId = actionPtr.Object().Session().GetInt64(teaconst.SessionAdminId)
list, ok := sharedAdminModuleMapping[adminId]
var result = ""
if ok {
result = list.Lang
}
if len(result) == 0 {
result = langs.ParseLangFromAction(actionPtr)
}
return result
}
// AllModuleMaps 所有权限列表
func AllModuleMaps() []maps.Map {
func AllModuleMaps(langCode string) []maps.Map {
var m = []maps.Map{
{
"name": "看板",
"name": langs.Message(langCode, codes.AdminMenu_Dashboard),
"code": AdminModuleCodeDashboard,
"url": "/dashboard",
},
{
"name": "网站服务",
"name": langs.Message(langCode, codes.AdminMenu_Servers),
"code": AdminModuleCodeServer,
"url": "/servers",
},
{
"name": "边缘节点",
"name": langs.Message(langCode, codes.AdminMenu_Nodes),
"code": AdminModuleCodeNode,
"url": "/clusters",
},
{
"name": "域名解析",
"name": langs.Message(langCode, codes.AdminMenu_DNS),
"code": AdminModuleCodeDNS,
"url": "/dns",
},
}
if teaconst.IsPlus {
m = append(m, maps.Map{
"name": "智能DNS",
"name": langs.Message(langCode, codes.AdminMenu_NS),
"code": AdminModuleCodeNS,
"url": "/ns",
})
}
m = append(m, []maps.Map{
{
"name": "平台用户",
"name": langs.Message(langCode, codes.AdminMenu_Users),
"code": AdminModuleCodeUser,
"url": "/users",
},
{
"name": "系统用户",
"name": langs.Message(langCode, codes.AdminMenu_Admins),
"code": AdminModuleCodeAdmin,
"url": "/admins",
},
{
"name": "财务管理",
"name": langs.Message(langCode, codes.AdminMenu_Finance),
"code": AdminModuleCodeFinance,
"url": "/finance",
},
@@ -210,12 +255,12 @@ func AllModuleMaps() []maps.Map {
if teaconst.IsPlus {
m = append(m, []maps.Map{
{
"name": "套餐管理",
"name": langs.Message(langCode, codes.AdminMenu_Plans),
"code": AdminModuleCodePlan,
"url": "/plans",
},
{
"name": "工单系统",
"name": langs.Message(langCode, codes.AdminMenu_Tickets),
"code": AdminModuleCodeTicket,
"url": "/tickets",
},
@@ -224,12 +269,12 @@ func AllModuleMaps() []maps.Map {
m = append(m, []maps.Map{
{
"name": "日志审计",
"name": langs.Message(langCode, codes.AdminMenu_Logs),
"code": AdminModuleCodeLog,
"url": "/log",
},
{
"name": "系统设置",
"name": langs.Message(langCode, codes.AdminMenu_Settings),
"code": AdminModuleCodeSetting,
"url": "/settings",
},

View File

@@ -7,6 +7,7 @@ type AdminModuleList struct {
Modules []*systemconfigs.AdminModule
Fullname string
Theme string
Lang 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,6 +3,8 @@ package configloaders
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
@@ -107,8 +109,8 @@ func loadAdminUIConfig() (*systemconfigs.AdminUIConfig, error) {
func defaultAdminUIConfig() *systemconfigs.AdminUIConfig {
return &systemconfigs.AdminUIConfig{
ProductName: "GoEdge",
AdminSystemName: "GoEdge管理员系统",
ProductName: langs.DefaultMessage(codes.AdminUI_DefaultProductName),
AdminSystemName: langs.DefaultMessage(codes.AdminUI_DefaultSystemName),
ShowOpenSourceInfo: true,
ShowVersion: true,
ShowFinance: true,

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

@@ -29,7 +29,7 @@ func LoadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
return nil, err
}
v := reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig)
var v = reflect.Indirect(reflect.ValueOf(config)).Interface().(systemconfigs.SecurityConfig)
return &v, nil
}
@@ -83,7 +83,12 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
return sharedSecurityConfig, nil
}
config := &systemconfigs.SecurityConfig{}
var config = &systemconfigs.SecurityConfig{
Frame: FrameSameOrigin,
AllowLocal: true,
CheckClientFingerprint: false,
CheckClientRegion: true,
}
err = json.Unmarshal(resp.ValueJSON, config)
if err != nil {
logs.Println("[SECURITY_MANAGER]" + err.Error())
@@ -100,7 +105,9 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
func defaultSecurityConfig() *systemconfigs.SecurityConfig {
return &systemconfigs.SecurityConfig{
Frame: FrameSameOrigin,
AllowLocal: true,
Frame: FrameSameOrigin,
AllowLocal: true,
CheckClientFingerprint: false,
CheckClientRegion: true,
}
}

View File

@@ -1,96 +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,
BandwidthUnit: systemconfigs.BandwidthUnitBit,
ShowBandwidthCharts: true,
ShowTrafficCharts: true,
}
}

View File

@@ -1,6 +1,7 @@
package configs
import (
"errors"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/iwind/TeaGo/Tea"
"gopkg.in/yaml.v3"
@@ -8,12 +9,19 @@ import (
"path/filepath"
)
const ConfigFileName = "api_admin.yaml"
const oldConfigFileName = "api.yaml"
// APIConfig API配置
type APIConfig struct {
RPC struct {
OldRPC struct {
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
} `yaml:"rpc"`
} `yaml:"rpc,omitempty"`
RPCEndpoints []string `yaml:"rpc.endpoints,flow" json:"rpc.endpoints"`
RPCDisableUpdate bool `yaml:"rpc.disableUpdate" json:"rpc.disableUpdate"`
NodeId string `yaml:"nodeId"`
Secret string `yaml:"secret"`
}
@@ -21,23 +29,29 @@ type APIConfig struct {
// LoadAPIConfig 加载API配置
func LoadAPIConfig() (*APIConfig, error) {
// 候选文件
localFile := Tea.ConfigFile("api.yaml")
isFromLocal := false
paths := []string{localFile}
var realFile = Tea.ConfigFile(ConfigFileName)
var oldRealFile = Tea.ConfigFile(oldConfigFileName)
var isFromLocal = false
var paths = []string{realFile, oldRealFile}
homeDir, homeErr := os.UserHomeDir()
if homeErr == nil {
paths = append(paths, homeDir+"/."+teaconst.ProcessName+"/api.yaml")
paths = append(paths, homeDir+"/."+teaconst.ProcessName+"/"+ConfigFileName)
}
paths = append(paths, "/etc/"+teaconst.ProcessName+"/api.yaml")
paths = append(paths, "/etc/"+teaconst.ProcessName+"/"+ConfigFileName)
var data []byte
var err error
var isFromOld = false
for _, path := range paths {
data, err = os.ReadFile(path)
if err == nil {
if path == localFile {
if path == realFile || path == oldRealFile {
isFromLocal = true
}
// 自动生成新的配置文件
isFromOld = path == oldRealFile
break
}
}
@@ -45,15 +59,26 @@ func LoadAPIConfig() (*APIConfig, error) {
return nil, err
}
config := &APIConfig{}
var config = &APIConfig{}
err = yaml.Unmarshal(data, config)
if err != nil {
return nil, err
}
err = config.Init()
if err != nil {
return nil, errors.New("init error: " + err.Error())
}
if !isFromLocal {
// 恢复文件
_ = os.WriteFile(localFile, data, 0666)
_ = os.WriteFile(realFile, data, 0666)
}
// 自动生成新配置文件
if isFromOld {
config.OldRPC.Endpoints = nil
_ = config.WriteFile(Tea.ConfigFile(ConfigFileName))
}
return config, nil
@@ -61,10 +86,11 @@ func LoadAPIConfig() (*APIConfig, error) {
// ResetAPIConfig 重置配置
func ResetAPIConfig() error {
filename := "api.yaml"
var filename = ConfigFileName
// 重置 configs/api_admin.yaml
{
configFile := Tea.ConfigFile(filename)
var configFile = Tea.ConfigFile(filename)
stat, err := os.Stat(configFile)
if err == nil && !stat.IsDir() {
err = os.Remove(configFile)
@@ -74,10 +100,10 @@ func ResetAPIConfig() error {
}
}
// 重置 ~/.edge-admin/api.yaml
// 重置 ~/.edge-admin/api_admin.yaml
homeDir, homeErr := os.UserHomeDir()
if homeErr == nil {
configFile := homeDir + "/." + teaconst.ProcessName + "/" + filename
var configFile = homeDir + "/." + teaconst.ProcessName + "/" + filename
stat, err := os.Stat(configFile)
if err == nil && !stat.IsDir() {
err = os.Remove(configFile)
@@ -87,9 +113,9 @@ func ResetAPIConfig() error {
}
}
// 重置 /etc/edge-admin/api.yaml
// 重置 /etc/edge-admin/api_admin.yaml
{
configFile := "/etc/" + teaconst.ProcessName + "/" + filename
var configFile = "/etc/" + teaconst.ProcessName + "/" + filename
stat, err := os.Stat(configFile)
if err == nil && !stat.IsDir() {
err = os.Remove(configFile)
@@ -102,6 +128,22 @@ func ResetAPIConfig() error {
return nil
}
func IsNewInstalled() bool {
homeDir, err := os.UserHomeDir()
if err != nil {
return false
}
for _, filename := range []string{ConfigFileName, oldConfigFileName} {
_, err = os.Stat(homeDir + "/." + teaconst.ProcessName + "/" + filename)
if err == nil {
return false
}
}
return true
}
// WriteFile 写入API配置
func (this *APIConfig) WriteFile(path string) error {
data, err := yaml.Marshal(this)
@@ -154,3 +196,31 @@ func (this *APIConfig) WriteFile(path string) error {
return nil
}
// Clone 克隆当前配置
func (this *APIConfig) Clone() *APIConfig {
return &APIConfig{
NodeId: this.NodeId,
Secret: this.Secret,
}
}
func (this *APIConfig) Init() error {
// compatible with old
if len(this.RPCEndpoints) == 0 && len(this.OldRPC.Endpoints) > 0 {
this.RPCEndpoints = this.OldRPC.Endpoints
this.RPCDisableUpdate = this.OldRPC.DisableUpdate
}
if len(this.RPCEndpoints) == 0 {
return errors.New("no valid 'rpc.endpoints'")
}
if len(this.NodeId) == 0 {
return errors.New("'nodeId' required")
}
if len(this.Secret) == 0 {
return errors.New("'secret' required")
}
return nil
}

View File

@@ -2,6 +2,7 @@ package configs
import (
_ "github.com/iwind/TeaGo/bootstrap"
"gopkg.in/yaml.v3"
"testing"
)
@@ -11,16 +12,16 @@ func TestLoadAPIConfig(t *testing.T) {
t.Fatal(err)
}
t.Log(config)
configData, err := yaml.Marshal(config)
if err != nil {
t.Fatal(err)
}
t.Log(string(configData))
}
func TestAPIConfig_WriteFile(t *testing.T) {
config := &APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
}{},
NodeId: "1",
Secret: "2",
}
var config = &APIConfig{}
err := config.WriteFile("/tmp/api_config.yaml")
if err != nil {
t.Fatal(err)

View File

@@ -1,42 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package configs
import (
"encoding/json"
"github.com/iwind/TeaGo/Tea"
"os"
)
var plusConfigFile = "plus.cache.json"
type PlusConfig struct {
IsPlus bool `json:"isPlus"`
Components []string `json:"components"`
DayTo string `json:"dayTo"`
}
func ReadPlusConfig() *PlusConfig {
data, err := os.ReadFile(Tea.ConfigFile(plusConfigFile))
if err != nil {
return &PlusConfig{IsPlus: false}
}
var config = &PlusConfig{IsPlus: false}
err = json.Unmarshal(data, config)
if err != nil {
return config
}
return config
}
func WritePlusConfig(config *PlusConfig) error {
configJSON, err := json.Marshal(config)
if err != nil {
return err
}
err = os.WriteFile(Tea.ConfigFile(plusConfigFile), configJSON, 0777)
if err != nil {
return err
}
return nil
}

View File

@@ -1,9 +1,9 @@
package teaconst
const (
Version = "0.5.0"
Version = "1.3.3"
APINodeVersion = "0.5.0"
APINodeVersion = "1.3.3"
ProductName = "Edge Admin"
ProcessName = "edge-admin"
@@ -14,9 +14,9 @@ const (
EncryptKey = "8f983f4d69b83aaa0d74b21a212f6967"
EncryptMethod = "aes-256-cfb"
ErrServer = "服务器出了点小问题,请联系技术人员处理。"
CookieSID = "edgesid"
CookieSID = "edgesid"
SessionAdminId = "adminId"
SystemdServiceName = "edge-admin"
UpdatesURL = "https://goedge.cn/api/boot/versions?os=${os}&arch=${arch}"
UpdatesURL = "https://goedge.cn/api/boot/versions?os=${os}&arch=${arch}&version=${version}"
)

View File

@@ -2,6 +2,11 @@
package teaconst
import (
"os"
"strings"
)
var (
IsRecoverMode = false
@@ -10,4 +15,18 @@ var (
NewVersionCode = "" // 有新的版本
NewVersionDownloadURL = "" // 新版本下载地址
IsMain = checkMain()
)
// 检查是否为主程序
func checkMain() bool {
if len(os.Args) == 1 ||
(len(os.Args) >= 2 && os.Args[1] == "pprof") {
return true
}
exe, _ := os.Executable()
return strings.HasSuffix(exe, ".test") ||
strings.HasSuffix(exe, ".test.exe") ||
strings.Contains(exe, "___")
}

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

View File

@@ -19,7 +19,6 @@ func TestAES128CFBMethod_Encrypt(t *testing.T) {
dst = dst[:len(src)]
t.Log("dst:", string(dst))
src = make([]byte, len(src))
src, err = method.Decrypt(dst)
if err != nil {
t.Fatal(err)
@@ -64,7 +63,6 @@ func TestAES128CFBMethod_Encrypt2(t *testing.T) {
for _, dst := range sources {
dst2 := append([]byte{}, dst...)
src2 := make([]byte, len(dst2))
src2, err := method.Decrypt(dst2)
if err != nil {
t.Fatal(err)

View File

@@ -5,22 +5,22 @@ import "sync"
var eventsMap = map[string][]func(){} // event => []callbacks
var locker = sync.Mutex{}
// 增加事件回调
// On 增加事件回调
func On(event string, callback func()) {
locker.Lock()
defer locker.Unlock()
callbacks, _ := eventsMap[event]
var callbacks = eventsMap[event]
callbacks = append(callbacks, callback)
eventsMap[event] = callbacks
}
// 通知事件
// Notify 通知事件
func Notify(event string) {
locker.Lock()
callbacks, _ := eventsMap[event]
var callbacks = eventsMap[event]
locker.Unlock()
for _, callback := range callbacks {
callback()
}

View File

@@ -5,14 +5,16 @@ package gen
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"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"
"github.com/iwind/TeaGo/maps"
"io"
"os"
"path/filepath"
@@ -21,7 +23,7 @@ import (
func Generate() error {
err := generateComponentsJSFile()
if err != nil {
return errors.New("generate 'components.src.js' failed: " + err.Error())
return fmt.Errorf("generate 'components.src.js' failed: %w", err)
}
return nil
@@ -62,7 +64,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.REQUEST_COND_COMPONENTS = ")
buffer.Write(typesJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 条件操作符
@@ -72,7 +74,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.REQUEST_COND_OPERATORS = ")
buffer.Write(requestOperatorsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 请求变量
@@ -82,7 +84,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.REQUEST_VARIABLES = ")
buffer.Write(requestVariablesJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 指标
@@ -92,7 +94,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.METRIC_HTTP_KEYS = ")
buffer.Write(metricHTTPKeysJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// IP地址阈值项目
@@ -102,7 +104,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.IP_ADDR_THRESHOLD_ITEMS = ")
buffer.Write(ipAddrThresholdItemsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// IP地址阈值动作
@@ -112,7 +114,45 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.IP_ADDR_THRESHOLD_ACTIONS = ")
buffer.Write(ipAddrThresholdActionsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// WAF checkpoints
var wafCheckpointsMaps = []maps.Map{}
for _, checkpoint := range firewallconfigs.AllCheckpoints {
wafCheckpointsMaps = append(wafCheckpointsMaps, maps.Map{
"name": checkpoint.Name,
"prefix": checkpoint.Prefix,
"description": checkpoint.Description,
})
}
wafCheckpointsJSON, err := json.Marshal(wafCheckpointsMaps)
if err != nil {
logs.Println("ComponentsAction marshal waf rule checkpoints failed: " + err.Error())
} else {
buffer.WriteString("window.WAF_RULE_CHECKPOINTS = ")
buffer.Write(wafCheckpointsJSON)
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'})
}
// WAF验证码类型
captchaTypesJSON, err := json.Marshal(firewallconfigs.FindAllCaptchaTypes())
if err != nil {
logs.Println("ComponentsAction marshal captcha types failed: " + err.Error())
} else {
buffer.WriteString("window.WAF_CAPTCHA_TYPES = ")
buffer.Write(captchaTypesJSON)
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)

View File

@@ -1,9 +1,10 @@
package nodes
import (
"errors"
"fmt"
"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"
@@ -12,7 +13,6 @@ import (
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/sessions"
"github.com/iwind/TeaGo/types"
"github.com/iwind/gosock/pkg/gosock"
"gopkg.in/yaml.v3"
@@ -21,6 +21,8 @@ import (
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
)
@@ -64,7 +66,7 @@ func (this *AdminNode) Run() {
this.addPortsToFirewall()
// 监听信号
sigQueue := make(chan os.Signal)
var sigQueue = make(chan os.Signal, 8)
signal.Notify(sigQueue, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT)
go func() {
for range sigQueue {
@@ -84,21 +86,28 @@ func (this *AdminNode) Run() {
// 启动API节点
this.startAPINode()
// 启动IP库
this.startIPLibrary()
// 启动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() {
var sock = gosock.NewTmpSock(teaconst.ProcessName)
isDebug := lists.ContainsString(os.Args, "debug")
isDebug = true
var isDebug = lists.ContainsString(os.Args, "debug")
for {
conn, err := sock.Dial()
if err != nil {
@@ -176,7 +185,7 @@ func (this *AdminNode) checkServer() error {
if err == nil {
err = os.WriteFile(configFile, data, 0666)
if err != nil {
return errors.New("create config file failed: " + err.Error())
return fmt.Errorf("create config file failed: %w", err)
}
} else {
templateYAML := `# environment code
@@ -196,11 +205,11 @@ https:
`
err = os.WriteFile(configFile, []byte(templateYAML), 0666)
if err != nil {
return errors.New("create config file failed: " + err.Error())
return fmt.Errorf("create config file failed: %w", err)
}
}
} else {
return errors.New("can not read config from 'configs/server.yaml': " + err.Error())
return fmt.Errorf("can not read config from 'configs/server.yaml': %w", err)
}
return nil
@@ -246,7 +255,7 @@ func (this *AdminNode) addPortsToFirewall() {
// 启动API节点
func (this *AdminNode) startAPINode() {
configPath := Tea.Root + "/edge-api/configs/api.yaml"
var configPath = Tea.Root + "/edge-api/configs/api.yaml"
_, err := os.Stat(configPath)
canStart := false
if err == nil {
@@ -360,6 +369,16 @@ func (this *AdminNode) listenSock() error {
}
}
// 停止当前目录下的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)

View File

@@ -0,0 +1,18 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package nodes
import (
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/iwind/TeaGo/logs"
)
// 启动IP库
func (this *AdminNode) startIPLibrary() {
logs.Println("[NODE]initializing ip library ...")
err := iplibrary.InitDefault()
if err != nil {
logs.Println("[NODE]initialize ip library failed: "+err.Error())
}
}

View File

@@ -0,0 +1,112 @@
// 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/EdgeAdmin/internal/ttlcache"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
"strings"
"time"
)
// SessionManager SESSION管理
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{}
var cacheKey = "SESSION@" + sid
var item = ttlcache.DefaultCache.Read(cacheKey)
if item != nil && item.Value != nil {
itemMap, ok := item.Value.(map[string]string)
if ok {
return itemMap
}
}
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())
result["@error"] = 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())
}
// Write to cache
ttlcache.DefaultCache.Write(cacheKey, result, time.Now().Unix()+300 /** must not be too long **/)
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

@@ -5,12 +5,14 @@ import (
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/encrypt"
"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"
@@ -18,8 +20,11 @@ import (
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/encoding/gzip"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"net"
"net/url"
"strings"
"sync"
"time"
)
@@ -38,7 +43,7 @@ func NewRPCClient(apiConfig *configs.APIConfig, isPrimary bool) (*RPCClient, err
return nil, errors.New("api config should not be nil")
}
client := &RPCClient{
var client = &RPCClient{
apiConfig: apiConfig,
}
@@ -95,10 +100,6 @@ 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())
}
@@ -123,6 +124,10 @@ 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())
}
@@ -167,18 +172,10 @@ func (this *RPCClient) APIMethodStatRPC() pb.APIMethodStatServiceClient {
return pb.NewAPIMethodStatServiceClient(this.pickConn())
}
func (this *RPCClient) UserNodeRPC() pb.UserNodeServiceClient {
return pb.NewUserNodeServiceClient(this.pickConn())
}
func (this *RPCClient) DBNodeRPC() pb.DBNodeServiceClient {
return pb.NewDBNodeServiceClient(this.pickConn())
}
func (this *RPCClient) MonitorNodeRPC() pb.MonitorNodeServiceClient {
return pb.NewMonitorNodeServiceClient(this.pickConn())
}
func (this *RPCClient) DBRPC() pb.DBServiceClient {
return pb.NewDBServiceClient(this.pickConn())
}
@@ -284,34 +281,6 @@ 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())
}
@@ -320,6 +289,10 @@ 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())
}
@@ -396,26 +369,6 @@ 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) ServerBillRPC() pb.ServerBillServiceClient {
return pb.NewServerBillServiceClient(this.pickConn())
}
func (this *RPCClient) UserAccountRPC() pb.UserAccountServiceClient {
return pb.NewUserAccountServiceClient(this.pickConn())
}
func (this *RPCClient) UserAccountLogRPC() pb.UserAccountLogServiceClient {
return pb.NewUserAccountLogServiceClient(this.pickConn())
}
func (this *RPCClient) UserAccountDailyStatRPC() pb.UserAccountDailyStatServiceClient {
return pb.NewUserAccountDailyStatServiceClient(this.pickConn())
}
func (this *RPCClient) UserAccessKeyRPC() pb.UserAccessKeyServiceClient {
return pb.NewUserAccessKeyServiceClient(this.pickConn())
}
@@ -428,18 +381,14 @@ 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())
}
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())
}
@@ -472,22 +421,14 @@ func (this *RPCClient) ServerStatBoardChartRPC() pb.ServerStatBoardChartServiceC
return pb.NewServerStatBoardChartServiceClient(this.pickConn())
}
func (this *RPCClient) PlanRPC() pb.PlanServiceClient {
return pb.NewPlanServiceClient(this.pickConn())
}
func (this *RPCClient) UserPlanRPC() pb.UserPlanServiceClient {
return pb.NewUserPlanServiceClient(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,
@@ -502,15 +443,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
}
// 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,
@@ -525,7 +466,7 @@ 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
}
@@ -542,22 +483,47 @@ func (this *RPCClient) UpdateConfig(config *configs.APIConfig) error {
// 初始化
func (this *RPCClient) init() error {
// 当前的IP地址
var localIPAddrs = this.localIPAddrs()
// 重新连接
conns := []*grpc.ClientConn{}
for _, endpoint := range this.apiConfig.RPC.Endpoints {
var conns = []*grpc.ClientConn{}
for _, endpoint := range this.apiConfig.RPCEndpoints {
u, err := url.Parse(endpoint)
if err != nil {
return errors.New("parse endpoint failed: " + err.Error())
return fmt.Errorf("parse endpoint failed: %w", err)
}
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))
var callOptions = grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(128<<20),
grpc.MaxCallSendMsgSize(128<<20),
grpc.UseCompressor(gzip.Name),
)
var keepaliveParams = grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 30 * time.Second,
})
if u.Scheme == "http" {
conn, err = grpc.Dial(u.Host, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions)
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions, keepaliveParams)
} 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,
})), callOptions)
})), callOptions, keepaliveParams)
} else {
return errors.New("parse endpoint failed: invalid scheme '" + u.Scheme + "'")
}
@@ -581,41 +547,30 @@ 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 关闭
@@ -634,3 +589,29 @@ func (this *RPCClient) Close() error {
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

@@ -34,13 +34,9 @@ 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{"http://127.0.0.1:8004"},
},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
RPCEndpoints: []string{"https://127.0.0.1:8003"},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
}, true)
if err != nil {
t.Fatal(err)
@@ -55,13 +51,9 @@ 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{"https://127.0.0.1:8003"},
},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
RPCEndpoints: []string{"https://127.0.0.1:8003"},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
}, true)
if err != nil {
t.Fatal(err)
@@ -76,13 +68,9 @@ 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{"https://127.0.0.1:8004"},
},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
RPCEndpoints: []string{"https://127.0.0.1:8004"},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
}, true)
if err != nil {
t.Fatal(err)
@@ -94,3 +82,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

@@ -2,6 +2,9 @@ package rpc
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"strings"
"sync"
)
@@ -28,3 +31,39 @@ func SharedRPC() (*RPCClient, error) {
sharedRPC = client
return sharedRPC, nil
}
// IsConnError 是否为连接错误
func IsConnError(err error) bool {
if err == nil {
return false
}
// 检查是否为连接错误
statusErr, ok := status.FromError(err)
if ok {
var errorCode = statusErr.Code()
return errorCode == codes.Unavailable || errorCode == codes.Canceled
}
if strings.Contains(err.Error(), "code = Canceled") {
return true
}
return false
}
// IsUnimplementedError 检查是否为未实现错误
func IsUnimplementedError(err error) bool {
if err == nil {
return false
}
statusErr, ok := status.FromError(err)
if ok {
if statusErr.Code() == codes.Unimplemented {
return true
}
}
return false
}

View File

@@ -2,8 +2,6 @@ package setup
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"os"
)
var isConfigured bool
@@ -21,13 +19,5 @@ func IsConfigured() bool {
// 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
return configs.IsNewInstalled()
}

View File

@@ -5,6 +5,7 @@ package tasks
import (
"encoding/json"
"errors"
"fmt"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/events"
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
@@ -59,11 +60,11 @@ func (this *CheckUpdatesTask) Loop() error {
return err
}
var valueJSON = valueResp.ValueJSON
var config = &systemconfigs.CheckUpdatesConfig{AutoCheck: false}
var config = systemconfigs.NewCheckUpdatesConfig()
if len(valueJSON) > 0 {
err = json.Unmarshal(valueJSON, config)
if err != nil {
return errors.New("decode config failed: " + err.Error())
return fmt.Errorf("decode config failed: %w", err)
}
if !config.AutoCheck {
return nil
@@ -87,9 +88,10 @@ func (this *CheckUpdatesTask) Loop() error {
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())
return fmt.Errorf("read api failed: %w", err)
}
defer func() {
@@ -97,13 +99,13 @@ func (this *CheckUpdatesTask) Loop() error {
}()
data, err := io.ReadAll(resp.Body)
if err != nil {
return errors.New("read api failed: " + err.Error())
return fmt.Errorf("read api failed: %w", err)
}
var apiResponse = &Response{}
err = json.Unmarshal(data, apiResponse)
if err != nil {
return errors.New("decode version data failed: " + err.Error())
return fmt.Errorf("decode version data failed: %w", err)
}
if apiResponse.Code != 200 {
@@ -118,7 +120,7 @@ func (this *CheckUpdatesTask) Loop() error {
var vMap = maps.NewMap(version)
if vMap.GetString("code") == "admin" {
var latestVersion = vMap.GetString("version")
if stringutil.VersionCompare(teaconst.Version, latestVersion) < 0 {
if stringutil.VersionCompare(teaconst.Version, latestVersion) < 0 && (len(config.IgnoredVersion) == 0 || stringutil.VersionCompare(latestVersion, config.IgnoredVersion) > 0) {
teaconst.NewVersionCode = latestVersion
teaconst.NewVersionDownloadURL = dlHost + vMap.GetString("url")
return nil

View File

@@ -14,6 +14,7 @@ import (
"github.com/iwind/TeaGo/logs"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"net/url"
"sort"
"strings"
@@ -64,7 +65,7 @@ func (this *SyncAPINodesTask) Loop() error {
}
// 是否禁止自动升级
if config.RPC.DisableUpdate {
if config.RPCDisableUpdate {
return nil
}
@@ -87,7 +88,7 @@ func (this *SyncAPINodesTask) Loop() error {
}
// 和现有的对比
if this.isSame(newEndpoints, config.RPC.Endpoints) {
if this.isSame(newEndpoints, config.RPCEndpoints) {
return nil
}
@@ -98,14 +99,14 @@ func (this *SyncAPINodesTask) Loop() error {
}
// 修改RPC对象配置
config.RPC.Endpoints = newEndpoints
config.RPCEndpoints = newEndpoints
err = rpcClient.UpdateConfig(config)
if err != nil {
return err
}
// 保存到文件
err = config.WriteFile(Tea.ConfigFile("api.yaml"))
err = config.WriteFile(Tea.ConfigFile(configs.ConfigFileName))
if err != nil {
return err
}
@@ -143,8 +144,9 @@ func (this *SyncAPINodesTask) testEndpoints(endpoints []string) bool {
cancel()
}()
var conn *grpc.ClientConn
if u.Scheme == "http" {
conn, err = grpc.DialContext(ctx, u.Host, grpc.WithInsecure(), grpc.WithBlock())
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,

View File

@@ -10,8 +10,10 @@ var DefaultCache = NewCache()
// TTL缓存
// 最大的缓存时间为30 * 86400
// Piece数据结构
// Piece1 | Piece2 | Piece3 | ...
// [ Item1, Item2, ... | ...
//
// Piece1 | Piece2 | Piece3 | ...
// [ Item1, Item2, ... | ...
//
// KeyMap列表数据结构
// { timestamp1 => [key1, key2, ...] }, ...
type Cache struct {
@@ -109,19 +111,11 @@ func (this *Cache) Read(key string) (item *Item) {
return this.pieces[uint64Key%this.countPieces].Read(uint64Key)
}
func (this *Cache) readIntKey(key uint64) (value *Item) {
return this.pieces[key%this.countPieces].Read(key)
}
func (this *Cache) Delete(key string) {
uint64Key := HashKey([]byte(key))
this.pieces[uint64Key%this.countPieces].Delete(uint64Key)
}
func (this *Cache) deleteIntKey(key uint64) {
this.pieces[key%this.countPieces].Delete(key)
}
func (this *Cache) Count() (count int) {
for _, piece := range this.pieces {
count += piece.Count()

View File

@@ -0,0 +1,79 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package apinodeutils
import (
"crypto/md5"
"fmt"
"io"
"os"
)
// DeployFile 部署文件描述
type DeployFile struct {
OS string
Arch string
Version string
Path string
}
// Sum 计算概要
func (this *DeployFile) Sum() (string, error) {
fp, err := os.Open(this.Path)
if err != nil {
return "", err
}
defer func() {
_ = fp.Close()
}()
m := md5.New()
buffer := make([]byte, 128*1024)
for {
n, err := fp.Read(buffer)
if err != nil {
if err == io.EOF {
break
}
return "", err
}
_, err = m.Write(buffer[:n])
if err != nil {
return "", err
}
}
sum := m.Sum(nil)
return fmt.Sprintf("%x", sum), nil
}
// Read 读取一个片段数据
func (this *DeployFile) Read(offset int64) (data []byte, newOffset int64, err error) {
fp, err := os.Open(this.Path)
if err != nil {
return nil, offset, err
}
defer func() {
_ = fp.Close()
}()
stat, err := fp.Stat()
if err != nil {
return nil, offset, err
}
if offset >= stat.Size() {
return nil, offset, io.EOF
}
_, err = fp.Seek(offset, io.SeekStart)
if err != nil {
return nil, offset, err
}
buffer := make([]byte, 128*1024)
n, err := fp.Read(buffer)
if err != nil {
return nil, offset, err
}
return buffer[:n], offset + int64(n), nil
}

View File

@@ -0,0 +1,96 @@
package apinodeutils
import (
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
stringutil "github.com/iwind/TeaGo/utils/string"
"regexp"
)
// DeployManager 节点部署文件管理器
// 如果节点部署文件有变化需要重启API节点以便于生效
type DeployManager struct {
dir string
}
// NewDeployManager 获取新节点部署文件管理器
func NewDeployManager() *DeployManager {
var manager = &DeployManager{
dir: Tea.Root + "/edge-api/deploy",
}
manager.LoadNodeFiles()
manager.LoadNSNodeFiles()
return manager
}
// LoadNodeFiles 加载所有边缘节点文件
func (this *DeployManager) LoadNodeFiles() []*DeployFile {
var keyMap = map[string]*DeployFile{} // key => File
var reg = regexp.MustCompile(`^edge-node-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
for _, file := range files.NewFile(this.dir).List() {
var name = file.Name()
if !reg.MatchString(name) {
continue
}
var matches = reg.FindStringSubmatch(name)
var osName = matches[1]
var arch = matches[2]
var version = matches[3]
var key = osName + "_" + arch
oldFile, ok := keyMap[key]
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
continue
}
keyMap[key] = &DeployFile{
OS: osName,
Arch: arch,
Version: version,
Path: file.Path(),
}
}
var result = []*DeployFile{}
for _, v := range keyMap {
result = append(result, v)
}
return result
}
// LoadNSNodeFiles 加载所有NS节点安装文件
func (this *DeployManager) LoadNSNodeFiles() []*DeployFile {
var keyMap = map[string]*DeployFile{} // key => File
var reg = regexp.MustCompile(`^edge-dns-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
for _, file := range files.NewFile(this.dir).List() {
var name = file.Name()
if !reg.MatchString(name) {
continue
}
var matches = reg.FindStringSubmatch(name)
var osName = matches[1]
var arch = matches[2]
var version = matches[3]
var key = osName + "_" + arch
oldFile, ok := keyMap[key]
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
continue
}
keyMap[key] = &DeployFile{
OS: osName,
Arch: arch,
Version: version,
Path: file.Path(),
}
}
var result = []*DeployFile{}
for _, v := range keyMap {
result = append(result, v)
}
return result
}

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,349 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package apinodeutils
import (
"compress/gzip"
"context"
"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.RPCEndpoints = apiNode.AccessAddrs
rpcClient, err := rpc.NewRPCClient(newAPIConfig, false)
if err != nil {
return err
}
// 升级边缘节点
err = this.upgradeNodes(sharedClient.Context(0), rpcClient)
if err != nil {
return err
}
// 升级NS节点
err = this.upgradeNSNodes(sharedClient.Context(0), rpcClient)
if err != nil {
return err
}
// 升级API节点
err = this.upgradeAPINode(sharedClient.Context(0), rpcClient)
if err != nil {
return fmt.Errorf("upgrade api node failed: %w", err)
}
return nil
}
// Progress 查看升级进程
func (this *Upgrader) Progress() *Progress {
return this.progress
}
// 升级API节点
func (this *Upgrader) upgradeAPINode(ctx context.Context, rpcClient *rpc.RPCClient) error {
versionResp, err := rpcClient.APINodeRPC().FindCurrentAPINodeVersion(ctx, &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 := lookupLocalVersion()
if err != nil {
return fmt.Errorf("lookup version failed: %w", err)
}
// 检查要升级的文件
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) upgradeNodes(ctx context.Context, rpcClient *rpc.RPCClient) error {
// 本地的
var manager = NewDeployManager()
var localFileMap = map[string]*DeployFile{} // os_arch => *DeployFile
for _, deployFile := range manager.LoadNodeFiles() {
localFileMap[deployFile.OS+"_"+deployFile.Arch] = deployFile
}
remoteFilesResp, err := rpcClient.APINodeRPC().FindLatestDeployFiles(ctx, &pb.FindLatestDeployFilesRequest{})
if err != nil {
return err
}
var remoteFileMap = map[string]*pb.FindLatestDeployFilesResponse_DeployFile{} // os_arch => *DeployFile
for _, nodeFile := range remoteFilesResp.NodeDeployFiles {
remoteFileMap[nodeFile.Os+"_"+nodeFile.Arch] = nodeFile
}
// 对比版本
for key, deployFile := range localFileMap {
remoteDeployFile, ok := remoteFileMap[key]
if !ok || stringutil.VersionCompare(remoteDeployFile.Version, deployFile.Version) < 0 {
err = this.uploadNodeDeployFile(ctx, rpcClient, deployFile.Path)
if err != nil {
return fmt.Errorf("upload deploy file '%s' failed: %w", filepath.Base(deployFile.Path), err)
}
}
}
return nil
}
// 升级NS节点
func (this *Upgrader) upgradeNSNodes(ctx context.Context, rpcClient *rpc.RPCClient) error {
// 本地的
var manager = NewDeployManager()
var localFileMap = map[string]*DeployFile{} // os_arch => *DeployFile
for _, deployFile := range manager.LoadNSNodeFiles() {
localFileMap[deployFile.OS+"_"+deployFile.Arch] = deployFile
}
remoteFilesResp, err := rpcClient.APINodeRPC().FindLatestDeployFiles(ctx, &pb.FindLatestDeployFilesRequest{})
if err != nil {
return err
}
var remoteFileMap = map[string]*pb.FindLatestDeployFilesResponse_DeployFile{} // os_arch => *DeployFile
for _, nodeFile := range remoteFilesResp.NsNodeDeployFiles {
remoteFileMap[nodeFile.Os+"_"+nodeFile.Arch] = nodeFile
}
// 对比版本
for key, deployFile := range localFileMap {
remoteDeployFile, ok := remoteFileMap[key]
if !ok || stringutil.VersionCompare(remoteDeployFile.Version, deployFile.Version) < 0 {
err = this.uploadNodeDeployFile(ctx, rpcClient, deployFile.Path)
if err != nil {
return fmt.Errorf("upload deploy file '%s' failed: %w", filepath.Base(deployFile.Path), err)
}
}
}
return nil
}
// 上传节点文件
func (this *Upgrader) uploadNodeDeployFile(ctx context.Context, rpcClient *rpc.RPCClient, path string) error {
fp, err := os.Open(path)
if err != nil {
return err
}
defer func() {
_ = fp.Close()
}()
var buf = make([]byte, 128*4096)
var isFirst = true
var hash = md5.New()
for {
n, err := fp.Read(buf)
if n > 0 {
hash.Write(buf[:n])
_, uploadErr := rpcClient.APINodeRPC().UploadDeployFileToAPINode(ctx, &pb.UploadDeployFileToAPINodeRequest{
Filename: filepath.Base(path),
Sum: "",
ChunkData: buf[:n],
IsFirstChunk: isFirst,
IsLastChunk: false,
})
if uploadErr != nil {
return uploadErr
}
isFirst = false
}
if err != nil {
if err == io.EOF {
err = nil
_, uploadErr := rpcClient.APINodeRPC().UploadDeployFileToAPINode(ctx, &pb.UploadDeployFileToAPINodeRequest{
Filename: filepath.Base(path),
Sum: fmt.Sprintf("%x", hash.Sum(nil)),
ChunkData: nil,
IsFirstChunk: false,
IsLastChunk: true,
})
if uploadErr != nil {
return uploadErr
}
break
}
return err
}
}
return nil
}

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,77 @@
// 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 := lookupLocalVersion()
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 lookupLocalVersion() (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"))
}

162
internal/utils/exec/cmd.go Normal file
View File

@@ -0,0 +1,162 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package executils
import (
"bytes"
"context"
"os"
"os/exec"
"strings"
"time"
)
type Cmd struct {
name string
args []string
env []string
dir string
ctx context.Context
timeout time.Duration
cancelFunc func()
captureStdout bool
captureStderr bool
stdout *bytes.Buffer
stderr *bytes.Buffer
rawCmd *exec.Cmd
}
func NewCmd(name string, args ...string) *Cmd {
return &Cmd{
name: name,
args: args,
}
}
func NewTimeoutCmd(timeout time.Duration, name string, args ...string) *Cmd {
return (&Cmd{
name: name,
args: args,
}).WithTimeout(timeout)
}
func (this *Cmd) WithTimeout(timeout time.Duration) *Cmd {
this.timeout = timeout
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
this.ctx = ctx
this.cancelFunc = cancelFunc
return this
}
func (this *Cmd) WithStdout() *Cmd {
this.captureStdout = true
return this
}
func (this *Cmd) WithStderr() *Cmd {
this.captureStderr = true
return this
}
func (this *Cmd) WithEnv(env []string) *Cmd {
this.env = env
return this
}
func (this *Cmd) WithDir(dir string) *Cmd {
this.dir = dir
return this
}
func (this *Cmd) Start() error {
var cmd = this.compose()
return cmd.Start()
}
func (this *Cmd) Wait() error {
var cmd = this.compose()
return cmd.Wait()
}
func (this *Cmd) Run() error {
if this.cancelFunc != nil {
defer this.cancelFunc()
}
var cmd = this.compose()
return cmd.Run()
}
func (this *Cmd) RawStdout() string {
if this.stdout != nil {
return this.stdout.String()
}
return ""
}
func (this *Cmd) Stdout() string {
return strings.TrimSpace(this.RawStdout())
}
func (this *Cmd) RawStderr() string {
if this.stderr != nil {
return this.stderr.String()
}
return ""
}
func (this *Cmd) Stderr() string {
return strings.TrimSpace(this.RawStderr())
}
func (this *Cmd) String() string {
if this.rawCmd != nil {
return this.rawCmd.String()
}
var newCmd = exec.Command(this.name, this.args...)
return newCmd.String()
}
func (this *Cmd) Process() *os.Process {
if this.rawCmd != nil {
return this.rawCmd.Process
}
return nil
}
func (this *Cmd) compose() *exec.Cmd {
if this.rawCmd != nil {
return this.rawCmd
}
if this.ctx != nil {
this.rawCmd = exec.CommandContext(this.ctx, this.name, this.args...)
} else {
this.rawCmd = exec.Command(this.name, this.args...)
}
if this.env != nil {
this.rawCmd.Env = this.env
}
if len(this.dir) > 0 {
this.rawCmd.Dir = this.dir
}
if this.captureStdout {
this.stdout = &bytes.Buffer{}
this.rawCmd.Stdout = this.stdout
}
if this.captureStderr {
this.stderr = &bytes.Buffer{}
this.rawCmd.Stderr = this.stderr
}
return this.rawCmd
}

View File

@@ -0,0 +1,61 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package executils_test
import (
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
"testing"
"time"
)
func TestNewTimeoutCmd_Sleep(t *testing.T) {
var cmd = executils.NewTimeoutCmd(1*time.Second, "sleep", "3")
cmd.WithStdout()
cmd.WithStderr()
err := cmd.Run()
t.Log("error:", err)
t.Log("stdout:", cmd.Stdout())
t.Log("stderr:", cmd.Stderr())
}
func TestNewTimeoutCmd_Echo(t *testing.T) {
var cmd = executils.NewTimeoutCmd(10*time.Second, "echo", "-n", "hello")
cmd.WithStdout()
cmd.WithStderr()
err := cmd.Run()
t.Log("error:", err)
t.Log("stdout:", cmd.Stdout())
t.Log("stderr:", cmd.Stderr())
}
func TestNewTimeoutCmd_Echo2(t *testing.T) {
var cmd = executils.NewCmd("echo", "hello")
cmd.WithStdout()
cmd.WithStderr()
err := cmd.Run()
t.Log("error:", err)
t.Log("stdout:", cmd.Stdout())
t.Log("raw stdout:", cmd.RawStdout())
t.Log("stderr:", cmd.Stderr())
t.Log("raw stderr:", cmd.RawStderr())
}
func TestNewTimeoutCmd_Echo3(t *testing.T) {
var cmd = executils.NewCmd("echo", "-n", "hello")
err := cmd.Run()
t.Log("error:", err)
t.Log("stdout:", cmd.Stdout())
t.Log("stderr:", cmd.Stderr())
}
func TestCmd_Process(t *testing.T) {
var cmd = executils.NewCmd("echo", "-n", "hello")
err := cmd.Run()
t.Log("error:", err)
t.Log(cmd.Process())
}
func TestNewTimeoutCmd_String(t *testing.T) {
var cmd = executils.NewCmd("echo", "-n", "hello")
t.Log("stdout:", cmd.String())
}

View File

@@ -0,0 +1,58 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build linux
package executils
import (
"golang.org/x/sys/unix"
"io/fs"
"os"
"os/exec"
"syscall"
)
// LookPath customize our LookPath() function, to work in broken $PATH environment variable
func LookPath(file string) (string, error) {
result, err := exec.LookPath(file)
if err == nil && len(result) > 0 {
return result, nil
}
// add common dirs contains executable files these may be excluded in $PATH environment variable
var binPaths = []string{
"/usr/sbin",
"/usr/bin",
"/usr/local/sbin",
"/usr/local/bin",
}
for _, binPath := range binPaths {
var fullPath = binPath + string(os.PathSeparator) + file
stat, err := os.Stat(fullPath)
if err != nil {
continue
}
if stat.IsDir() {
return "", syscall.EISDIR
}
var mode = stat.Mode()
if mode.IsDir() {
return "", syscall.EISDIR
}
err = syscall.Faccessat(unix.AT_FDCWD, fullPath, unix.X_OK, unix.AT_EACCESS)
if err == nil || (err != syscall.ENOSYS && err != syscall.EPERM) {
return fullPath, err
}
if mode&0111 != 0 {
return fullPath, nil
}
return "", fs.ErrPermission
}
return "", &exec.Error{
Name: file,
Err: exec.ErrNotFound,
}
}

View File

@@ -0,0 +1,10 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !linux
package executils
import "os/exec"
func LookPath(file string) (string, error) {
return exec.LookPath(file)
}

View File

@@ -3,6 +3,7 @@
package utils
import (
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/types"
"os/exec"
@@ -14,7 +15,7 @@ func AddPortsToFirewall(ports []int) {
// Linux
if runtime.GOOS == "linux" {
// firewalld
firewallCmd, _ := exec.LookPath("firewall-cmd")
firewallCmd, _ := executils.LookPath("firewall-cmd")
if len(firewallCmd) > 0 {
err := exec.Command(firewallCmd, "--add-port="+types.String(port)+"/tcp").Run()
if err == nil {

View File

@@ -3,7 +3,9 @@
package utils
import (
"bytes"
"encoding/json"
"errors"
"reflect"
)
@@ -22,3 +24,37 @@ func JSONClone(v interface{}) (interface{}, error) {
return nv, nil
}
// JSONIsNull 判断JSON数据是否为null
func JSONIsNull(jsonData []byte) bool {
return len(jsonData) == 0 || bytes.Equal(jsonData, []byte("null"))
}
// JSONDecodeConfig 解码并重新编码
// 是为了去除原有JSON中不需要的数据
func JSONDecodeConfig(data []byte, ptr any) (encodeJSON []byte, err error) {
err = json.Unmarshal(data, ptr)
if err != nil {
return
}
encodeJSON, err = json.Marshal(ptr)
if err != nil {
return
}
// validate config
if ptr != nil {
config, ok := ptr.(interface {
Init() error
})
if ok {
initErr := config.Init()
if initErr != nil {
err = errors.New("validate config failed: " + initErr.Error())
}
}
}
return
}

View File

@@ -4,6 +4,7 @@ package utils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/iwind/TeaGo/assert"
"testing"
)
@@ -23,3 +24,12 @@ func TestJSONClone(t *testing.T) {
t.Logf("%p, %#v", c, c)
}
}
func TestJSONIsNull(t *testing.T) {
var a = assert.NewAssertion(t)
a.IsTrue(utils.JSONIsNull(nil))
a.IsTrue(utils.JSONIsNull([]byte{}))
a.IsTrue(utils.JSONIsNull([]byte("null")))
a.IsFalse(utils.JSONIsNull([]byte{1, 2, 3}))
}

View File

@@ -1,35 +1,81 @@
package utils
import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs"
"github.com/miekg/dns"
"sync"
)
var sharedDNSClient *dns.Client
var sharedDNSConfig *dns.ClientConfig
func init() {
if !teaconst.IsMain {
return
}
config, err := dns.ClientConfigFromFile("/etc/resolv.conf")
if err != nil {
logs.Println("ERROR: configure dns client failed: " + err.Error())
return
}
sharedDNSConfig = config
sharedDNSClient = &dns.Client{}
}
// LookupCNAME 获取CNAME
func LookupCNAME(host string) (string, error) {
config, err := dns.ClientConfigFromFile("/etc/resolv.conf")
if err != nil {
return "", err
}
c := new(dns.Client)
m := new(dns.Msg)
var m = new(dns.Msg)
m.SetQuestion(host+".", dns.TypeCNAME)
m.RecursionDesired = true
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
}
var success = false
var result = ""
return r.Answer[0].(*dns.CNAME).Target, nil
var serverAddrs = sharedDNSConfig.Servers
{
var publicDNSHosts = []string{"8.8.8.8" /** Google **/, "8.8.4.4" /** Google **/}
for _, publicDNSHost := range publicDNSHosts {
if !lists.ContainsString(serverAddrs, publicDNSHost) {
serverAddrs = append(serverAddrs, publicDNSHost)
}
}
}
var wg = &sync.WaitGroup{}
for _, serverAddr := range serverAddrs {
wg.Add(1)
go func(serverAddr string) {
defer wg.Done()
r, _, err := sharedDNSClient.Exchange(m, configutils.QuoteIP(serverAddr)+":"+sharedDNSConfig.Port)
if err != nil {
lastErr = err
return
}
success = true
if len(r.Answer) == 0 {
return
}
result = r.Answer[0].(*dns.CNAME).Target
}(serverAddr)
}
wg.Wait()
if success {
return result, nil
}
return "", lastErr
}

View File

@@ -0,0 +1,15 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package utils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"testing"
)
func TestLookupCNAME(t *testing.T) {
for _, domain := range []string{"www.yun4s.cn", "example.com"} {
result, err := utils.LookupCNAME(domain)
t.Log(domain, "=>", result, err)
}
}

View File

@@ -4,22 +4,24 @@
package nodelogutils
import (
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/iwind/TeaGo/maps"
)
// FindCommonTags 查找常用的标签
func FindNodeCommonTags() []maps.Map {
func FindNodeCommonTags(langCode langs.LangCode) []maps.Map {
return []maps.Map{
{
"name": "端口监听",
"name": langs.Message(langCode, codes.Log_TagListener),
"code": "LISTENER",
},
{
"name": "WAF",
"name": langs.Message(langCode, codes.Log_TagWAF),
"code": "WAF",
},
{
"name": "访问日志",
"name": langs.Message(langCode, codes.Log_TagAccessLog),
"code": "ACCESS_LOG",
},
}

View File

@@ -3,7 +3,9 @@ package numberutils
import (
"fmt"
"github.com/iwind/TeaGo/types"
"regexp"
"strconv"
"strings"
)
func FormatInt64(value int64) string {
@@ -28,17 +30,35 @@ func FormatBytes(bytes int64) string {
if bytes < Pow1024(1) {
return FormatInt64(bytes) + "B"
} else if bytes < Pow1024(2) {
return fmt.Sprintf("%.2fKB", float64(bytes)/float64(Pow1024(1)))
return TrimZeroSuffix(fmt.Sprintf("%.2fKiB", float64(bytes)/float64(Pow1024(1))))
} else if bytes < Pow1024(3) {
return fmt.Sprintf("%.2fMB", float64(bytes)/float64(Pow1024(2)))
return TrimZeroSuffix(fmt.Sprintf("%.2fMiB", float64(bytes)/float64(Pow1024(2))))
} else if bytes < Pow1024(4) {
return fmt.Sprintf("%.2fGB", float64(bytes)/float64(Pow1024(3)))
return TrimZeroSuffix(fmt.Sprintf("%.2fGiB", float64(bytes)/float64(Pow1024(3))))
} else if bytes < Pow1024(5) {
return fmt.Sprintf("%.2fTB", float64(bytes)/float64(Pow1024(4)))
return TrimZeroSuffix(fmt.Sprintf("%.2fTiB", float64(bytes)/float64(Pow1024(4))))
} else if bytes < Pow1024(6) {
return fmt.Sprintf("%.2fPB", float64(bytes)/float64(Pow1024(5)))
return TrimZeroSuffix(fmt.Sprintf("%.2fPiB", float64(bytes)/float64(Pow1024(5))))
} else {
return fmt.Sprintf("%.2fEB", float64(bytes)/float64(Pow1024(6)))
return TrimZeroSuffix(fmt.Sprintf("%.2fEiB", float64(bytes)/float64(Pow1024(6))))
}
}
func FormatBits(bits int64) string {
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 TrimZeroSuffix(fmt.Sprintf("%.4fEbps", float64(bits)/float64(Pow1024(6))))
}
}
@@ -55,22 +75,92 @@ func FormatCount(count int64) string {
return fmt.Sprintf("%.1fB", float32(count)/1000/1000/1000)
}
func FormatFloat(f interface{}, decimal int) string {
func FormatFloat(f any, 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 types.String(x)
return formatDigit(types.String(x))
case string:
return x
}
return ""
}
func FormatFloat2(f interface{}) string {
func FormatFloat2(f any) string {
return FormatFloat(f, 2)
}
// PadFloatZero 为浮点型数字字符串填充足够的0
func PadFloatZero(s string, countZero int) string {
if countZero <= 0 {
return s
}
if len(s) == 0 {
s = "0"
}
var index = strings.Index(s, ".")
if index < 0 {
return s + "." + strings.Repeat("0", countZero)
}
var decimalLen = len(s) - 1 - index
if decimalLen < countZero {
return s + strings.Repeat("0", countZero-decimalLen)
}
return s
}
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

@@ -4,6 +4,7 @@ package numberutils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/numberutils"
"github.com/iwind/TeaGo/assert"
"testing"
)
@@ -33,4 +34,53 @@ func TestFormatFloat(t *testing.T) {
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 TestFormatFloat2(t *testing.T) {
t.Log(numberutils.FormatFloat2(0))
t.Log(numberutils.FormatFloat2(0.0))
t.Log(numberutils.FormatFloat2(1.23456))
t.Log(numberutils.FormatFloat2(1.0))
}
func TestPadFloatZero(t *testing.T) {
var a = assert.NewAssertion(t)
a.IsTrue(numberutils.PadFloatZero("1", 0) == "1")
a.IsTrue(numberutils.PadFloatZero("1", 2) == "1.00")
a.IsTrue(numberutils.PadFloatZero("1.1", 2) == "1.10")
a.IsTrue(numberutils.PadFloatZero("1.12", 2) == "1.12")
a.IsTrue(numberutils.PadFloatZero("1.123", 2) == "1.123")
a.IsTrue(numberutils.PadFloatZero("10000.123", 2) == "10000.123")
a.IsTrue(numberutils.PadFloatZero("", 2) == "0.00")
}
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

@@ -39,7 +39,7 @@ func (this *ServiceManager) setup() {
this.onceLocker.Do(func() {
logFile := files.NewFile(Tea.Root + "/logs/service.log")
if logFile.Exists() {
logFile.Delete()
_ = logFile.Delete()
}
//logger

View File

@@ -5,6 +5,7 @@ package utils
import (
"errors"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
"os"
@@ -21,7 +22,7 @@ func (this *ServiceManager) Install(exePath string, args []string) error {
return errors.New("only root users can install the service")
}
systemd, err := exec.LookPath("systemctl")
systemd, err := executils.LookPath("systemctl")
if err != nil {
return this.installInitService(exePath, args)
}
@@ -36,7 +37,7 @@ func (this *ServiceManager) Start() error {
}
if files.NewFile(systemdServiceFile).Exists() {
systemd, err := exec.LookPath("systemctl")
systemd, err := executils.LookPath("systemctl")
if err != nil {
return err
}
@@ -53,16 +54,16 @@ func (this *ServiceManager) Uninstall() error {
}
if files.NewFile(systemdServiceFile).Exists() {
systemd, err := exec.LookPath("systemctl")
systemd, err := executils.LookPath("systemctl")
if err != nil {
return err
}
// disable service
exec.Command(systemd, "disable", teaconst.SystemdServiceName+".service").Start()
_ = exec.Command(systemd, "disable", teaconst.SystemdServiceName+".service").Start()
// reload
exec.Command(systemd, "daemon-reload")
_ = exec.Command(systemd, "daemon-reload").Start()
return files.NewFile(systemdServiceFile).Delete()
}
@@ -93,7 +94,7 @@ func (this *ServiceManager) installInitService(exePath string, args []string) er
return err
}
chkCmd, err := exec.LookPath("chkconfig")
chkCmd, err := executils.LookPath("chkconfig")
if err != nil {
return err
}
@@ -111,7 +112,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
@@ -127,7 +129,7 @@ After=network-online.target
[Service]
Type=simple
Restart=always
RestartSec=1s
RestartSec=5s
ExecStart=` + exePath + ` daemon
ExecStop=` + exePath + ` stop
ExecReload=` + exePath + ` reload
@@ -142,10 +144,10 @@ WantedBy=multi-user.target`
}
// stop current systemd service if running
exec.Command(systemd, "stop", shortName+".service")
_ = exec.Command(systemd, "stop", shortName+".service").Start()
// reload
exec.Command(systemd, "daemon-reload")
_ = exec.Command(systemd, "daemon-reload").Start()
// enable
cmd := exec.Command(systemd, "enable", shortName+".service")

View File

@@ -4,6 +4,7 @@ package utils
import (
"fmt"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/iwind/TeaGo/Tea"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
@@ -15,7 +16,7 @@ import (
func (this *ServiceManager) Install(exePath string, args []string) error {
m, err := mgr.Connect()
if err != nil {
return fmt.Errorf("connecting: %s please 'Run as administrator' again", err.Error())
return fmt.Errorf("connecting: %w please 'Run as administrator' again", err)
}
defer m.Disconnect()
s, err := m.OpenService(this.Name)
@@ -30,7 +31,7 @@ func (this *ServiceManager) Install(exePath string, args []string) error {
StartType: windows.SERVICE_AUTO_START,
}, args...)
if err != nil {
return fmt.Errorf("creating: %s", err.Error())
return fmt.Errorf("creating: %w", err)
}
defer s.Close()
@@ -46,12 +47,12 @@ func (this *ServiceManager) Start() error {
defer m.Disconnect()
s, err := m.OpenService(this.Name)
if err != nil {
return fmt.Errorf("could not access service: %v", err)
return fmt.Errorf("could not access service: %w", err)
}
defer s.Close()
err = s.Start("service")
if err != nil {
return fmt.Errorf("could not start service: %v", err)
return fmt.Errorf("could not start service: %w", err)
}
return nil
@@ -61,12 +62,12 @@ func (this *ServiceManager) Start() error {
func (this *ServiceManager) Uninstall() error {
m, err := mgr.Connect()
if err != nil {
return fmt.Errorf("connecting: %s please 'Run as administrator' again", err.Error())
return fmt.Errorf("connecting: %w please 'Run as administrator' again", err)
}
defer m.Disconnect()
s, err := m.OpenService(this.Name)
if err != nil {
return fmt.Errorf("open service: %s", err.Error())
return fmt.Errorf("open service: %w", err)
}
// shutdown service
@@ -78,7 +79,7 @@ func (this *ServiceManager) Uninstall() error {
defer s.Close()
err = s.Delete()
if err != nil {
return fmt.Errorf("deleting: %s", err.Error())
return fmt.Errorf("deleting: %w", err)
}
return nil
}

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())
}

View File

@@ -0,0 +1,56 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package taskutils
import (
"errors"
"reflect"
"sync"
)
func RunConcurrent(tasks any, concurrent int, f func(task any)) error {
if tasks == nil {
return nil
}
var tasksValue = reflect.ValueOf(tasks)
if tasksValue.Type().Kind() != reflect.Slice {
return errors.New("ony works for slice")
}
var countTasks = tasksValue.Len()
if countTasks == 0 {
return nil
}
if concurrent <= 0 {
concurrent = 8
}
if concurrent > countTasks {
concurrent = countTasks
}
var taskChan = make(chan any, countTasks)
for i := 0; i < countTasks; i++ {
taskChan <- tasksValue.Index(i).Interface()
}
var wg = &sync.WaitGroup{}
wg.Add(concurrent)
for i := 0; i < concurrent; i++ {
go func() {
defer wg.Done()
for {
select {
case task := <-taskChan:
f(task)
default:
return
}
}
}()
}
wg.Wait()
return nil
}

View File

@@ -0,0 +1,17 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package taskutils_test
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils/taskutils"
"testing"
)
func TestRunConcurrent(t *testing.T) {
err := taskutils.RunConcurrent([]string{"a", "b", "c", "d", "e"}, 3, func(task any) {
t.Log("run", task)
})
if err != nil {
t.Fatal(err)
}
}

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
}

95
internal/utils/unzip.go Normal file
View File

@@ -0,0 +1,95 @@
package utils
import (
"archive/zip"
"errors"
"io"
"os"
)
type Unzip struct {
zipFile string
targetDir string
}
func NewUnzip(zipFile string, targetDir string) *Unzip {
return &Unzip{
zipFile: zipFile,
targetDir: targetDir,
}
}
func (this *Unzip) Run() error {
if len(this.zipFile) == 0 {
return errors.New("zip file should not be empty")
}
if len(this.targetDir) == 0 {
return errors.New("target dir should not be empty")
}
reader, err := zip.OpenReader(this.zipFile)
if err != nil {
return err
}
defer func() {
_ = reader.Close()
}()
for _, file := range reader.File {
var info = file.FileInfo()
var target = this.targetDir + "/" + file.Name
// 目录
if info.IsDir() {
stat, err := os.Stat(target)
if err != nil {
if !os.IsNotExist(err) {
return err
} else {
err = os.MkdirAll(target, info.Mode())
if err != nil {
return err
}
}
} else if !stat.IsDir() {
err = os.MkdirAll(target, info.Mode())
if err != nil {
return err
}
}
continue
}
// 文件
err = func(file *zip.File, target string) error {
fileReader, err := file.Open()
if err != nil {
return err
}
defer func() {
_ = fileReader.Close()
}()
// remove old
_ = os.Remove(target)
// create new
fileWriter, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, file.FileInfo().Mode())
if err != nil {
return err
}
defer func() {
_ = fileWriter.Close()
}()
_, err = io.Copy(fileWriter, fileReader)
return err
}(file, target)
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,308 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
"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, _ := executils.LookPath("unzip")
// 检查cp
cpExe, _ := executils.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 fmt.Errorf("create url request failed: %w", err)
}
req.Header.Set("User-Agent", "Edge-Admin/"+teaconst.Version)
resp, err := this.client.Do(req)
if err != nil {
return fmt.Errorf("read latest version failed: %w", err)
}
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 fmt.Errorf("read latest version failed: %w", err)
}
var m = maps.Map{}
err = json.Unmarshal(data, &m)
if err != nil {
return fmt.Errorf("invalid response data: %w, origin data: %s", err, 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 fmt.Errorf("create download request failed: %w", err)
}
req.Header.Set("User-Agent", "Edge-Admin/"+teaconst.Version)
resp, err := this.client.Do(req)
if err != nil {
return fmt.Errorf("download failed: '%s': %w", downloadURL, err)
}
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 fmt.Errorf("create file failed: %w", err)
}
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 fmt.Errorf("download failed: %w", err)
}
_ = 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 fmt.Errorf("remove old dir '%s' failed: %w", unzipDir, err)
}
}
if len(unzipExe) > 0 {
var unzipCmd = exec.Command(unzipExe, "-q", "-o", destFile, "-d", unzipDir)
var unzipStderr = &bytes.Buffer{}
unzipCmd.Stderr = unzipStderr
err = unzipCmd.Run()
if err != nil {
return fmt.Errorf("unzip installation file failed: %w: %s", err, unzipStderr.String())
}
} else {
var unzipCmd = &Unzip{
zipFile: destFile,
targetDir: unzipDir,
}
err = unzipCmd.Run()
if err != nil {
return fmt.Errorf("unzip installation file failed: %w", err)
}
}
installationFiles, err := filepath.Glob(unzipDir + "/edge-" + this.component + "/*")
if err != nil {
return fmt.Errorf("lookup installation files failed: %w", err)
}
// cp to target dir
currentExe, err := os.Executable()
if err != nil {
return fmt.Errorf("reveal current executable file path failed: %w", err)
}
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

@@ -5,9 +5,13 @@ import (
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index/loginutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/logs"
@@ -35,7 +39,7 @@ func (this *ParentAction) ErrorPage(err error) {
}
// 日志
this.CreateLog(oplogs.LevelError, "系统发生错误:%s", err.Error())
this.CreateLog(oplogs.LevelError, codes.AdminCommon_LogSystemError, err.Error())
if this.Request.Method == http.MethodGet {
FailPage(this, err)
@@ -49,7 +53,11 @@ 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 {
@@ -85,11 +93,12 @@ func (this *ParentAction) TinyMenu(menuItem string) {
}
func (this *ParentAction) AdminId() int64 {
return this.Context.GetInt64("adminId")
return this.Context.GetInt64(teaconst.SessionAdminId)
}
func (this *ParentAction) CreateLog(level string, description string, args ...interface{}) {
desc := fmt.Sprintf(description, args...)
func (this *ParentAction) CreateLog(level string, messageCode langs.MessageCode, args ...any) {
var description = messageCode.For(this.LangCode())
var desc = fmt.Sprintf(description, args...)
if level == oplogs.LevelInfo {
if this.Code != 200 {
level = oplogs.LevelWarn
@@ -98,14 +107,14 @@ func (this *ParentAction) CreateLog(level string, description string, args ...in
}
}
}
err := dao.SharedLogDAO.CreateAdminLog(this.AdminContext(), level, this.Request.URL.Path, desc, this.RequestRemoteIP())
err := dao.SharedLogDAO.CreateAdminLog(this.AdminContext(), level, this.Request.URL.Path, desc, loginutils.RemoteIP(&this.ActionObject), messageCode, args)
if err != nil {
utils.PrintError(err)
}
}
func (this *ParentAction) CreateLogInfo(description string, args ...interface{}) {
this.CreateLog(oplogs.LevelInfo, description, args...)
func (this *ParentAction) CreateLogInfo(messageCode langs.MessageCode, args ...any) {
this.CreateLog(oplogs.LevelInfo, messageCode, args...)
}
// RPC 获取RPC
@@ -126,11 +135,8 @@ func (this *ParentAction) RPC() *rpc.RPCClient {
}
// AdminContext 获取Context
// 每个请求的context都必须是一个新的实例
func (this *ParentAction) AdminContext() context.Context {
if this.ctx != nil {
return this.ctx
}
if this.rpcClient == nil {
rpcClient, err := rpc.SharedRPC()
if err != nil {
@@ -147,3 +153,19 @@ func (this *ParentAction) AdminContext() context.Context {
func (this *ParentAction) ViewData() maps.Map {
return this.Data
}
func (this *ParentAction) LangCode() string {
return configloaders.FindAdminLangForAction(this)
}
func (this *ParentAction) Lang(messageCode langs.MessageCode, args ...any) string {
return langs.Message(this.LangCode(), messageCode, args...)
}
func (this *ParentAction) FailLang(messageCode langs.MessageCode, args ...any) {
this.Fail(langs.Message(this.LangCode(), messageCode, args...))
}
func (this *ParentAction) FailFieldLang(field string, messageCode langs.MessageCode, args ...any) {
this.FailField(field, langs.Message(this.LangCode(), messageCode, args...))
}

View File

@@ -2,37 +2,49 @@ package actionutils
import (
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type TabItem struct {
Name string `json:"name"`
SubName string `json:"subName"`
URL string `json:"url"`
Icon string `json:"icon"`
IsActive bool `json:"isActive"`
IsRight bool `json:"isRight"`
IsTitle bool `json:"isTitle"`
IsDisabled bool `json:"isDisabled"`
}
// Tabbar Tabbar定义
type Tabbar struct {
items []maps.Map
items []*TabItem
}
// NewTabbar 获取新对象
func NewTabbar() *Tabbar {
return &Tabbar{
items: []maps.Map{},
items: []*TabItem{},
}
}
// Add 添加菜单项
func (this *Tabbar) Add(name string, subName string, url string, icon string, active bool) maps.Map {
m := maps.Map{
"name": name,
"subName": subName,
"url": url,
"icon": icon,
"active": active,
"right": false,
func (this *Tabbar) Add(name string, subName string, url string, icon string, active bool) *TabItem {
var m = &TabItem{
Name: name,
SubName: subName,
URL: url,
Icon: icon,
IsActive: active,
IsRight: false,
IsTitle: false,
IsDisabled: false,
}
this.items = append(this.items, m)
return m
}
// Items 取得所有的Items
func (this *Tabbar) Items() []maps.Map {
func (this *Tabbar) Items() []*TabItem {
return this.items
}

View File

@@ -4,13 +4,15 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
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"
@@ -18,70 +20,50 @@ import (
"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 + "" + err.Error() + "")
}
// FailPage 提示页面错误信息
func FailPage(action actions.ActionWrapper, err error) {
func Fail(actionPtr actions.ActionWrapper, err error) {
if err == nil {
err = errors.New("unknown error")
}
logs.Println("[" + reflect.TypeOf(action).String() + "]" + findStack(err.Error()))
var langCode = configloaders.FindAdminLangForAction(actionPtr)
var serverErrString = codes.AdminCommon_ServerError.For(langCode)
// 当前API终端地址
var apiEndpoints = []string{}
apiConfig, apiConfigErr := configs.LoadAPIConfig()
if apiConfigErr == nil && apiConfig != nil {
apiEndpoints = append(apiEndpoints, apiConfig.RPC.Endpoints...)
logs.Println("[" + reflect.TypeOf(actionPtr).String() + "]" + findStack(err.Error()))
_, _, isLocalAPI, issuesHTML := parseAPIErr(actionPtr, err)
if isLocalAPI && len(issuesHTML) > 0 {
actionPtr.Object().Fail(serverErrString + "" + err.Error() + ";最近一次错误提示:" + issuesHTML + "")
} else {
actionPtr.Object().Fail(serverErrString + "" + err.Error() + "")
}
var isRPCConnError bool
err, isRPCConnError = rpcerrors.HumanError(err, apiEndpoints, Tea.ConfigFile("api.yaml"))
}
action.Object().ResponseWriter.WriteHeader(http.StatusInternalServerError)
if len(action.Object().Request.Header.Get("X-Requested-With")) > 0 {
action.Object().WriteString(teaconst.ErrServer)
// FailPage 提示页面错误信息
func FailPage(actionPtr actions.ActionWrapper, err error) {
if err == nil {
err = errors.New("unknown error")
}
var langCode = configloaders.FindAdminLangForAction(actionPtr)
var serverErrString = codes.AdminCommon_ServerError.For(langCode)
logs.Println("[" + reflect.TypeOf(actionPtr).String() + "]" + findStack(err.Error()))
actionPtr.Object().ResponseWriter.WriteHeader(http.StatusInternalServerError)
if len(actionPtr.Object().Request.Header.Get("X-Requested-With")) > 0 {
actionPtr.Object().WriteString(serverErrString)
} 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")
}
}
}
apiNodeIsStarting, apiNodeProgress, _, issuesHTML := parseAPIErr(actionPtr, err)
var html = `<!DOCTYPE html>
<html>
<head>
<title>有系统错误需要处理</title>
<title>正在处理...</title>
<meta charset="UTF-8"/>
<style type="text/css">
hr { border-top: 1px #ccc solid; }
@@ -90,17 +72,28 @@ func FailPage(action actions.ActionWrapper, err error) {
</head>
<body>
<div style="background: #eee; border: 1px #ccc solid; padding: 10px; font-size: 12px; line-height: 1.8">
` + teaconst.ErrServer + `
`
if apiNodeIsStarting { // API节点正在启动
html += "<div class=\"red\">API节点正在启动请耐心等待完成"
if len(apiNodeProgress) > 0 {
html += "" + apiNodeProgress + "(刷新当前页面查看最新状态)"
}
html += "</div>"
} else {
html += serverErrString + `
<div>可以通过查看 <strong><em>$安装目录/logs/run.log</em></strong> 日志文件查看具体的错误提示。</div>
<hr/>
<div class="red">Error: ` + err.Error() + `</div>`
if len(issuesHTML) > 0 {
html += ` <hr/>
if len(issuesHTML) > 0 {
html += ` <hr/>
<div class="red">` + issuesHTML + `</div>`
}
}
action.Object().WriteString(html + `
actionPtr.Object().WriteString(html + `
</div>
</body>
</html>`)
@@ -114,13 +107,13 @@ func MatchPath(action *actions.ActionObject, path string) bool {
// FindParentAction 查找父级Action
func FindParentAction(actionPtr actions.ActionWrapper) *ParentAction {
parentActionValue := reflect.ValueOf(actionPtr).Elem().FieldByName("ParentAction")
if parentActionValue.IsValid() {
parentAction, isOk := parentActionValue.Interface().(ParentAction)
if isOk {
return &parentAction
}
action, ok := actionPtr.(interface {
Parent() *ParentAction
})
if ok {
return action.Parent()
}
return nil
}
@@ -147,7 +140,7 @@ func findStack(err string) string {
filename = filename[strings.Index(filename, "src"):]
}
err += "\n\t\t" + string(filename) + ":" + fmt.Sprintf("%d", lineNo)
err += "\n\t\t" + filename + ":" + fmt.Sprintf("%d", lineNo)
break
}
@@ -155,3 +148,61 @@ func findStack(err string) string {
return err
}
// 分析API节点的错误信息
func parseAPIErr(action actions.ActionWrapper, err error) (apiNodeIsStarting bool, apiNodeProgress string, isLocalAPI bool, issuesHTML string) {
// 当前API终端地址
var apiEndpoints = []string{}
apiConfig, apiConfigErr := configs.LoadAPIConfig()
if apiConfigErr == nil && apiConfig != nil {
apiEndpoints = append(apiEndpoints, apiConfig.RPCEndpoints...)
}
var isRPCConnError bool
_, isRPCConnError = rpcerrors.HumanError(err, apiEndpoints, Tea.ConfigFile(configs.ConfigFileName))
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")
}
}
}
// 本地的一些错误提示
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
}
}
}
}
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")
}
}
}
return
}

View File

@@ -4,6 +4,7 @@ package accesskeys
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
@@ -43,7 +44,7 @@ func (this *CreatePopupAction) RunPost(params struct {
return
}
defer this.CreateLogInfo("创建AccessKey %d", accessKeyIdResp.UserAccessKeyId)
defer this.CreateLogInfo(codes.UserAccessKey_LogCreateUserAccessKey, accessKeyIdResp.UserAccessKeyId)
this.Success()
}

View File

@@ -2,6 +2,7 @@ package accesskeys
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
@@ -12,7 +13,7 @@ type DeleteAction struct {
func (this *DeleteAction) RunPost(params struct {
AccessKeyId int64
}) {
defer this.CreateLogInfo("删除AccessKey %d", params.AccessKeyId)
defer this.CreateLogInfo(codes.UserAccessKey_LogDeleteUserAccessKey, params.AccessKeyId)
_, err := this.RPC().UserAccessKeyRPC().DeleteUserAccessKey(this.AdminContext(), &pb.DeleteUserAccessKeyRequest{UserAccessKeyId: params.AccessKeyId})
if err != nil {

View File

@@ -2,6 +2,7 @@ package accesskeys
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
@@ -13,7 +14,7 @@ func (this *UpdateIsOnAction) RunPost(params struct {
AccessKeyId int64
IsOn bool
}) {
defer this.CreateLogInfo("设置AccessKey %d 启用状态", params.AccessKeyId)
defer this.CreateLogInfo(codes.UserAccessKey_LogUpdateUserAccessKeyIsOn, params.AccessKeyId)
_, err := this.RPC().UserAccessKeyRPC().UpdateUserAccessKeyIsOn(this.AdminContext(), &pb.UpdateUserAccessKeyIsOnRequest{
UserAccessKeyId: params.AccessKeyId,

View File

@@ -45,12 +45,13 @@ func (this *AdminAction) RunGet(params struct {
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"canLogin": admin.CanLogin,
"hasWeakPassword": admin.HasWeakPassword,
"countAccessKeys": countAccessKeys,
}
// 权限
moduleMaps := []maps.Map{}
for _, m := range configloaders.AllModuleMaps() {
for _, m := range configloaders.AllModuleMaps(this.LangCode()) {
code := m.GetString("code")
isChecked := false
for _, module := range admin.Modules {

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/actions"
@@ -20,7 +21,7 @@ func (this *CreatePopupAction) Init() {
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Data["modules"] = configloaders.AllModuleMaps()
this.Data["modules"] = configloaders.AllModuleMaps(this.LangCode())
this.Show()
}
@@ -114,7 +115,7 @@ func (this *CreatePopupAction) RunPost(params struct {
}
}
defer this.CreateLogInfo("创建系统用户 %d", createResp.AdminId)
defer this.CreateLogInfo(codes.Admin_LogCreateAdmin, createResp.AdminId)
// 通知更改
err = configloaders.NotifyAdminModuleMappingChange()

View File

@@ -3,6 +3,7 @@ package admins
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
@@ -13,7 +14,7 @@ type DeleteAction struct {
func (this *DeleteAction) RunPost(params struct {
AdminId int64
}) {
defer this.CreateLogInfo("删除系统用户 %d", params.AdminId)
defer this.CreateLogInfo(codes.Admin_LogDeleteAdmin, params.AdminId)
_, err := this.RPC().AdminRPC().DeleteAdmin(this.AdminContext(), &pb.DeleteAdminRequest{AdminId: params.AdminId})
if err != nil {

View File

@@ -15,34 +15,46 @@ func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
countResp, err := this.RPC().AdminRPC().CountAllEnabledAdmins(this.AdminContext(), &pb.CountAllEnabledAdminsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
page := this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
func (this *IndexAction) RunGet(params struct {
Keyword string
HasWeakPassword bool
}) {
this.Data["keyword"] = params.Keyword
this.Data["hasWeakPassword"] = params.HasWeakPassword
adminsResp, err := this.RPC().AdminRPC().ListEnabledAdmins(this.AdminContext(), &pb.ListEnabledAdminsRequest{
Offset: page.Offset,
Size: page.Size,
countResp, err := this.RPC().AdminRPC().CountAllEnabledAdmins(this.AdminContext(), &pb.CountAllEnabledAdminsRequest{
Keyword: params.Keyword,
HasWeakPassword: params.HasWeakPassword,
})
if err != nil {
this.ErrorPage(err)
return
}
adminMaps := []maps.Map{}
var page = this.NewPage(countResp.Count)
this.Data["page"] = page.AsHTML()
adminsResp, err := this.RPC().AdminRPC().ListEnabledAdmins(this.AdminContext(), &pb.ListEnabledAdminsRequest{
Keyword: params.Keyword,
HasWeakPassword: params.HasWeakPassword,
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
var adminMaps = []maps.Map{}
for _, admin := range adminsResp.Admins {
adminMaps = append(adminMaps, maps.Map{
"id": admin.Id,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"username": admin.Username,
"fullname": admin.Fullname,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", admin.CreatedAt),
"otpLoginIsOn": admin.OtpLogin != nil && admin.OtpLogin.IsOn,
"canLogin": admin.CanLogin,
"id": admin.Id,
"isOn": admin.IsOn,
"isSuper": admin.IsSuper,
"username": admin.Username,
"fullname": admin.Fullname,
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", admin.CreatedAt),
"otpLoginIsOn": admin.OtpLogin != nil && admin.OtpLogin.IsOn,
"canLogin": admin.CanLogin,
"hasWeakPassword": admin.HasWeakPassword,
})
}
this.Data["admins"] = adminMaps

View File

@@ -22,7 +22,7 @@ func init() {
Post("/options", new(OptionsAction)).
// AccessKeys
Prefix("/admins/accessKeys").
Prefix("/admins/accesskeys").
Get("", new(accesskeys.IndexAction)).
GetPost("/createPopup", new(accesskeys.CreatePopupAction)).
Post("/delete", new(accesskeys.DeleteAction)).

View File

@@ -67,5 +67,5 @@ func (this *OtpQrcodeAction) RunGet(params struct {
return
}
this.AddHeader("Content-Type", "image/png")
this.Write(data)
_, _ = this.Write(data)
}

View File

@@ -1,81 +0,0 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"regexp"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
AdminId int64
InstanceId int64
User string
TelegramToken string
GroupIds string
Description string
TimeFromHour string
TimeFromMinute string
TimeFromSecond string
TimeToHour string
TimeToMinute string
TimeToSecond string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("adminId", params.AdminId).
Gt(0, "请选择系统用户").
Field("instanceId", params.InstanceId).
Gt(0, "请选择媒介")
groupIds := utils.SplitNumbers(params.GroupIds)
var digitReg = regexp.MustCompile(`^\d+$`)
var timeFrom = ""
if digitReg.MatchString(params.TimeFromHour) && digitReg.MatchString(params.TimeFromMinute) && digitReg.MatchString(params.TimeFromSecond) {
timeFrom = params.TimeFromHour + ":" + params.TimeFromMinute + ":" + params.TimeFromSecond
}
var timeTo = ""
if digitReg.MatchString(params.TimeToHour) && digitReg.MatchString(params.TimeToMinute) && digitReg.MatchString(params.TimeToSecond) {
timeTo = params.TimeToHour + ":" + params.TimeToMinute + ":" + params.TimeToSecond
}
resp, err := this.RPC().MessageRecipientRPC().CreateMessageRecipient(this.AdminContext(), &pb.CreateMessageRecipientRequest{
AdminId: params.AdminId,
MessageMediaInstanceId: params.InstanceId,
User: params.User,
MessageRecipientGroupIds: groupIds,
Description: params.Description,
TimeFrom: timeFrom,
TimeTo: timeTo,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo("创建媒介接收人 %d", resp.MessageRecipientId)
this.Success()
}

View File

@@ -1,24 +0,0 @@
package recipients
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 {
RecipientId int64
}) {
defer this.CreateLogInfo("删除媒介接收人 %d", params.RecipientId)
_, err := this.RPC().MessageRecipientRPC().DeleteMessageRecipient(this.AdminContext(), &pb.DeleteMessageRecipientRequest{MessageRecipientId: params.RecipientId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,38 +0,0 @@
package groups
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{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入分组名称")
_, err := this.RPC().MessageRecipientGroupRPC().CreateMessageRecipientGroup(this.AdminContext(), &pb.CreateMessageRecipientGroupRequest{Name: params.Name})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,22 +0,0 @@
package groups
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 {
GroupId int64
}) {
_, err := this.RPC().MessageRecipientGroupRPC().DeleteMessageRecipientGroup(this.AdminContext(), &pb.DeleteMessageRecipientGroupRequest{MessageRecipientGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,34 +0,0 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "group")
}
func (this *IndexAction) RunGet(params struct{}) {
groupsResp, err := this.RPC().MessageRecipientGroupRPC().FindAllEnabledMessageRecipientGroups(this.AdminContext(), &pb.FindAllEnabledMessageRecipientGroupsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
groupMaps := []maps.Map{}
for _, group := range groupsResp.MessageRecipientGroups {
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": group.Name,
"isOn": group.IsOn,
})
}
this.Data["groups"] = groupMaps
this.Show()
}

View File

@@ -1,23 +0,0 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "recipients").
Prefix("/admins/recipients/groups").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/updatePopup", new(UpdatePopupAction)).
Post("/delete", new(DeleteAction)).
Get("/selectPopup", new(SelectPopupAction)).
EndAll()
})
}

View File

@@ -1,39 +0,0 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
)
type SelectPopupAction struct {
actionutils.ParentAction
}
func (this *SelectPopupAction) RunGet(params struct {
GroupIds string
}) {
selectedGroupIds := utils.SplitNumbers(params.GroupIds)
groupsResp, err := this.RPC().MessageRecipientGroupRPC().FindAllEnabledMessageRecipientGroups(this.AdminContext(), &pb.FindAllEnabledMessageRecipientGroupsRequest{})
if err != nil {
this.ErrorPage(err)
return
}
groupMaps := []maps.Map{}
for _, group := range groupsResp.MessageRecipientGroups {
if lists.ContainsInt64(selectedGroupIds, group.Id) {
continue
}
groupMaps = append(groupMaps, maps.Map{
"id": group.Id,
"name": group.Name,
"isOn": group.IsOn,
})
}
this.Data["groups"] = groupMaps
this.Show()
}

View File

@@ -1,64 +0,0 @@
package groups
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type UpdatePopupAction struct {
actionutils.ParentAction
}
func (this *UpdatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdatePopupAction) RunGet(params struct {
GroupId int64
}) {
groupResp, err := this.RPC().MessageRecipientGroupRPC().FindEnabledMessageRecipientGroup(this.AdminContext(), &pb.FindEnabledMessageRecipientGroupRequest{MessageRecipientGroupId: params.GroupId})
if err != nil {
this.ErrorPage(err)
return
}
group := groupResp.MessageRecipientGroup
if group == nil {
this.NotFound("messageRecipientGroup", params.GroupId)
return
}
this.Data["group"] = maps.Map{
"id": group.Id,
"name": group.Name,
"isOn": group.IsOn,
}
this.Show()
}
func (this *UpdatePopupAction) RunPost(params struct {
GroupId int64
Name string
IsOn bool
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入分组名称")
_, err := this.RPC().MessageRecipientGroupRPC().UpdateMessageRecipientGroup(this.AdminContext(), &pb.UpdateMessageRecipientGroupRequest{
MessageRecipientGroupId: params.GroupId,
Name: params.Name,
IsOn: params.IsOn,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,74 +0,0 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "recipient")
}
func (this *IndexAction) RunGet(params struct {
}) {
// TODO 增加系统用户、媒介类型等条件搜索
countResp, err := this.RPC().MessageRecipientRPC().CountAllEnabledMessageRecipients(this.AdminContext(), &pb.CountAllEnabledMessageRecipientsRequest{
AdminId: 0,
MediaType: "",
MessageRecipientGroupId: 0,
Keyword: "",
})
if err != nil {
this.ErrorPage(err)
return
}
count := countResp.Count
page := this.NewPage(count)
this.Data["page"] = page.AsHTML()
recipientsResp, err := this.RPC().MessageRecipientRPC().ListEnabledMessageRecipients(this.AdminContext(), &pb.ListEnabledMessageRecipientsRequest{
AdminId: 0,
MediaType: "",
MessageRecipientGroupId: 0,
Keyword: "",
Offset: page.Offset,
Size: page.Size,
})
if err != nil {
this.ErrorPage(err)
return
}
recipientMaps := []maps.Map{}
for _, recipient := range recipientsResp.MessageRecipients {
if recipient.Admin == nil {
continue
}
if recipient.MessageMediaInstance == nil {
continue
}
recipientMaps = append(recipientMaps, maps.Map{
"id": recipient.Id,
"admin": maps.Map{
"id": recipient.Admin.Id,
"fullname": recipient.Admin.Fullname,
"username": recipient.Admin.Username,
},
"groups": recipient.MessageRecipientGroups,
"isOn": recipient.IsOn,
"instance": maps.Map{
"name": recipient.MessageMediaInstance.Name,
},
"user": recipient.User,
"description": recipient.Description,
})
}
this.Data["recipients"] = recipientMaps
this.Show()
}

View File

@@ -1,25 +0,0 @@
package recipients
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeAdmin)).
Data("teaMenu", "admins").
Data("teaSubMenu", "recipients").
Prefix("/admins/recipients").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).
GetPost("/update", new(UpdateAction)).
Post("/delete", new(DeleteAction)).
Post("/mediaOptions", new(MediaOptionsAction)).
Get("/recipient", new(RecipientAction)).
GetPost("/test", new(TestAction)).
EndAll()
})
}

View File

@@ -1,266 +0,0 @@
package instances
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/monitorconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
)
type CreatePopupAction struct {
actionutils.ParentAction
}
func (this *CreatePopupAction) Init() {
this.Nav("", "", "")
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *CreatePopupAction) RunPost(params struct {
Name string
MediaType string
EmailSmtp string
EmailUsername string
EmailPassword string
EmailFrom string
WebHookURL string
WebHookMethod string
WebHookHeaderNames []string
WebHookHeaderValues []string
WebHookContentType string
WebHookParamNames []string
WebHookParamValues []string
WebHookBody string
ScriptType string
ScriptPath string
ScriptLang string
ScriptCode string
ScriptCwd string
ScriptEnvNames []string
ScriptEnvValues []string
DingTalkWebHookURL string
QyWeixinCorporateId string
QyWeixinAgentId string
QyWeixinAppSecret string
QyWeixinTextFormat string
QyWeixinRobotWebHookURL string
QyWeixinRobotTextFormat string
AliyunSmsSign string
AliyunSmsTemplateCode string
AliyunSmsTemplateVarNames []string
AliyunSmsTemplateVarValues []string
AliyunSmsAccessKeyId string
AliyunSmsAccessKeySecret string
TelegramToken string
RateMinutes int32
RateCount int32
HashLife int32
Description string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("name", params.Name).
Require("请输入媒介名称").
Field("mediaType", params.MediaType).
Require("请选择媒介类型")
options := maps.Map{}
switch params.MediaType {
case "email":
params.Must.
Field("emailSmtp", params.EmailSmtp).
Require("请输入SMTP地址").
Field("emailUsername", params.EmailUsername).
Require("请输入邮箱账号").
Field("emailPassword", params.EmailPassword).
Require("请输入密码或授权码")
options["smtp"] = params.EmailSmtp
options["username"] = params.EmailUsername
options["password"] = params.EmailPassword
options["from"] = params.EmailFrom
case "webHook":
params.Must.
Field("webHookURL", params.WebHookURL).
Require("请输入URL地址").
Match("(?i)^(http|https)://", "URL地址必须以http或https开头").
Field("webHookMethod", params.WebHookMethod).
Require("请选择请求方法")
options["url"] = params.WebHookURL
options["method"] = params.WebHookMethod
options["contentType"] = params.WebHookContentType
headers := []maps.Map{}
if len(params.WebHookHeaderNames) > 0 {
for index, name := range params.WebHookHeaderNames {
if index < len(params.WebHookHeaderValues) {
headers = append(headers, maps.Map{
"name": name,
"value": params.WebHookHeaderValues[index],
})
}
}
}
options["headers"] = headers
if params.WebHookContentType == "params" {
webHookParams := []maps.Map{}
for index, name := range params.WebHookParamNames {
if index < len(params.WebHookParamValues) {
webHookParams = append(webHookParams, maps.Map{
"name": name,
"value": params.WebHookParamValues[index],
})
}
}
options["params"] = webHookParams
} else if params.WebHookContentType == "body" {
options["body"] = params.WebHookBody
}
case "script":
if params.ScriptType == "path" {
params.Must.
Field("scriptPath", params.ScriptPath).
Require("请输入脚本路径")
} else if params.ScriptType == "code" {
params.Must.
Field("scriptCode", params.ScriptCode).
Require("请输入脚本代码")
} else {
params.Must.
Field("scriptPath", params.ScriptPath).
Require("请输入脚本路径")
}
options["scriptType"] = params.ScriptType
options["path"] = params.ScriptPath
options["scriptLang"] = params.ScriptLang
options["script"] = params.ScriptCode
options["cwd"] = params.ScriptCwd
env := []maps.Map{}
for index, envName := range params.ScriptEnvNames {
if index < len(params.ScriptEnvValues) {
env = append(env, maps.Map{
"name": envName,
"value": params.ScriptEnvValues[index],
})
}
}
options["env"] = env
case "dingTalk":
params.Must.
Field("dingTalkWebHookURL", params.DingTalkWebHookURL).
Require("请输入Hook地址").
Match("^https:", "Hook地址必须以https://开头")
options["webHookURL"] = params.DingTalkWebHookURL
case "qyWeixin":
params.Must.
Field("qyWeixinCorporateId", params.QyWeixinCorporateId).
Require("请输入企业ID").
Field("qyWeixinAgentId", params.QyWeixinAgentId).
Require("请输入应用AgentId").
Field("qyWeixinSecret", params.QyWeixinAppSecret).
Require("请输入应用Secret")
options["corporateId"] = params.QyWeixinCorporateId
options["agentId"] = params.QyWeixinAgentId
options["appSecret"] = params.QyWeixinAppSecret
options["textFormat"] = params.QyWeixinTextFormat
case "qyWeixinRobot":
params.Must.
Field("qyWeixinRobotWebHookURL", params.QyWeixinRobotWebHookURL).
Require("请输入WebHook地址").
Match("^https:", "WebHook地址必须以https://开头")
options["webHookURL"] = params.QyWeixinRobotWebHookURL
options["textFormat"] = params.QyWeixinRobotTextFormat
case "aliyunSms":
params.Must.
Field("aliyunSmsSign", params.AliyunSmsSign).
Require("请输入签名名称").
Field("aliyunSmsTemplateCode", params.AliyunSmsTemplateCode).
Require("请输入模板CODE").
Field("aliyunSmsAccessKeyId", params.AliyunSmsAccessKeyId).
Require("请输入AccessKey ID").
Field("aliyunSmsAccessKeySecret", params.AliyunSmsAccessKeySecret).
Require("请输入AccessKey Secret")
options["sign"] = params.AliyunSmsSign
options["templateCode"] = params.AliyunSmsTemplateCode
options["accessKeyId"] = params.AliyunSmsAccessKeyId
options["accessKeySecret"] = params.AliyunSmsAccessKeySecret
variables := []maps.Map{}
for index, name := range params.AliyunSmsTemplateVarNames {
if index < len(params.AliyunSmsTemplateVarValues) {
variables = append(variables, maps.Map{
"name": name,
"value": params.AliyunSmsTemplateVarValues[index],
})
}
}
options["variables"] = variables
case "telegram":
params.Must.
Field("telegramToken", params.TelegramToken).
Require("请输入机器人Token")
options["token"] = params.TelegramToken
default:
this.Fail("错误的媒介类型")
}
optionsJSON, err := json.Marshal(options)
if err != nil {
this.ErrorPage(err)
return
}
var rateConfig = &monitorconfigs.RateConfig{
Minutes: params.RateMinutes,
Count: params.RateCount,
}
rateJSON, err := json.Marshal(rateConfig)
if err != nil {
this.ErrorPage(err)
return
}
resp, err := this.RPC().MessageMediaInstanceRPC().CreateMessageMediaInstance(this.AdminContext(), &pb.CreateMessageMediaInstanceRequest{
Name: params.Name,
MediaType: params.MediaType,
ParamsJSON: optionsJSON,
Description: params.Description,
RateJSON: rateJSON,
HashLife: params.HashLife,
})
if err != nil {
this.ErrorPage(err)
return
}
defer this.CreateLogInfo("创建消息媒介 %d", resp.MessageMediaInstanceId)
this.Success()
}

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