Compare commits

...

153 Commits

Author SHA1 Message Date
刘祥超
619a2817ce 检查版本更新时增加当前版本参数 2023-03-12 21:24:08 +08:00
刘祥超
12a33ee9fc 自动安装的MySQL binlog过期时间改成7天 2023-03-12 20:58:13 +08:00
刘祥超
26302ca930 重启edge-admin时确保同目录下的edge-api也能重启 2023-03-11 22:02:29 +08:00
刘祥超
7e85555ba7 安装过程中可以选择自动在本机安装MySQL 2023-03-11 18:52:40 +08:00
刘祥超
2546676f6a 删除页脚捐助作者链接 2023-03-11 14:44:19 +08:00
刘祥超
93b7edf5c4 安装过程中节点主机地址允许填写域名 2023-03-10 17:00:53 +08:00
刘祥超
571e432263 WAF cc防护增加“检查请求来源指纹”选项 2023-03-10 10:34:50 +08:00
刘祥超
580b158567 优化tips弹窗 2023-03-09 17:40:49 +08:00
刘祥超
395aa665d7 可以在运行日志页面中删除使用关键词匹配的运行日志 2023-03-09 16:20:09 +08:00
刘祥超
a80e54e0b3 优化<url-patterns-box>组件 2023-03-09 12:09:28 +08:00
刘祥超
8ccd41b551 优化路由规则标签、菜单、删除UAM组件 2023-03-09 11:42:15 +08:00
刘祥超
9d6a3a8a0d 优化<url-patterns-box>组件 2023-03-07 17:18:13 +08:00
刘祥超
a9d1b4b863 集群服务设置增加“记录找不到网站日志”选项 2023-03-07 10:30:11 +08:00
刘祥超
459d664a60 更新go.mod 2023-03-06 21:51:10 +08:00
刘祥超
5f10b0156c 5秒盾增加例外URL和限制URL 2023-03-06 21:48:29 +08:00
刘祥超
348c07f847 优化网站服务WAF设置界面 2023-03-06 16:40:42 +08:00
刘祥超
934b1894c4 使用edge-admin upgrade升级时可以通过--url参数指定升级包URL 2023-03-05 19:52:48 +08:00
刘祥超
a660f4af93 上传components.js 2023-03-05 19:51:46 +08:00
刘祥超
12c0d39b13 优化缓存条件设置界面 2023-03-05 16:48:01 +08:00
刘祥超
bc6de68006 远程升级API节点限制版本号必须不小于0.6.4 2023-03-05 15:32:27 +08:00
刘祥超
52bb753594 实现API节点远程升级 2023-03-05 12:05:18 +08:00
刘祥超
ed6b763d06 远程升级API节点(部分实现) 2023-03-04 21:04:30 +08:00
刘祥超
07e421afea 更新components.js 2023-03-04 09:34:17 +08:00
刘祥超
71352841bf 修复无法启动安装的Bug 2023-03-04 09:34:10 +08:00
刘祥超
91e8fcbb24 使用edge-admin upgrade升级系统时URL请求增加User-Agent 2023-03-03 10:44:04 +08:00
刘祥超
5b67a85624 WAF阻止动作也增加最大封禁时长 2023-03-01 19:18:22 +08:00
刘祥超
b49efa0d5a WAF拦截动作可以设置最大封禁时间,从而实现封禁时间随机 2023-03-01 18:59:42 +08:00
刘祥超
abeb585a0d 优化代码 2023-03-01 16:58:38 +08:00
刘祥超
cf621f1cc9 WAF支持忽略全局WAF规则 2023-03-01 16:46:49 +08:00
刘祥超
389a494e00 优化界面 2023-03-01 15:30:32 +08:00
刘祥超
a9c55dc23b 在节点列表中IP地址中显示对应的专属集群 2023-03-01 15:23:36 +08:00
刘祥超
3d35b7e71b 节点IP地址可以设置专属集群 2023-03-01 11:38:21 +08:00
刘祥超
6f78146711 可以修改单个用户的带宽算法 2023-02-27 10:46:48 +08:00
刘祥超
8afa47c351 删除不必要的文件 2023-02-22 19:25:38 +08:00
刘祥超
20838cfc3e 增加<js-page>组件 2023-02-22 17:34:32 +08:00
刘祥超
d00acd6d2f 上传日期相关公共函数 2023-02-18 20:45:05 +08:00
刘祥超
0b58a36779 优化界面 2023-02-17 10:30:25 +08:00
刘祥超
54bc98e9c1 优化组件显示 2023-02-13 09:44:10 +08:00
刘祥超
836daf2ad9 Dockerfile版本号修改为0.6.3 2023-02-10 18:17:57 +08:00
刘祥超
9a8cd9bd87 优化缓存请求条件界面 2023-02-10 11:15:06 +08:00
刘祥超
c555d91503 文件扩展名相关变量使用${requestPathLowerExtension} 2023-02-10 10:44:29 +08:00
刘祥超
e0e7c1bcc4 修复表单能够上传的数据过小问题 2023-02-07 11:40:28 +08:00
刘祥超
4bf733beec 在管理员登录后才验证OTP 2023-02-04 16:44:33 +08:00
刘祥超
e93f23c943 使用数据库存储SESSION 2023-02-04 15:18:26 +08:00
刘祥超
5384f4d9f2 优化WAF规则大小写敏感显示 2023-02-02 16:14:07 +08:00
刘祥超
3c0a97c3cc 修复防盗链设置中域名无法修改的Bug 2023-02-01 18:16:41 +08:00
刘祥超
129db6cf4e 修复无法显示IPv6最近日志的Bug 2023-02-01 10:36:30 +08:00
刘祥超
c6bfa5652f 版本号修改为0.6.4 2023-01-14 17:19:03 +08:00
刘祥超
780472d83e 更新components.js 2023-01-14 17:18:34 +08:00
刘祥超
0d02e3f15a 优化WAF创建规则界面中参数选项显示 2023-01-13 18:51:14 +08:00
刘祥超
bf82f22d0f WAF规则中如果对比值为空,则显示空字样 2023-01-13 15:58:35 +08:00
刘祥超
e18f182ce6 版本号修改为0.6.3 2023-01-11 15:45:03 +08:00
刘祥超
761c26b587 Update Dockerfile 2023-01-10 21:32:20 +08:00
刘祥超
5e62769dcf 修改版本为0.6.2 2023-01-10 21:17:20 +08:00
刘祥超
86b8a718a0 在Dockerfile中增加local repository提示 2023-01-10 18:31:28 +08:00
刘祥超
a729cfc31d 更新Dockerfile文件 2023-01-10 10:53:48 +08:00
刘祥超
96cfda852a Dockerfile版本号更新到0.6.0 2023-01-09 20:45:04 +08:00
刘祥超
0423d9246c 更新components.js 2023-01-09 18:12:45 +08:00
刘祥超
985798757f 修复<values-box>组件内容中如果出现数字的显示问题 2023-01-09 18:08:24 +08:00
刘祥超
72876f6749 版本修改为0.6.1 2023-01-09 16:06:13 +08:00
刘祥超
03d6e223d8 更新components.js 2023-01-09 09:31:19 +08:00
刘祥超
62d9f2ed97 优化文字提示 2023-01-08 19:38:50 +08:00
刘祥超
a550a44a52 集群服务设置增加自动读超时选项 2023-01-07 20:04:42 +08:00
刘祥超
b19d586949 更新components.js 2023-01-07 19:34:48 +08:00
刘祥超
bbfa3ee57f 优化任务Badge检查频率 2023-01-07 19:34:40 +08:00
刘祥超
af409dd3b8 优化WAF规则显示样式 2023-01-06 20:06:30 +08:00
刘祥超
3db79ca149 更新components.js 2023-01-06 19:17:31 +08:00
刘祥超
e880420494 WAF规则使用中文显示运算符 2023-01-06 19:12:53 +08:00
刘祥超
28ec17b8fe 优化请求条件操作符描述 2023-01-06 16:05:06 +08:00
刘祥超
8026a40807 修改一处错别字 2023-01-05 10:27:59 +08:00
刘祥超
068c6d406a useragent改为userAgent 2023-01-02 18:20:42 +08:00
刘祥超
57470e4ef0 集群服务设置中增加性能设置 2023-01-01 19:26:59 +08:00
刘祥超
ca8e1537f5 修复文字错误 2023-01-01 18:33:05 +08:00
刘祥超
d67b818398 华为云可以设置终端节点(endpoint) 2023-01-01 18:28:37 +08:00
刘祥超
f5f46424bb 集群/节点阈值切换监控项时同时切换参数描述 2022-12-31 18:33:42 +08:00
刘祥超
1e259717ce 优化代码 2022-12-31 17:31:17 +08:00
刘祥超
91ece99a9c 优化证书加载速度 2022-12-31 17:21:48 +08:00
刘祥超
d30ebdb369 优化证书数量很多时的页面加载速度 2022-12-31 17:12:49 +08:00
刘祥超
ade8522b69 实现UA名单功能 2022-12-30 20:48:38 +08:00
刘祥超
159b308f31 内容压缩增加默认内容长度限制 2022-12-30 14:37:50 +08:00
刘祥超
837bf25f7b 内容压缩支持例外扩展名 2022-12-30 12:04:17 +08:00
刘祥超
8301d3669b 默认情况下内容压缩不支持Partial Content 2022-12-30 11:43:47 +08:00
刘祥超
cc752e8d80 优化文字提示 2022-12-29 17:19:53 +08:00
刘祥超
d20e6bd42f 增加CORS自适应跨域 2022-12-29 17:16:07 +08:00
刘祥超
bfee9fe233 优化文字提示 2022-12-27 18:55:18 +08:00
刘祥超
9c962b09f1 调整文字提示 2022-12-22 11:43:31 +08:00
刘祥超
46edefead7 服务没有设置所属用户时可以设置一个用户 2022-12-21 16:13:39 +08:00
刘祥超
725cfc8a2b 优化代码 2022-12-15 16:18:10 +08:00
刘祥超
1ce48b9ef4 安全设置中检查搜索引擎和爬虫时不区分大小写 2022-12-13 18:22:11 +08:00
刘祥超
37cc28f225 优化界面 2022-12-13 18:21:31 +08:00
刘祥超
5ebe3bb8e0 实时访问日志有弹窗打开时,暂时不更新数据 2022-12-10 19:01:57 +08:00
刘祥超
aa01512f89 优化<more-options-indicator>组件 2022-12-10 15:57:39 +08:00
刘祥超
37ff2b886a 初步完成用户电子邮箱绑定(激活) 2022-12-08 20:25:20 +08:00
刘祥超
ce18212756 自定义线路增加区域之间关系设置 2022-12-06 22:32:32 +08:00
刘祥超
08f50a274a 修复HTTPS之HSTS设置无法手动输入有效时间的Bug 2022-12-06 14:38:59 +08:00
刘祥超
892ee0013a 修复点击修改WAF规则时未保存时也会生效的Bug 2022-12-06 10:46:46 +08:00
刘祥超
e9a47041fd 刷新预热页面增加功能说明 2022-12-06 09:52:57 +08:00
刘祥超
d419fa06e8 优化文字提示 2022-12-03 14:29:30 +08:00
刘祥超
8b961a890c 优化文字提示 2022-12-01 14:40:17 +08:00
刘祥超
db32915114 更新components.js 2022-11-29 15:40:37 +08:00
刘祥超
2ffdb10cce 版本号更新为0.6.0 2022-11-29 15:40:23 +08:00
刘祥超
507fd7e5d4 更新Dockerfile中的版本号 2022-11-29 15:40:11 +08:00
刘祥超
7df599b5a9 DNS线路选择器增加代号搜索 2022-11-28 19:00:14 +08:00
刘祥超
9987334f55 版本号修改为0.5.10 2022-11-28 18:59:40 +08:00
刘祥超
d8c3365384 节点时间相差30秒钟以上才提示 2022-11-28 15:57:41 +08:00
刘祥超
2e284b5af9 版本号修改为0.5.9 2022-11-28 15:57:18 +08:00
刘祥超
89ddd4e6a3 更新依赖库 2022-11-28 11:39:38 +08:00
刘祥超
36524ea481 修复一处测试用例package引用错误 2022-11-28 11:39:08 +08:00
刘祥超
35cf693610 更新Dockerfile 2022-11-28 11:34:02 +08:00
刘祥超
42148a66bd 改进文字提示 2022-11-27 22:00:35 +08:00
刘祥超
96878715bf 优化界面 2022-11-26 19:04:55 +08:00
刘祥超
1a5f3342e7 优化文字提示 2022-11-26 15:28:01 +08:00
刘祥超
3613d13a2b 优化文字说明 2022-11-25 19:05:11 +08:00
刘祥超
7786140d85 优化代码 2022-11-24 17:20:08 +08:00
刘祥超
3a23b57f1b 优化智能DNS访问日志组件 2022-11-24 10:24:04 +08:00
刘祥超
5a6e6fba69 节点详情显示API连接状况 2022-11-22 11:27:48 +08:00
刘祥超
910b3a6162 在节点详情中显示API节点地址 2022-11-21 21:07:51 +08:00
刘祥超
0dc19bed45 节点可以单独设置所使用的API节点地址 2022-11-21 19:54:54 +08:00
刘祥超
a7bdb64301 记录名第一段允许通配符 2022-11-20 20:08:03 +08:00
刘祥超
4739072a85 Update go.mod 2022-11-18 17:37:25 +08:00
刘祥超
07bdae2488 安装时检测本地数据库时增加更多的候选密码 2022-11-18 17:07:12 +08:00
刘祥超
b84035d821 优化代码 2022-11-18 17:02:38 +08:00
刘祥超
dc0a7b9dae 优化文字提示 2022-11-18 15:45:13 +08:00
刘祥超
2937bd8de0 提交components.js 2022-11-17 17:34:21 +08:00
刘祥超
37315ef4d9 优化代码 2022-11-17 10:41:56 +08:00
刘祥超
1986fece07 优化代码 2022-11-17 10:40:57 +08:00
刘祥超
16b1657f35 优化WAF规则相关界面 2022-11-16 15:43:08 +08:00
刘祥超
c115c62cd9 去掉服务CNAME末尾的点符号,防止误解 2022-11-16 14:44:07 +08:00
刘祥超
d2df7f8d5b 节点列表页带宽使用bps显示 2022-11-16 12:02:56 +08:00
刘祥超
6cb79864e6 边缘节点支持设置多个缓存目录 2022-11-15 20:35:45 +08:00
刘祥超
982d28c7b4 docker build增加--no-cache参数 2022-11-14 15:01:13 +08:00
刘祥超
0f57516fdc Dockerfile: 使用alpine:latest代替alpine:edge 2022-11-14 14:49:19 +08:00
刘祥超
75a89defcb 构建Dockerfile时从官网下载最新版本安装包 2022-11-14 10:01:22 +08:00
刘祥超
22a6c52060 增加Docker镜像构建脚本 2022-11-13 18:46:30 +08:00
刘祥超
37607e4a41 安装过程显示更详细内容 2022-11-11 21:47:36 +08:00
刘祥超
5936155998 优化文字提示 2022-11-11 17:46:10 +08:00
刘祥超
8d76de935f 优化文字提示 2022-11-10 15:07:15 +08:00
刘祥超
9baa530064 优化文字提示 2022-11-09 18:20:18 +08:00
刘祥超
103414b338 优化<checkbox>组件 2022-11-09 17:50:22 +08:00
刘祥超
72fe68ebfe 访问日志搜索method:XXX和requestMethod:XXX方法 2022-11-09 11:58:39 +08:00
刘祥超
cfed31958b 优化文字提示 2022-11-08 09:22:09 +08:00
刘祥超
3d5fca2d36 调整默认压缩的mime types 2022-11-08 09:18:13 +08:00
刘祥超
a5710286ec 优化代码 2022-11-06 20:34:19 +08:00
刘祥超
d0ce0c6c58 优化界面 2022-11-06 15:16:21 +08:00
刘祥超
779e2cf0f2 域名跳转增加忽略跳转前端口选项 2022-11-04 20:59:20 +08:00
刘祥超
2108474777 删除不必要的文件 2022-11-04 15:19:30 +08:00
刘祥超
e25e0f1747 删除不必要的文件 2022-11-04 15:01:36 +08:00
刘祥超
e8e74b639c 优化datepicker组件 2022-11-04 14:35:32 +08:00
刘祥超
a14fcd1e50 添加和修改API节点时,检查HTTP API端口是否和GRPC端口冲突 2022-11-04 12:04:07 +08:00
刘祥超
485c0e0891 修复系统用户和平台用户access key无法禁用和删除的问题 2022-11-04 11:06:45 +08:00
刘祥超
00a19e9d43 时钟同步增加是否检查chrony选项 2022-11-03 14:58:32 +08:00
刘祥超
67d0dc0783 版本修改为0.5.8 2022-11-02 15:10:47 +08:00
刘祥超
3718c35842 提交components.js 2022-11-02 15:10:39 +08:00
268 changed files with 7370 additions and 1496 deletions

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"bytes" "bytes"
"flag"
"fmt" "fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/apps" "github.com/TeaOSLab/EdgeAdmin/internal/apps"
"github.com/TeaOSLab/EdgeAdmin/internal/configs" "github.com/TeaOSLab/EdgeAdmin/internal/configs"
@@ -24,7 +25,7 @@ func main() {
var app = apps.NewAppCmd(). var app = apps.NewAppCmd().
Version(teaconst.Version). Version(teaconst.Version).
Product(teaconst.ProductName). Product(teaconst.ProductName).
Usage(teaconst.ProcessName+" [-v|start|stop|restart|service|daemon|reset|recover|demo|upgrade]"). Usage(teaconst.ProcessName+" [-h|-v|start|stop|restart|service|daemon|reset|recover|demo|upgrade]").
Usage(teaconst.ProcessName+" [dev|prod]"). Usage(teaconst.ProcessName+" [dev|prod]").
Option("-h", "show this help"). Option("-h", "show this help").
Option("-v", "show version"). Option("-v", "show version").
@@ -38,7 +39,7 @@ func main() {
Option("demo", "switch to demo mode"). Option("demo", "switch to demo mode").
Option("dev", "switch to 'dev' mode"). Option("dev", "switch to 'dev' mode").
Option("prod", "switch to 'prod' mode"). Option("prod", "switch to 'prod' mode").
Option("upgrade", "upgrade from official site") Option("upgrade [--url=URL]", "upgrade from official site or an url")
app.On("daemon", func() { app.On("daemon", func() {
nodes.NewAdminNode().Daemon() nodes.NewAdminNode().Daemon()
@@ -138,7 +139,12 @@ func main() {
} }
}) })
app.On("upgrade", func() { app.On("upgrade", func() {
var manager = utils.NewUpgradeManager("admin") 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 ...") log.Println("checking latest version ...")
var ticker = time.NewTicker(1 * time.Second) var ticker = time.NewTicker(1 * time.Second)
go func() { go func() {

1
docker/.gitignore vendored Normal file
View File

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

37
docker/Dockerfile Normal file
View File

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

5
docker/build.sh Executable file
View File

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

5
docker/run.sh Executable file
View File

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

13
go.mod
View File

@@ -8,16 +8,16 @@ require (
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000 github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
github.com/cespare/xxhash v1.1.0 github.com/cespare/xxhash v1.1.0
github.com/go-sql-driver/mysql v1.5.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-20230304012706-c1f4a4e27470
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
github.com/miekg/dns v1.1.43 github.com/miekg/dns v1.1.43
github.com/shirou/gopsutil/v3 v3.22.5 github.com/shirou/gopsutil/v3 v3.22.5
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/tealeg/xlsx/v3 v3.2.3 github.com/tealeg/xlsx/v3 v3.2.3
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad golang.org/x/sys v0.5.0
google.golang.org/grpc v1.45.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 ( require (
@@ -26,18 +26,15 @@ require (
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.0.0 // indirect github.com/google/btree v1.0.0 // indirect
github.com/google/go-cmp v0.5.8 // indirect github.com/google/go-cmp v0.5.8 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/pretty v0.2.1 // indirect github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.2.0 // 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/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/rogpeppe/fastuuid v1.2.0 // indirect github.com/rogpeppe/fastuuid v1.2.0 // indirect
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/net v0.7.0 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.7.0 // indirect
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

38
go.sum
View File

@@ -21,7 +21,6 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/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/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -74,13 +73,16 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/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/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-20210411134150-ddf57e240c2f/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc= github.com/iwind/TeaGo v0.0.0-20230207032553-d6dcde0cd518 h1:zuWjQ57zc67ZSTHpxNK95JYoa9Ph/JRSnapsTY/hlhQ=
github.com/iwind/TeaGo v0.0.0-20230207032553-d6dcde0cd518/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/TeaGo v0.0.0-20230303070415-9d0689db6456 h1:xv3AVaxuwjThkBDptAfsFSmuHQIrRrvt8BRaekWnsvs=
github.com/iwind/TeaGo v0.0.0-20230303070415-9d0689db6456/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470 h1:TuRxvKRv9PxKVijWOkUnZm5TeanQqWGUJyPx9u6cra4=
github.com/iwind/TeaGo v0.0.0-20230304012706-c1f4a4e27470/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A= github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA= 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.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.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -92,11 +94,7 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= 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/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-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 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/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/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.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -111,7 +109,6 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug= github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
@@ -131,7 +128,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.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/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tealeg/xlsx/v3 v3.2.3 h1:MXnVh+9Y8cUglowItTy2HL3Kv6z+q/0aNjeKuTsVqZQ= github.com/tealeg/xlsx/v3 v3.2.3 h1:MXnVh+9Y8cUglowItTy2HL3Kv6z+q/0aNjeKuTsVqZQ=
github.com/tealeg/xlsx/v3 v3.2.3/go.mod h1:0hGmAEoZ48SS1ZAE6eqZJkJVXgOMY+8a33vjXa8S8HA= github.com/tealeg/xlsx/v3 v3.2.3/go.mod h1:0hGmAEoZ48SS1ZAE6eqZJkJVXgOMY+8a33vjXa8S8HA=
@@ -170,8 +166,8 @@ 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-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-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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -198,20 +194,16 @@ 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-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-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-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-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.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/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.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-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-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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -266,7 +258,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.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.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-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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -209,7 +209,7 @@ func (this *AppCmd) runStop() {
fmt.Println(this.product+" stopped ok, pid:", types.String(pid)) fmt.Println(this.product+" stopped ok, pid:", types.String(pid))
} }
// 重启 // RunRestart 重启
func (this *AppCmd) RunRestart() { func (this *AppCmd) RunRestart() {
this.runStop() this.runStop()
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)

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

@@ -155,3 +155,15 @@ func (this *APIConfig) WriteFile(path string) error {
return nil return nil
} }
// Clone 克隆当前配置
func (this *APIConfig) Clone() *APIConfig {
return &APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
}{},
NodeId: this.NodeId,
Secret: this.Secret,
}
}

View File

@@ -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 package teaconst
const ( const (
Version = "0.5.7" Version = "0.6.4"
APINodeVersion = "0.5.7" APINodeVersion = "0.6.4"
ProductName = "Edge Admin" ProductName = "Edge Admin"
ProcessName = "edge-admin" ProcessName = "edge-admin"
@@ -18,5 +18,5 @@ const (
CookieSID = "edgesid" CookieSID = "edgesid"
SystemdServiceName = "edge-admin" 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

@@ -9,6 +9,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/conds/condutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/conds/condutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files" "github.com/iwind/TeaGo/files"
@@ -115,6 +116,16 @@ func generateComponentsJSFile() error {
buffer.Write([]byte{'\n', '\n'}) buffer.Write([]byte{'\n', '\n'})
} }
// WAF操作符
wafOperatorsJSON, err := json.Marshal(firewallconfigs.AllRuleOperators)
if err != nil {
logs.Println("ComponentsAction marshal waf rule operators failed: " + err.Error())
} else {
buffer.WriteString("window.WAF_RULE_OPERATORS = ")
buffer.Write(wafOperatorsJSON)
buffer.Write([]byte{'\n', '\n'})
}
fp, err := os.OpenFile(filepath.Clean(Tea.PublicFile("/js/components.src.js")), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777) fp, err := os.OpenFile(filepath.Clean(Tea.PublicFile("/js/components.src.js")), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
if err != nil { if err != nil {
return err return err

View File

@@ -12,7 +12,6 @@ import (
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/sessions"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"github.com/iwind/gosock/pkg/gosock" "github.com/iwind/gosock/pkg/gosock"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@@ -21,6 +20,8 @@ import (
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
"path/filepath"
"strings"
"syscall" "syscall"
"time" "time"
) )
@@ -85,10 +86,15 @@ func (this *AdminNode) Run() {
this.startAPINode() this.startAPINode()
// 启动Web服务 // 启动Web服务
sessionManager, err := NewSessionManager()
if err != nil {
log.Fatal("start session failed: " + err.Error())
return
}
TeaGo.NewServer(false). TeaGo.NewServer(false).
AccessLog(false). AccessLog(false).
EndAll(). EndAll().
Session(sessions.NewFileSessionManager(86400, secret), teaconst.CookieSID). Session(sessionManager, teaconst.CookieSID).
ReadHeaderTimeout(3 * time.Second). ReadHeaderTimeout(3 * time.Second).
ReadTimeout(1200 * time.Second). ReadTimeout(1200 * time.Second).
Start() Start()
@@ -360,6 +366,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) events.Notify(events.EventQuit)
os.Exit(0) os.Exit(0)

View File

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

View File

@@ -407,6 +407,10 @@ func (this *RPCClient) LoginRPC() pb.LoginServiceClient {
return pb.NewLoginServiceClient(this.pickConn()) return pb.NewLoginServiceClient(this.pickConn())
} }
func (this *RPCClient) LoginSessionRPC() pb.LoginSessionServiceClient {
return pb.NewLoginSessionServiceClient(this.pickConn())
}
func (this *RPCClient) NodeTaskRPC() pb.NodeTaskServiceClient { func (this *RPCClient) NodeTaskRPC() pb.NodeTaskServiceClient {
return pb.NewNodeTaskServiceClient(this.pickConn()) return pb.NewNodeTaskServiceClient(this.pickConn())
} }
@@ -571,41 +575,30 @@ func (this *RPCClient) pickConn() *grpc.ClientConn {
defer this.locker.Unlock() defer this.locker.Unlock()
// 检查连接状态 // 检查连接状态
if len(this.conns) > 0 { var countConns = len(this.conns)
var availableConns = []*grpc.ClientConn{} if countConns > 0 {
for _, state := range []connectivity.State{connectivity.Ready, connectivity.Idle, connectivity.Connecting} { 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 { for _, conn := range this.conns {
if conn.GetState() == state { if conn.GetState() == state {
availableConns = append(availableConns, conn) availableConns = append(availableConns, conn)
} }
} }
if len(availableConns) > 0 { 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()
}
} }
// 重新初始化 return this.randConn(this.conns)
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)]
} }
// Close 关闭 // Close 关闭
@@ -639,3 +632,14 @@ func (this *RPCClient) localIPAddrs() []string {
} }
return localIPAddrs 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

@@ -87,6 +87,7 @@ func (this *CheckUpdatesTask) Loop() error {
var apiURL = teaconst.UpdatesURL var apiURL = teaconst.UpdatesURL
apiURL = strings.ReplaceAll(apiURL, "${os}", runtime.GOOS) apiURL = strings.ReplaceAll(apiURL, "${os}", runtime.GOOS)
apiURL = strings.ReplaceAll(apiURL, "${arch}", runtime.GOARCH) apiURL = strings.ReplaceAll(apiURL, "${arch}", runtime.GOARCH)
apiURL = strings.ReplaceAll(apiURL, "${version}", teaconst.Version)
resp, err := http.Get(apiURL) resp, err := http.Get(apiURL)
if err != nil { if err != nil {
return errors.New("read api failed: " + err.Error()) return errors.New("read api failed: " + err.Error())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
package utils_test package utils_test
import ( import (
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/iwind/TeaGo/assert" "github.com/iwind/TeaGo/assert"
"testing" "testing"
) )

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

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

View File

@@ -52,11 +52,14 @@ type UpgradeManager struct {
writer *UpgradeFileWriter writer *UpgradeFileWriter
body io.ReadCloser body io.ReadCloser
isCancelled bool isCancelled bool
downloadURL string
} }
func NewUpgradeManager(component string) *UpgradeManager { func NewUpgradeManager(component string, downloadURL string) *UpgradeManager {
return &UpgradeManager{ return &UpgradeManager{
component: component, component: component,
downloadURL: downloadURL,
client: &http.Client{ client: &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
@@ -96,8 +99,8 @@ func (this *UpgradeManager) Start() error {
} }
// 检查新版本 // 检查新版本
var downloadURL = "" var downloadURL = this.downloadURL
{ if len(downloadURL) == 0 {
var url = teaconst.UpdatesURL var url = teaconst.UpdatesURL
var osName = runtime.GOOS var osName = runtime.GOOS
if Tea.IsTesting() && osName == "darwin" { if Tea.IsTesting() && osName == "darwin" {
@@ -105,10 +108,12 @@ func (this *UpgradeManager) Start() error {
} }
url = strings.ReplaceAll(url, "${os}", osName) url = strings.ReplaceAll(url, "${os}", osName)
url = strings.ReplaceAll(url, "${arch}", runtime.GOARCH) url = strings.ReplaceAll(url, "${arch}", runtime.GOARCH)
url = strings.ReplaceAll(url, "${version}", teaconst.Version)
req, err := http.NewRequest(http.MethodGet, url, nil) req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil { if err != nil {
return errors.New("create url request failed: " + err.Error()) return errors.New("create url request failed: " + err.Error())
} }
req.Header.Set("User-Agent", "Edge-Admin/"+teaconst.Version)
resp, err := this.client.Do(req) resp, err := this.client.Do(req)
if err != nil { if err != nil {
@@ -169,6 +174,7 @@ func (this *UpgradeManager) Start() error {
if err != nil { if err != nil {
return errors.New("create download request failed: " + err.Error()) return errors.New("create download request failed: " + err.Error())
} }
req.Header.Set("User-Agent", "Edge-Admin/"+teaconst.Version)
resp, err := this.client.Do(req) resp, err := this.client.Do(req)
if err != nil { if err != nil {

View File

@@ -49,7 +49,11 @@ func (this *ParentAction) ErrorText(err string) {
} }
func (this *ParentAction) NotFound(name string, itemId int64) { 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 { func (this *ParentAction) NewPage(total int64, size ...int64) *Page {
@@ -126,11 +130,8 @@ func (this *ParentAction) RPC() *rpc.RPCClient {
} }
// AdminContext 获取Context // AdminContext 获取Context
// 每个请求的context都必须是一个新的实例
func (this *ParentAction) AdminContext() context.Context { func (this *ParentAction) AdminContext() context.Context {
if this.ctx != nil {
return this.ctx
}
if this.rpcClient == nil { if this.rpcClient == nil {
rpcClient, err := rpc.SharedRPC() rpcClient, err := rpc.SharedRPC()
if err != nil { if err != nil {

View File

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

View File

@@ -3,12 +3,15 @@ package api
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/apinodeutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
stringutil "github.com/iwind/TeaGo/utils/string"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"time" "time"
) )
@@ -44,7 +47,7 @@ func (this *IndexAction) RunGet(params struct{}) {
for _, node := range nodesResp.ApiNodes { for _, node := range nodesResp.ApiNodes {
// 状态 // 状态
status := &nodeconfigs.NodeStatus{} var status = &nodeconfigs.NodeStatus{}
if len(node.StatusJSON) > 0 { if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status) err = json.Unmarshal(node.StatusJSON, &status)
if err != nil { if err != nil {
@@ -55,7 +58,7 @@ func (this *IndexAction) RunGet(params struct{}) {
} }
// Rest地址 // Rest地址
restAccessAddrs := []string{} var restAccessAddrs = []string{}
if node.RestIsOn { if node.RestIsOn {
if len(node.RestHTTPJSON) > 0 { if len(node.RestHTTPJSON) > 0 {
httpConfig := &serverconfigs.HTTPProtocolConfig{} httpConfig := &serverconfigs.HTTPProtocolConfig{}
@@ -86,6 +89,9 @@ func (this *IndexAction) RunGet(params struct{}) {
} }
} }
var shouldUpgrade = status.IsActive && len(status.BuildVersion) > 0 && stringutil.VersionCompare(teaconst.APINodeVersion, status.BuildVersion) > 0
canUpgrade, _ := apinodeutils.CanUpgrade(status.BuildVersion, status.OS, status.Arch)
nodeMaps = append(nodeMaps, maps.Map{ nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id, "id": node.Id,
"isOn": node.IsOn, "isOn": node.IsOn,
@@ -94,14 +100,17 @@ func (this *IndexAction) RunGet(params struct{}) {
"restAccessAddrs": restAccessAddrs, "restAccessAddrs": restAccessAddrs,
"isPrimary": node.IsPrimary, "isPrimary": node.IsPrimary,
"status": maps.Map{ "status": maps.Map{
"isActive": status.IsActive, "isActive": status.IsActive,
"updatedAt": status.UpdatedAt, "updatedAt": status.UpdatedAt,
"hostname": status.Hostname, "hostname": status.Hostname,
"cpuUsage": status.CPUUsage, "cpuUsage": status.CPUUsage,
"cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100), "cpuUsageText": fmt.Sprintf("%.2f%%", status.CPUUsage*100),
"memUsage": status.MemoryUsage, "memUsage": status.MemoryUsage,
"memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100), "memUsageText": fmt.Sprintf("%.2f%%", status.MemoryUsage*100),
"buildVersion": status.BuildVersion, "buildVersion": status.BuildVersion,
"latestVersion": teaconst.APINodeVersion,
"shouldUpgrade": shouldUpgrade,
"canUpgrade": shouldUpgrade && canUpgrade,
}, },
}) })
} }

View File

@@ -4,10 +4,12 @@ import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs" "github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
) )
type CreatePopupAction struct { type CreatePopupAction struct {
@@ -40,11 +42,11 @@ func (this *CreatePopupAction) RunPost(params struct {
Field("name", params.Name). Field("name", params.Name).
Require("请输入API节点名称") Require("请输入API节点名称")
httpConfig := &serverconfigs.HTTPProtocolConfig{} var httpConfig = &serverconfigs.HTTPProtocolConfig{}
httpsConfig := &serverconfigs.HTTPSProtocolConfig{} var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
// 监听地址 // 监听地址
listens := []*serverconfigs.NetworkAddressConfig{} var listens = []*serverconfigs.NetworkAddressConfig{}
err := json.Unmarshal(params.ListensJSON, &listens) err := json.Unmarshal(params.ListensJSON, &listens)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
@@ -64,15 +66,19 @@ func (this *CreatePopupAction) RunPost(params struct {
} }
// Rest监听地址 // Rest监听地址
restHTTPConfig := &serverconfigs.HTTPProtocolConfig{} var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
restHTTPSConfig := &serverconfigs.HTTPSProtocolConfig{} var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{}
if params.RestIsOn { if params.RestIsOn {
restListens := []*serverconfigs.NetworkAddressConfig{} var restListens = []*serverconfigs.NetworkAddressConfig{}
err = json.Unmarshal(params.RestListensJSON, &restListens) err = json.Unmarshal(params.RestListensJSON, &restListens)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
if len(restListens) == 0 {
this.Fail("请至少添加一个HTTP API监听端口")
return
}
for _, addr := range restListens { for _, addr := range restListens {
if addr.Protocol.IsHTTPFamily() { if addr.Protocol.IsHTTPFamily() {
restHTTPConfig.IsOn = true restHTTPConfig.IsOn = true
@@ -82,10 +88,35 @@ func (this *CreatePopupAction) RunPost(params struct {
restHTTPSConfig.Listen = append(restHTTPSConfig.Listen, addr) restHTTPSConfig.Listen = append(restHTTPSConfig.Listen, addr)
} }
} }
// 是否有端口冲突
var rpcAddresses = []string{}
for _, listen := range listens {
err := listen.Init()
if err != nil {
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
return
}
rpcAddresses = append(rpcAddresses, listen.Addresses()...)
}
for _, listen := range restListens {
err := listen.Init()
if err != nil {
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
return
}
for _, address := range listen.Addresses() {
if lists.ContainsString(rpcAddresses, address) {
this.Fail("HTTP API地址 '" + address + "' 和 GRPC地址冲突请修改后提交")
return
}
}
}
} }
// 证书 // 证书
certIds := []int64{} var certIds = []int64{}
if len(params.CertIdsJSON) > 0 { if len(params.CertIdsJSON) > 0 {
err = json.Unmarshal(params.CertIdsJSON, &certIds) err = json.Unmarshal(params.CertIdsJSON, &certIds)
if err != nil { if err != nil {
@@ -97,7 +128,7 @@ func (this *CreatePopupAction) RunPost(params struct {
this.Fail("请添加至少一个证书") this.Fail("请添加至少一个证书")
} }
certRefs := []*sslconfigs.SSLCertRef{} var certRefs = []*sslconfigs.SSLCertRef{}
for _, certId := range certIds { for _, certId := range certIds {
certRefs = append(certRefs, &sslconfigs.SSLCertRef{ certRefs = append(certRefs, &sslconfigs.SSLCertRef{
IsOn: true, IsOn: true,
@@ -131,7 +162,7 @@ func (this *CreatePopupAction) RunPost(params struct {
} }
// 访问地址 // 访问地址
accessAddrs := []*serverconfigs.NetworkAddressConfig{} var accessAddrs = []*serverconfigs.NetworkAddressConfig{}
err = json.Unmarshal(params.AccessAddrsJSON, &accessAddrs) err = json.Unmarshal(params.AccessAddrsJSON, &accessAddrs)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -24,7 +24,10 @@ func init() {
GetPost("/update", new(UpdateAction)). GetPost("/update", new(UpdateAction)).
Get("/install", new(InstallAction)). Get("/install", new(InstallAction)).
Get("/logs", new(LogsAction)). Get("/logs", new(LogsAction)).
GetPost("/upgradePopup", new(UpgradePopupAction)).
Post("/upgradeCheck", new(UpgradeCheckAction)).
//
EndAll() EndAll()
}) })
} }

View File

@@ -4,10 +4,12 @@ import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs" "github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
) )
@@ -175,12 +177,16 @@ func (this *UpdateAction) RunPost(params struct {
var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{} var restHTTPConfig = &serverconfigs.HTTPProtocolConfig{}
var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{} var restHTTPSConfig = &serverconfigs.HTTPSProtocolConfig{}
if params.RestIsOn { if params.RestIsOn {
restListens := []*serverconfigs.NetworkAddressConfig{} var restListens = []*serverconfigs.NetworkAddressConfig{}
err = json.Unmarshal(params.RestListensJSON, &restListens) err = json.Unmarshal(params.RestListensJSON, &restListens)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
if len(restListens) == 0 {
this.Fail("请至少添加一个HTTP API监听端口")
return
}
for _, addr := range restListens { for _, addr := range restListens {
if addr.Protocol.IsHTTPFamily() { if addr.Protocol.IsHTTPFamily() {
restHTTPConfig.IsOn = true restHTTPConfig.IsOn = true
@@ -190,6 +196,31 @@ func (this *UpdateAction) RunPost(params struct {
restHTTPSConfig.Listen = append(restHTTPSConfig.Listen, addr) restHTTPSConfig.Listen = append(restHTTPSConfig.Listen, addr)
} }
} }
// 是否有端口冲突
var rpcAddresses = []string{}
for _, listen := range listens {
err := listen.Init()
if err != nil {
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
return
}
rpcAddresses = append(rpcAddresses, listen.Addresses()...)
}
for _, listen := range restListens {
err := listen.Init()
if err != nil {
this.Fail("校验配置失败:" + configutils.QuoteIP(listen.Host) + ":" + listen.PortRange + ": " + err.Error())
return
}
for _, address := range listen.Addresses() {
if lists.ContainsString(rpcAddresses, address) {
this.Fail("HTTP API地址 '" + address + "' 和 GRPC地址冲突请修改后提交")
return
}
}
}
} }
// 证书 // 证书

View File

@@ -0,0 +1,67 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package node
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
// UpgradeCheckAction 检查升级结果
type UpgradeCheckAction struct {
actionutils.ParentAction
}
func (this *UpgradeCheckAction) Init() {
this.Nav("", "", "")
}
func (this *UpgradeCheckAction) RunPost(params struct {
NodeId int64
}) {
this.Data["isOk"] = false
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: params.NodeId})
if err != nil {
this.Success()
return
}
var node = nodeResp.ApiNode
if node == nil || len(node.AccessAddrs) == 0 {
this.Success()
return
}
apiConfig, err := configs.LoadAPIConfig()
if err != nil {
this.Success()
return
}
var newAPIConfig = apiConfig.Clone()
newAPIConfig.RPC.Endpoints = node.AccessAddrs
rpcClient, err := rpc.NewRPCClient(newAPIConfig, false)
if err != nil {
this.Success()
return
}
versionResp, err := rpcClient.APINodeRPC().FindCurrentAPINodeVersion(rpcClient.Context(0), &pb.FindCurrentAPINodeVersionRequest{})
if err != nil {
this.Success()
return
}
if versionResp.Version != teaconst.Version {
this.Success()
return
}
this.Data["isOk"] = true
this.Success()
}

View File

@@ -0,0 +1,124 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package node
import (
"encoding/json"
"errors"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/apinodeutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"strings"
)
type UpgradePopupAction struct {
actionutils.ParentAction
}
func (this *UpgradePopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpgradePopupAction) RunGet(params struct {
NodeId int64
}) {
this.Data["nodeId"] = params.NodeId
this.Data["nodeName"] = ""
this.Data["currentVersion"] = ""
this.Data["latestVersion"] = ""
this.Data["result"] = ""
this.Data["resultIsOk"] = true
this.Data["canUpgrade"] = false
this.Data["isUpgrading"] = false
nodeResp, err := this.RPC().APINodeRPC().FindEnabledAPINode(this.AdminContext(), &pb.FindEnabledAPINodeRequest{ApiNodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var node = nodeResp.ApiNode
if node == nil {
this.Data["result"] = "要升级的节点不存在"
this.Data["resultIsOk"] = false
this.Show()
return
}
this.Data["nodeName"] = node.Name + " / [" + strings.Join(node.AccessAddrs, ", ") + "]"
// 节点状态
var status = &nodeconfigs.NodeStatus{}
if len(node.StatusJSON) > 0 {
err = json.Unmarshal(node.StatusJSON, &status)
if err != nil {
this.ErrorPage(errors.New("decode status failed: " + err.Error()))
return
}
this.Data["currentVersion"] = status.BuildVersion
} else {
this.Data["result"] = "无法检测到节点当前版本"
this.Data["resultIsOk"] = false
this.Show()
return
}
this.Data["latestVersion"] = teaconst.APINodeVersion
if status.IsActive && len(status.BuildVersion) > 0 {
canUpgrade, reason := apinodeutils.CanUpgrade(status.BuildVersion, status.OS, status.Arch)
if !canUpgrade {
this.Data["result"] = reason
this.Data["resultIsOk"] = false
this.Show()
return
}
this.Data["canUpgrade"] = true
this.Data["result"] = "等待升级"
this.Data["resultIsOk"] = true
} else {
this.Data["result"] = "当前节点非连接状态无法远程升级"
this.Data["resultIsOk"] = false
this.Show()
return
}
// 是否正在升级
var oldUpgrader = apinodeutils.SharedManager.FindUpgrader(params.NodeId)
if oldUpgrader != nil {
this.Data["result"] = "正在升级中..."
this.Data["resultIsOk"] = false
this.Data["isUpgrading"] = true
}
this.Show()
}
func (this *UpgradePopupAction) RunPost(params struct {
NodeId int64
Must *actions.Must
CSRF *actionutils.CSRF
}) {
var manager = apinodeutils.SharedManager
var oldUpgrader = manager.FindUpgrader(params.NodeId)
if oldUpgrader != nil {
this.Fail("正在升级中,无需重复提交 ...")
return
}
var upgrader = apinodeutils.NewUpgrader(params.NodeId)
manager.AddUpgrader(upgrader)
defer func() {
manager.RemoveUpgrader(upgrader)
}()
err := upgrader.Upgrade()
if err != nil {
this.Fail("升级失败:" + err.Error())
return
}
this.Success()
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses/ipaddressutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses/ipaddressutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"time" "time"
@@ -88,7 +89,7 @@ func (this *DetailAction) RunGet(params struct {
return return
} }
var ipAddresses = ipAddressesResp.NodeIPAddresses var ipAddresses = ipAddressesResp.NodeIPAddresses
ipAddressMaps := []maps.Map{} var ipAddressMaps = []maps.Map{}
for _, addr := range ipAddressesResp.NodeIPAddresses { for _, addr := range ipAddressesResp.NodeIPAddresses {
thresholds, err := ipaddressutils.InitNodeIPAddressThresholds(this.Parent(), addr.Id) thresholds, err := ipaddressutils.InitNodeIPAddressThresholds(this.Parent(), addr.Id)
if err != nil { if err != nil {
@@ -102,6 +103,15 @@ func (this *DetailAction) RunGet(params struct {
addr.Ip = addr.BackupIP addr.Ip = addr.BackupIP
} }
// 专属集群
var addrClusterMaps = []maps.Map{}
for _, addrCluster := range addr.NodeClusters {
addrClusterMaps = append(addrClusterMaps, maps.Map{
"id": addrCluster.Id,
"name": addrCluster.Name,
})
}
ipAddressMaps = append(ipAddressMaps, maps.Map{ ipAddressMaps = append(ipAddressMaps, maps.Map{
"id": addr.Id, "id": addr.Id,
"name": addr.Name, "name": addr.Name,
@@ -110,6 +120,7 @@ func (this *DetailAction) RunGet(params struct {
"canAccess": addr.CanAccess, "canAccess": addr.CanAccess,
"isOn": addr.IsOn, "isOn": addr.IsOn,
"isUp": addr.IsUp, "isUp": addr.IsUp,
"clusters": addrClusterMaps,
"thresholds": thresholds, "thresholds": thresholds,
}) })
} }
@@ -151,16 +162,31 @@ func (this *DetailAction) RunGet(params struct {
if !addr.CanAccess || !addr.IsUp || !addr.IsOn { if !addr.CanAccess || !addr.IsUp || !addr.IsOn {
continue continue
} }
// 过滤集群
if len(addr.NodeClusters) > 0 {
var inCluster = false
for _, addrCluster := range addr.NodeClusters {
if addrCluster.Id == cluster.Id {
inCluster = true
}
}
if !inCluster {
continue
}
}
for _, route := range dnsInfo.Routes { for _, route := range dnsInfo.Routes {
var recordType = "A" var recordType = "A"
if utils.IsIPv6(addr.Ip) { if utils.IsIPv6(addr.Ip) {
recordType = "AAAA" recordType = "AAAA"
} }
recordMaps = append(recordMaps, maps.Map{ recordMaps = append(recordMaps, maps.Map{
"name": dnsInfo.NodeClusterDNSName + "." + domainName, "name": dnsInfo.NodeClusterDNSName + "." + domainName,
"type": recordType, "type": recordType,
"route": route.Name, "route": route.Name,
"value": addr.Ip, "value": addr.Ip,
"clusterName": cluster.Name,
}) })
} }
} }
@@ -178,8 +204,8 @@ func (this *DetailAction) RunGet(params struct {
} }
} }
grantMap := maps.Map{} var grantMap = maps.Map{}
grantId := loginParams.GetInt64("grantId") var grantId = loginParams.GetInt64("grantId")
if grantId > 0 { if grantId > 0 {
grantResp, err := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{NodeGrantId: grantId}) grantResp, err := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{NodeGrantId: grantId})
if err != nil { if err != nil {
@@ -300,6 +326,22 @@ func (this *DetailAction) RunGet(params struct {
lnAddrs = []string{} lnAddrs = []string{}
} }
// API节点地址
var apiNodeAddrStrings = []string{}
var apiNodeAddrs = []*serverconfigs.NetworkAddressConfig{}
if len(node.ApiNodeAddrsJSON) > 0 {
err = json.Unmarshal(node.ApiNodeAddrsJSON, &apiNodeAddrs)
if err != nil {
this.ErrorPage(err)
return
}
for _, addr := range apiNodeAddrs {
if addr.Init() == nil {
apiNodeAddrStrings = append(apiNodeAddrStrings, addr.FullAddresses()...)
}
}
}
this.Data["node"] = maps.Map{ this.Data["node"] = maps.Map{
"id": node.Id, "id": node.Id,
"name": node.Name, "name": node.Name,
@@ -319,6 +361,7 @@ func (this *DetailAction) RunGet(params struct {
"levelInfo": nodeconfigs.FindNodeLevel(int(node.Level)), "levelInfo": nodeconfigs.FindNodeLevel(int(node.Level)),
"lnAddrs": lnAddrs, "lnAddrs": lnAddrs,
"enableIPLists": node.EnableIPLists, "enableIPLists": node.EnableIPLists,
"apiNodeAddrs": apiNodeAddrStrings,
"status": maps.Map{ "status": maps.Map{
"isActive": status.IsActive, "isActive": status.IsActive,
@@ -338,6 +381,8 @@ func (this *DetailAction) RunGet(params struct {
"cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize), "cacheTotalDiskSize": numberutils.FormatBytes(status.CacheTotalDiskSize),
"cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize), "cacheTotalMemorySize": numberutils.FormatBytes(status.CacheTotalMemorySize),
"exePath": status.ExePath, "exePath": status.ExePath,
"apiSuccessPercent": status.APISuccessPercent,
"apiAvgCostSeconds": status.APIAvgCostSeconds,
}, },
"group": groupMap, "group": groupMap,

View File

@@ -7,6 +7,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
@@ -57,9 +58,19 @@ func (this *IndexAction) RunGet(params struct {
} }
} }
var diskSubDirs = []*serverconfigs.CacheDir{}
if len(node.CacheDiskSubDirsJSON) > 0 {
err = json.Unmarshal(node.CacheDiskSubDirsJSON, &diskSubDirs)
if err != nil {
this.ErrorPage(err)
return
}
}
var nodeMap = this.Data["node"].(maps.Map) var nodeMap = this.Data["node"].(maps.Map)
nodeMap["maxCacheDiskCapacity"] = maxCacheDiskCapacity nodeMap["maxCacheDiskCapacity"] = maxCacheDiskCapacity
nodeMap["cacheDiskDir"] = node.CacheDiskDir nodeMap["cacheDiskDir"] = node.CacheDiskDir
nodeMap["cacheDiskSubDirs"] = diskSubDirs
nodeMap["maxCacheMemoryCapacity"] = maxCacheMemoryCapacity nodeMap["maxCacheMemoryCapacity"] = maxCacheMemoryCapacity
this.Show() this.Show()
@@ -69,6 +80,7 @@ func (this *IndexAction) RunPost(params struct {
NodeId int64 NodeId int64
MaxCacheDiskCapacityJSON []byte MaxCacheDiskCapacityJSON []byte
CacheDiskDir string CacheDiskDir string
CacheDiskSubDirsJSON []byte
MaxCacheMemoryCapacityJSON []byte MaxCacheMemoryCapacityJSON []byte
Must *actions.Must Must *actions.Must
@@ -105,10 +117,20 @@ func (this *IndexAction) RunPost(params struct {
} }
} }
if len(params.CacheDiskSubDirsJSON) > 0 {
var cacheSubDirs = []*serverconfigs.CacheDir{}
err := json.Unmarshal(params.CacheDiskSubDirsJSON, &cacheSubDirs)
if err != nil {
this.ErrorPage(err)
return
}
}
_, err := this.RPC().NodeRPC().UpdateNodeCache(this.AdminContext(), &pb.UpdateNodeCacheRequest{ _, err := this.RPC().NodeRPC().UpdateNodeCache(this.AdminContext(), &pb.UpdateNodeCacheRequest{
NodeId: params.NodeId, NodeId: params.NodeId,
MaxCacheDiskCapacity: pbMaxCacheDiskCapacity, MaxCacheDiskCapacity: pbMaxCacheDiskCapacity,
CacheDiskDir: params.CacheDiskDir, CacheDiskDir: params.CacheDiskDir,
CacheDiskSubDirsJSON: params.CacheDiskSubDirsJSON,
MaxCacheMemoryCapacity: pbMaxCacheMemoryCapacity, MaxCacheMemoryCapacity: pbMaxCacheMemoryCapacity,
}) })
if err != nil { if err != nil {

View File

@@ -8,6 +8,7 @@ import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
) )
@@ -50,6 +51,22 @@ func (this *IndexAction) RunGet(params struct {
} }
this.Data["dnsResolverConfig"] = dnsResolverConfig this.Data["dnsResolverConfig"] = dnsResolverConfig
// API相关
apiConfigResp, err := this.RPC().NodeRPC().FindNodeAPIConfig(this.AdminContext(), &pb.FindNodeAPIConfigRequest{NodeId: params.NodeId})
if err != nil {
this.ErrorPage(err)
return
}
var apiNodeAddrs = []*serverconfigs.NetworkAddressConfig{}
if len(apiConfigResp.ApiNodeAddrsJSON) > 0 {
err = json.Unmarshal(apiConfigResp.ApiNodeAddrsJSON, &apiNodeAddrs)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["apiNodeAddrs"] = apiNodeAddrs
this.Show() this.Show()
} }
@@ -59,6 +76,8 @@ func (this *IndexAction) RunPost(params struct {
DnsResolverJSON []byte DnsResolverJSON []byte
ApiNodeAddrsJSON []byte
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
}) { }) {
@@ -68,6 +87,7 @@ func (this *IndexAction) RunPost(params struct {
this.Fail("CPU线程数不能小于0") this.Fail("CPU线程数不能小于0")
} }
// 系统设置
_, err := this.RPC().NodeRPC().UpdateNodeSystem(this.AdminContext(), &pb.UpdateNodeSystemRequest{ _, err := this.RPC().NodeRPC().UpdateNodeSystem(this.AdminContext(), &pb.UpdateNodeSystemRequest{
NodeId: params.NodeId, NodeId: params.NodeId,
MaxCPU: params.MaxCPU, MaxCPU: params.MaxCPU,
@@ -77,6 +97,7 @@ func (this *IndexAction) RunPost(params struct {
return return
} }
// DNS解析设置
var dnsResolverConfig = nodeconfigs.DefaultDNSResolverConfig() var dnsResolverConfig = nodeconfigs.DefaultDNSResolverConfig()
err = json.Unmarshal(params.DnsResolverJSON, dnsResolverConfig) err = json.Unmarshal(params.DnsResolverJSON, dnsResolverConfig)
if err != nil { if err != nil {
@@ -98,5 +119,28 @@ func (this *IndexAction) RunPost(params struct {
return return
} }
// API节点设置
var apiNodeAddrs = []*serverconfigs.NetworkAddressConfig{}
if len(params.ApiNodeAddrsJSON) > 0 {
err = json.Unmarshal(params.ApiNodeAddrsJSON, &apiNodeAddrs)
if err != nil {
this.Fail("API节点地址校验错误" + err.Error())
}
for _, addr := range apiNodeAddrs {
err = addr.Init()
if err != nil {
this.Fail("API节点地址校验错误" + err.Error())
}
}
}
_, err = this.RPC().NodeRPC().UpdateNodeAPIConfig(this.AdminContext(), &pb.UpdateNodeAPIConfigRequest{
NodeId: params.NodeId,
ApiNodeAddrsJSON: params.ApiNodeAddrsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success() this.Success()
} }

View File

@@ -64,12 +64,22 @@ func (this *UpdateAction) RunGet(params struct {
} }
var ipAddressMaps = []maps.Map{} var ipAddressMaps = []maps.Map{}
for _, addr := range ipAddressesResp.NodeIPAddresses { for _, addr := range ipAddressesResp.NodeIPAddresses {
// 阈值
thresholds, err := ipaddressutils.InitNodeIPAddressThresholds(this.Parent(), addr.Id) thresholds, err := ipaddressutils.InitNodeIPAddressThresholds(this.Parent(), addr.Id)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
// 专属集群
var clusterMaps = []maps.Map{}
for _, addrCluster := range addr.NodeClusters {
clusterMaps = append(clusterMaps, maps.Map{
"id": addrCluster.Id,
"name": addrCluster.Name,
})
}
ipAddressMaps = append(ipAddressMaps, maps.Map{ ipAddressMaps = append(ipAddressMaps, maps.Map{
"id": addr.Id, "id": addr.Id,
"name": addr.Name, "name": addr.Name,
@@ -78,6 +88,7 @@ func (this *UpdateAction) RunGet(params struct {
"isOn": addr.IsOn, "isOn": addr.IsOn,
"isUp": addr.IsUp, "isUp": addr.IsUp,
"thresholds": thresholds, "thresholds": thresholds,
"clusters": clusterMaps,
}) })
} }

View File

@@ -142,8 +142,17 @@ func (this *NodesAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
ipAddresses := []maps.Map{} var ipAddresses = []maps.Map{}
for _, addr := range ipAddressesResp.NodeIPAddresses { for _, addr := range ipAddressesResp.NodeIPAddresses {
// 专属集群
var addrClusterMaps = []maps.Map{}
for _, addrCluster := range addr.NodeClusters {
addrClusterMaps = append(addrClusterMaps, maps.Map{
"id": addrCluster.Id,
"name": addrCluster.Name,
})
}
ipAddresses = append(ipAddresses, maps.Map{ ipAddresses = append(ipAddresses, maps.Map{
"id": addr.Id, "id": addr.Id,
"name": addr.Name, "name": addr.Name,
@@ -151,6 +160,7 @@ func (this *NodesAction) RunGet(params struct {
"canAccess": addr.CanAccess, "canAccess": addr.CanAccess,
"isUp": addr.IsUp, "isUp": addr.IsUp,
"isOn": addr.IsOn, "isOn": addr.IsOn,
"clusters": addrClusterMaps,
}) })
} }

View File

@@ -82,9 +82,14 @@ func (this *IndexAction) RunPost(params struct {
HttpAccessLogEnableResponseHeaders bool HttpAccessLogEnableResponseHeaders bool
HttpAccessLogCommonRequestHeadersOnly bool HttpAccessLogCommonRequestHeadersOnly bool
HttpAccessLogEnableCookies bool HttpAccessLogEnableCookies bool
HttpAccessLogEnableServerNotFound bool
LogRecordServerError bool LogRecordServerError bool
PerformanceAutoReadTimeout bool
PerformanceAutoWriteTimeout bool
PerformanceDebug bool
Must *actions.Must Must *actions.Must
CSRF *actionutils.CSRF CSRF *actionutils.CSRF
}) { }) {
@@ -124,17 +129,26 @@ func (this *IndexAction) RunPost(params struct {
} }
} }
// 域名
config.HTTPAll.AllowMismatchDomains = allowMismatchDomains config.HTTPAll.AllowMismatchDomains = allowMismatchDomains
config.HTTPAll.AllowNodeIP = params.HttpAllAllowNodeIP config.HTTPAll.AllowNodeIP = params.HttpAllAllowNodeIP
config.HTTPAll.DefaultDomain = params.HttpAllDefaultDomain config.HTTPAll.DefaultDomain = params.HttpAllDefaultDomain
// 访问日志
config.HTTPAccessLog.EnableRequestHeaders = params.HttpAccessLogEnableRequestHeaders config.HTTPAccessLog.EnableRequestHeaders = params.HttpAccessLogEnableRequestHeaders
config.HTTPAccessLog.EnableResponseHeaders = params.HttpAccessLogEnableResponseHeaders config.HTTPAccessLog.EnableResponseHeaders = params.HttpAccessLogEnableResponseHeaders
config.HTTPAccessLog.CommonRequestHeadersOnly = params.HttpAccessLogCommonRequestHeadersOnly config.HTTPAccessLog.CommonRequestHeadersOnly = params.HttpAccessLogCommonRequestHeadersOnly
config.HTTPAccessLog.EnableCookies = params.HttpAccessLogEnableCookies config.HTTPAccessLog.EnableCookies = params.HttpAccessLogEnableCookies
config.HTTPAccessLog.EnableServerNotFound = params.HttpAccessLogEnableServerNotFound
// 日志
config.Log.RecordServerError = params.LogRecordServerError config.Log.RecordServerError = params.LogRecordServerError
// 性能
config.Performance.AutoReadTimeout = params.PerformanceAutoReadTimeout
config.Performance.AutoWriteTimeout = params.PerformanceAutoWriteTimeout
config.Performance.Debug = params.PerformanceDebug
err = config.Init() err = config.Init()
if err != nil { if err != nil {
this.Fail("配置校验失败:" + err.Error()) this.Fail("配置校验失败:" + err.Error())

View File

@@ -89,6 +89,19 @@ func (this *IndexAction) RunGet(params struct {
} }
} }
// DNS信息
var fullDomainName = ""
if len(cluster.DnsName) > 0 && cluster.DnsDomainId > 0 {
domainResp, err := this.RPC().DNSDomainRPC().FindBasicDNSDomain(this.AdminContext(), &pb.FindBasicDNSDomainRequest{DnsDomainId: cluster.DnsDomainId})
if err != nil {
this.ErrorPage(err)
return
}
if domainResp.DnsDomain != nil {
fullDomainName = cluster.DnsName + "." + domainResp.DnsDomain.Name
}
}
this.Data["cluster"] = maps.Map{ this.Data["cluster"] = maps.Map{
"id": cluster.Id, "id": cluster.Id,
"name": cluster.Name, "name": cluster.Name,
@@ -100,6 +113,7 @@ func (this *IndexAction) RunGet(params struct {
"autoRemoteStart": cluster.AutoRemoteStart, "autoRemoteStart": cluster.AutoRemoteStart,
"autoInstallNftables": cluster.AutoInstallNftables, "autoInstallNftables": cluster.AutoInstallNftables,
"sshParams": sshParams, "sshParams": sshParams,
"domainName": fullDomainName,
} }
// 默认值 // 默认值
@@ -122,6 +136,7 @@ func (this *IndexAction) RunPost(params struct {
AutoOpenPorts bool AutoOpenPorts bool
ClockAutoSync bool ClockAutoSync bool
ClockServer string ClockServer string
ClockCheckChrony bool
AutoRemoteStart bool AutoRemoteStart bool
AutoInstallNftables bool AutoInstallNftables bool
@@ -154,6 +169,7 @@ func (this *IndexAction) RunPost(params struct {
var clockConfig = nodeconfigs.DefaultClockConfig() var clockConfig = nodeconfigs.DefaultClockConfig()
clockConfig.AutoSync = params.ClockAutoSync clockConfig.AutoSync = params.ClockAutoSync
clockConfig.Server = params.ClockServer clockConfig.Server = params.ClockServer
clockConfig.CheckChrony = params.ClockCheckChrony
clockConfigJSON, err := json.Marshal(clockConfig) clockConfigJSON, err := json.Marshal(clockConfig)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -60,7 +60,7 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
var tabbar = actionutils.NewTabbar() var tabbar = actionutils.NewTabbar()
tabbar.Add("集群列表", "", "/clusters", "", false) tabbar.Add("集群列表", "", "/clusters", "", false)
if teaconst.IsPlus { if teaconst.IsPlus {
tabbar.Add("集群看板", "", "/clusters/cluster/boards?clusterId="+clusterIdString, "board", selectedTabbar == "board") tabbar.Add("集群看板", "", "/clusters/cluster/boards?clusterId="+clusterIdString, "chart line area", selectedTabbar == "board")
} }
tabbar.Add("集群节点", "", "/clusters/cluster/nodes?clusterId="+clusterIdString, "server", selectedTabbar == "node") tabbar.Add("集群节点", "", "/clusters/cluster/nodes?clusterId="+clusterIdString, "server", selectedTabbar == "node")
tabbar.Add("集群设置", "", "/clusters/cluster/settings?clusterId="+clusterIdString, "setting", selectedTabbar == "setting") tabbar.Add("集群设置", "", "/clusters/cluster/settings?clusterId="+clusterIdString, "setting", selectedTabbar == "setting")
@@ -68,7 +68,7 @@ func (this *ClusterHelper) BeforeAction(actionPtr actions.ActionWrapper) (goNext
actionutils.SetTabbar(action, tabbar) actionutils.SetTabbar(action, tabbar)
// 左侧菜单 // 左侧菜单
secondMenuItem := action.Data.GetString("secondMenuItem") var secondMenuItem = action.Data.GetString("secondMenuItem")
switch selectedTabbar { switch selectedTabbar {
case "setting": case "setting":
var menuItems = this.createSettingMenu(cluster, clusterInfo, secondMenuItem) var menuItems = this.createSettingMenu(cluster, clusterInfo, secondMenuItem)

View File

@@ -0,0 +1,60 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package logs
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
type DeleteAllAction struct {
actionutils.ParentAction
}
func (this *DeleteAllAction) RunPost(params struct {
DayFrom string
DayTo string
Keyword string
Level string
Type string // unread, needFix
Tag string
ClusterId int64
NodeId int64
}) {
defer this.CreateLogInfo("批量删除节点运行日志")
// 目前仅允许通过关键词删除,防止误删
if len(params.Keyword) == 0 {
this.Fail("目前仅允许通过关键词删除")
return
}
var fixedState configutils.BoolState = 0
var allServers = false
if params.Type == "needFix" {
fixedState = configutils.BoolStateNo
allServers = true
}
_, err := this.RPC().NodeLogRPC().DeleteNodeLogs(this.AdminContext(), &pb.DeleteNodeLogsRequest{
NodeClusterId: params.ClusterId,
NodeId: params.NodeId,
Role: nodeconfigs.NodeRoleNode,
DayFrom: params.DayFrom,
DayTo: params.DayTo,
Keyword: params.Keyword,
Level: params.Level,
IsUnread: params.Type == "unread",
Tag: params.Tag,
FixedState: int32(fixedState),
AllServers: allServers,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -39,6 +39,7 @@ func (this *IndexAction) RunGet(params struct {
this.Data["dayFrom"] = params.DayFrom this.Data["dayFrom"] = params.DayFrom
this.Data["dayTo"] = params.DayTo this.Data["dayTo"] = params.DayTo
this.Data["keyword"] = params.Keyword this.Data["keyword"] = params.Keyword
this.Data["searchedKeyword"] = params.Keyword
this.Data["level"] = params.Level this.Data["level"] = params.Level
this.Data["type"] = params.Type this.Data["type"] = params.Type
this.Data["tag"] = params.Tag this.Data["tag"] = params.Tag
@@ -97,6 +98,8 @@ func (this *IndexAction) RunGet(params struct {
return return
} }
var count = countResp.Count var count = countResp.Count
this.Data["countLogs"] = count
var page = this.NewPage(count) var page = this.NewPage(count)
this.Data["page"] = page.AsHTML() this.Data["page"] = page.AsHTML()

View File

@@ -18,6 +18,7 @@ func init() {
Post("/readAllLogs", new(ReadAllLogsAction)). Post("/readAllLogs", new(ReadAllLogsAction)).
Post("/fix", new(FixAction)). Post("/fix", new(FixAction)).
Post("/fixAll", new(FixAllAction)). Post("/fixAll", new(FixAllAction)).
Post("/deleteAll", new(DeleteAllAction)).
EndAll() EndAll()
}) })
} }

View File

@@ -144,8 +144,17 @@ func (this *NodesAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
ipAddresses := []maps.Map{} var ipAddresses = []maps.Map{}
for _, addr := range ipAddressesResp.NodeIPAddresses { for _, addr := range ipAddressesResp.NodeIPAddresses {
// 专属集群
var addrClusterMaps = []maps.Map{}
for _, addrCluster := range addr.NodeClusters {
addrClusterMaps = append(addrClusterMaps, maps.Map{
"id": addrCluster.Id,
"name": addrCluster.Name,
})
}
ipAddresses = append(ipAddresses, maps.Map{ ipAddresses = append(ipAddresses, maps.Map{
"id": addr.Id, "id": addr.Id,
"name": addr.Name, "name": addr.Name,
@@ -153,6 +162,7 @@ func (this *NodesAction) RunGet(params struct {
"canAccess": addr.CanAccess, "canAccess": addr.CanAccess,
"isUp": addr.IsUp, "isUp": addr.IsUp,
"isOn": addr.IsOn, "isOn": addr.IsOn,
"clusters": addrClusterMaps,
}) })
} }
@@ -175,7 +185,7 @@ func (this *NodesAction) RunGet(params struct {
} }
// DNS // DNS
dnsRouteNames := []string{} var dnsRouteNames = []string{}
for _, route := range node.DnsRoutes { for _, route := range node.DnsRoutes {
dnsRouteNames = append(dnsRouteNames, route.Name) dnsRouteNames = append(dnsRouteNames, route.Name)
} }

View File

@@ -3,23 +3,38 @@ package tasks
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"time"
) )
type CheckAction struct { type CheckAction struct {
actionutils.ParentAction actionutils.ParentAction
} }
func (this *CheckAction) RunPost(params struct{}) { func (this *CheckAction) RunPost(params struct {
resp, err := this.RPC().NodeTaskRPC().ExistsNodeTasks(this.AdminContext(), &pb.ExistsNodeTasksRequest{ IsDoing bool
ExcludeTypes: []string{"ipItemChanged"}, HasError bool
}) IsUpdated bool
if err != nil { }) {
this.ErrorPage(err) var maxTries = 10
return for i := 0; i < maxTries; i++ {
} resp, err := this.RPC().NodeTaskRPC().ExistsNodeTasks(this.AdminContext(), &pb.ExistsNodeTasksRequest{
ExcludeTypes: []string{"ipItemChanged"},
})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["isDoing"] = resp.ExistTasks // 如果没有数据变化,继续查询
this.Data["hasError"] = resp.ExistError if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError {
time.Sleep(3 * time.Second)
continue
}
this.Data["isDoing"] = resp.ExistTasks
this.Data["hasError"] = resp.ExistError
break
}
this.Success() this.Success()
} }

View File

@@ -71,7 +71,10 @@ func ValidateRecordName(name string) bool {
} }
pieces := strings.Split(name, ".") pieces := strings.Split(name, ".")
for _, piece := range pieces { for index, piece := range pieces {
if index == 0 && piece == "*" {
continue
}
if piece == "-" || if piece == "-" ||
strings.HasPrefix(piece, "-") || strings.HasPrefix(piece, "-") ||
strings.HasSuffix(piece, "-") || strings.HasSuffix(piece, "-") ||
@@ -130,6 +133,11 @@ func ValidateRecordValue(recordType dnsconfigs.RecordType, value string) (messag
message = "请输入正确的邮件服务器域名" message = "请输入正确的邮件服务器域名"
return return
} }
case dnsconfigs.RecordTypeSRV:
if len(value) == 0 {
message = "请输入主机名"
return
}
case dnsconfigs.RecordTypeTXT: case dnsconfigs.RecordTypeTXT:
if len(value) > 512 { if len(value) > 512 {
message = "文本长度不能超出512字节" message = "文本长度不能超出512字节"

View File

@@ -64,6 +64,7 @@ func (this *CreatePopupAction) RunPost(params struct {
// HuaweiDNS // HuaweiDNS
ParamHuaweiAccessKeyId string ParamHuaweiAccessKeyId string
ParamHuaweiAccessKeySecret string ParamHuaweiAccessKeySecret string
ParamHuaweiEndpoint string
// CloudFlare // CloudFlare
ParamCloudFlareAPIKey string ParamCloudFlareAPIKey string
@@ -119,6 +120,7 @@ func (this *CreatePopupAction) RunPost(params struct {
apiParams["accessKeyId"] = params.ParamHuaweiAccessKeyId apiParams["accessKeyId"] = params.ParamHuaweiAccessKeyId
apiParams["accessKeySecret"] = params.ParamHuaweiAccessKeySecret apiParams["accessKeySecret"] = params.ParamHuaweiAccessKeySecret
apiParams["endpoint"] = params.ParamHuaweiEndpoint
case "cloudFlare": case "cloudFlare":
params.Must. params.Must.
Field("paramCloudFlareAPIKey", params.ParamCloudFlareAPIKey). Field("paramCloudFlareAPIKey", params.ParamCloudFlareAPIKey).

View File

@@ -91,6 +91,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
// HuaweiDNS // HuaweiDNS
ParamHuaweiAccessKeyId string ParamHuaweiAccessKeyId string
ParamHuaweiAccessKeySecret string ParamHuaweiAccessKeySecret string
ParamHuaweiEndpoint string
// CloudFlare // CloudFlare
ParamCloudFlareAPIKey string ParamCloudFlareAPIKey string
@@ -148,6 +149,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
apiParams["accessKeyId"] = params.ParamHuaweiAccessKeyId apiParams["accessKeyId"] = params.ParamHuaweiAccessKeyId
apiParams["accessKeySecret"] = params.ParamHuaweiAccessKeySecret apiParams["accessKeySecret"] = params.ParamHuaweiAccessKeySecret
apiParams["endpoint"] = params.ParamHuaweiEndpoint
case "cloudFlare": case "cloudFlare":
params.Must. params.Must.
Field("paramCloudFlareAPIKey", params.ParamCloudFlareAPIKey). Field("paramCloudFlareAPIKey", params.ParamCloudFlareAPIKey).

View File

@@ -3,21 +3,36 @@ package tasks
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"time"
) )
type CheckAction struct { type CheckAction struct {
actionutils.ParentAction actionutils.ParentAction
} }
func (this *CheckAction) RunPost(params struct{}) { func (this *CheckAction) RunPost(params struct {
resp, err := this.RPC().DNSTaskRPC().ExistsDNSTasks(this.AdminContext(), &pb.ExistsDNSTasksRequest{}) IsDoing bool
if err != nil { HasError bool
this.ErrorPage(err) IsUpdated bool
return }) {
} var maxTries = 10
for i := 0; i < maxTries; i++ {
resp, err := this.RPC().DNSTaskRPC().ExistsDNSTasks(this.AdminContext(), &pb.ExistsDNSTasksRequest{})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["isDoing"] = resp.ExistTasks // 如果没有数据变化,继续查询
this.Data["hasError"] = resp.ExistError if i < maxTries-1 && params.IsUpdated && resp.ExistTasks == params.IsDoing && resp.ExistError == params.HasError {
time.Sleep(3 * time.Second)
continue
}
this.Data["isDoing"] = resp.ExistTasks
this.Data["hasError"] = resp.ExistError
break
}
this.Success() this.Success()
} }

View File

@@ -1,30 +0,0 @@
package index
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
)
// 检查是否需要OTP
type CheckOTPAction struct {
actionutils.ParentAction
}
func (this *CheckOTPAction) Init() {
this.Nav("", "", "")
}
func (this *CheckOTPAction) RunPost(params struct {
Username string
Must *actions.Must
}) {
checkResp, err := this.RPC().AdminRPC().CheckAdminOTPWithUsername(this.AdminContext(), &pb.CheckAdminOTPWithUsernameRequest{Username: params.Username})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["requireOTP"] = checkResp.RequireOTP
this.Success()
}

View File

@@ -1,7 +1,6 @@
package index package index
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders" "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const" teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
@@ -14,10 +13,8 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string" stringutil "github.com/iwind/TeaGo/utils/string"
"github.com/xlzd/gotp"
"time" "time"
) )
@@ -27,7 +24,8 @@ type IndexAction struct {
// 首页(登录页) // 首页(登录页)
var TokenSalt = stringutil.Rand(32) // TokenKey 加密用的密钥
var TokenKey = stringutil.Rand(32)
func (this *IndexAction) RunGet(params struct { func (this *IndexAction) RunGet(params struct {
From string From string
@@ -59,7 +57,7 @@ func (this *IndexAction) RunGet(params struct {
this.Data["menu"] = "signIn" this.Data["menu"] = "signIn"
var timestamp = fmt.Sprintf("%d", time.Now().Unix()) var timestamp = fmt.Sprintf("%d", time.Now().Unix())
this.Data["token"] = stringutil.Md5(TokenSalt+timestamp) + timestamp this.Data["token"] = stringutil.Md5(TokenKey+timestamp) + timestamp
this.Data["from"] = params.From this.Data["from"] = params.From
uiConfig, err := configloaders.LoadAdminUIConfig() uiConfig, err := configloaders.LoadAdminUIConfig()
@@ -93,9 +91,10 @@ func (this *IndexAction) RunPost(params struct {
Password string Password string
OtpCode string OtpCode string
Remember bool Remember bool
Must *actions.Must
Auth *helpers.UserShouldAuth Must *actions.Must
CSRF *actionutils.CSRF Auth *helpers.UserShouldAuth
CSRF *actionutils.CSRF
}) { }) {
params.Must. params.Must.
Field("username", params.Username). Field("username", params.Username).
@@ -112,7 +111,7 @@ func (this *IndexAction) RunPost(params struct {
this.Fail("请通过登录页面登录") this.Fail("请通过登录页面登录")
} }
var timestampString = params.Token[32:] var timestampString = params.Token[32:]
if stringutil.Md5(TokenSalt+timestampString) != params.Token[:32] { if stringutil.Md5(TokenKey+timestampString) != params.Token[:32] {
this.FailField("refresh", "登录页面已过期,请刷新后重试") this.FailField("refresh", "登录页面已过期,请刷新后重试")
} }
var timestamp = types.Int64(timestampString) var timestamp = types.Int64(timestampString)
@@ -123,6 +122,7 @@ func (this *IndexAction) RunPost(params struct {
rpcClient, err := rpc.SharedRPC() rpcClient, err := rpc.SharedRPC()
if err != nil { if err != nil {
this.Fail("服务器出了点小问题:" + err.Error()) this.Fail("服务器出了点小问题:" + err.Error())
return
} }
resp, err := rpcClient.AdminRPC().LoginAdmin(rpcClient.Context(0), &pb.LoginAdminRequest{ resp, err := rpcClient.AdminRPC().LoginAdmin(rpcClient.Context(0), &pb.LoginAdminRequest{
Username: params.Username, Username: params.Username,
@@ -136,6 +136,7 @@ func (this *IndexAction) RunPost(params struct {
} }
actionutils.Fail(this, err) actionutils.Fail(this, err)
return
} }
if !resp.IsOk { if !resp.IsOk {
@@ -145,31 +146,37 @@ func (this *IndexAction) RunPost(params struct {
} }
this.Fail("请输入正确的用户名密码") this.Fail("请输入正确的用户名密码")
return
} }
var adminId = resp.AdminId
// 检查OTP // 检查是否支持OTP
otpLoginResp, err := this.RPC().LoginRPC().FindEnabledLogin(this.AdminContext(), &pb.FindEnabledLoginRequest{ checkOTPResp, err := this.RPC().AdminRPC().CheckAdminOTPWithUsername(this.AdminContext(), &pb.CheckAdminOTPWithUsernameRequest{Username: params.Username})
AdminId: resp.AdminId,
Type: "otp",
})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
if otpLoginResp.Login != nil && otpLoginResp.Login.IsOn { var requireOTP = checkOTPResp.RequireOTP
var loginParams = maps.Map{} this.Data["requireOTP"] = requireOTP
err = json.Unmarshal(otpLoginResp.Login.ParamsJSON, &loginParams) if requireOTP {
this.Data["remember"] = params.Remember
var sid = this.Session().Sid
this.Data["sid"] = sid
_, err = this.RPC().LoginSessionRPC().WriteLoginSessionValue(this.AdminContext(), &pb.WriteLoginSessionValueRequest{
Sid: sid + "_otp",
Key: "adminId",
Value: types.String(adminId),
})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
secret := loginParams.GetString("secret") this.Success()
if gotp.NewDefaultTOTP(secret).Now() != params.OtpCode { return
this.Fail("请输入正确的OTP动态密码")
}
} }
var adminId = resp.AdminId // 写入SESSION
params.Auth.StoreAdmin(adminId, params.Remember) params.Auth.StoreAdmin(adminId, params.Remember)
// 记录日志 // 记录日志

View File

@@ -7,9 +7,9 @@ import (
func init() { func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) { TeaGo.BeforeStart(func(server *TeaGo.Server) {
server. server.
Post("/checkOTP", new(CheckOTPAction)). Prefix("").
Prefix("/"). GetPost("/", new(IndexAction)).
GetPost("", new(IndexAction)). GetPost("/index/otp", new(OtpAction)).
EndAll() EndAll()
}) })
} }

View File

@@ -0,0 +1,154 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package index
import (
"encoding/json"
"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/setup"
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps"
stringutil "github.com/iwind/TeaGo/utils/string"
"github.com/xlzd/gotp"
"time"
)
type OtpAction struct {
actionutils.ParentAction
}
func (this *OtpAction) Init() {
this.Nav("", "", "")
}
func (this *OtpAction) RunGet(params struct {
From string
Sid string
Remember bool
}) {
// 检查系统是否已经配置过
if !setup.IsConfigured() {
this.RedirectURL("/setup")
return
}
//// 是否新安装
if setup.IsNewInstalled() {
this.RedirectURL("/setup/confirm")
return
}
this.Data["isUser"] = false
this.Data["menu"] = "signIn"
var timestamp = fmt.Sprintf("%d", time.Now().Unix())
this.Data["token"] = stringutil.Md5(TokenKey+timestamp) + timestamp
this.Data["from"] = params.From
this.Data["sid"] = params.Sid
uiConfig, err := configloaders.LoadAdminUIConfig()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["systemName"] = uiConfig.AdminSystemName
this.Data["showVersion"] = uiConfig.ShowVersion
if len(uiConfig.Version) > 0 {
this.Data["version"] = uiConfig.Version
} else {
this.Data["version"] = teaconst.Version
}
this.Data["faviconFileId"] = uiConfig.FaviconFileId
this.Data["remember"] = params.Remember
this.Show()
}
func (this *OtpAction) RunPost(params struct {
Sid string
OtpCode string
Remember bool
Must *actions.Must
Auth *helpers.UserShouldAuth
}) {
if len(params.OtpCode) == 0 {
this.FailField("otpCode", "请输入正确的OTP动态密码")
return
}
var sid = params.Sid
if len(sid) == 0 || len(sid) > 64 {
this.Fail("参数错误请重新登录001")
return
}
sid += "_otp"
// 获取SESSION
sessionResp, err := this.RPC().LoginSessionRPC().FindLoginSession(this.AdminContext(), &pb.FindLoginSessionRequest{Sid: sid})
if err != nil {
this.ErrorPage(err)
return
}
var session = sessionResp.LoginSession
if session == nil || session.AdminId <= 0 {
this.Fail("参数错误请重新登录002")
return
}
var adminId = session.AdminId
// 检查OTP
otpLoginResp, err := this.RPC().LoginRPC().FindEnabledLogin(this.AdminContext(), &pb.FindEnabledLoginRequest{
AdminId: adminId,
Type: "otp",
})
if err != nil {
this.ErrorPage(err)
return
}
if otpLoginResp.Login != nil && otpLoginResp.Login.IsOn {
var loginParams = maps.Map{}
err = json.Unmarshal(otpLoginResp.Login.ParamsJSON, &loginParams)
if err != nil {
this.ErrorPage(err)
return
}
var secret = loginParams.GetString("secret")
if gotp.NewDefaultTOTP(secret).Now() != params.OtpCode {
this.FailField("otpCode", "请输入正确的OTP动态密码")
return
}
}
// 写入SESSION
params.Auth.StoreAdmin(adminId, params.Remember)
// 删除OTP SESSION
_, err = this.RPC().LoginSessionRPC().DeleteLoginSession(this.AdminContext(), &pb.DeleteLoginSessionRequest{Sid: sid})
if err != nil {
this.ErrorPage(err)
return
}
// 记录日志
rpcClient, err := rpc.SharedRPC()
if err != nil {
this.ErrorPage(err)
return
}
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(adminId), oplogs.LevelInfo, this.Request.URL.Path, "成功通过OTP验证登录系统", this.RequestRemoteIP())
if err != nil {
utils.PrintError(err)
}
this.Success()
}

View File

@@ -5,6 +5,7 @@ import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const" teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils" "github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses/ipaddressutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
@@ -20,8 +21,18 @@ func (this *CreatePopupAction) Init() {
} }
func (this *CreatePopupAction) RunGet(params struct { func (this *CreatePopupAction) RunGet(params struct {
NodeId int64
SupportThresholds bool SupportThresholds bool
}) { }) {
// 专属集群
clusterMaps, err := ipaddressutils.FindNodeClusterMapsWithNodeId(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["clusters"] = clusterMaps
// 阈值
this.Data["supportThresholds"] = params.SupportThresholds this.Data["supportThresholds"] = params.SupportThresholds
this.Show() this.Show()
@@ -33,6 +44,7 @@ func (this *CreatePopupAction) RunPost(params struct {
Name string Name string
IsUp bool IsUp bool
ThresholdsJSON []byte ThresholdsJSON []byte
ClusterIds []int64
Must *actions.Must Must *actions.Must
}) { }) {
@@ -57,6 +69,14 @@ func (this *CreatePopupAction) RunPost(params struct {
_ = json.Unmarshal(params.ThresholdsJSON, &thresholds) _ = json.Unmarshal(params.ThresholdsJSON, &thresholds)
} }
// 专属集群
// 目前只考虑CDN边缘集群
clusterMaps, err := ipaddressutils.FindNodeClusterMaps(this.Parent(), params.ClusterIds)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["ipAddress"] = maps.Map{ this.Data["ipAddress"] = maps.Map{
"name": params.Name, "name": params.Name,
"canAccess": params.CanAccess, "canAccess": params.CanAccess,
@@ -65,6 +85,7 @@ func (this *CreatePopupAction) RunPost(params struct {
"isOn": true, "isOn": true,
"isUp": params.IsUp, "isUp": params.IsUp,
"thresholds": thresholds, "thresholds": thresholds,
"clusters": clusterMaps,
} }
this.Success() this.Success()
} }

View File

@@ -11,14 +11,28 @@ import (
// UpdateNodeIPAddresses 保存一组IP地址 // UpdateNodeIPAddresses 保存一组IP地址
func UpdateNodeIPAddresses(parentAction *actionutils.ParentAction, nodeId int64, role nodeconfigs.NodeRole, ipAddressesJSON []byte) error { func UpdateNodeIPAddresses(parentAction *actionutils.ParentAction, nodeId int64, role nodeconfigs.NodeRole, ipAddressesJSON []byte) error {
addresses := []maps.Map{} var addresses = []maps.Map{}
err := json.Unmarshal(ipAddressesJSON, &addresses) err := json.Unmarshal(ipAddressesJSON, &addresses)
if err != nil { if err != nil {
return err return err
} }
for _, addr := range addresses { for _, addr := range addresses {
var resultAddrIds = []int64{} var resultAddrIds = []int64{}
addrId := addr.GetInt64("id") var addrId = addr.GetInt64("id")
// 专属集群
var addrClusterIds = []int64{}
var addrClusters = addr.GetSlice("clusters")
if len(addrClusters) > 0 {
for _, addrCluster := range addrClusters {
var m = maps.NewMap(addrCluster)
var clusterId = m.GetInt64("id")
if clusterId > 0 {
addrClusterIds = append(addrClusterIds, clusterId)
}
}
}
if addrId > 0 { if addrId > 0 {
resultAddrIds = append(resultAddrIds, addrId) resultAddrIds = append(resultAddrIds, addrId)
@@ -36,6 +50,7 @@ func UpdateNodeIPAddresses(parentAction *actionutils.ParentAction, nodeId int64,
CanAccess: addr.GetBool("canAccess"), CanAccess: addr.GetBool("canAccess"),
IsOn: isOn, IsOn: isOn,
IsUp: addr.GetBool("isUp"), IsUp: addr.GetBool("isUp"),
ClusterIds: addrClusterIds,
}) })
if err != nil { if err != nil {
return err return err
@@ -47,12 +62,13 @@ func UpdateNodeIPAddresses(parentAction *actionutils.ParentAction, nodeId int64,
if len(result) == 1 { if len(result) == 1 {
// 单个创建 // 单个创建
createResp, err := parentAction.RPC().NodeIPAddressRPC().CreateNodeIPAddress(parentAction.AdminContext(), &pb.CreateNodeIPAddressRequest{ createResp, err := parentAction.RPC().NodeIPAddressRPC().CreateNodeIPAddress(parentAction.AdminContext(), &pb.CreateNodeIPAddressRequest{
NodeId: nodeId, NodeId: nodeId,
Role: role, Role: role,
Name: addr.GetString("name"), Name: addr.GetString("name"),
Ip: result[0], Ip: result[0],
CanAccess: addr.GetBool("canAccess"), CanAccess: addr.GetBool("canAccess"),
IsUp: addr.GetBool("isUp"), IsUp: addr.GetBool("isUp"),
NodeClusterIds: addrClusterIds,
}) })
if err != nil { if err != nil {
return err return err
@@ -62,13 +78,14 @@ func UpdateNodeIPAddresses(parentAction *actionutils.ParentAction, nodeId int64,
} else if len(result) > 1 { } else if len(result) > 1 {
// 批量创建 // 批量创建
createResp, err := parentAction.RPC().NodeIPAddressRPC().CreateNodeIPAddresses(parentAction.AdminContext(), &pb.CreateNodeIPAddressesRequest{ createResp, err := parentAction.RPC().NodeIPAddressRPC().CreateNodeIPAddresses(parentAction.AdminContext(), &pb.CreateNodeIPAddressesRequest{
NodeId: nodeId, NodeId: nodeId,
Role: role, Role: role,
Name: addr.GetString("name"), Name: addr.GetString("name"),
IpList: result, IpList: result,
CanAccess: addr.GetBool("canAccess"), CanAccess: addr.GetBool("canAccess"),
IsUp: addr.GetBool("isUp"), IsUp: addr.GetBool("isUp"),
GroupValue: ipStrings, GroupValue: ipStrings,
NodeClusterIds: addrClusterIds,
}) })
if err != nil { if err != nil {
return err return err
@@ -140,3 +157,53 @@ func InitNodeIPAddressThresholds(parentAction *actionutils.ParentAction, addrId
} }
return thresholds, nil return thresholds, nil
} }
// FindNodeClusterMapsWithNodeId 根据节点读取集群信息
func FindNodeClusterMapsWithNodeId(parentAction *actionutils.ParentAction, nodeId int64) ([]maps.Map, error) {
var clusterMaps = []maps.Map{}
if nodeId > 0 { // CDN边缘节点
nodeResp, err := parentAction.RPC().NodeRPC().FindEnabledNode(parentAction.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: nodeId})
if err != nil {
return nil, err
}
var node = nodeResp.Node
if node != nil {
var clusters = []*pb.NodeCluster{}
if node.NodeCluster != nil {
clusters = append(clusters, nodeResp.Node.NodeCluster)
}
if len(node.SecondaryNodeClusters) > 0 {
clusters = append(clusters, node.SecondaryNodeClusters...)
}
for _, cluster := range clusters {
clusterMaps = append(clusterMaps, maps.Map{
"id": cluster.Id,
"name": cluster.Name,
"isChecked": false,
})
}
}
}
return clusterMaps, nil
}
// FindNodeClusterMaps 根据一组集群ID读取集群信息
func FindNodeClusterMaps(parentAction *actionutils.ParentAction, clusterIds []int64) ([]maps.Map, error) {
var clusterMaps = []maps.Map{}
if len(clusterIds) > 0 {
for _, clusterId := range clusterIds {
clusterResp, err := parentAction.RPC().NodeClusterRPC().FindEnabledNodeCluster(parentAction.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: clusterId})
if err != nil {
return nil, err
}
var cluster = clusterResp.NodeCluster
if cluster != nil {
clusterMaps = append(clusterMaps, maps.Map{
"id": cluster.Id,
"name": cluster.Name,
})
}
}
}
return clusterMaps, nil
}

View File

@@ -5,6 +5,7 @@ import (
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const" teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/utils" "github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/nodes/ipAddresses/ipaddressutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/actions"
@@ -21,9 +22,18 @@ func (this *UpdatePopupAction) Init() {
} }
func (this *UpdatePopupAction) RunGet(params struct { func (this *UpdatePopupAction) RunGet(params struct {
NodeId int64
AddressId int64 AddressId int64
SupportThresholds bool SupportThresholds bool
}) { }) {
// 专属集群
clusterMaps, err := ipaddressutils.FindNodeClusterMapsWithNodeId(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["clusters"] = clusterMaps
this.Data["supportThresholds"] = params.SupportThresholds this.Data["supportThresholds"] = params.SupportThresholds
this.Show() this.Show()
@@ -37,6 +47,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
IsOn bool IsOn bool
IsUp bool IsUp bool
ThresholdsJSON []byte ThresholdsJSON []byte
ClusterIds []int64
Must *actions.Must Must *actions.Must
}) { }) {
@@ -81,6 +92,14 @@ func (this *UpdatePopupAction) RunPost(params struct {
_ = json.Unmarshal(params.ThresholdsJSON, &thresholds) _ = json.Unmarshal(params.ThresholdsJSON, &thresholds)
} }
// 专属集群
// 目前只考虑CDN边缘集群
clusterMaps, err := ipaddressutils.FindNodeClusterMaps(this.Parent(), params.ClusterIds)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["ipAddress"] = maps.Map{ this.Data["ipAddress"] = maps.Map{
"name": params.Name, "name": params.Name,
"ip": params.IP, "ip": params.IP,
@@ -89,6 +108,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
"isOn": params.IsOn, "isOn": params.IsOn,
"isUp": isUp, "isUp": isUp,
"thresholds": thresholds, "thresholds": thresholds,
"clusters": clusterMaps,
} }
this.Success() this.Success()

View File

@@ -32,6 +32,7 @@ func (this *CreateRulePopupAction) RunGet(params struct {
"params": checkpoint.Params, "params": checkpoint.Params,
"options": checkpoint.Options, "options": checkpoint.Options,
"isComposed": checkpoint.IsComposed, "isComposed": checkpoint.IsComposed,
"dataType": checkpoint.DataType,
}) })
} }
} }

View File

@@ -55,10 +55,11 @@ func (this *IndexAction) RunGet(params struct {
// 服务列表 // 服务列表
serversResp, err := this.RPC().ServerRPC().ListEnabledServersMatch(this.AdminContext(), &pb.ListEnabledServersMatchRequest{ serversResp, err := this.RPC().ServerRPC().ListEnabledServersMatch(this.AdminContext(), &pb.ListEnabledServersMatchRequest{
Offset: page.Offset, Offset: page.Offset,
Size: page.Size, Size: page.Size,
ServerGroupId: params.GroupId, ServerGroupId: params.GroupId,
Keyword: params.Keyword, Keyword: params.Keyword,
IgnoreSSLCerts: true,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -43,7 +43,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
headerPolicyId := createHeaderPolicyResp.HeaderPolicyId headerPolicyId := createHeaderPolicyResp.HttpHeaderPolicyId
ref := &shared.HTTPHeaderPolicyRef{ ref := &shared.HTTPHeaderPolicyRef{
IsPrior: false, IsPrior: false,
IsOn: true, IsOn: true,
@@ -70,7 +70,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
headerPolicyId := createHeaderPolicyResp.HeaderPolicyId headerPolicyId := createHeaderPolicyResp.HttpHeaderPolicyId
ref := &shared.HTTPHeaderPolicyRef{ ref := &shared.HTTPHeaderPolicyRef{
IsPrior: false, IsPrior: false,
IsOn: true, IsOn: true,

View File

@@ -99,6 +99,7 @@ func (this *IndexAction) RunGet(params struct {
TrafficOutAsc: params.TrafficOutOrder == "asc", TrafficOutAsc: params.TrafficOutOrder == "asc",
UserId: params.UserId, UserId: params.UserId,
IgnoreServerNames: true, IgnoreServerNames: true,
IgnoreSSLCerts: true,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -30,6 +30,9 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
this.Data["regions"] = ""
this.Data["isp"] = ""
if regionResp.IpRegion != nil { if regionResp.IpRegion != nil {
var regionName = regionResp.IpRegion.Summary var regionName = regionResp.IpRegion.Summary
@@ -39,10 +42,8 @@ func (this *IndexAction) RunGet(params struct {
regionName = regionName[:index] regionName = regionName[:index]
} }
this.Data["regions"] = regionName this.Data["regions"] = regionName
} else { this.Data["isp"] = regionResp.IpRegion.Isp
this.Data["regions"] = ""
} }
this.Data["isp"] = regionResp.IpRegion.Isp
// IP列表 // IP列表
ipListResp, err := this.RPC().IPListRPC().FindEnabledIPListContainsIP(this.AdminContext(), &pb.FindEnabledIPListContainsIPRequest{ ipListResp, err := this.RPC().IPListRPC().FindEnabledIPListContainsIP(this.AdminContext(), &pb.FindEnabledIPListContainsIPRequest{

View File

@@ -48,7 +48,10 @@ func (this *UpdateCNAMEPopupAction) RunPost(params struct {
this.FailField("dnsName", "CNAME中只能包含数字、英文字母") this.FailField("dnsName", "CNAME中只能包含数字、英文字母")
} }
serverResp, err := this.RPC().ServerRPC().FindEnabledServer(this.AdminContext(), &pb.FindEnabledServerRequest{ServerId: params.ServerId}) serverResp, err := this.RPC().ServerRPC().FindEnabledServer(this.AdminContext(), &pb.FindEnabledServerRequest{
ServerId: params.ServerId,
IgnoreSSLCerts: true,
})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return

View File

@@ -40,13 +40,13 @@ func (this *CreateDeletePopupAction) RunPost(params struct {
Field("name", params.Name). Field("name", params.Name).
Require("名称不能为空") Require("名称不能为空")
policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HeaderPolicyId: params.HeaderPolicyId}) policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HttpHeaderPolicyId: params.HeaderPolicyId})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
policyConfig := &shared.HTTPHeaderPolicy{} policyConfig := &shared.HTTPHeaderPolicy{}
err = json.Unmarshal(policyConfigResp.HeaderPolicyJSON, policyConfig) err = json.Unmarshal(policyConfigResp.HttpHeaderPolicyJSON, policyConfig)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
@@ -55,8 +55,8 @@ func (this *CreateDeletePopupAction) RunPost(params struct {
deleteHeaders := policyConfig.DeleteHeaders deleteHeaders := policyConfig.DeleteHeaders
deleteHeaders = append(deleteHeaders, params.Name) deleteHeaders = append(deleteHeaders, params.Name)
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicyDeletingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicyDeletingHeadersRequest{ _, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicyDeletingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicyDeletingHeadersRequest{
HeaderPolicyId: params.HeaderPolicyId, HttpHeaderPolicyId: params.HeaderPolicyId,
HeaderNames: deleteHeaders, HeaderNames: deleteHeaders,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -52,13 +52,13 @@ func (this *CreateSetPopupAction) RunPost(params struct {
Field("name", params.Name). Field("name", params.Name).
Require("请输入Header名称") Require("请输入Header名称")
configResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HeaderPolicyId: params.HeaderPolicyId}) configResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HttpHeaderPolicyId: params.HeaderPolicyId})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
policyConfig := &shared.HTTPHeaderPolicy{} policyConfig := &shared.HTTPHeaderPolicy{}
err = json.Unmarshal(configResp.HeaderPolicyJSON, policyConfig) err = json.Unmarshal(configResp.HttpHeaderPolicyJSON, policyConfig)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
@@ -135,8 +135,8 @@ func (this *CreateSetPopupAction) RunPost(params struct {
} }
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicySettingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicySettingHeadersRequest{ _, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicySettingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicySettingHeadersRequest{
HeaderPolicyId: params.HeaderPolicyId, HttpHeaderPolicyId: params.HeaderPolicyId,
HeadersJSON: refsJSON, HeadersJSON: refsJSON,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -21,14 +21,14 @@ func (this *DeleteAction) RunPost(params struct {
defer this.CreateLog(oplogs.LevelInfo, "删除请求HeaderHeaderPolicyId:%d, HeaderId:%d", params.HeaderPolicyId, params.HeaderId) defer this.CreateLog(oplogs.LevelInfo, "删除请求HeaderHeaderPolicyId:%d, HeaderId:%d", params.HeaderPolicyId, params.HeaderId)
policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{ policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{
HeaderPolicyId: params.HeaderPolicyId, HttpHeaderPolicyId: params.HeaderPolicyId,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
policyConfig := &shared.HTTPHeaderPolicy{} policyConfig := &shared.HTTPHeaderPolicy{}
err = json.Unmarshal(policyConfigResp.HeaderPolicyJSON, policyConfig) err = json.Unmarshal(policyConfigResp.HttpHeaderPolicyJSON, policyConfig)
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
@@ -48,8 +48,8 @@ func (this *DeleteAction) RunPost(params struct {
return return
} }
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicySettingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicySettingHeadersRequest{ _, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicySettingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicySettingHeadersRequest{
HeaderPolicyId: params.HeaderPolicyId, HttpHeaderPolicyId: params.HeaderPolicyId,
HeadersJSON: resultJSON, HeadersJSON: resultJSON,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -19,12 +19,12 @@ func (this *DeleteDeletingHeaderAction) RunPost(params struct {
// 日志 // 日志
defer this.CreateLog(oplogs.LevelInfo, "删除需要删除的请求HeaderHeaderPolicyId:%d, HeaderName:%s", params.HeaderPolicyId, params.HeaderName) defer this.CreateLog(oplogs.LevelInfo, "删除需要删除的请求HeaderHeaderPolicyId:%d, HeaderName:%s", params.HeaderPolicyId, params.HeaderName)
policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HeaderPolicyId: params.HeaderPolicyId}) policyConfigResp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HttpHeaderPolicyId: params.HeaderPolicyId})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
policyConfigJSON := policyConfigResp.HeaderPolicyJSON policyConfigJSON := policyConfigResp.HttpHeaderPolicyJSON
policyConfig := &shared.HTTPHeaderPolicy{} policyConfig := &shared.HTTPHeaderPolicy{}
err = json.Unmarshal(policyConfigJSON, policyConfig) err = json.Unmarshal(policyConfigJSON, policyConfig)
if err != nil { if err != nil {
@@ -40,8 +40,8 @@ func (this *DeleteDeletingHeaderAction) RunPost(params struct {
headerNames = append(headerNames, h) headerNames = append(headerNames, h)
} }
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicyDeletingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicyDeletingHeadersRequest{ _, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicyDeletingHeaders(this.AdminContext(), &pb.UpdateHTTPHeaderPolicyDeletingHeadersRequest{
HeaderPolicyId: params.HeaderPolicyId, HttpHeaderPolicyId: params.HeaderPolicyId,
HeaderNames: headerNames, HeaderNames: headerNames,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)

View File

@@ -47,7 +47,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
headerPolicyId := createHeaderPolicyResp.HeaderPolicyId var headerPolicyId = createHeaderPolicyResp.HttpHeaderPolicyId
ref := &shared.HTTPHeaderPolicyRef{ ref := &shared.HTTPHeaderPolicyRef{
IsPrior: false, IsPrior: false,
IsOn: true, IsOn: true,
@@ -74,7 +74,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
headerPolicyId := createHeaderPolicyResp.HeaderPolicyId headerPolicyId := createHeaderPolicyResp.HttpHeaderPolicyId
ref := &shared.HTTPHeaderPolicyRef{ ref := &shared.HTTPHeaderPolicyRef{
IsPrior: false, IsPrior: false,
IsOn: true, IsOn: true,

View File

@@ -19,6 +19,7 @@ func init() {
GetPost("/createDeletePopup", new(CreateDeletePopupAction)). GetPost("/createDeletePopup", new(CreateDeletePopupAction)).
Post("/deleteDeletingHeader", new(DeleteDeletingHeaderAction)). Post("/deleteDeletingHeader", new(DeleteDeletingHeaderAction)).
Post("/delete", new(DeleteAction)). Post("/delete", new(DeleteAction)).
GetPost("/updateCORSPopup", new(UpdateCORSPopupAction)).
EndAll() EndAll()
}) })
} }

View File

@@ -0,0 +1,75 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package headers
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
)
type UpdateCORSPopupAction struct {
actionutils.ParentAction
}
func (this *UpdateCORSPopupAction) Init() {
this.Nav("", "", "")
}
func (this *UpdateCORSPopupAction) RunGet(params struct {
HeaderPolicyId int64
}) {
this.Data["headerPolicyId"] = params.HeaderPolicyId
resp, err := this.RPC().HTTPHeaderPolicyRPC().FindEnabledHTTPHeaderPolicyConfig(this.AdminContext(), &pb.FindEnabledHTTPHeaderPolicyConfigRequest{HttpHeaderPolicyId: params.HeaderPolicyId})
if err != nil {
this.ErrorPage(err)
return
}
var headerPolicyJSON = resp.HttpHeaderPolicyJSON
var headerPolicy = &shared.HTTPHeaderPolicy{}
if len(headerPolicyJSON) > 0 {
err = json.Unmarshal(headerPolicyJSON, headerPolicy)
if err != nil {
this.ErrorPage(err)
return
}
}
this.Data["cors"] = headerPolicy.CORS
this.Show()
}
func (this *UpdateCORSPopupAction) RunPost(params struct {
HeaderPolicyId int64
CorsJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
var config = &shared.HTTPCORSHeaderConfig{}
err := json.Unmarshal(params.CorsJSON, config)
if err != nil {
this.Fail("配置校验失败:" + err.Error())
return
}
err = config.Init()
if err != nil {
this.Fail("配置校验失败:" + err.Error())
return
}
_, err = this.RPC().HTTPHeaderPolicyRPC().UpdateHTTPHeaderPolicyCORS(this.AdminContext(), &pb.UpdateHTTPHeaderPolicyCORSRequest{
HttpHeaderPolicyId: params.HeaderPolicyId,
CorsJSON: params.CorsJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -31,7 +31,7 @@ func (this *IndexAction) RunGet(params struct {
if !isOk { if !isOk {
return return
} }
httpsConfig := &serverconfigs.HTTPSProtocolConfig{} var httpsConfig = &serverconfigs.HTTPSProtocolConfig{}
if len(server.HttpsJSON) > 0 { if len(server.HttpsJSON) > 0 {
err := json.Unmarshal(server.HttpsJSON, httpsConfig) err := json.Unmarshal(server.HttpsJSON, httpsConfig)
if err != nil { if err != nil {
@@ -44,12 +44,15 @@ func (this *IndexAction) RunGet(params struct {
var sslPolicy *sslconfigs.SSLPolicy var sslPolicy *sslconfigs.SSLPolicy
if httpsConfig.SSLPolicyRef != nil && httpsConfig.SSLPolicyRef.SSLPolicyId > 0 { if httpsConfig.SSLPolicyRef != nil && httpsConfig.SSLPolicyRef.SSLPolicyId > 0 {
sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId}) sslPolicyConfigResp, err := this.RPC().SSLPolicyRPC().FindEnabledSSLPolicyConfig(this.AdminContext(), &pb.FindEnabledSSLPolicyConfigRequest{
SslPolicyId: httpsConfig.SSLPolicyRef.SSLPolicyId,
IgnoreData: true,
})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
sslPolicyConfigJSON := sslPolicyConfigResp.SslPolicyJSON var sslPolicyConfigJSON = sslPolicyConfigResp.SslPolicyJSON
if len(sslPolicyConfigJSON) > 0 { if len(sslPolicyConfigJSON) > 0 {
sslPolicy = &sslconfigs.SSLPolicy{} sslPolicy = &sslconfigs.SSLPolicy{}
err = json.Unmarshal(sslPolicyConfigJSON, sslPolicy) err = json.Unmarshal(sslPolicyConfigJSON, sslPolicy)

View File

@@ -42,7 +42,10 @@ func (this *IndexAction) RunGet(params struct {
this.Data["clusters"] = clusterMaps this.Data["clusters"] = clusterMaps
// 当前服务信息 // 当前服务信息
serverResp, err := this.RPC().ServerRPC().FindEnabledServer(this.AdminContext(), &pb.FindEnabledServerRequest{ServerId: params.ServerId}) serverResp, err := this.RPC().ServerRPC().FindEnabledServer(this.AdminContext(), &pb.FindEnabledServerRequest{
ServerId: params.ServerId,
IgnoreSSLCerts: true,
})
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
@@ -121,6 +124,7 @@ func (this *IndexAction) RunGet(params struct {
// RunPost 保存 // RunPost 保存
func (this *IndexAction) RunPost(params struct { func (this *IndexAction) RunPost(params struct {
ServerId int64 ServerId int64
UserId int64
Name string Name string
Description string Description string
ClusterId int64 ClusterId int64
@@ -157,16 +161,28 @@ func (this *IndexAction) RunPost(params struct {
return return
} }
// 修改套餐 // 修改用户
if params.UserPlanId > 0 { if params.UserId > 0 {
_, err = this.RPC().ServerRPC().UpdateServerUserPlan(this.AdminContext(), &pb.UpdateServerUserPlanRequest{ _, err = this.RPC().ServerRPC().UpdateServerUser(this.AdminContext(), &pb.UpdateServerUserRequest{
ServerId: params.ServerId, ServerId: params.ServerId,
UserPlanId: params.UserPlanId, UserId: params.UserId,
}) })
if err != nil { if err != nil {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
} else {
// 修改套餐
if params.UserPlanId > 0 {
_, err = this.RPC().ServerRPC().UpdateServerUserPlan(this.AdminContext(), &pb.UpdateServerUserPlanRequest{
ServerId: params.ServerId,
UserPlanId: params.UserPlanId,
})
if err != nil {
this.ErrorPage(err)
return
}
}
} }
this.Success() this.Success()

View File

@@ -36,7 +36,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
headerPolicyId := createHeaderPolicyResp.HeaderPolicyId headerPolicyId := createHeaderPolicyResp.HttpHeaderPolicyId
ref := &shared.HTTPHeaderPolicyRef{ ref := &shared.HTTPHeaderPolicyRef{
IsPrior: false, IsPrior: false,
IsOn: true, IsOn: true,
@@ -59,7 +59,7 @@ func (this *IndexAction) RunGet(params struct {
this.ErrorPage(err) this.ErrorPage(err)
return return
} }
headerPolicyId := createHeaderPolicyResp.HeaderPolicyId headerPolicyId := createHeaderPolicyResp.HttpHeaderPolicyId
ref := &shared.HTTPHeaderPolicyRef{ ref := &shared.HTTPHeaderPolicyRef{
IsPrior: false, IsPrior: false,
IsOn: true, IsOn: true,

View File

@@ -109,6 +109,12 @@ func (this *LocationHelper) createMenus(serverIdString string, locationIdString
"isActive": secondMenuItem == "referer", "isActive": secondMenuItem == "referer",
"isOn": locationConfig.Web != nil && locationConfig.Web.Referers != nil && locationConfig.Web.Referers.IsPrior, "isOn": locationConfig.Web != nil && locationConfig.Web.Referers != nil && locationConfig.Web.Referers.IsPrior,
}) })
menuItems = append(menuItems, maps.Map{
"name": "UA名单",
"url": "/servers/server/settings/locations/userAgent?serverId=" + serverIdString + "&locationId=" + locationIdString,
"isActive": secondMenuItem == "userAgent",
"isOn": locationConfig.Web != nil && locationConfig.Web.UserAgent != nil && locationConfig.Web.UserAgent.IsPrior,
})
menuItems = append(menuItems, maps.Map{ menuItems = append(menuItems, maps.Map{
"name": "字符编码", "name": "字符编码",
"url": "/servers/server/settings/locations/charset?serverId=" + serverIdString + "&locationId=" + locationIdString, "url": "/servers/server/settings/locations/charset?serverId=" + serverIdString + "&locationId=" + locationIdString,

View File

@@ -1,6 +1,6 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package uam package referers
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"

View File

@@ -1,4 +1,4 @@
package uam package referers
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders" "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"

View File

@@ -0,0 +1,62 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package userAgent
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "index")
this.SecondMenu("userAgent")
}
func (this *IndexAction) RunGet(params struct {
LocationId int64
}) {
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithLocationId(this.AdminContext(), params.LocationId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["webId"] = webConfig.Id
var userAgentConfig = webConfig.UserAgent
if userAgentConfig == nil {
userAgentConfig = serverconfigs.NewUserAgentConfig()
}
this.Data["userAgentConfig"] = userAgentConfig
this.Show()
}
func (this *IndexAction) RunPost(params struct {
WebId int64
UserAgentJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("修改Web %d User-Agent设置", params.WebId)
_, err := this.RPC().HTTPWebRPC().UpdateHTTPWebUserAgent(this.AdminContext(), &pb.UpdateHTTPWebUserAgentRequest{
HttpWebId: params.WebId,
UserAgentJSON: params.UserAgentJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -0,0 +1,22 @@
package userAgent
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/server/settings/locations/locationutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/serverutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo"
)
func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) {
server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
Helper(locationutils.NewLocationHelper()).
Helper(serverutils.NewServerHelper()).
Data("tinyMenuItem", "userAgent").
Prefix("/servers/server/settings/locations/userAgent").
GetPost("", new(IndexAction)).
EndAll()
})
}

View File

@@ -39,10 +39,11 @@ func (this *CreatePopupAction) RunPost(params struct {
KeepArgs bool KeepArgs bool
// 域名 // 域名
DomainsAll bool DomainsAll bool
DomainsBeforeJSON []byte DomainsBeforeJSON []byte
DomainAfter string DomainBeforeIgnorePorts bool
DomainAfterScheme string DomainAfter string
DomainAfterScheme string
// 端口 // 端口
PortsAll bool PortsAll bool
@@ -128,6 +129,7 @@ func (this *CreatePopupAction) RunPost(params struct {
return return
} }
} }
config.DomainBeforeIgnorePorts = params.DomainBeforeIgnorePorts
if len(params.DomainAfter) == 0 { if len(params.DomainAfter) == 0 {
this.FailField("domainAfter", "请输入跳转后域名") this.FailField("domainAfter", "请输入跳转后域名")
return return

View File

@@ -1,6 +1,6 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package uam package referers
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"

View File

@@ -1,4 +1,4 @@
package uam package referers
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders" "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"

View File

@@ -0,0 +1,64 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package userAgent
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/actions"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "index")
this.SecondMenu("userAgent")
}
func (this *IndexAction) RunGet(params struct {
ServerId int64
}) {
this.Data["serverId"] = params.ServerId
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["webId"] = webConfig.Id
var userAgentConfig = webConfig.UserAgent
if userAgentConfig == nil {
userAgentConfig = serverconfigs.NewUserAgentConfig()
}
this.Data["userAgentConfig"] = userAgentConfig
this.Show()
}
func (this *IndexAction) RunPost(params struct {
WebId int64
UserAgentJSON []byte
Must *actions.Must
CSRF *actionutils.CSRF
}) {
defer this.CreateLogInfo("修改Web %d User-Agent设置", params.WebId)
_, err := this.RPC().HTTPWebRPC().UpdateHTTPWebUserAgent(this.AdminContext(), &pb.UpdateHTTPWebUserAgentRequest{
HttpWebId: params.WebId,
UserAgentJSON: params.UserAgentJSON,
})
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -1,8 +1,8 @@
package userui package userAgent
import ( import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders" "github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/settings/settingutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/servers/serverutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers" "github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
"github.com/iwind/TeaGo" "github.com/iwind/TeaGo"
) )
@@ -10,9 +10,9 @@ import (
func init() { func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) { TeaGo.BeforeStart(func(server *TeaGo.Server) {
server. server.
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeCommon)). Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeServer)).
Helper(settingutils.NewHelper("userUI")). Helper(serverutils.NewServerHelper()).
Prefix("/settings/user-ui"). Prefix("/servers/server/settings/userAgent").
GetPost("", new(IndexAction)). GetPost("", new(IndexAction)).
EndAll() EndAll()
}) })

View File

@@ -11,3 +11,7 @@ import (
func filterMenuItems(serverConfig *serverconfigs.ServerConfig, menuItems []maps.Map, serverIdString string, secondMenuItem string) []maps.Map { func filterMenuItems(serverConfig *serverconfigs.ServerConfig, menuItems []maps.Map, serverIdString string, secondMenuItem string) []maps.Map {
return menuItems return menuItems
} }
func filterMenuItems2(serverConfig *serverconfigs.ServerConfig, menuItems []maps.Map, serverIdString string, secondMenuItem string) []maps.Map {
return menuItems
}

View File

@@ -57,7 +57,10 @@ func (this *ServerHelper) createLeftMenu(action *actions.ActionObject) {
return return
} }
serverResp, err := rpcClient.ServerRPC().FindEnabledServer(rpcClient.Context(action.Context.GetInt64("adminId")), &pb.FindEnabledServerRequest{ServerId: serverId}) serverResp, err := rpcClient.ServerRPC().FindEnabledServer(rpcClient.Context(action.Context.GetInt64("adminId")), &pb.FindEnabledServerRequest{
ServerId: serverId,
IgnoreSSLCerts: true,
})
if err != nil { if err != nil {
logs.Error(err) logs.Error(err)
return return
@@ -252,21 +255,7 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"isOn": serverConfig.ReverseProxyRef != nil && serverConfig.ReverseProxyRef.IsOn, "isOn": serverConfig.ReverseProxyRef != nil && serverConfig.ReverseProxyRef.IsOn,
}) })
if teaconst.IsPlus { menuItems = filterMenuItems(serverConfig, menuItems, serverIdString, secondMenuItem)
menuItems = append(menuItems, maps.Map{
"name": "-",
"url": "",
"isActive": false,
})
menuItems = append(menuItems, maps.Map{
"name": "5秒盾",
"url": "/servers/server/settings/uam?serverId=" + serverIdString,
"isActive": secondMenuItem == "uam",
"isOn": serverConfig.UAM != nil && serverConfig.UAM.IsOn,
"isImportant": serverConfig.UAM != nil && serverConfig.UAM.IsOn,
})
}
menuItems = append(menuItems, maps.Map{ menuItems = append(menuItems, maps.Map{
"name": "-", "name": "-",
@@ -315,6 +304,12 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"isActive": secondMenuItem == "referer", "isActive": secondMenuItem == "referer",
"isOn": serverConfig.Web != nil && serverConfig.Web.Referers != nil && serverConfig.Web.Referers.IsOn, "isOn": serverConfig.Web != nil && serverConfig.Web.Referers != nil && serverConfig.Web.Referers.IsOn,
}) })
menuItems = append(menuItems, maps.Map{
"name": "UA名单",
"url": "/servers/server/settings/userAgent?serverId=" + serverIdString,
"isActive": secondMenuItem == "userAgent",
"isOn": serverConfig.Web != nil && serverConfig.Web.UserAgent != nil && serverConfig.Web.UserAgent.IsOn,
})
menuItems = append(menuItems, maps.Map{ menuItems = append(menuItems, maps.Map{
"name": "字符编码", "name": "字符编码",
"url": "/servers/server/settings/charset?serverId=" + serverIdString, "url": "/servers/server/settings/charset?serverId=" + serverIdString,
@@ -397,7 +392,7 @@ func (this *ServerHelper) createSettingsMenu(secondMenuItem string, serverIdStri
"isOn": serverConfig.Web != nil && serverConfig.Web.RequestLimit != nil && serverConfig.Web.RequestLimit.IsOn, "isOn": serverConfig.Web != nil && serverConfig.Web.RequestLimit != nil && serverConfig.Web.RequestLimit.IsOn,
}) })
menuItems = filterMenuItems(serverConfig, menuItems, serverIdString, secondMenuItem) menuItems = filterMenuItems2(serverConfig, menuItems, serverIdString, secondMenuItem)
menuItems = append(menuItems, maps.Map{ menuItems = append(menuItems, maps.Map{
"name": "-", "name": "-",

View File

@@ -8,14 +8,17 @@ import (
"strconv" "strconv"
) )
// 查找Server // FindServer 查找服务信息
func FindServer(p *actionutils.ParentAction, serverId int64) (*pb.Server, *serverconfigs.ServerConfig, bool) { func FindServer(p *actionutils.ParentAction, serverId int64) (*pb.Server, *serverconfigs.ServerConfig, bool) {
serverResp, err := p.RPC().ServerRPC().FindEnabledServer(p.AdminContext(), &pb.FindEnabledServerRequest{ServerId: serverId}) serverResp, err := p.RPC().ServerRPC().FindEnabledServer(p.AdminContext(), &pb.FindEnabledServerRequest{
ServerId: serverId,
IgnoreSSLCerts: true,
})
if err != nil { if err != nil {
p.ErrorPage(err) p.ErrorPage(err)
return nil, nil, false return nil, nil, false
} }
server := serverResp.Server var server = serverResp.Server
if server == nil { if server == nil {
p.ErrorPage(errors.New("not found server with id '" + strconv.FormatInt(serverId, 10) + "'")) p.ErrorPage(errors.New("not found server with id '" + strconv.FormatInt(serverId, 10) + "'"))
return nil, nil, false return nil, nil, false

View File

@@ -58,6 +58,7 @@ func (this *IndexAction) RunPost(params struct {
var apiURL = teaconst.UpdatesURL var apiURL = teaconst.UpdatesURL
apiURL = strings.ReplaceAll(apiURL, "${os}", runtime.GOOS) apiURL = strings.ReplaceAll(apiURL, "${os}", runtime.GOOS)
apiURL = strings.ReplaceAll(apiURL, "${arch}", runtime.GOARCH) apiURL = strings.ReplaceAll(apiURL, "${arch}", runtime.GOARCH)
apiURL = strings.ReplaceAll(apiURL, "${version}", teaconst.Version)
resp, err := http.Get(apiURL) resp, err := http.Get(apiURL)
if err != nil { if err != nil {
this.Data["result"] = maps.Map{ this.Data["result"] = maps.Map{

View File

@@ -1,181 +0,0 @@
package userui
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/actions"
"io"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "", "")
}
func (this *IndexAction) RunGet(params struct{}) {
config, err := configloaders.LoadUserUIConfig()
if err != nil {
this.ErrorPage(err)
return
}
this.Data["config"] = config
// 时区
this.Data["timeZoneGroups"] = nodeconfigs.FindAllTimeZoneGroups()
this.Data["timeZoneLocations"] = nodeconfigs.FindAllTimeZoneLocations()
if len(config.TimeZone) == 0 {
config.TimeZone = nodeconfigs.DefaultTimeZoneLocation
}
this.Data["timeZoneLocation"] = nodeconfigs.FindTimeZoneLocation(config.TimeZone)
this.Show()
}
func (this *IndexAction) RunPost(params struct {
ProductName string
UserSystemName string
ShowOpenSourceInfo bool
ShowVersion bool
Version string
ShowFinance bool
FaviconFile *actions.File
LogoFile *actions.File
TimeZone string
ShowTrafficCharts bool
ShowBandwidthCharts bool
BandwidthUnit string
Must *actions.Must
CSRF *actionutils.CSRF
}) {
params.Must.
Field("productName", params.ProductName).
Require("请输入产品名称").
Field("userSystemName", params.UserSystemName).
Require("请输入管理员系统名称")
config, err := configloaders.LoadUserUIConfig()
if err != nil {
this.ErrorPage(err)
return
}
config.ProductName = params.ProductName
config.UserSystemName = params.UserSystemName
config.ShowOpenSourceInfo = params.ShowOpenSourceInfo
config.ShowVersion = params.ShowVersion
config.Version = params.Version
config.ShowFinance = params.ShowFinance
config.ShowTrafficCharts = params.ShowTrafficCharts
config.ShowBandwidthCharts = params.ShowBandwidthCharts
config.BandwidthUnit = params.BandwidthUnit
config.TimeZone = params.TimeZone
// 上传Favicon文件
if params.FaviconFile != nil {
createResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
Filename: params.FaviconFile.Filename,
Size: params.FaviconFile.Size,
IsPublic: true,
})
if err != nil {
this.ErrorPage(err)
return
}
fileId := createResp.FileId
// 上传内容
buf := make([]byte, 512*1024)
reader, err := params.FaviconFile.OriginFile.Open()
if err != nil {
this.ErrorPage(err)
return
}
for {
n, err := reader.Read(buf)
if n > 0 {
_, err = this.RPC().FileChunkRPC().CreateFileChunk(this.AdminContext(), &pb.CreateFileChunkRequest{
FileId: fileId,
Data: buf[:n],
})
if err != nil {
this.Fail("上传失败:" + err.Error())
}
}
if err != nil {
if err == io.EOF {
break
}
this.Fail("上传失败:" + err.Error())
}
}
// 置为已完成
_, err = this.RPC().FileRPC().UpdateFileFinished(this.AdminContext(), &pb.UpdateFileFinishedRequest{FileId: fileId})
if err != nil {
this.ErrorPage(err)
}
config.FaviconFileId = fileId
}
// 上传Logo文件
if params.LogoFile != nil {
createResp, err := this.RPC().FileRPC().CreateFile(this.AdminContext(), &pb.CreateFileRequest{
Filename: params.LogoFile.Filename,
Size: params.LogoFile.Size,
IsPublic: true,
})
if err != nil {
this.ErrorPage(err)
return
}
fileId := createResp.FileId
// 上传内容
buf := make([]byte, 512*1024)
reader, err := params.LogoFile.OriginFile.Open()
if err != nil {
this.ErrorPage(err)
return
}
for {
n, err := reader.Read(buf)
if n > 0 {
_, err = this.RPC().FileChunkRPC().CreateFileChunk(this.AdminContext(), &pb.CreateFileChunkRequest{
FileId: fileId,
Data: buf[:n],
})
if err != nil {
this.Fail("上传失败:" + err.Error())
}
}
if err != nil {
if err == io.EOF {
break
}
this.Fail("上传失败:" + err.Error())
}
}
// 置为已完成
_, err = this.RPC().FileRPC().UpdateFileFinished(this.AdminContext(), &pb.UpdateFileFinishedRequest{FileId: fileId})
if err != nil {
this.ErrorPage(err)
}
config.LogoFileId = fileId
}
err = configloaders.UpdateUserUIConfig(config)
if err != nil {
this.ErrorPage(err)
return
}
this.Success()
}

View File

@@ -8,6 +8,8 @@ import (
"github.com/iwind/TeaGo/dbs" "github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"net" "net"
"os"
"runtime"
"strings" "strings"
"time" "time"
) )
@@ -31,10 +33,21 @@ func (this *DetectDBAction) RunPost(params struct{}) {
localPort = "3306" localPort = "3306"
var username = "root" var username = "root"
for _, pass := range []string{"", "123456", "654321", "Aa_123456"} { var passwords = []string{"", "123456", "654321", "Aa_123456", "111111"}
// 使用 foolish-mysql 安装的MySQL
localGeneratedPasswordData, err := os.ReadFile("/usr/local/mysql/generated-password.txt")
if err == nil {
var localGeneratedPassword = strings.TrimSpace(string(localGeneratedPasswordData))
if len(localGeneratedPassword) > 0 {
passwords = append(passwords, localGeneratedPassword)
}
}
for _, pass := range passwords {
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{ db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
Driver: "mysql", Driver: "mysql",
Dsn: username + ":" + pass + "@tcp(" + configutils.QuoteIP(localHost) + ":" + localPort + ")/edges11111", Dsn: username + ":" + pass + "@tcp(" + configutils.QuoteIP(localHost) + ":" + localPort + ")/edges",
Prefix: "", Prefix: "",
}) })
if err == nil { if err == nil {
@@ -51,10 +64,11 @@ func (this *DetectDBAction) RunPost(params struct{}) {
} }
this.Data["localDB"] = maps.Map{ this.Data["localDB"] = maps.Map{
"host": localHost, "host": localHost,
"port": localPort, "port": localPort,
"username": localUsername, "username": localUsername,
"password": localPassword, "password": localPassword,
"canInstall": runtime.GOOS == "linux" && runtime.GOARCH == "amd64" && os.Getgid() == 0,
} }
this.Success() this.Success()

View File

@@ -22,7 +22,7 @@ func (this *IndexAction) RunGet(params struct{}) {
currentHost = host currentHost = host
} }
} }
if net.ParseIP(currentHost) != nil { if net.ParseIP(currentHost) != nil && currentHost != "localhost" && currentHost != "127.0.0.1" {
this.Data["currentHost"] = currentHost this.Data["currentHost"] = currentHost
} else { } else {
this.Data["currentHost"] = "" this.Data["currentHost"] = ""

View File

@@ -1,6 +1,9 @@
package setup package setup
import "github.com/iwind/TeaGo" import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/setup/mysql"
"github.com/iwind/TeaGo"
)
func init() { func init() {
TeaGo.BeforeStart(func(server *TeaGo.Server) { TeaGo.BeforeStart(func(server *TeaGo.Server) {
@@ -15,6 +18,8 @@ func init() {
Post("/status", new(StatusAction)). Post("/status", new(StatusAction)).
Post("/detectDB", new(DetectDBAction)). Post("/detectDB", new(DetectDBAction)).
Post("/checkLocalIP", new(CheckLocalIPAction)). Post("/checkLocalIP", new(CheckLocalIPAction)).
GetPost("/mysql/installPopup", new(mysql.InstallPopupAction)).
Post("/mysql/installLogs", new(mysql.InstallLogsAction)).
EndAll() EndAll()
}) })
} }

View File

@@ -17,13 +17,17 @@ import (
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/gosock/pkg/gosock" "github.com/iwind/gosock/pkg/gosock"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"io"
"os" "os"
"os/exec" "os/exec"
"strings"
"time" "time"
) )
type InstallAction struct { type InstallAction struct {
actionutils.ParentAction actionutils.ParentAction
apiSetupFinished bool
} }
func (this *InstallAction) RunPost(params struct { func (this *InstallAction) RunPost(params struct {
@@ -40,7 +44,7 @@ func (this *InstallAction) RunPost(params struct {
// API节点配置 // API节点配置
currentStatusText = "正在检查API节点配置" currentStatusText = "正在检查API节点配置"
apiNodeMap := maps.Map{} var apiNodeMap = maps.Map{}
err := json.Unmarshal(params.ApiNodeJSON, &apiNodeMap) err := json.Unmarshal(params.ApiNodeJSON, &apiNodeMap)
if err != nil { if err != nil {
this.Fail("API节点配置数据解析错误请刷新页面后重新尝试安装错误信息" + err.Error()) this.Fail("API节点配置数据解析错误请刷新页面后重新尝试安装错误信息" + err.Error())
@@ -48,7 +52,7 @@ func (this *InstallAction) RunPost(params struct {
// 数据库 // 数据库
currentStatusText = "正在检查数据库配置" currentStatusText = "正在检查数据库配置"
dbMap := maps.Map{} var dbMap = maps.Map{}
err = json.Unmarshal(params.DbJSON, &dbMap) err = json.Unmarshal(params.DbJSON, &dbMap)
if err != nil { if err != nil {
this.Fail("数据库配置数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error()) this.Fail("数据库配置数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error())
@@ -56,14 +60,14 @@ func (this *InstallAction) RunPost(params struct {
// 管理员 // 管理员
currentStatusText = "正在检查管理员配置" currentStatusText = "正在检查管理员配置"
adminMap := maps.Map{} var adminMap = maps.Map{}
err = json.Unmarshal(params.AdminJSON, &adminMap) err = json.Unmarshal(params.AdminJSON, &adminMap)
if err != nil { if err != nil {
this.Fail("管理员数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error()) this.Fail("管理员数据解析错误,请刷新页面后重新尝试安装,错误信息:" + err.Error())
} }
// 安装API节点 // 安装API节点
mode := apiNodeMap.GetString("mode") var mode = apiNodeMap.GetString("mode")
if mode == "new" { if mode == "new" {
currentStatusText = "准备启动新API节点" currentStatusText = "准备启动新API节点"
@@ -74,7 +78,7 @@ func (this *InstallAction) RunPost(params struct {
// ... // ...
// 检查环境 // 检查环境
apiNodeDir := Tea.Root + "/edge-api" var apiNodeDir = Tea.Root + "/edge-api"
for _, dir := range []string{"edge-api", "edge-api/configs", "edge-api/bin"} { for _, dir := range []string{"edge-api", "edge-api/configs", "edge-api/bin"} {
apiNodeDir := Tea.Root + "/" + dir apiNodeDir := Tea.Root + "/" + dir
_, err = os.Stat(apiNodeDir) _, err = os.Stat(apiNodeDir)
@@ -87,7 +91,7 @@ func (this *InstallAction) RunPost(params struct {
} }
// 保存数据库配置 // 保存数据库配置
dsn := dbMap.GetString("username") + ":" + dbMap.GetString("password") + "@tcp(" + configutils.QuoteIP(dbMap.GetString("host")) + ":" + dbMap.GetString("port") + ")/" + dbMap.GetString("database") + "?charset=utf8mb4&timeout=30s" var dsn = dbMap.GetString("username") + ":" + dbMap.GetString("password") + "@tcp(" + configutils.QuoteIP(dbMap.GetString("host")) + ":" + dbMap.GetString("port") + ")/" + dbMap.GetString("database") + "?charset=utf8mb4&timeout=30s"
dbConfig := &dbs.Config{ dbConfig := &dbs.Config{
DBs: map[string]*dbs.DBConfig{ DBs: map[string]*dbs.DBConfig{
"prod": { "prod": {
@@ -108,7 +112,7 @@ func (this *InstallAction) RunPost(params struct {
// 生成备份文件 // 生成备份文件
homeDir, _ := os.UserHomeDir() homeDir, _ := os.UserHomeDir()
backupDirs := []string{"/etc/edge-api"} var backupDirs = []string{"/etc/edge-api"}
if len(homeDir) > 0 { if len(homeDir) > 0 {
backupDirs = append(backupDirs, homeDir+"/.edge-api") backupDirs = append(backupDirs, homeDir+"/.edge-api")
} }
@@ -151,15 +155,21 @@ func (this *InstallAction) RunPost(params struct {
var resultMap = maps.Map{} var resultMap = maps.Map{}
logs.Println("[INSTALL]setup edge-api") logs.Println("[INSTALL]setup edge-api")
{ {
cmd := exec.Command(apiNodeDir+"/bin/edge-api", "setup", "-api-node-protocol=http", "-api-node-host=\""+apiNodeMap.GetString("newHost")+"\"", "-api-node-port=\""+apiNodeMap.GetString("newPort")+"\"") this.apiSetupFinished = false
output := bytes.NewBuffer([]byte{}) var cmd = exec.Command(apiNodeDir+"/bin/edge-api", "setup", "-api-node-protocol=http", "-api-node-host=\""+apiNodeMap.GetString("newHost")+"\"", "-api-node-port=\""+apiNodeMap.GetString("newPort")+"\"")
var output = bytes.NewBuffer([]byte{})
cmd.Stdout = output cmd.Stdout = output
// 试图读取执行日志
go this.startReadingAPIInstallLog()
err = cmd.Run() err = cmd.Run()
this.apiSetupFinished = true
if err != nil { if err != nil {
this.Fail("安装失败:" + err.Error()) this.Fail("安装失败:" + err.Error())
} }
resultData := output.Bytes() var resultData = output.Bytes()
err = json.Unmarshal(resultData, &resultMap) err = json.Unmarshal(resultData, &resultMap)
if err != nil { if err != nil {
this.Fail("安装节点时返回数据错误:" + err.Error() + "(" + string(resultData) + ")") this.Fail("安装节点时返回数据错误:" + err.Error() + "(" + string(resultData) + ")")
@@ -175,7 +185,7 @@ func (this *InstallAction) RunPost(params struct {
// 关闭正在运行的API节点防止冲突 // 关闭正在运行的API节点防止冲突
logs.Println("[INSTALL]stop edge-api") logs.Println("[INSTALL]stop edge-api")
{ {
cmd := exec.Command(apiNodeDir+"/bin/edge-api", "stop") var cmd = exec.Command(apiNodeDir+"/bin/edge-api", "stop")
_ = cmd.Run() _ = cmd.Run()
} }
@@ -183,7 +193,7 @@ func (this *InstallAction) RunPost(params struct {
currentStatusText = "正在启动API节点" currentStatusText = "正在启动API节点"
logs.Println("[INSTALL]start edge-api") logs.Println("[INSTALL]start edge-api")
{ {
cmd := exec.Command(apiNodeDir + "/bin/edge-api") var cmd = exec.Command(apiNodeDir + "/bin/edge-api")
err = cmd.Start() err = cmd.Start()
if err != nil { if err != nil {
this.Fail("API节点启动失败" + err.Error()) this.Fail("API节点启动失败" + err.Error())
@@ -220,7 +230,7 @@ func (this *InstallAction) RunPost(params struct {
} }
// 写入API节点配置完成安装 // 写入API节点配置完成安装
apiConfig := &configs.APIConfig{ var apiConfig = &configs.APIConfig{
RPC: struct { RPC: struct {
Endpoints []string `yaml:"endpoints"` Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"` DisableUpdate bool `yaml:"disableUpdate"`
@@ -283,7 +293,7 @@ func (this *InstallAction) RunPost(params struct {
this.Success() this.Success()
} else if mode == "old" { } else if mode == "old" {
// 构造RPC // 构造RPC
apiConfig := &configs.APIConfig{ var apiConfig = &configs.APIConfig{
RPC: struct { RPC: struct {
Endpoints []string `yaml:"endpoints"` Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"` DisableUpdate bool `yaml:"disableUpdate"`
@@ -303,7 +313,7 @@ func (this *InstallAction) RunPost(params struct {
}() }()
// 设置管理员 // 设置管理员
ctx := client.APIContext(0) var ctx = client.APIContext(0)
_, err = client.AdminRPC().CreateOrUpdateAdmin(ctx, &pb.CreateOrUpdateAdminRequest{ _, err = client.AdminRPC().CreateOrUpdateAdmin(ctx, &pb.CreateOrUpdateAdminRequest{
Username: adminMap.GetString("username"), Username: adminMap.GetString("username"),
Password: adminMap.GetString("password"), Password: adminMap.GetString("password"),
@@ -344,3 +354,73 @@ func (this *InstallAction) RunPost(params struct {
this.Fail("错误的API节点模式'" + mode + "'") this.Fail("错误的API节点模式'" + mode + "'")
} }
} }
// 读取API安装时的日志以便于显示当前正在执行的任务
func (this *InstallAction) startReadingAPIInstallLog() {
var tmpDir = os.TempDir()
if len(tmpDir) == 0 {
return
}
var logFile = tmpDir + "/edge-install.log"
var logFp *os.File
var err error
// 尝试5秒钟
for i := 0; i < 10; i++ {
logFp, err = os.Open(logFile)
if err != nil {
time.Sleep(1 * time.Second)
continue
} else {
break
}
}
if err != nil {
return
}
if this.apiSetupFinished {
_ = logFp.Close()
return
}
go func() {
defer func() {
_ = logFp.Close()
}()
var ticker = time.NewTicker(1 * time.Second)
var logBuf = make([]byte, 256)
for range ticker.C {
if this.apiSetupFinished {
return
}
_, err = logFp.Seek(-256, io.SeekEnd)
if err != nil {
currentStatusText = ""
return
}
n, err := logFp.Read(logBuf)
if err != nil {
currentStatusText = ""
return
}
if n > 0 {
var logData = string(logBuf[:n])
var lines = strings.Split(logData, "\n")
if len(lines) >= 3 {
var line = strings.TrimSpace(lines[len(lines)-2])
if len(line) > 0 {
if !this.apiSetupFinished {
currentStatusText = "正在执行 " + line + " ..."
}
}
}
}
}
}()
}

View File

@@ -0,0 +1,17 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package mysql
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/setup/mysql/mysqlinstallers/utils"
)
type InstallLogsAction struct {
actionutils.ParentAction
}
func (this *InstallLogsAction) RunPost(params struct{}) {
this.Data["logs"] = utils.SharedLogger.ReadAll()
this.Success()
}

View File

@@ -0,0 +1,47 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package mysql
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/setup/mysql/mysqlinstallers"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/setup/mysql/mysqlinstallers/utils"
)
type InstallPopupAction struct {
actionutils.ParentAction
}
func (this *InstallPopupAction) RunGet(params struct{}) {
this.Show()
}
func (this *InstallPopupAction) RunPost(params struct{}) {
// 清空日志
utils.SharedLogger.Reset()
this.Data["isOk"] = false
var installer = mysqlinstallers.NewMySQLInstaller()
var targetDir = "/usr/local/mysql"
xzFile, err := installer.Download()
if err != nil {
this.Data["err"] = "download failed: " + err.Error()
this.Success()
return
}
err = installer.InstallFromFile(xzFile, targetDir)
if err != nil {
this.Data["err"] = "install from '" + xzFile + "' failed: " + err.Error()
this.Success()
return
}
this.Data["user"] = "root"
this.Data["password"] = installer.Password()
this.Data["dir"] = targetDir
this.Data["isOk"] = true
this.Success()
}

View File

@@ -0,0 +1,612 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package mysqlinstallers
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/setup/mysql/mysqlinstallers/utils"
timeutil "github.com/iwind/TeaGo/utils/time"
"io"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
type MySQLInstaller struct {
password string
}
func NewMySQLInstaller() *MySQLInstaller {
return &MySQLInstaller{}
}
func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string) error {
// check whether mysql already running
this.log("checking mysqld ...")
var oldPid = utils.FindPidWithName("mysqld")
if oldPid > 0 {
return errors.New("there is already a running mysql server process, pid: '" + strconv.Itoa(oldPid) + "'")
}
// check target dir
this.log("checking target dir '" + targetDir + "' ...")
_, err := os.Stat(targetDir)
if err == nil {
// check target dir
matches, _ := filepath.Glob(targetDir + "/*")
if len(matches) > 0 {
return errors.New("target dir '" + targetDir + "' already exists and not empty")
} else {
err = os.Remove(targetDir)
if err != nil {
return errors.New("clean target dir '" + targetDir + "' failed: " + err.Error())
}
}
}
// check 'tar' command
this.log("checking 'tar' command ...")
var tarExe, _ = exec.LookPath("tar")
if len(tarExe) == 0 {
this.log("installing 'tar' command ...")
err = this.installTarCommand()
if err != nil {
this.log("WARN: failed to install 'tar' ...")
}
}
// check commands
this.log("checking system commands ...")
var cmdList = []string{"tar" /** again **/, "chown", "sh"}
for _, cmd := range cmdList {
cmdPath, err := exec.LookPath(cmd)
if err != nil || len(cmdPath) == 0 {
return errors.New("could not find '" + cmd + "' command in this system")
}
}
groupAddExe, err := this.lookupGroupAdd()
if err != nil {
return errors.New("could not find 'groupadd' command in this system")
}
userAddExe, err := this.lookupUserAdd()
if err != nil {
return errors.New("could not find 'useradd' command in this system")
}
// ubuntu apt
aptExe, err := exec.LookPath("apt")
if err == nil && len(aptExe) > 0 {
for _, lib := range []string{"libaio1", "libncurses5"} {
this.log("checking " + lib + " ...")
var cmd = utils.NewCmd("apt", "-y", "install", lib)
cmd.WithStderr()
err = cmd.Run()
if err != nil {
return errors.New("install " + lib + " failed: " + cmd.Stderr())
}
time.Sleep(1 * time.Second)
}
} else { // yum
yumExe, err := exec.LookPath("yum")
if err == nil && len(yumExe) > 0 {
for _, lib := range []string{"libaio", "ncurses-libs", "ncurses-compat-libs"} {
var cmd = utils.NewCmd("yum", "-y", "install", lib)
_ = cmd.Run()
time.Sleep(1 * time.Second)
}
}
}
// create 'mysql' user group
this.log("checking 'mysql' user group ...")
{
data, err := os.ReadFile("/etc/group")
if err != nil {
return errors.New("check user group failed: " + err.Error())
}
if !bytes.Contains(data, []byte("\nmysql:")) {
var cmd = utils.NewCmd(groupAddExe, "mysql")
cmd.WithStderr()
err = cmd.Run()
if err != nil {
return errors.New("add 'mysql' user group failed: " + cmd.Stderr())
}
}
}
// create 'mysql' user
this.log("checking 'mysql' user ...")
{
data, err := os.ReadFile("/etc/passwd")
if err != nil {
return errors.New("check user failed: " + err.Error())
}
if !bytes.Contains(data, []byte("\nmysql:")) {
var cmd *utils.Cmd
if strings.HasSuffix(userAddExe, "useradd") {
cmd = utils.NewCmd(userAddExe, "mysql", "-g", "mysql")
} else { // adduser
cmd = utils.NewCmd(userAddExe, "-S", "-G", "mysql", "mysql")
}
cmd.WithStderr()
err = cmd.Run()
if err != nil {
return errors.New("add 'mysql' user failed: " + cmd.Stderr())
}
}
}
// mkdir
{
var parentDir = filepath.Dir(targetDir)
stat, err := os.Stat(parentDir)
if err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(parentDir, 0777)
if err != nil {
return errors.New("try to create dir '" + parentDir + "' failed: " + err.Error())
}
} else {
return errors.New("check dir '" + parentDir + "' failed: " + err.Error())
}
} else {
if !stat.IsDir() {
return errors.New("'" + parentDir + "' should be a directory")
}
}
}
// check installer file .xz
this.log("checking installer file ...")
{
stat, err := os.Stat(xzFilePath)
if err != nil {
return errors.New("could not open the installer file: " + err.Error())
}
if stat.IsDir() {
return errors.New("'" + xzFilePath + "' not a valid file")
}
var basename = filepath.Base(xzFilePath)
if !strings.HasSuffix(basename, ".xz") {
return errors.New("installer file should has '.xz' extension")
}
}
// extract
this.log("extracting installer file ...")
var tmpDir = os.TempDir() + "/goedge-mysql-tmp"
{
_, err := os.Stat(tmpDir)
if err == nil {
err = os.RemoveAll(tmpDir)
if err != nil {
return errors.New("clean temporary directory '" + tmpDir + "' failed: " + err.Error())
}
}
err = os.Mkdir(tmpDir, 0777)
if err != nil {
return errors.New("create temporary directory '" + tmpDir + "' failed: " + err.Error())
}
}
{
var cmd = utils.NewCmd("tar", "-xJvf", xzFilePath, "-C", tmpDir)
cmd.WithStderr()
err = cmd.Run()
if err != nil {
return errors.New("extract installer file '" + xzFilePath + "' failed: " + cmd.Stderr())
}
}
// create datadir
matches, err := filepath.Glob(tmpDir + "/mysql-*")
if err != nil || len(matches) == 0 {
return errors.New("could not find mysql installer directory from '" + tmpDir + "'")
}
var baseDir = matches[0]
var dataDir = baseDir + "/data"
_, err = os.Stat(dataDir)
if err != nil {
if os.IsNotExist(err) {
err = os.Mkdir(dataDir, 0777)
if err != nil {
return errors.New("create data dir '" + dataDir + "' failed: " + err.Error())
}
} else {
return errors.New("check data dir '" + dataDir + "' failed: " + err.Error())
}
}
// chown datadir
{
var cmd = utils.NewCmd("chown", "mysql:mysql", dataDir)
cmd.WithStderr()
err = cmd.Run()
if err != nil {
return errors.New("chown data dir '" + dataDir + "' failed: " + err.Error())
}
}
// create my.cnf
var myCnfFile = "/etc/my.cnf"
_, err = os.Stat(myCnfFile)
if err == nil {
// backup it
err = os.Rename(myCnfFile, "/etc/my.cnf."+timeutil.Format("YmdHis"))
if err != nil {
return errors.New("backup '/etc/my.cnf' failed: " + err.Error())
}
}
// mysql server options https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html
var myCnfTemplate = this.createMyCnf(baseDir, dataDir)
err = os.WriteFile(myCnfFile, []byte(myCnfTemplate), 0666)
if err != nil {
return errors.New("write '" + myCnfFile + "' failed: " + err.Error())
}
// initialize
this.log("initializing mysql ...")
var generatedPassword = ""
{
var cmd = utils.NewCmd(baseDir+"/bin/mysqld", "--initialize", "--user=mysql")
cmd.WithStderr()
cmd.WithStdout()
err = cmd.Run()
if err != nil {
return errors.New("initialize failed: " + cmd.Stderr())
}
// read from stdout
var match = regexp.MustCompile(`temporary password is.+:\s*(.+)`).FindStringSubmatch(cmd.Stdout())
if len(match) == 0 {
// read from stderr
match = regexp.MustCompile(`temporary password is.+:\s*(.+)`).FindStringSubmatch(cmd.Stderr())
if len(match) == 0 {
return errors.New("initialize successfully, but could not find generated password, please report to developer")
}
}
generatedPassword = strings.TrimSpace(match[1])
// write password to file
var passwordFile = baseDir + "/generated-password.txt"
err = os.WriteFile(passwordFile, []byte(generatedPassword), 0666)
if err != nil {
return errors.New("write password failed: " + err.Error())
}
}
// move to right place
this.log("moving files to target dir ...")
err = os.Rename(baseDir, targetDir)
if err != nil {
return errors.New("move '" + baseDir + "' to '" + targetDir + "' failed: " + err.Error())
}
baseDir = targetDir
// change my.cnf
myCnfTemplate = this.createMyCnf(baseDir, baseDir+"/data")
err = os.WriteFile(myCnfFile, []byte(myCnfTemplate), 0666)
if err != nil {
return errors.New("create new '" + myCnfFile + "' failed: " + err.Error())
}
// start mysql
this.log("starting mysql ...")
{
var cmd = utils.NewCmd(baseDir+"/bin/mysqld_safe", "--user=mysql")
cmd.WithStderr()
err = cmd.Start()
if err != nil {
return errors.New("start failed '" + cmd.String() + "': " + cmd.Stderr())
}
// waiting for startup
for i := 0; i < 5; i++ {
_, err = net.Dial("tcp", "127.0.0.1:3306")
if err != nil {
time.Sleep(1 * time.Second)
} else {
break
}
}
time.Sleep(1 * time.Second)
}
// change password
newPassword, err := this.generatePassword()
if err != nil {
return errors.New("generate new password failed: " + err.Error())
}
this.log("changing mysql password ...")
var passwordSQL = "ALTER USER 'root'@'localhost' IDENTIFIED BY '" + newPassword + "';"
{
var cmd = utils.NewCmd("sh", "-c", baseDir+"/bin/mysql --user=root --password=\""+generatedPassword+"\" --execute=\""+passwordSQL+"\" --connect-expired-password")
cmd.WithStderr()
err = cmd.Run()
if err != nil {
return errors.New("change password failed: " + cmd.String() + ": " + cmd.Stderr())
}
}
this.password = newPassword
var passwordFile = baseDir + "/generated-password.txt"
err = os.WriteFile(passwordFile, []byte(this.password), 0666)
if err != nil {
return errors.New("write generated file failed: " + err.Error())
}
// remove temporary directory
_ = os.Remove(tmpDir)
// create link to 'mysql' client command
var clientExe = "/usr/local/bin/mysql"
_, err = os.Stat(clientExe)
if err != nil && os.IsNotExist(err) {
err = os.Symlink(baseDir+"/bin/mysql", clientExe)
if err == nil {
this.log("created symbolic link '" + clientExe + "' to '" + baseDir + "/bin/mysql'")
} else {
this.log("WARN: failed to create symbolic link '" + clientExe + "' to '" + baseDir + "/bin/mysql': " + err.Error())
}
}
// install service
// this is not required, so we ignore all errors
err = this.installService(baseDir)
if err != nil {
this.log("WARN: install service failed: " + err.Error())
}
this.log("finished")
return nil
}
func (this *MySQLInstaller) Download() (path string, err error) {
var client = &http.Client{}
// check latest version
this.log("checking mysql latest version ...")
var latestVersion = "8.0.31" // 默认版本
{
req, err := http.NewRequest(http.MethodGet, "https://dev.mysql.com/downloads/mysql/", nil)
if err != nil {
return "", err
}
req.Header.Set("User-Agent", "curl/7.61.1")
resp, err := client.Do(req)
if err != nil {
return "", errors.New("check latest version failed: " + err.Error())
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode == http.StatusOK {
data, err := io.ReadAll(resp.Body)
if err != nil {
return "", errors.New("read latest version failed: " + err.Error())
}
var reg = regexp.MustCompile(`<h1>MySQL Community Server ([\d.]+) </h1>`)
var matches = reg.FindSubmatch(data)
if len(matches) > 0 {
latestVersion = string(matches[1])
}
}
}
this.log("found version: v" + latestVersion)
// download
this.log("start downloading ...")
var downloadURL = "https://cdn.mysql.com/Downloads/MySQL-8.0/mysql-" + latestVersion + "-linux-glibc2.17-x86_64-minimal.tar.xz"
{
req, err := http.NewRequest(http.MethodGet, downloadURL, nil)
if err != nil {
return "", err
}
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36")
resp, err := client.Do(req)
if err != nil {
return "", errors.New("check latest version failed: " + err.Error())
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
return "", errors.New("check latest version failed: invalid response code: " + strconv.Itoa(resp.StatusCode))
}
path = filepath.Base(downloadURL)
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
return "", errors.New("create download file '" + path + "' failed: " + err.Error())
}
var writer = utils.NewProgressWriter(fp, resp.ContentLength)
var ticker = time.NewTicker(1 * time.Second)
var done = make(chan bool, 1)
go func() {
var lastProgress float32 = -1
for {
select {
case <-ticker.C:
var progress = writer.Progress()
if lastProgress < 0 || progress-lastProgress > 0.1 || progress == 1 {
lastProgress = progress
this.log(fmt.Sprintf("%.2f%%", progress*100))
}
case <-done:
return
}
}
}()
_, err = io.Copy(writer, resp.Body)
if err != nil {
_ = fp.Close()
done <- true
return "", errors.New("download failed: " + err.Error())
}
err = fp.Close()
if err != nil {
done <- true
return "", errors.New("download failed: " + err.Error())
}
time.Sleep(1 * time.Second) // waiting for progress printing
done <- true
}
return path, nil
}
// Password get generated password
func (this *MySQLInstaller) Password() string {
return this.password
}
// create my.cnf content
func (this *MySQLInstaller) createMyCnf(baseDir string, dataDir string) string {
return `
[mysqld]
port=3306
basedir="` + baseDir + `"
datadir="` + dataDir + `"
max_connections=256
innodb_flush_log_at_trx_commit=2
max_prepared_stmt_count=65535
binlog_cache_size=1M
binlog_stmt_cache_size=1M
thread_cache_size=32
binlog_expire_logs_seconds=604800
`
}
// generate random password
func (this *MySQLInstaller) generatePassword() (string, error) {
var p = make([]byte, 16)
n, err := rand.Read(p)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", p[:n]), nil
}
// print log
func (this *MySQLInstaller) log(message string) {
utils.SharedLogger.Push("[" + timeutil.Format("H:i:s") + "]" + message)
}
// copy file
func (this *MySQLInstaller) installService(baseDir string) error {
_, err := exec.LookPath("systemctl")
if err != nil {
return err
}
this.log("registering systemd service ...")
var desc = `### BEGIN INIT INFO
# Provides: mysql
# Required-Start: $local_fs $network $remote_fs
# Should-Start: ypbind nscd ldap ntpd xntpd
# Required-Stop: $local_fs $network $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: start and stop MySQL
# Description: MySQL is a very fast and reliable SQL database engine.
### END INIT INFO
[Unit]
Description=MySQL Service
Before=shutdown.target
After=network-online.target
[Service]
Type=simple
Restart=always
RestartSec=1s
ExecStart=${BASE_DIR}/support-files/mysql.server start
ExecStop=${BASE_DIR}/support-files/mysql.server stop
ExecRestart=${BASE_DIR}/support-files/mysql.server restart
ExecStatus=${BASE_DIR}/support-files/mysql.server status
ExecReload=${BASE_DIR}/support-files/mysql.server reload
[Install]
WantedBy=multi-user.target`
desc = strings.ReplaceAll(desc, "${BASE_DIR}", baseDir)
err = os.WriteFile("/etc/systemd/system/mysqld.service", []byte(desc), 0666)
if err != nil {
return err
}
var cmd = utils.NewTimeoutCmd(5*time.Second, "systemctl", "enable", "mysqld.service")
cmd.WithStderr()
err = cmd.Run()
if err != nil {
return errors.New("enable mysqld.service failed: " + cmd.Stderr())
}
return nil
}
// install 'tar' command automatically
func (this *MySQLInstaller) installTarCommand() error {
// yum
yumExe, err := exec.LookPath("yum")
if err == nil && len(yumExe) > 0 {
var cmd = utils.NewTimeoutCmd(10*time.Second, yumExe, "-y", "install", "tar")
return cmd.Run()
}
// apt
aptExe, err := exec.LookPath("apt")
if err == nil && len(aptExe) > 0 {
var cmd = utils.NewTimeoutCmd(10*time.Second, aptExe, "-y", "install", "tar")
return cmd.Run()
}
return nil
}
func (this *MySQLInstaller) lookupGroupAdd() (string, error) {
for _, cmd := range []string{"groupadd", "addgroup"} {
path, err := exec.LookPath(cmd)
if err == nil && len(path) > 0 {
return path, nil
}
}
return "", errors.New("not found")
}
func (this *MySQLInstaller) lookupUserAdd() (string, error) {
for _, cmd := range []string{"useradd", "adduser"} {
path, err := exec.LookPath(cmd)
if err == nil && len(path) > 0 {
return path, nil
}
}
return "", errors.New("not found")
}

View File

@@ -0,0 +1,162 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
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,46 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
var SharedLogger = NewLogger()
type Logger struct {
c chan string
}
func NewLogger() *Logger {
return &Logger{
c: make(chan string, 1024),
}
}
func (this *Logger) Push(msg string) {
select {
case this.c <- msg:
default:
}
}
func (this *Logger) ReadAll() (msgList []string) {
msgList = []string{}
for {
select {
case msg := <-this.c:
msgList = append(msgList, msg)
default:
return
}
}
}
func (this *Logger) Reset() {
for {
select {
case <-this.c:
default:
return
}
}
}

View File

@@ -0,0 +1,37 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
import (
"os"
"path/filepath"
"strconv"
"strings"
)
const (
ProcDir = "/proc"
)
func FindPidWithName(name string) int {
// process name
commFiles, err := filepath.Glob(ProcDir + "/*/comm")
if err != nil {
return 0
}
for _, commFile := range commFiles {
data, err := os.ReadFile(commFile)
if err != nil {
continue
}
if strings.TrimSpace(string(data)) == name {
var pieces = strings.Split(commFile, "/")
var pid = pieces[len(pieces)-2]
pidInt, _ := strconv.Atoi(pid)
return pidInt
}
}
return 0
}

View File

@@ -0,0 +1,31 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
import "io"
type ProgressWriter struct {
rawWriter io.Writer
total int64
written int64
}
func NewProgressWriter(rawWriter io.Writer, total int64) *ProgressWriter {
return &ProgressWriter{
rawWriter: rawWriter,
total: total,
}
}
func (this *ProgressWriter) Write(p []byte) (n int, err error) {
n, err = this.rawWriter.Write(p)
this.written += int64(n)
return
}
func (this *ProgressWriter) Progress() float32 {
if this.total <= 0 {
return 0
}
return float32(float64(this.written) / float64(this.total))
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"net" "net"
"regexp"
"strings" "strings"
) )
@@ -61,7 +62,24 @@ func (this *ValidateApiAction) RunPost(params struct {
} }
if net.ParseIP(params.NewHost) == nil { if net.ParseIP(params.NewHost) == nil {
this.FailField("newHost", "请输入正确的API节点主机地址") // 检查是否为域名
var domainReg = regexp.MustCompile(`^[\w-]+$`) // 这里暂不支持 unicode 域名
var digitReg = regexp.MustCompile(`^\d+$`)
var isIP = true
for _, piece := range strings.Split(params.NewHost, ".") {
if !domainReg.MatchString(piece) {
this.FailField("newHost", "请输入正确的API节点主机地址")
return
}
if !digitReg.MatchString(piece) {
isIP = false
}
}
if isIP {
this.FailField("newHost", "请输入正确的API节点主机地址")
return
}
} }
params.Must. params.Must.

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