Compare commits

...

52 Commits

Author SHA1 Message Date
刘祥超
05f77f7a0d 修复go.mod 2023-11-24 10:21:07 +08:00
刘祥超
4334b6f148 修复若干测试用例 2023-11-24 09:13:18 +08:00
刘祥超
72955759d7 更新go.sum 2023-11-24 08:53:40 +08:00
刘祥超
18dc52996d 更新Dockerfile中版本 2023-11-23 17:40:28 +08:00
刘祥超
0b16d09330 版本号修改为1.3.1 2023-11-23 17:26:24 +08:00
刘祥超
027d81a8cd 优化SSH认证sudo设置 2023-11-23 16:12:41 +08:00
刘祥超
d833158784 DNSPod改名为腾讯云DNSPod/DNSPod 支持腾讯云API密钥 2023-11-23 15:14:34 +08:00
刘祥超
75024c9364 优化X-Cache报头设置文字提示 2023-11-22 19:02:18 +08:00
刘祥超
1dd5db3c42 优化第三方域名解析azure dns提示文字 2023-11-22 10:30:29 +08:00
刘祥超
faba80e315 GRPC增加Keepalive参数 2023-11-20 09:56:35 +08:00
刘祥超
1b7dc8eb96 优化下载升级新版本文字提示 2023-11-20 08:25:34 +08:00
刘祥超
7c0af45dd0 更新Dockerfile中版本号 2023-11-19 17:34:15 +08:00
刘祥超
daea270132 更新components.js 2023-11-19 17:33:56 +08:00
刘祥超
a42e3ac4a6 优化CAPTCHA最大失败次数文字提示 2023-11-19 15:43:37 +08:00
刘祥超
c94c526b43 提交components.js 2023-11-19 09:09:50 +08:00
刘祥超
e531787773 集群WAF策略处于非防御模式时在地区封禁页面提示用户 2023-11-18 15:25:14 +08:00
刘祥超
0eda84a99a 修复修改单个WAF动作时名称变为阻止的问题 2023-11-18 08:51:50 +08:00
刘祥超
89b791448a 优化DNS.LA域名解析提示文字 2023-11-17 18:13:54 +08:00
刘祥超
2239328b2e 优化GoDaddy域名解析提示文字 2023-11-17 18:01:50 +08:00
刘祥超
e440a46080 源站支持404内容自动重试其他源站 2023-11-15 19:05:04 +08:00
刘祥超
4a0fec6e35 优化WAF设置中人机识别验证方式 2023-11-15 17:19:29 +08:00
刘祥超
a9b46987bc 更新components.js 2023-11-15 15:13:09 +08:00
刘祥超
6771afead5 WAF人机识别实现点击验证和滑动解锁验证/单个网站可以设置默认的人机识别方式 2023-11-15 15:12:58 +08:00
刘祥超
553fca2981 版本号修改为1.3.0 2023-11-14 14:47:52 +08:00
刘祥超
8ef1e36b8b 优化界面 2023-11-13 17:47:24 +08:00
刘祥超
9e6c49b67c 调教components.js 2023-11-13 17:00:22 +08:00
刘祥超
170632ddbc 增加用户列表菜单 2023-11-13 11:53:30 +08:00
刘祥超
4a74488cac 自定义页面增加例外URL和限制URL设置 2023-11-13 10:45:13 +08:00
刘祥超
5fd6a08988 第三方域名解析支持微软Azure DNS(商业版) 2023-11-12 20:31:16 +08:00
刘祥超
c86b91ead6 自定义页面增加“跳转URL”功能 2023-11-10 16:36:03 +08:00
刘祥超
aeebb763c1 优化自定义页面组件界面 2023-11-10 14:59:53 +08:00
刘祥超
b05630e3e0 优化集群设置 -- 网站设置界面 2023-11-10 11:46:02 +08:00
刘祥超
51905a0ee3 URL跳转中增加例外域名和仅限域名 2023-11-10 11:05:05 +08:00
刘祥超
869ebd59d1 优化Ln节点域名解析选项提示 2023-11-09 17:02:15 +08:00
刘祥超
8a6aedca16 集群设置--DNS设置中增加”解析记录“页面 2023-11-09 16:24:52 +08:00
刘祥超
cf98c3453a 域名解析支持Amazon Route 53(商业版) 2023-11-08 21:17:32 +08:00
刘祥超
1b6f234f39 第三方域名解析增加火山引擎TrafficRoute 2023-11-06 16:21:19 +08:00
刘祥超
26c7f0f566 优化多行消息显示 2023-11-03 16:39:20 +08:00
刘祥超
617ad9b16e 申请ACME证书时防止重复提交 2023-11-03 16:26:20 +08:00
刘祥超
04a6792dbf 删除不需要的文件 2023-11-03 11:00:26 +08:00
刘祥超
f2bf8f2dbf 删除不需要的文件 2023-11-03 10:48:34 +08:00
刘祥超
3aba0abd24 优化硬盘不足提示算法 2023-11-03 09:06:33 +08:00
刘祥超
b56ccc7cef 优化数据看板 2023-10-30 16:44:01 +08:00
刘祥超
aeebf55e60 优化数据看板 2023-10-30 11:33:00 +08:00
刘祥超
9a9e0d25f3 优化数据看板图表排列 2023-10-30 11:30:51 +08:00
刘祥超
6b3033e240 删除不需要的文件 2023-10-27 09:12:19 +08:00
刘祥超
2859a59340 优化界面 2023-10-26 15:13:35 +08:00
刘祥超
45367dd579 将看板中的“服务”文字改为“网站” 2023-10-26 11:35:31 +08:00
刘祥超
fee3fd743f MySQL安装程序支持8.2 2023-10-26 08:33:35 +08:00
刘祥超
ba4cfea47c 优化文字提示 2023-10-25 15:22:13 +08:00
刘祥超
5c11f07f8e 优化域名解析文字提示 2023-10-17 15:53:58 +08:00
刘祥超
ffac080611 将版本号修改为1.2.11 2023-10-17 13:50:03 +08:00
119 changed files with 2842 additions and 1862 deletions

View File

@@ -1,7 +1,7 @@
FROM --platform=linux/amd64 alpine:latest
LABEL maintainer="goedge.cdn@gmail.com"
ENV TZ "Asia/Shanghai"
ENV VERSION 1.2.10
ENV VERSION 1.3.1
ENV ROOT_DIR /usr/local/goedge
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip

20
go.mod
View File

@@ -11,13 +11,13 @@ require (
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
github.com/miekg/dns v1.1.43
github.com/quic-go/quic-go v0.36.0
github.com/quic-go/quic-go v0.37.4
github.com/shirou/gopsutil/v3 v3.22.5
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/tealeg/xlsx/v3 v3.2.3
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
golang.org/x/crypto v0.10.0
golang.org/x/sys v0.9.0
golang.org/x/crypto v0.12.0
golang.org/x/sys v0.11.0
google.golang.org/grpc v1.45.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -30,7 +30,7 @@ require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
github.com/google/pprof v0.0.0-20230808223545-4887780b67fb // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
@@ -38,17 +38,17 @@ require (
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
github.com/quic-go/qtls-go1-20 v0.3.2 // indirect
github.com/rogpeppe/fastuuid v1.2.0 // indirect
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
github.com/tdewolff/minify/v2 v2.12.7 // indirect
github.com/tdewolff/parse/v2 v2.6.6 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.11.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/tools v0.10.0 // indirect
golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/tools v0.12.0 // indirect
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

34
go.sum
View File

@@ -84,6 +84,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/google/pprof v0.0.0-20230808223545-4887780b67fb h1:oqpb3Cwpc7EOml5PVGMYbSGmwNui2R7i8IW83gs4W0c=
github.com/google/pprof v0.0.0-20230808223545-4887780b67fb/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -132,10 +134,14 @@ github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI=
github.com/quic-go/qtls-go1-20 v0.3.2/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.36.0 h1:JIrO7p7Ug6hssFcARjWDiqS2RAKJHCiwPxBAA989rbI=
github.com/quic-go/quic-go v0.36.0/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ=
github.com/quic-go/quic-go v0.37.3 h1:pkHH3xaMNUNAh6OtgEV/0K6Fz+YIJXhPzgd/ShiRDm4=
github.com/quic-go/quic-go v0.37.3/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU=
github.com/quic-go/quic-go v0.37.4 h1:ke8B73yMCWGq9MfrCCAw0Uzdm7GaViC3i39dsIdDlH4=
github.com/quic-go/quic-go v0.37.4/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU=
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
@@ -175,13 +181,17 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/exp v0.0.0-20230807204917-050eac23e9de h1:l5Za6utMv/HsBWWqzt4S8X17j+kt1uVETUX5UFhn2rE=
golang.org/x/exp v0.0.0-20230807204917-050eac23e9de/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U=
golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -193,6 +203,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -208,6 +220,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -237,16 +251,16 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -257,6 +271,8 @@ golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -1,9 +1,9 @@
package teaconst
const (
Version = "1.2.10"
Version = "1.3.1"
APINodeVersion = "1.2.10"
APINodeVersion = "1.3.1"
ProductName = "Edge Admin"
ProcessName = "edge-admin"

View File

@@ -63,7 +63,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.REQUEST_COND_COMPONENTS = ")
buffer.Write(typesJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 条件操作符
@@ -73,7 +73,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.REQUEST_COND_OPERATORS = ")
buffer.Write(requestOperatorsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 请求变量
@@ -83,7 +83,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.REQUEST_VARIABLES = ")
buffer.Write(requestVariablesJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 指标
@@ -93,7 +93,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.METRIC_HTTP_KEYS = ")
buffer.Write(metricHTTPKeysJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// IP地址阈值项目
@@ -103,7 +103,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.IP_ADDR_THRESHOLD_ITEMS = ")
buffer.Write(ipAddrThresholdItemsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// IP地址阈值动作
@@ -113,7 +113,7 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.IP_ADDR_THRESHOLD_ACTIONS = ")
buffer.Write(ipAddrThresholdActionsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// WAF操作符
@@ -123,7 +123,17 @@ func generateComponentsJSFile() error {
} else {
buffer.WriteString("window.WAF_RULE_OPERATORS = ")
buffer.Write(wafOperatorsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// WAF验证码类型
captchaTypesJSON, err := json.Marshal(firewallconfigs.FindAllCaptchaTypes())
if err != nil {
logs.Println("ComponentsAction marshal captcha types failed: " + err.Error())
} else {
buffer.WriteString("window.WAF_CAPTCHA_TYPES = ")
buffer.Write(captchaTypesJSON)
buffer.Write([]byte{';', '\n', '\n'})
}
fp, err := os.OpenFile(filepath.Clean(Tea.PublicFile("/js/components.src.js")), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)

View File

@@ -20,6 +20,7 @@ import (
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/encoding/gzip"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"net"
"net/url"
@@ -514,12 +515,15 @@ func (this *RPCClient) init() error {
grpc.MaxCallSendMsgSize(128<<20),
grpc.UseCompressor(gzip.Name),
)
var keepaliveParams = grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 30 * time.Second,
})
if u.Scheme == "http" {
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions)
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions, keepaliveParams)
} else if u.Scheme == "https" {
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true,
})), callOptions)
})), callOptions, keepaliveParams)
} else {
return errors.New("parse endpoint failed: invalid scheme '" + u.Scheme + "'")
}

View File

@@ -34,14 +34,9 @@ func TestRPCClient_NodeRPC(t *testing.T) {
func TestRPC_Dial_HTTP(t *testing.T) {
client, err := NewRPCClient(&configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
}{
Endpoints: []string{"http://127.0.0.1:8004"},
},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
RPCEndpoints: []string{"https://127.0.0.1:8003"},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
}, true)
if err != nil {
t.Fatal(err)
@@ -56,14 +51,9 @@ func TestRPC_Dial_HTTP(t *testing.T) {
func TestRPC_Dial_HTTP_2(t *testing.T) {
client, err := NewRPCClient(&configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
}{
Endpoints: []string{"https://127.0.0.1:8003"},
},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
RPCEndpoints: []string{"https://127.0.0.1:8003"},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
}, true)
if err != nil {
t.Fatal(err)
@@ -78,14 +68,9 @@ func TestRPC_Dial_HTTP_2(t *testing.T) {
func TestRPC_Dial_HTTPS(t *testing.T) {
client, err := NewRPCClient(&configs.APIConfig{
RPC: struct {
Endpoints []string `yaml:"endpoints"`
DisableUpdate bool `yaml:"disableUpdate"`
}{
Endpoints: []string{"https://127.0.0.1:8004"},
},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
RPCEndpoints: []string{"https://127.0.0.1:8004"},
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
}, true)
if err != nil {
t.Fatal(err)

View File

@@ -1,62 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package thresholds
import (
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
)
type IndexAction struct {
actionutils.ParentAction
}
func (this *IndexAction) Init() {
this.Nav("", "node", "update")
this.SecondMenu("threshold")
}
func (this *IndexAction) RunGet(params struct {
ClusterId int64
NodeId int64
}) {
_, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
if err != nil {
this.ErrorPage(err)
return
}
this.Data["nodeId"] = params.NodeId
// 列出所有阈值
thresholdsResp, err := this.RPC().NodeThresholdRPC().FindAllEnabledNodeThresholds(this.AdminContext(), &pb.FindAllEnabledNodeThresholdsRequest{
Role: "node",
NodeClusterId: params.ClusterId,
NodeId: params.NodeId,
})
if err != nil {
this.ErrorPage(err)
return
}
thresholdMaps := []maps.Map{}
for _, threshold := range thresholdsResp.NodeThresholds {
thresholdMaps = append(thresholdMaps, maps.Map{
"id": threshold.Id,
"itemName": nodeconfigs.FindNodeValueItemName(threshold.Item),
"paramName": nodeconfigs.FindNodeValueItemParamName(threshold.Item, threshold.Param),
"operatorName": nodeconfigs.FindNodeValueOperatorName(threshold.Operator),
"value": nodeconfigs.UnmarshalNodeValue(threshold.ValueJSON),
"sumMethodName": nodeconfigs.FindNodeValueSumMethodName(threshold.SumMethod),
"duration": threshold.Duration,
"durationUnitName": nodeconfigs.FindNodeValueDurationUnitName(threshold.DurationUnit),
"isOn": threshold.IsOn,
})
}
this.Data["thresholds"] = thresholdMaps
this.Show()
}

View File

@@ -14,7 +14,7 @@ type IndexAction struct {
}
func (this *IndexAction) Init() {
this.Nav("", "setting", "")
this.Nav("", "setting", "index")
this.SecondMenu("dns")
}

View File

@@ -0,0 +1,303 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package dns
import (
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type RecordsAction struct {
actionutils.ParentAction
}
func (this *RecordsAction) Init() {
this.Nav("", "setting", "records")
this.SecondMenu("dns")
}
func (this *RecordsAction) RunGet(params struct {
ClusterId int64
}) {
// 集群信息
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var cluster = clusterResp.NodeCluster
if cluster == nil {
this.NotFound("nodeCluster", params.ClusterId)
return
}
this.Data["cluster"] = maps.Map{
"id": cluster.Id,
"name": cluster.Name,
}
// DNS信息
dnsResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var defaultRoute = dnsResp.DefaultRoute
var domainName = ""
var dnsMap = maps.Map{
"dnsName": dnsResp.Name,
"domainId": 0,
"domainName": "",
"providerId": 0,
"providerName": "",
"providerTypeName": "",
}
if dnsResp.Domain != nil {
domainName = dnsResp.Domain.Name
dnsMap["domainId"] = dnsResp.Domain.Id
dnsMap["domainName"] = dnsResp.Domain.Name
}
if dnsResp.Provider != nil {
dnsMap["providerId"] = dnsResp.Provider.Id
dnsMap["providerName"] = dnsResp.Provider.Name
dnsMap["providerTypeName"] = dnsResp.Provider.TypeName
}
if len(dnsResp.CnameRecords) > 0 {
dnsMap["cnameRecords"] = dnsResp.CnameRecords
} else {
dnsMap["cnameRecords"] = []string{}
}
this.Data["dnsInfo"] = dnsMap
// 未安装的节点
notInstalledNodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{
NodeClusterId: params.ClusterId,
IsInstalled: false,
})
if err != nil {
this.ErrorPage(err)
return
}
var allNodes = notInstalledNodesResp.Nodes
// 节点DNS解析记录
nodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{
NodeClusterId: params.ClusterId,
IsInstalled: true,
})
if err != nil {
this.ErrorPage(err)
return
}
var installedNodeIdsMap = map[int64]bool{}
for _, node := range nodesResp.Nodes {
installedNodeIdsMap[node.Id] = true
}
allNodes = append(allNodes, nodesResp.Nodes...)
var nodeMaps = []maps.Map{}
for _, node := range allNodes {
var isInstalled = installedNodeIdsMap[node.Id]
if len(node.Routes) > 0 {
for _, route := range node.Routes {
// 检查是否已解析
var isResolved = false
if isInstalled && cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(node.IpAddr) > 0 {
var recordType = "A"
if utils.IsIPv6(node.IpAddr) {
recordType = "AAAA"
}
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
DnsDomainId: cluster.DnsDomainId,
Name: cluster.DnsName,
Type: recordType,
Route: route.Code,
Value: node.IpAddr,
})
if err != nil {
this.ErrorPage(err)
return
}
isResolved = checkResp.IsOk
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"name": node.Name,
"ipAddr": node.IpAddr,
"ipAddrId": node.NodeIPAddressId,
"route": maps.Map{
"name": route.Name,
"code": route.Code,
},
"clusterId": node.NodeClusterId,
"isResolved": isResolved,
"isInstalled": isInstalled,
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
"isOffline": node.IsOffline,
})
}
} else {
// 默认线路
var isResolved = false
if isInstalled && len(defaultRoute) > 0 {
var recordType = "A"
if utils.IsIPv6(node.IpAddr) {
recordType = "AAAA"
}
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
DnsDomainId: cluster.DnsDomainId,
Name: cluster.DnsName,
Type: recordType,
Route: defaultRoute,
Value: node.IpAddr,
})
if err != nil {
this.ErrorPage(err)
return
}
isResolved = checkResp.IsOk
}
nodeMaps = append(nodeMaps, maps.Map{
"id": node.Id,
"name": node.Name,
"ipAddr": node.IpAddr,
"ipAddrId": node.NodeIPAddressId,
"route": maps.Map{
"name": "",
"code": "",
},
"clusterId": node.NodeClusterId,
"isResolved": isResolved,
"isInstalled": isInstalled,
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
"isOffline": node.IsOffline,
})
}
}
this.Data["nodes"] = nodeMaps
// 代理服务解析记录
serversResp, err := this.RPC().ServerRPC().FindAllEnabledServersDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledServersDNSWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
var serverMaps = []maps.Map{}
for _, server := range serversResp.Servers {
// 检查是否已解析
isResolved := false
if cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(server.DnsName) > 0 && len(domainName) > 0 {
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
DnsDomainId: cluster.DnsDomainId,
Name: server.DnsName,
Type: "CNAME",
Value: cluster.DnsName + "." + domainName,
})
if err != nil {
this.ErrorPage(err)
return
}
isResolved = checkResp.IsOk
}
serverMaps = append(serverMaps, maps.Map{
"id": server.Id,
"name": server.Name,
"dnsName": server.DnsName,
"isResolved": isResolved,
})
}
this.Data["servers"] = serverMaps
// 检查解析记录是否有变化
checkChangesResp, err := this.RPC().NodeClusterRPC().CheckNodeClusterDNSChanges(this.AdminContext(), &pb.CheckNodeClusterDNSChangesRequest{NodeClusterId: params.ClusterId})
if err != nil {
this.ErrorPage(err)
return
}
this.Data["dnsHasChanges"] = checkChangesResp.IsChanged
// 需要解决的问题
issuesResp, err := this.RPC().DNSRPC().FindAllDNSIssues(this.AdminContext(), &pb.FindAllDNSIssuesRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
var issueMaps = []maps.Map{}
for _, issue := range issuesResp.Issues {
issueMaps = append(issueMaps, maps.Map{
"target": issue.Target,
"targetId": issue.TargetId,
"type": issue.Type,
"description": issue.Description,
"params": issue.Params,
})
}
this.Data["issues"] = issueMaps
// 当前正在执行的任务
resp, err := this.RPC().DNSTaskRPC().FindAllDoingDNSTasks(this.AdminContext(), &pb.FindAllDoingDNSTasksRequest{
NodeClusterId: params.ClusterId,
})
if err != nil {
this.ErrorPage(err)
return
}
var taskMaps = []maps.Map{}
for _, task := range resp.DnsTasks {
var clusterMap maps.Map = nil
var nodeMap maps.Map = nil
var serverMap maps.Map = nil
var domainMap maps.Map = nil
if task.NodeCluster != nil {
clusterMap = maps.Map{
"id": task.NodeCluster.Id,
"name": task.NodeCluster.Name,
}
}
if task.Node != nil {
nodeMap = maps.Map{
"id": task.Node.Id,
"name": task.Node.Name,
}
}
if task.Server != nil {
serverMap = maps.Map{
"id": task.Server.Id,
"name": task.Server.Name,
}
}
if task.DnsDomain != nil {
domainMap = maps.Map{
"id": task.DnsDomain.Id,
"name": task.DnsDomain.Name,
}
}
taskMaps = append(taskMaps, maps.Map{
"id": task.Id,
"type": task.Type,
"isDone": task.IsDone,
"isOk": task.IsOk,
"error": task.Error,
"updatedTime": timeutil.FormatTime("Y-m-d H:i:s", task.UpdatedAt),
"cluster": clusterMap,
"node": nodeMap,
"server": serverMap,
"domain": domainMap,
})
}
this.Data["tasks"] = taskMaps
this.Show()
}

View File

@@ -40,6 +40,7 @@ func init() {
// DNS
Prefix("/clusters/cluster/settings/dns").
GetPost("", new(dns.IndexAction)).
Get("/records", new(dns.RecordsAction)).
Post("/randomName", new(dns.RandomNameAction)).
// 系统服务设置

View File

@@ -8,7 +8,6 @@ import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/lists"
@@ -58,7 +57,7 @@ func CheckDiskPartitions(thresholdPercent float64) (path string, usage uint64, u
stat, _ := disk.Usage(p.Mountpoint)
if stat != nil {
if stat.Used < 2*uint64(sizes.G) {
if stat.Used < (5<<30) || stat.Free > (100<<30) {
continue
}
if stat.UsedPercent > thresholdPercent {

View File

@@ -52,9 +52,13 @@ func (this *CreatePopupAction) RunPost(params struct {
Type string
// DNSPod
ParamId string
ParamToken string
ParamRegion string
ParamDNSPodId string
ParamDNSPodToken string
ParamDNSPodRegion string
ParamDNSPodAPIType string
ParamDNSPodAccessKeyId string
ParamDNSPodAccessKeySecret string
// AliDNS
ParamAliDNSAccessKeyId string
@@ -89,18 +93,31 @@ func (this *CreatePopupAction) RunPost(params struct {
Field("type", params.Type).
Require("请选择服务商厂家")
apiParams := maps.Map{}
var apiParams = maps.Map{}
switch params.Type {
case "dnspod":
params.Must.
Field("paramId", params.ParamId).
Require("请输入密钥ID").
Field("paramToken", params.ParamToken).
Require("请输入密钥Token")
apiParams["apiType"] = params.ParamDNSPodAPIType
switch params.ParamDNSPodAPIType {
case "tencentDNS":
params.Must.
Field("paramDNSPodAccessKeyId", params.ParamDNSPodAccessKeyId).
Require("请输入SecretId").
Field("paramDNSPodAccessKeySecret", params.ParamDNSPodAccessKeySecret).
Require("请输入SecretKey")
apiParams["accessKeyId"] = params.ParamDNSPodAccessKeyId
apiParams["accessKeySecret"] = params.ParamDNSPodAccessKeySecret
apiParams["region"] = params.ParamDNSPodRegion
default:
params.Must.
Field("paramId", params.ParamDNSPodId).
Require("请输入密钥ID").
Field("paramToken", params.ParamDNSPodToken).
Require("请输入密钥Token")
apiParams["id"] = params.ParamId
apiParams["token"] = params.ParamToken
apiParams["region"] = params.ParamRegion
apiParams["id"] = params.ParamDNSPodId
apiParams["token"] = params.ParamDNSPodToken
apiParams["region"] = params.ParamDNSPodRegion
}
case "alidns":
params.Must.
Field("paramAliDNSAccessKeyId", params.ParamAliDNSAccessKeyId).

View File

@@ -79,9 +79,13 @@ func (this *UpdatePopupAction) RunPost(params struct {
Type string
// DNSPod
ParamId string
ParamToken string
ParamRegion string
ParamDNSPodId string
ParamDNSPodToken string
ParamDNSPodRegion string
ParamDNSPodAPIType string
ParamDNSPodAccessKeyId string
ParamDNSPodAccessKeySecret string
// AliDNS
ParamAliDNSAccessKeyId string
@@ -118,18 +122,31 @@ func (this *UpdatePopupAction) RunPost(params struct {
Field("type", params.Type).
Require("请选择服务商厂家")
apiParams := maps.Map{}
var apiParams = maps.Map{}
switch params.Type {
case "dnspod":
params.Must.
Field("paramId", params.ParamId).
Require("请输入密钥ID").
Field("paramToken", params.ParamToken).
Require("请输入密钥Token")
apiParams["apiType"] = params.ParamDNSPodAPIType
switch params.ParamDNSPodAPIType {
case "tencentDNS":
params.Must.
Field("paramDNSPodAccessKeyId", params.ParamDNSPodAccessKeyId).
Require("请输入SecretId").
Field("paramDNSPodAccessKeySecret", params.ParamDNSPodAccessKeySecret).
Require("请输入SecretKey")
apiParams["accessKeyId"] = params.ParamDNSPodAccessKeyId
apiParams["accessKeySecret"] = params.ParamDNSPodAccessKeySecret
apiParams["region"] = params.ParamDNSPodRegion
default:
params.Must.
Field("paramId", params.ParamDNSPodId).
Require("请输入密钥ID").
Field("paramToken", params.ParamDNSPodToken).
Require("请输入密钥Token")
apiParams["id"] = params.ParamId
apiParams["token"] = params.ParamToken
apiParams["region"] = params.ParamRegion
apiParams["id"] = params.ParamDNSPodId
apiParams["token"] = params.ParamDNSPodToken
apiParams["region"] = params.ParamDNSPodRegion
}
case "alidns":
params.Must.
Field("paramAliDNSAccessKeyId", params.ParamAliDNSAccessKeyId).

View File

@@ -111,6 +111,7 @@ func (this *SettingAction) RunPost(params struct {
FollowRedirects: reverseProxyConfig.FollowRedirects,
ProxyProtocolJSON: proxyProtocolJSON,
Retry50X: reverseProxyConfig.Retry50X,
Retry40X: reverseProxyConfig.Retry40X,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -111,6 +111,7 @@ func (this *SettingAction) RunPost(params struct {
FollowRedirects: reverseProxyConfig.FollowRedirects,
ProxyProtocolJSON: proxyProtocolJSON,
Retry50X: reverseProxyConfig.Retry50X,
Retry40X: reverseProxyConfig.Retry40X,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -102,6 +102,7 @@ func (this *SettingAction) RunPost(params struct {
FollowRedirects: reverseProxyConfig.FollowRedirects,
ProxyProtocolJSON: proxyProtocolJSON,
Retry50X: reverseProxyConfig.Retry50X,
Retry40X: reverseProxyConfig.Retry40X,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -20,7 +20,7 @@ func (this *CreatePopupAction) Init() {
}
func (this *CreatePopupAction) RunGet(params struct{}) {
this.Data["bodyTypes"] = shared.FindAllBodyTypes()
this.Data["bodyTypes"] = serverconfigs.FindAllHTTPPageBodyTypes()
this.Show()
}
@@ -32,22 +32,33 @@ func (this *CreatePopupAction) RunPost(params struct {
URL string `alias:"url"`
Body string
ExceptURLPatternsJSON []byte
OnlyURLPatternsJSON []byte
NewStatus int
Must *actions.Must
}) {
// TODO 对状态码进行更多校验
params.Must.
Field("status", params.Status).
Require("请输入响应状态码")
if len(params.Status) != 3 {
this.FailField("status", "状态码长度必须为3位")
return
}
switch params.BodyType {
case shared.BodyTypeURL:
case serverconfigs.HTTPPageBodyTypeURL:
params.Must.
Field("url", params.URL).
Require("请输入要显示的URL").
Match( `^(?i)(http|https)://`, "请输入正确的URL")
case shared.BodyTypeHTML:
Match(`^(?i)(http|https)://`, "请输入正确的URL")
case serverconfigs.HTTPPageBodyTypeRedirectURL:
params.Must.
Field("url", params.URL).
Require("请输入要跳转的URL").
Match(`^(?i)(http|https)://`, "请输入正确的URL")
case serverconfigs.HTTPPageBodyTypeHTML:
params.Must.
Field("body", params.Body).
Require("请输入要显示的HTML内容")
@@ -58,12 +69,32 @@ func (this *CreatePopupAction) RunPost(params struct {
}
}
var exceptURLPatterns = []*shared.URLPattern{}
if len(params.ExceptURLPatternsJSON) > 0 {
err := json.Unmarshal(params.ExceptURLPatternsJSON, &exceptURLPatterns)
if err != nil {
this.ErrorPage(err)
return
}
}
var onlyURLPatterns = []*shared.URLPattern{}
if len(params.OnlyURLPatternsJSON) > 0 {
err := json.Unmarshal(params.OnlyURLPatternsJSON, &onlyURLPatterns)
if err != nil {
this.ErrorPage(err)
return
}
}
createResp, err := this.RPC().HTTPPageRPC().CreateHTTPPage(this.AdminContext(), &pb.CreateHTTPPageRequest{
StatusList: []string{params.Status},
BodyType: params.BodyType,
Url: params.URL,
Body: params.Body,
NewStatus: types.Int32(params.NewStatus),
StatusList: []string{params.Status},
BodyType: params.BodyType,
Url: params.URL,
Body: params.Body,
NewStatus: types.Int32(params.NewStatus),
ExceptURLPatternsJSON: params.ExceptURLPatternsJSON,
OnlyURLPatternsJSON: params.OnlyURLPatternsJSON,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -7,7 +7,6 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/types"
"regexp"
@@ -77,7 +76,11 @@ func (this *IndexAction) RunPost(params struct {
}
// check url
if page.BodyType == shared.BodyTypeURL && !urlReg.MatchString(page.URL) {
if page.BodyType == serverconfigs.HTTPPageBodyTypeURL && !urlReg.MatchString(page.URL) {
this.Fail("自定义页面中 '" + page.URL + "' 不是一个正确的URL请进行修改")
return
}
if page.BodyType == serverconfigs.HTTPPageBodyTypeRedirectURL && !urlReg.MatchString(page.URL) {
this.Fail("自定义页面中 '" + page.URL + "' 不是一个正确的URL请进行修改")
return
}
@@ -99,7 +102,7 @@ func (this *IndexAction) RunPost(params struct {
return
}
if shutdownConfig.BodyType == shared.BodyTypeURL {
if shutdownConfig.BodyType == serverconfigs.HTTPPageBodyTypeURL {
if len(shutdownConfig.URL) > 512 {
this.Fail("临时关闭页面中URL过长不能超过512字节")
return
@@ -109,7 +112,17 @@ func (this *IndexAction) RunPost(params struct {
this.Fail("临时关闭页面中 '" + shutdownConfig.URL + "' 不是一个正确的URL请进行修改")
return
}
} else if shutdownConfig.Body == shared.BodyTypeHTML {
} else if shutdownConfig.BodyType == serverconfigs.HTTPPageBodyTypeRedirectURL {
if len(shutdownConfig.URL) > 512 {
this.Fail("临时关闭页面中URL过长不能超过512字节")
return
}
if shutdownConfig.IsOn /** 只有启用的时候才校验 **/ && !urlReg.MatchString(shutdownConfig.URL) {
this.Fail("临时关闭页面中 '" + shutdownConfig.URL + "' 不是一个正确的URL请进行修改")
return
}
} else if shutdownConfig.Body == serverconfigs.HTTPPageBodyTypeHTML {
if len(shutdownConfig.Body) > 32*1024 {
this.Fail("临时关闭页面中HTML内容长度不能超过32K")
return

View File

@@ -22,7 +22,7 @@ func (this *UpdatePopupAction) Init() {
func (this *UpdatePopupAction) RunGet(params struct {
PageId int64
}) {
this.Data["bodyTypes"] = shared.FindAllBodyTypes()
this.Data["bodyTypes"] = serverconfigs.FindAllHTTPPageBodyTypes()
configResp, err := this.RPC().HTTPPageRPC().FindEnabledHTTPPageConfig(this.AdminContext(), &pb.FindEnabledHTTPPageConfigRequest{HttpPageId: params.PageId})
if err != nil {
@@ -30,12 +30,18 @@ func (this *UpdatePopupAction) RunGet(params struct {
return
}
pageConfig := &serverconfigs.HTTPPageConfig{}
var pageConfig = &serverconfigs.HTTPPageConfig{}
err = json.Unmarshal(configResp.PageJSON, pageConfig)
if err != nil {
this.ErrorPage(err)
return
}
if pageConfig.ExceptURLPatterns == nil {
pageConfig.ExceptURLPatterns = []*shared.URLPattern{}
}
if pageConfig.OnlyURLPatterns == nil {
pageConfig.OnlyURLPatterns = []*shared.URLPattern{}
}
this.Data["pageConfig"] = pageConfig
this.Show()
@@ -50,6 +56,9 @@ func (this *UpdatePopupAction) RunPost(params struct {
URL string `alias:"url"`
Body string
ExceptURLPatternsJSON []byte
OnlyURLPatternsJSON []byte
NewStatus int
Must *actions.Must
@@ -61,13 +70,23 @@ func (this *UpdatePopupAction) RunPost(params struct {
Field("status", params.Status).
Require("请输入响应状态码")
if len(params.Status) != 3 {
this.FailField("status", "状态码长度必须为3位")
return
}
switch params.BodyType {
case shared.BodyTypeURL:
case serverconfigs.HTTPPageBodyTypeURL:
params.Must.
Field("url", params.URL).
Require("请输入要显示的URL").
Match( `^(?i)(http|https)://`, "请输入正确的URL")
case shared.BodyTypeHTML:
Match(`^(?i)(http|https)://`, "请输入正确的URL")
case serverconfigs.HTTPPageBodyTypeRedirectURL:
params.Must.
Field("url", params.URL).
Require("请输入要跳转的URL").
Match(`^(?i)(http|https)://`, "请输入正确的URL")
case serverconfigs.HTTPPageBodyTypeHTML:
params.Must.
Field("body", params.Body).
Require("请输入要显示的HTML内容")
@@ -78,13 +97,33 @@ func (this *UpdatePopupAction) RunPost(params struct {
}
}
var exceptURLPatterns = []*shared.URLPattern{}
if len(params.ExceptURLPatternsJSON) > 0 {
err := json.Unmarshal(params.ExceptURLPatternsJSON, &exceptURLPatterns)
if err != nil {
this.ErrorPage(err)
return
}
}
var onlyURLPatterns = []*shared.URLPattern{}
if len(params.OnlyURLPatternsJSON) > 0 {
err := json.Unmarshal(params.OnlyURLPatternsJSON, &onlyURLPatterns)
if err != nil {
this.ErrorPage(err)
return
}
}
_, err := this.RPC().HTTPPageRPC().UpdateHTTPPage(this.AdminContext(), &pb.UpdateHTTPPageRequest{
HttpPageId: params.PageId,
StatusList: []string{params.Status},
BodyType: params.BodyType,
Url: params.URL,
Body: params.Body,
NewStatus: types.Int32(params.NewStatus),
HttpPageId: params.PageId,
StatusList: []string{params.Status},
BodyType: params.BodyType,
Url: params.URL,
Body: params.Body,
NewStatus: types.Int32(params.NewStatus),
ExceptURLPatternsJSON: params.ExceptURLPatternsJSON,
OnlyURLPatternsJSON: params.OnlyURLPatternsJSON,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -52,7 +52,11 @@ func (this *CreatePopupAction) RunPost(params struct {
PortAfter int
PortAfterScheme string
Status int
Status int
ExceptDomainsJSON []byte
OnlyDomainsJSON []byte
CondsJSON []byte
IsOn bool
@@ -186,6 +190,27 @@ func (this *CreatePopupAction) RunPost(params struct {
Field("status", params.Status).
Gte(0, "请选择正确的跳转状态码")
// 域名
if len(params.ExceptDomainsJSON) > 0 {
var exceptDomains = []string{}
err := json.Unmarshal(params.ExceptDomainsJSON, &exceptDomains)
if err != nil {
this.ErrorPage(err)
return
}
config.ExceptDomains = exceptDomains
}
if len(params.OnlyDomainsJSON) > 0 {
var onlyDomains = []string{}
err := json.Unmarshal(params.OnlyDomainsJSON, &onlyDomains)
if err != nil {
this.ErrorPage(err)
return
}
config.OnlyDomains = onlyDomains
}
// 校验匹配条件
var conds *shared.HTTPRequestCondsConfig
if len(params.CondsJSON) > 0 {

View File

@@ -134,6 +134,7 @@ func (this *SettingAction) RunPost(params struct {
FollowRedirects: reverseProxyConfig.FollowRedirects,
RequestHostExcludingPort: reverseProxyConfig.RequestHostExcludingPort,
Retry50X: reverseProxyConfig.Retry50X,
Retry40X: reverseProxyConfig.Retry40X,
})
if err != nil {
this.ErrorPage(err)

View File

@@ -42,6 +42,7 @@ func (this *CountriesAction) RunGet(params struct {
this.NotFound("firewallPolicy", params.FirewallPolicyId)
return
}
var deniedCountryIds = []int64{}
var allowedCountryIds = []int64{}
var countryHTML = ""
@@ -95,6 +96,24 @@ func (this *CountriesAction) RunGet(params struct {
}
this.Data["wafIsOn"] = webConfig.FirewallRef != nil && webConfig.FirewallRef.IsOn
// 获取当前服务所在集群的WAF设置
clusterFirewallPolicy, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledHTTPFirewallPolicyWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
this.ErrorPage(err)
return
}
if clusterFirewallPolicy != nil {
this.Data["clusterFirewallPolicy"] = maps.Map{
"id": clusterFirewallPolicy.Id,
"name": clusterFirewallPolicy.Name,
"isOn": clusterFirewallPolicy.IsOn,
"mode": clusterFirewallPolicy.Mode,
"modeInfo": firewallconfigs.FindFirewallMode(clusterFirewallPolicy.Mode),
}
} else {
this.Data["clusterFirewallPolicy"] = nil
}
this.Show()
}

View File

@@ -96,6 +96,24 @@ func (this *ProvincesAction) RunGet(params struct {
}
this.Data["wafIsOn"] = webConfig.FirewallRef != nil && webConfig.FirewallRef.IsOn
// 获取当前服务所在集群的WAF设置
clusterFirewallPolicy, err := dao.SharedHTTPFirewallPolicyDAO.FindEnabledHTTPFirewallPolicyWithServerId(this.AdminContext(), params.ServerId)
if err != nil {
this.ErrorPage(err)
return
}
if clusterFirewallPolicy != nil {
this.Data["clusterFirewallPolicy"] = maps.Map{
"id": clusterFirewallPolicy.Id,
"name": clusterFirewallPolicy.Name,
"isOn": clusterFirewallPolicy.IsOn,
"mode": clusterFirewallPolicy.Mode,
"modeInfo": firewallconfigs.FindFirewallMode(clusterFirewallPolicy.Mode),
}
} else {
this.Data["clusterFirewallPolicy"] = nil
}
this.Show()
}

View File

@@ -408,7 +408,8 @@ func (this *MySQLInstaller) Download() (path string, err error) {
// check latest version
this.log("checking mysql latest version ...")
var latestVersion = "8.1.0" // default version
var latestVersion = "8.2.0" // default version
var majorVersion = "8.2"
{
req, err := http.NewRequest(http.MethodGet, "https://dev.mysql.com/downloads/mysql/", nil)
if err != nil {
@@ -434,6 +435,10 @@ func (this *MySQLInstaller) Download() (path string, err error) {
var matches = reg.FindSubmatch(data)
if len(matches) > 0 {
latestVersion = string(matches[1])
var matchPieces = strings.Split(latestVersion, ".")
if len(matchPieces) >= 2 {
majorVersion = strings.Join(matchPieces[:2], ".")
}
}
}
}
@@ -441,7 +446,7 @@ func (this *MySQLInstaller) Download() (path string, err error) {
// download
this.log("start downloading ...")
var downloadURL = "https://cdn.mysql.com/Downloads/MySQL-8.1/mysql-" + latestVersion + "-linux-glibc2.17-x86_64-minimal.tar.xz"
var downloadURL = "https://cdn.mysql.com/Downloads/MySQL-" + majorVersion + "/mysql-" + latestVersion + "-linux-glibc2.17-x86_64-minimal.tar.xz"
{
this.log("downloading from url '" + downloadURL + "' ...")

View File

@@ -71,7 +71,7 @@ func (this *ComponentsAction) RunGet(params struct{}) {
} else {
buffer.WriteString("window.REQUEST_COND_COMPONENTS = ")
buffer.Write(typesJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 条件操作符
@@ -81,7 +81,7 @@ func (this *ComponentsAction) RunGet(params struct{}) {
} else {
buffer.WriteString("window.REQUEST_COND_OPERATORS = ")
buffer.Write(requestOperatorsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 请求变量
@@ -91,7 +91,7 @@ func (this *ComponentsAction) RunGet(params struct{}) {
} else {
buffer.WriteString("window.REQUEST_VARIABLES = ")
buffer.Write(requestVariablesJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// 指标
@@ -101,7 +101,7 @@ func (this *ComponentsAction) RunGet(params struct{}) {
} else {
buffer.WriteString("window.METRIC_HTTP_KEYS = ")
buffer.Write(metricHTTPKeysJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// IP地址阈值项目
@@ -111,7 +111,7 @@ func (this *ComponentsAction) RunGet(params struct{}) {
} else {
buffer.WriteString("window.IP_ADDR_THRESHOLD_ITEMS = ")
buffer.Write(ipAddrThresholdItemsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// IP地址阈值动作
@@ -121,7 +121,7 @@ func (this *ComponentsAction) RunGet(params struct{}) {
} else {
buffer.WriteString("window.IP_ADDR_THRESHOLD_ACTIONS = ")
buffer.Write(ipAddrThresholdActionsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// WAF操作符
@@ -131,7 +131,17 @@ func (this *ComponentsAction) RunGet(params struct{}) {
} else {
buffer.WriteString("window.WAF_RULE_OPERATORS = ")
buffer.Write(wafOperatorsJSON)
buffer.Write([]byte{'\n', '\n'})
buffer.Write([]byte{';', '\n', '\n'})
}
// WAF验证码类型
captchaTypesJSON, err := json.Marshal(firewallconfigs.FindAllCaptchaTypes())
if err != nil {
logs.Println("ComponentsAction marshal captcha types failed: " + err.Error())
} else {
buffer.WriteString("window.WAF_CAPTCHA_TYPES = ")
buffer.Write(captchaTypesJSON)
buffer.Write([]byte{';', '\n', '\n'})
}
componentsData = buffer.Bytes()

View File

@@ -13,6 +13,7 @@ func init() {
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeUser)).
Data("teaMenu", "users").
Prefix("/users").
Data("teaSubMenu", "users").
Get("", new(IndexAction)).
GetPost("/createPopup", new(CreatePopupAction)).

View File

@@ -147,6 +147,13 @@ func FindAllMenuMaps(langCode string, nodeLogsType string, countUnreadNodeLogs i
"module": configloaders.AdminModuleCodeUser,
"name": langs.Message(langCode, codes.AdminMenu_Users),
"icon": "users",
"subItems": []maps.Map{
{
"name": langs.Message(langCode, codes.AdminMenu_UserList),
"url": "/users",
"code": "users",
},
},
},
{
"code": "admins",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,80 @@
Vue.component("chart-columns-grid", {
props: [],
mounted: function () {
this.columns = this.calculateColumns()
let that = this
window.addEventListener("resize", function () {
that.columns = that.calculateColumns()
})
},
updated: function () {
let totalElements = this.$el.getElementsByClassName("column").length
if (totalElements == this.totalElements) {
return
}
this.totalElements = totalElements
this.calculateColumns()
},
data: function () {
return {
columns: "four",
totalElements: 0
}
},
methods: {
calculateColumns: function () {
let w = window.innerWidth
let columns = Math.floor(w / 500)
if (columns == 0) {
columns = 1
}
let columnElements = this.$el.getElementsByClassName("column")
if (columnElements.length == 0) {
return "one"
}
let maxColumns = columnElements.length
if (columns > maxColumns) {
columns = maxColumns
}
// 添加右侧边框
for (let index = 0; index < columnElements.length; index++) {
let el = columnElements[index]
el.className = el.className.replace("with-border", "")
if (index % columns == columns - 1 || index == columnElements.length - 1 /** 最后一个 **/) {
el.className += " with-border"
}
}
switch (columns) {
case 1:
return "one"
case 2:
return "two"
case 3:
return "three"
case 4:
return "four"
case 5:
return "five"
case 6:
return "six"
case 7:
return "seven"
case 8:
return "eight"
case 9:
return "nine"
case 10:
return "ten"
default:
return "ten"
}
}
},
template: `<div :class="'ui ' + columns + ' columns grid chart-grid'">
<slot></slot>
</div>`
})

View File

@@ -5,6 +5,7 @@ Vue.component("url-patterns-box", {
if (this.value != null) {
patterns = this.value
}
return {
patterns: patterns,
isAdding: false,
@@ -12,7 +13,9 @@ Vue.component("url-patterns-box", {
addingPattern: {"type": "wildcard", "pattern": ""},
editingIndex: -1,
patternIsInvalid: false
patternIsInvalid: false,
windowIsSmall: window.innerWidth < 600
}
},
methods: {
@@ -105,7 +108,7 @@ Vue.component("url-patterns-box", {
</div>
</div>
<div v-show="isAdding" style="margin-top: 0.5em">
<div class="ui fields inline">
<div :class="{'ui fields inline': !windowIsSmall}">
<div class="ui field">
<select class="ui dropdown auto-width" v-model="addingPattern.type">
<option value="wildcard">通配符</option>

View File

@@ -34,6 +34,14 @@ Vue.component("traffic-map-box", {
},
methods: {
render: function () {
if (this.$el.offsetWidth < 300) {
let that = this
setTimeout(function () {
that.render()
}, 100)
return
}
this.chart = teaweb.initChart(document.getElementById("traffic-map-box"));
let that = this
this.chart.setOption({

View File

@@ -64,7 +64,7 @@ Vue.component("message-row", {
</tr>
<tr :class="{error: message.level == 'error', positive: message.level == 'success', warning: message.level == 'warning'}">
<td>
{{message.body}}
<pre style="padding: 0; margin:0; word-break: break-all;">{{message.body}}</pre>
<!-- 健康检查 -->
<div v-if="message.type == 'HealthCheckFailed'" style="margin-top: 0.8em">

View File

@@ -122,10 +122,10 @@ Vue.component("http-cache-config-box", {
</td>
</tr>
<tr>
<td>添加X-Cache Header</td>
<td>添加X-Cache报头</td>
<td>
<checkbox v-model="cacheConfig.addStatusHeader"></checkbox>
<p class="comment">选中后自动在响应Header中增加<code-label>X-Cache: BYPASS|MISS|HIT|PURGE</code-label>。</p>
<p class="comment">选中后自动在响应Header中增加<code-label>X-Cache: BYPASS|MISS|HIT|PURGE</code-label>在浏览器端查看X-Cache值时请先禁用浏览器缓存避免影响观察。</p>
</td>
</tr>
<tr>

View File

@@ -338,6 +338,9 @@ Vue.component("http-firewall-actions-box", {
this.editingIndex = index
this.actionCode = config.code
this.action = this.actions.$find(function (k, v) {
return v.code == config.code
})
switch (config.code) {
case "block":
@@ -771,7 +774,7 @@ Vue.component("http-firewall-actions-box", {
<input type="text" style="width: 5em" maxlength="9" v-model="captchaMaxFails" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">次</span>
</div>
<p class="comment">允许用户失败尝试的最多次数超过这个次数将被自动加入黑名单如果为空或者为0表示默认。</p>
<p class="comment"><span v-if="captchaMaxFails > 0 && captchaMaxFails < 5" class="red">建议填入一个不小于5的数字以减少误判几率。</span>允许用户失败尝试的最多次数超过这个次数将被自动加入黑名单如果为空或者为0表示默认。</p>
</td>
</tr>
<tr v-if="actionCode == 'captcha'">

View File

@@ -25,7 +25,8 @@ Vue.component("http-firewall-captcha-options-viewer", {
}
return {
options: options,
summary: ""
summary: "",
captchaTypes: window.WAF_CAPTCHA_TYPES
}
},
methods: {
@@ -43,8 +44,18 @@ Vue.component("http-firewall-captcha-options-viewer", {
if (this.options.failBlockScopeAll) {
summaryList.push("全局封禁")
}
if (this.options.uiIsOn) {
summaryList.push("定制UI")
let that = this
let typeDef = this.captchaTypes.$find(function (k, v) {
return v.code == that.options.captchaType
})
if (typeDef != null) {
summaryList.push("默认验证方式:" + typeDef.name)
}
if (this.options.captchaType == "default") {
if (this.options.uiIsOn) {
summaryList.push("定制UI")
}
}
if (summaryList.length == 0) {
this.summary = "默认配置"

View File

@@ -7,6 +7,7 @@ Vue.component("http-firewall-captcha-options", {
let options = this.vCaptchaOptions
if (options == null) {
options = {
captchaType: "default",
countLetters: 0,
life: 0,
maxFails: 0,
@@ -27,11 +28,17 @@ Vue.component("http-firewall-captcha-options", {
if (options.countLetters <= 0) {
options.countLetters = 6
}
if (options.captchaType == null || options.captchaType.length == 0) {
options.captchaType = "default"
}
return {
options: options,
isEditing: false,
summary: "",
uiBodyWarning: ""
uiBodyWarning: "",
captchaTypes: window.WAF_CAPTCHA_TYPES
}
},
watch: {
@@ -73,6 +80,9 @@ Vue.component("http-firewall-captcha-options", {
"options.failBlockScopeAll": function (v) {
this.updateSummary()
},
"options.captchaType": function (v) {
this.updateSummary()
},
"options.uiIsOn": function (v) {
this.updateSummary()
},
@@ -102,9 +112,21 @@ Vue.component("http-firewall-captcha-options", {
if (this.options.failBlockScopeAll) {
summaryList.push("全局封禁")
}
if (this.options.uiIsOn) {
summaryList.push("定制UI")
let that = this
let typeDef = this.captchaTypes.$find(function (k, v) {
return v.code == that.options.captchaType
})
if (typeDef != null) {
summaryList.push("默认验证方式:" + typeDef.name)
}
if (this.options.captchaType == "default") {
if (this.options.uiIsOn) {
summaryList.push("定制UI")
}
}
if (summaryList.length == 0) {
this.summary = "默认配置"
} else {
@@ -121,6 +143,15 @@ Vue.component("http-firewall-captcha-options", {
<div v-show="isEditing" style="margin-top: 0.5em">
<table class="ui table definition selectable">
<tbody>
<tr>
<td>默认验证方式</td>
<td>
<select class="ui dropdown auto-width" v-model="options.captchaType">
<option v-for="captchaDef in captchaTypes" :value="captchaDef.code">{{captchaDef.name}}</option>
</select>
<p class="comment" v-for="captchaDef in captchaTypes" v-if="captchaDef.code == options.captchaType">{{captchaDef.description}}</p>
</td>
</tr>
<tr>
<td class="title">有效时间</td>
<td>
@@ -138,7 +169,7 @@ Vue.component("http-firewall-captcha-options", {
<input type="text" style="width: 5em" maxlength="9" v-model="options.maxFails" @keyup.enter="confirm()" @keypress.enter.prevent="1"/>
<span class="ui label">次</span>
</div>
<p class="comment">允许用户失败尝试的最多次数超过这个次数将被自动加入黑名单。如果为空或者为0表示不限制。</p>
<p class="comment"><span v-if="options.maxFails > 0 && options.maxFails < 5" class="red">建议填入一个不小于5的数字以减少误判几率。</span>允许用户失败尝试的最多次数超过这个次数将被自动加入黑名单。如果为空或者为0表示不限制。</p>
</td>
</tr>
<tr>
@@ -158,7 +189,8 @@ Vue.component("http-firewall-captcha-options", {
<p class="comment">是否在失败时全局封禁,默认为只封禁对单个网站的访问。</p>
</td>
</tr>
<tr>
<tr v-show="options.captchaType == 'default'">
<td>验证码中数字个数</td>
<td>
<select class="ui dropdown auto-width" v-model="options.countLetters">
@@ -166,12 +198,13 @@ Vue.component("http-firewall-captcha-options", {
</select>
</td>
</tr>
<tr>
<tr v-show="options.captchaType == 'default'">
<td class="color-border">定制UI</td>
<td><checkbox v-model="options.uiIsOn"></checkbox></td>
</tr>
</tbody>
<tbody v-show="options.uiIsOn">
<tbody v-show="options.uiIsOn && options.captchaType == 'default'">
<tr>
<td class="color-border">页面标题</td>
<td>

View File

@@ -7,14 +7,20 @@ Vue.component("http-firewall-config-box", {
isPrior: false,
isOn: false,
firewallPolicyId: 0,
ignoreGlobalRules: false
ignoreGlobalRules: false,
defaultCaptchaType: "none"
}
}
if (firewall.defaultCaptchaType == null || firewall.defaultCaptchaType.length == 0) {
firewall.defaultCaptchaType = "none"
}
return {
firewall: firewall,
moreOptionsVisible: false,
execGlobalRules: !firewall.ignoreGlobalRules
execGlobalRules: !firewall.ignoreGlobalRules,
captchaTypes: window.WAF_CAPTCHA_TYPES
}
},
watch: {
@@ -46,7 +52,7 @@ Vue.component("http-firewall-config-box", {
<prior-checkbox :v-config="firewall" v-if="vIsLocation || vIsGroup"></prior-checkbox>
<tbody v-show="(!vIsLocation && !vIsGroup) || firewall.isPrior">
<tr>
<td class="title">启用WAF</td>
<td class="title">启用Web防火墙</td>
<td>
<checkbox v-model="firewall.isOn"></checkbox>
<p class="comment">选中后表示启用当前网站的WAF功能。</p>
@@ -55,6 +61,17 @@ Vue.component("http-firewall-config-box", {
</tbody>
<more-options-tbody @change="changeOptionsVisible" v-show="firewall.isOn"></more-options-tbody>
<tbody v-show="moreOptionsVisible">
<tr>
<td>人机识别验证方式</td>
<td>
<select class="ui dropdown auto-width" v-model="firewall.defaultCaptchaType">
<option value="none">默认</option>
<option v-for="captchaType in captchaTypes" :value="captchaType.code">{{captchaType.name}}</option>
</select>
<p class="comment" v-if="firewall.defaultCaptchaType == 'none'">使用系统默认的设置。</p>
<p class="comment" v-for="captchaType in captchaTypes" v-if="captchaType.code == firewall.defaultCaptchaType">{{captchaType.description}}</p>
</td>
</tr>
<tr>
<td>启用系统全局规则</td>
<td>

View File

@@ -93,6 +93,7 @@ Vue.component("http-header-policy-box", {
},
addSettingHeader: function (policyId) {
teaweb.popup("/servers/server/settings/headers/createSetPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&type=" + this.type, {
height: "22em",
callback: function () {
teaweb.successRefresh("保存成功")
}
@@ -114,6 +115,7 @@ Vue.component("http-header-policy-box", {
},
updateSettingPopup: function (policyId, headerId) {
teaweb.popup("/servers/server/settings/headers/updateSetPopup?" + this.vParams + "&headerPolicyId=" + policyId + "&headerId=" + headerId + "&type=" + this.type, {
height: "22em",
callback: function () {
teaweb.successRefresh("保存成功")
}

View File

@@ -45,7 +45,7 @@ Vue.component("http-host-redirect-box", {
teaweb.popup("/servers/server/settings/redirects/createPopup", {
width: "50em",
height: "30em",
height: "36em",
callback: function (resp) {
that.id++
resp.data.redirect.id = that.id
@@ -60,7 +60,7 @@ Vue.component("http-host-redirect-box", {
teaweb.popup("/servers/server/settings/redirects/createPopup", {
width: "50em",
height: "30em",
height: "36em",
callback: function (resp) {
resp.data.redirect.id = redirect.id
Vue.set(that.redirects, index, resp.data.redirect)
@@ -119,6 +119,8 @@ Vue.component("http-host-redirect-box", {
<grey-label v-if="redirect.matchPrefix">匹配前缀</grey-label>
<grey-label v-if="redirect.matchRegexp">正则匹配</grey-label>
<grey-label v-if="!redirect.matchPrefix && !redirect.matchRegexp">精准匹配</grey-label>
<grey-label v-if="redirect.exceptDomains != null && redirect.exceptDomains.length > 0" v-for="domain in redirect.exceptDomains">排除:{{domain}}</grey-label>
<grey-label v-if="redirect.onlyDomains != null && redirect.onlyDomains.length > 0" v-for="domain in redirect.onlyDomains">仅限:{{domain}}</grey-label>
</div>
</div>
<div v-if="redirect.type == 'domain'">

View File

@@ -48,29 +48,32 @@ Vue.component("http-pages-and-shutdown-box", {
addPage: function () {
let that = this
teaweb.popup("/servers/server/settings/pages/createPopup", {
height: "26em",
height: "30em",
callback: function (resp) {
that.pages.push(resp.data.page)
that.notifyChange()
}
})
},
updatePage: function (pageIndex, pageId) {
let that = this
teaweb.popup("/servers/server/settings/pages/updatePopup?pageId=" + pageId, {
height: "26em",
height: "30em",
callback: function (resp) {
Vue.set(that.pages, pageIndex, resp.data.page)
that.notifyChange()
}
})
},
removePage: function (pageIndex) {
let that = this
teaweb.confirm("确定要除此页面吗?", function () {
teaweb.confirm("确定要除此自定义页面吗?", function () {
that.pages.$remove(pageIndex)
that.notifyChange()
})
},
addShutdownHTMLTemplate: function () {
this.shutdownConfig.body = `<!DOCTYPE html>
this.shutdownConfig.body = `<!DOCTYPE html>
<html lang="en">
<head>
\t<title>升级中</title>
@@ -89,80 +92,157 @@ Vue.component("http-pages-and-shutdown-box", {
</body>
</html>`
},
notifyChange: function () {
let parent = this.$el.parentNode
while (true) {
if (parent == null) {
break
}
if (parent.tagName == "FORM") {
break
}
parent = parent.parentNode
}
if (parent != null) {
setTimeout(function () {
Tea.runActionOn(parent)
}, 100)
}
}
},
template: `<div>
<input type="hidden" name="pagesJSON" :value="JSON.stringify(pages)"/>
<input type="hidden" name="shutdownJSON" :value="JSON.stringify(shutdownConfig)"/>
<table class="ui table selectable definition">
<tr>
<td class="title">自定义页面</td>
<td>
<div v-if="pages.length > 0">
<div class="ui label small basic" v-for="(page,index) in pages">
{{page.status}} -&gt; <span v-if="page.bodyType == 'url'">{{page.url}}</span><span v-if="page.bodyType == 'html'">[HTML内容]</span> <a href="" title="修改" @click.prevent="updatePage(index, page.id)"><i class="icon pencil small"></i></a> <a href="" title="删除" @click.prevent="removePage(index)"><i class="icon remove"></i></a>
<h4 style="margin-bottom: 0.5em">自定义页面</h4>
<p class="comment" style="padding-top: 0; margin-top: 0">根据响应状态码返回一些自定义页面比如404500等错误页面。</p>
<div v-if="pages.length > 0">
<table class="ui table selectable celled">
<thead>
<tr>
<th class="two wide">响应状态码</th>
<th>页面类型</th>
<th class="two wide">新状态码</th>
<th>例外URL</th>
<th>限制URL</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="(page,index) in pages">
<td>
<a href="" @click.prevent="updatePage(index, page.id)">
<span v-if="page.status != null && page.status.length == 1">{{page.status[0]}}</span>
<span v-else>{{page.status}}</span>
<i class="icon expand small"></i>
</a>
</td>
<td style="word-break: break-all">
<div v-if="page.bodyType == 'url'">
{{page.url}}
<div>
<grey-label>读取URL</grey-label>
</div>
</div>
<div class="ui divider"></div>
</div>
<div>
<button class="ui button small" type="button" @click.prevent="addPage()">+</button>
</div>
<p class="comment">根据响应状态码返回一些自定义页面比如404500等错误页面。</p>
</td>
</tr>
<tr>
<td>临时关闭页面</td>
<td>
<div>
<table class="ui table selectable definition">
<prior-checkbox :v-config="shutdownConfig" v-if="vIsLocation"></prior-checkbox>
<tbody v-show="!vIsLocation || shutdownConfig.isPrior">
<tr>
<td class="title">临时关闭网站</td>
<td>
<div class="ui checkbox">
<input type="checkbox" value="1" v-model="shutdownConfig.isOn" />
<label></label>
</div>
<p class="comment">选中后,表示临时关闭当前网站,并显示自定义内容。</p>
</td>
</tr>
</tbody>
<tbody v-show="(!vIsLocation || shutdownConfig.isPrior) && shutdownConfig.isOn">
<tr>
<td>显示内容类型 *</td>
<td>
<select class="ui dropdown auto-width" v-model="shutdownConfig.bodyType">
<option value="html">HTML</option>
<option value="url">读取URL</option>
</select>
</td>
</tr>
<tr v-show="shutdownConfig.bodyType == 'url'">
<td class="title">显示页面URL *</td>
<td>
<input type="text" v-model="shutdownConfig.url" placeholder="类似于 https://example.com/page.html"/>
<p class="comment">将从此URL中读取内容。</p>
</td>
</tr>
<tr v-show="shutdownConfig.bodyType == 'html'">
<td>显示页面HTML *</td>
<td>
<textarea name="body" ref="shutdownHTMLBody" v-model="shutdownConfig.body"></textarea>
<p class="comment"><a href="" @click.prevent="addShutdownHTMLTemplate">[使用模板]</a>。填写页面的HTML内容支持请求变量。</p>
</td>
</tr>
<tr>
<td>状态码</td>
<td><input type="text" size="3" maxlength="3" name="shutdownStatus" style="width:5.2em" placeholder="状态码" v-model="shutdownStatus"/></td>
</tr>
</tbody>
</table>
<p class="comment">开启临时关闭页面时,所有请求都会直接显示此页面。可用于临时升级网站或者禁止用户访问某个网页。</p>
</div>
</td>
</tr>
</table>
<div v-if="page.bodyType == 'redirectURL'">
{{page.url}}
<div>
<grey-label>跳转URL</grey-label>
<grey-label v-if="page.newStatus > 0">{{page.newStatus}}</grey-label>
</div>
</div>
<div v-if="page.bodyType == 'html'">
[HTML内容]
<div>
<grey-label v-if="page.newStatus > 0">{{page.newStatus}}</grey-label>
</div>
</div>
</td>
<td>
<span v-if="page.newStatus > 0">{{page.newStatus}}</span>
<span v-else class="disabled">保持</span>
</td>
<td>
<div v-if="page.exceptURLPatterns != null && page.exceptURLPatterns">
<span v-for="urlPattern in page.exceptURLPatterns" class="ui basic label small">{{urlPattern.pattern}}</span>
</div>
<span v-else class="disabled">-</span>
</td>
<td>
<div v-if="page.onlyURLPatterns != null && page.onlyURLPatterns">
<span v-for="urlPattern in page.onlyURLPatterns" class="ui basic label small">{{urlPattern.pattern}}</span>
</div>
<span v-else class="disabled">-</span>
</td>
<td>
<a href="" title="修改" @click.prevent="updatePage(index, page.id)">修改</a> &nbsp;
<a href="" title="删除" @click.prevent="removePage(index)">删除</a>
</td>
</tr>
</table>
</div>
<div style="margin-top: 1em">
<button class="ui button small" type="button" @click.prevent="addPage()">+添加自定义页面</button>
</div>
<h4 style="margin-top: 2em;">临时关闭页面</h4>
<p class="comment" style="margin-top: 0; padding-top: 0">开启临时关闭页面时,所有请求都会直接显示此页面。可用于临时升级网站或者禁止用户访问某个网页。</p>
<div>
<table class="ui table selectable definition">
<prior-checkbox :v-config="shutdownConfig" v-if="vIsLocation"></prior-checkbox>
<tbody v-show="!vIsLocation || shutdownConfig.isPrior">
<tr>
<td class="title">启用临时关闭网站</td>
<td>
<div class="ui checkbox">
<input type="checkbox" value="1" v-model="shutdownConfig.isOn" />
<label></label>
</div>
<p class="comment">选中后,表示临时关闭当前网站,并显示自定义内容。</p>
</td>
</tr>
</tbody>
<tbody v-show="(!vIsLocation || shutdownConfig.isPrior) && shutdownConfig.isOn">
<tr>
<td>显示内容类型 *</td>
<td>
<select class="ui dropdown auto-width" v-model="shutdownConfig.bodyType">
<option value="html">HTML</option>
<option value="url">读取URL</option>
<option value="redirectURL">跳转URL</option>
</select>
</td>
</tr>
<tr v-if="shutdownConfig.bodyType == 'url'">
<td class="title">显示页面URL *</td>
<td>
<input type="text" v-model="shutdownConfig.url" placeholder="类似于 https://example.com/page.html"/>
<p class="comment">将从此URL中读取内容。</p>
</td>
</tr>
<tr v-if="shutdownConfig.bodyType == 'redirectURL'">
<td class="title">跳转到URL *</td>
<td>
<input type="text" v-model="shutdownConfig.url" placeholder="类似于 https://example.com/page.html"/>
<p class="comment">将会跳转到此URL。</p>
</td>
</tr>
<tr v-show="shutdownConfig.bodyType == 'html'">
<td>显示页面HTML *</td>
<td>
<textarea name="body" ref="shutdownHTMLBody" v-model="shutdownConfig.body"></textarea>
<p class="comment"><a href="" @click.prevent="addShutdownHTMLTemplate">[使用模板]</a>。填写页面的HTML内容支持请求变量。</p>
</td>
</tr>
<tr>
<td>状态码</td>
<td><input type="text" size="3" maxlength="3" name="shutdownStatus" style="width:5.2em" placeholder="状态码" v-model="shutdownStatus"/></td>
</tr>
</tbody>
</table>
</div>
<div class="ui margin"></div>
</div>`
})

View File

@@ -17,6 +17,7 @@ Vue.component("http-pages-box", {
height: "26em",
callback: function (resp) {
that.pages.push(resp.data.page)
that.notifyChange()
}
})
},
@@ -26,6 +27,7 @@ Vue.component("http-pages-box", {
height: "26em",
callback: function (resp) {
Vue.set(that.pages, pageIndex, resp.data.page)
that.notifyChange()
}
})
},
@@ -33,28 +35,98 @@ Vue.component("http-pages-box", {
let that = this
teaweb.confirm("确定要移除此页面吗?", function () {
that.pages.$remove(pageIndex)
that.notifyChange()
})
},
notifyChange: function () {
let parent = this.$el.parentNode
while (true) {
if (parent == null) {
break
}
if (parent.tagName == "FORM") {
break
}
parent = parent.parentNode
}
if (parent != null) {
setTimeout(function () {
Tea.runActionOn(parent)
}, 100)
}
}
},
template: `<div>
<input type="hidden" name="pagesJSON" :value="JSON.stringify(pages)"/>
<table class="ui table selectable definition">
<tr>
<td class="title">自定义页面</td>
<td>
<div v-if="pages.length > 0">
<div class="ui label small basic" v-for="(page,index) in pages">
{{page.status}} -&gt; <span v-if="page.bodyType == 'url'">{{page.url}}</span><span v-if="page.bodyType == 'html'">[HTML内容]</span> <a href="" title="修改" @click.prevent="updatePage(index, page.id)"><i class="icon pencil small"></i></a> <a href="" title="删除" @click.prevent="removePage(index)"><i class="icon remove"></i></a>
<div v-if="pages.length > 0">
<table class="ui table selectable celled">
<thead>
<tr>
<th class="two wide">响应状态码</th>
<th>页面类型</th>
<th class="two wide">新状态码</th>
<th>例外URL</th>
<th>限制URL</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="(page,index) in pages">
<td>
<a href="" @click.prevent="updatePage(index, page.id)">
<span v-if="page.status != null && page.status.length == 1">{{page.status[0]}}</span>
<span v-else>{{page.status}}</span>
<i class="icon expand small"></i>
</a>
</td>
<td style="word-break: break-all">
<div v-if="page.bodyType == 'url'">
{{page.url}}
<div>
<grey-label>读取URL</grey-label>
</div>
</div>
<div class="ui divider"></div>
</div>
<div>
<button class="ui button small" type="button" @click.prevent="addPage()">+</button>
</div>
<p class="comment">根据响应状态码返回一些自定义页面比如404500等错误页面。</p>
</td>
</tr>
</table>
<div v-if="page.bodyType == 'redirectURL'">
{{page.url}}
<div>
<grey-label>跳转URL</grey-label>
<grey-label v-if="page.newStatus > 0">{{page.newStatus}}</grey-label>
</div>
</div>
<div v-if="page.bodyType == 'html'">
[HTML内容]
<div>
<grey-label v-if="page.newStatus > 0">{{page.newStatus}}</grey-label>
</div>
</div>
</td>
<td>
<span v-if="page.newStatus > 0">{{page.newStatus}}</span>
<span v-else class="disabled">保持</span>
</td>
<td>
<div v-if="page.exceptURLPatterns != null && page.exceptURLPatterns">
<span v-for="urlPattern in page.exceptURLPatterns" class="ui basic label small">{{urlPattern.pattern}}</span>
</div>
<span v-else class="disabled">-</span>
</td>
<td>
<div v-if="page.onlyURLPatterns != null && page.onlyURLPatterns">
<span v-for="urlPattern in page.onlyURLPatterns" class="ui basic label small">{{urlPattern.pattern}}</span>
</div>
<span v-else class="disabled">-</span>
</td>
<td>
<a href="" title="修改" @click.prevent="updatePage(index, page.id)">修改</a> &nbsp;
<a href="" title="删除" @click.prevent="removePage(index)">删除</a>
</td>
</tr>
</table>
</div>
<div style="margin-top: 1em">
<button class="ui button small" type="button" @click.prevent="addPage()">+添加自定义页面</button>
</div>
<div class="ui margin"></div>
</div>`
})

View File

@@ -1,6 +1,6 @@
// 指标图表
Vue.component("metric-chart", {
props: ["v-chart", "v-stats", "v-item"],
props: ["v-chart", "v-stats", "v-item", "v-column" /** in column? **/],
mounted: function () {
this.load()
},
@@ -188,7 +188,7 @@ Vue.component("metric-chart", {
color: teaweb.DefaultChartColor
},
areaStyle: {},
barWidth: "20em"
barWidth: "10em"
}
]
})
@@ -346,7 +346,7 @@ Vue.component("metric-chart", {
color: teaweb.DefaultChartColor
},
areaStyle: {},
barWidth: "20em"
barWidth: "10em"
}
]
})
@@ -414,7 +414,7 @@ Vue.component("metric-chart", {
return time
}
},
template: `<div style="float: left" :style="{'width': width}">
template: `<div style="float: left" :style="{'width': this.vColumn ? '' : width}" :class="{'ui column':this.vColumn}">
<h4>{{chart.name}} <span>{{valueTypeName}}</span></h4>
<div class="ui divider"></div>
<div style="height: 14em; padding-bottom: 1em; " :id="chartId" :class="{'scroll-box': chart.type == 'table'}"></div>

View File

@@ -26,7 +26,8 @@ Vue.component("reverse-proxy-box", {
maxConns: 0,
maxIdleConns: 0,
followRedirects: false,
retry50X: true
retry50X: false,
retry40X: false
}
}
if (reverseProxyConfig.addHeaders == null) {
@@ -236,6 +237,38 @@ Vue.component("reverse-proxy-box", {
<p class="comment">开启后将自动刷新缓冲区数据到客户端在类似于SSEserver-sent events等场景下很有用。</p>
</td>
</tr>
<tr v-show="family == null || family == 'http'">
<td>自动重试50X</td>
<td>
<checkbox v-model="reverseProxyConfig.retry50X"></checkbox>
<p class="comment">选中后表示当源站返回状态码为50X比如502、504等自动重试其他源站。</p>
</td>
</tr>
<tr v-show="family == null || family == 'http'">
<td>自动重试40X</td>
<td>
<checkbox v-model="reverseProxyConfig.retry40X"></checkbox>
<p class="comment">选中后表示当源站返回状态码为40X403或404自动重试其他源站。</p>
</td>
</tr>
<tr v-show="family != 'unix'">
<td>PROXY Protocol</td>
<td>
<checkbox name="proxyProtocolIsOn" v-model="reverseProxyConfig.proxyProtocol.isOn"></checkbox>
<p class="comment">选中后表示启用PROXY Protocol每次连接源站时都会在头部写入客户端地址信息。</p>
</td>
</tr>
<tr v-show="family != 'unix' && reverseProxyConfig.proxyProtocol.isOn">
<td>PROXY Protocol版本</td>
<td>
<select class="ui dropdown auto-width" name="proxyProtocolVersion" v-model="reverseProxyConfig.proxyProtocol.version">
<option value="1">1</option>
<option value="2">2</option>
</select>
<p class="comment" v-if="reverseProxyConfig.proxyProtocol.version == 1">发送类似于<code-label>PROXY TCP4 192.168.1.1 192.168.1.10 32567 443</code-label>的头部信息。</p>
<p class="comment" v-if="reverseProxyConfig.proxyProtocol.version == 2">发送二进制格式的头部信息。</p>
</td>
</tr>
<tr v-if="family == null || family == 'http'">
<td class="color-border">源站连接失败超时时间</td>
<td>
@@ -300,31 +333,6 @@ Vue.component("reverse-proxy-box", {
<p class="comment">源站保持等待的空闲超时时间0表示使用默认时间。</p>
</td>
</tr>
<tr v-show="family == null || family == 'http'">
<td>自动重试50X</td>
<td>
<checkbox v-model="reverseProxyConfig.retry50X"></checkbox>
<p class="comment">选中后表示当源站返回状态码为50X比如502、504自动重试。</p>
</td>
</tr>
<tr v-show="family != 'unix'">
<td>PROXY Protocol</td>
<td>
<checkbox name="proxyProtocolIsOn" v-model="reverseProxyConfig.proxyProtocol.isOn"></checkbox>
<p class="comment">选中后表示启用PROXY Protocol每次连接源站时都会在头部写入客户端地址信息。</p>
</td>
</tr>
<tr v-show="family != 'unix' && reverseProxyConfig.proxyProtocol.isOn">
<td>PROXY Protocol版本</td>
<td>
<select class="ui dropdown auto-width" name="proxyProtocolVersion" v-model="reverseProxyConfig.proxyProtocol.version">
<option value="1">1</option>
<option value="2">2</option>
</select>
<p class="comment" v-if="reverseProxyConfig.proxyProtocol.version == 1">发送类似于<code-label>PROXY TCP4 192.168.1.1 192.168.1.10 32567 443</code-label>的头部信息。</p>
<p class="comment" v-if="reverseProxyConfig.proxyProtocol.version == 2">发送二进制格式的头部信息。</p>
</td>
</tr>
</tbody>
</table>
<div class="margin"></div>

View File

@@ -592,7 +592,8 @@ window.teaweb = {
itemStyle: {
color: this.DefaultChartColor
},
barWidth: "20em"
barWidth: "10em",
areaStyle: {}
}
],
animation: true,

File diff suppressed because one or more lines are too long

View File

@@ -41,10 +41,37 @@
}
.column:hover {
background: rgba(0, 0, 0, .03)!important;
background: rgba(0, 0, 0, .03) !important;
a {
display: inline;
}
}
}
.grid.chart-grid {
margin-top: 1em !important;
margin-left: 0.4em !important;
.column {
margin-bottom: 1em;
border: 1px rgba(0, 0, 0, .1) solid;
border-right: 0;
.menu {
margin-top: -0.6em !important;
margin-bottom: -0.6em !important;
}
h4 {
span {
font-size: 0.8em;
color: grey;
}
}
}
.column.with-border {
border-right: 1px rgba(0, 0, 0, .1) solid;
}
}

View File

@@ -151,6 +151,26 @@ body.expanded .right-box {
.grid.counter-chart .column:hover a {
display: inline;
}
.grid.chart-grid {
margin-top: 1em !important;
margin-left: 0.4em !important;
}
.grid.chart-grid .column {
margin-bottom: 1em;
border: 1px rgba(0, 0, 0, 0.1) solid;
border-right: 0;
}
.grid.chart-grid .column .menu {
margin-top: -0.6em !important;
margin-bottom: -0.6em !important;
}
.grid.chart-grid .column h4 span {
font-size: 0.8em;
color: grey;
}
.grid.chart-grid .column.with-border {
border-right: 1px rgba(0, 0, 0, 0.1) solid;
}
/** 通用 **/
* {
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;

File diff suppressed because one or more lines are too long

View File

@@ -107,6 +107,7 @@ secret: "{{node.secret}}"</source-code-box>
<source-code-box id="rpc-code" type="text/yaml">rpc.endpoints: [ {{apiEndpoints}} ]
nodeId: "{{node.uniqueId}}"
secret: "{{node.secret}}"</source-code-box>
<p class="comment">每个节点的配置文件内容均不相同,不能混用。</p>
</td>
</tr>
<tr>

View File

@@ -1,41 +0,0 @@
{$layout}
{$template "/clusters/cluster/node/node_menu"}
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<div>
<second-menu>
<menu-item @click.prevent="createThreshold">[添加阈值]</menu-item>
</second-menu>
</div>
<p class="comment" v-if="thresholds.length == 0">暂时还没有设置阈值。</p>
<table class="ui table selectable celled" v-if="thresholds.length > 0">
<thead>
<tr>
<th>监控项</th>
<th>参数</th>
<th>操作符</th>
<th>对比值</th>
<th>统计时间段</th>
<th class="two wide">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="threshold in thresholds">
<td>{{threshold.itemName}}</td>
<td>{{threshold.paramName}}</td>
<td>{{threshold.operatorName}}</td>
<td>{{threshold.value}}</td>
<td>{{threshold.duration}}{{threshold.durationUnitName}}</td>
<td>
<label-on :v-is-on="threshold.isOn"></label-on>
</td>
<td>
<a href="" @click.prevent="updateThreshold(threshold.id)">修改</a> &nbsp;
<a href="" @click.prevent="deleteThreshold(threshold.id)">删除</a>
</td>
</tr>
</table>
</div>

View File

@@ -1,41 +0,0 @@
Tea.context(function () {
this.createThreshold = function () {
teaweb.popup(Tea.url("/clusters/cluster/settings/thresholds/createPopup", {
clusterId: this.clusterId,
nodeId: this.nodeId
}), {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.updateThreshold = function (thresholdId) {
teaweb.popup(Tea.url("/clusters/cluster/settings/thresholds/updatePopup", {
thresholdId: thresholdId
}), {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteThreshold = function (thresholdId) {
let that = this
teaweb.confirm("确定要删除这个阈值吗?", function () {
that.$post("/clusters/cluster/settings/thresholds/delete")
.params({
thresholdId: thresholdId
})
.success(function () {
teaweb.success("删除成功", function () {
teaweb.reload()
})
})
})
}
})

View File

@@ -0,0 +1,4 @@
<first-menu>
<menu-item :href="'.?clusterId=' + clusterId" code="index">DNS设置</menu-item>
<menu-item :href="'.records?clusterId=' + clusterId" code="records">解析记录</menu-item>
</first-menu>

View File

@@ -3,7 +3,8 @@
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<p class="comment"><a :href="'/dns/clusters/cluster?clusterId=' + clusterId">查看DNS解析记录 &raquo;</a></p>
{$template "menu"}
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
<input type="hidden" name="clusterId" :value="clusterId"/>
<csrf-token></csrf-token>
@@ -61,7 +62,7 @@
<td>包含Ln节点</td>
<td>
<checkbox name="includingLnNodes" v-model="includingLnNodes"></checkbox>
<p class="comment">选中后表示域名解析中包含L2及以上级别节点。</p>
<p class="comment">选中后表示域名解析中包含L2及以上级别节点,也就意味着用户请求可能会被分配到这些节点</p>
</td>
</tr>
<tr>

View File

@@ -0,0 +1,203 @@
{$layout}
{$template "../menu"}
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
{$template "menu"}
<!-- 基本信息 -->
<table class="ui table definition selectable">
<tr>
<td class="title">DNS子域名</td>
<td>
<span v-if="dnsInfo.domainName.length > 0"><var>{{dnsInfo.dnsName}}</var>.{{dnsInfo.domainName}}</span>
<span v-else class="disabled">没有设置</span>
</td>
</tr>
<tr>
<td>DNS服务商</td>
<td>
<div v-if="dnsInfo.providerName.length > 0">
<link-icon :href="'/dns/providers/provider?providerId=' + dnsInfo.providerId">{{dnsInfo.providerTypeName}} - {{dnsInfo.providerName}}</link-icon>
</div>
<span v-else-if="dnsInfo.domainName.length == 0" class="disabled">请先设置域名</span>
<span v-else class="disabled">没有设置</span>
</td>
</tr>
<tr>
<td>自动设置CNAME记录</td>
<td>
<span v-if="dnsInfo.cnameRecords.length == 0" class="disabled">暂时还没有设置。</span>
<div v-else>
<span v-for="record in dnsInfo.cnameRecords" class="ui label basic small">{{record}}</span>
</div>
</td>
</tr>
<tr v-if="dnsInfo.domainName.length > 0">
<td>操作</td>
<td>
<div v-if="!isSyncing">
<link-red v-if="dnsHasChanges" @click.prevent="syncCluster(cluster.id)">检测到解析记录有变化,需要同步</link-red>
<a href="" @click.prevent="syncCluster(cluster.id)" v-else>DNS服务商同步</a>
</div>
<span v-else>DNS服务商同步中...</span>
</td>
</tr>
</table>
<!-- 当前任务 -->
<div v-if="tasks.length > 0">
<h3>正在执行的任务</h3>
<table class="ui table selectable celled">
<thead>
<tr>
<th>对象</th>
<th>任务</th>
<th>状态</th>
<th>触发时间</th>
<th></th>
</tr>
</thead>
<tr v-for="task in tasks">
<td>
<span v-if="(task.type == 'clusterChange' || task.type == 'clusterNodesChange') && task.cluster != null">{{task.cluster.name}}
<link-icon :href="'/dns/clusters/cluster?clusterId=' + task.cluster.id" target="_top"></link-icon>
</span>
<span v-if="task.type == 'nodeChange'">{{task.node.name}}</span>
<span v-if="task.type == 'serverChange'">{{task.server.name}}</span>
<span v-if="task.type == 'domainChange'">{{task.domain.name}}</span>
</td>
<td>
<span v-if="task.type == 'clusterChange' || task.type == 'clusterNodesChange'">集群</span>
<span v-if="task.type == 'nodeChange'">节点</span>
<span v-if="task.type == 'serverChange'">网站</span>
<span v-if="task.type == 'domainChange'">域名</span>
</td>
<td style="word-break: break-word; width: 26em">
<span v-if="task.isDone" class="red">{{task.error}}</span>
<span v-else>正在同步...</span>
</td>
<td>{{task.updatedTime}}</td>
<td>
<a href="" title="删除" class="remove-btn" @click.prevent="deleteTask(task.id)"><i class="icon remove small grey"></i></a>
</td>
</tr>
</table>
</div>
<!-- 问题合集 -->
<div v-if="issues.length > 0">
<h3>需要修复的问题</h3>
<table class="ui table selectable celled" v-if="issues.length > 0">
<thead>
<tr>
<th style="width: 50%">问题对象</th>
<th>问题描述</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="issue in issues">
<td>
<div v-if="issue.type == 'cluster'">
集群 "{{issue.target}}" <link-icon :href="'/clusters/cluster?clusterId=' + issue.targetId"></link-icon>
</div>
<div v-if="issue.type == 'node'">
集群 "{{issue.params.clusterName}}" 节点 "{{issue.target}}" <link-icon :href="'/clusters/cluster/node?clusterId=' + issue.params.clusterId + '&nodeId=' + issue.targetId"></link-icon>
</div>
</td>
<td>
<span>{{issue.description}}</span>
</td>
<td>
<div v-if="issue.type == 'cluster'">
<link-red @click.prevent="updateCluster(issue.targetId)">修复</link-red>
</div>
<div v-if="issue.type == 'node'">
<link-red @click.prevent="updateNode(issue.params.clusterId, issue.targetId)">修复</link-red>
</div>
</td>
</tr>
</table>
<div class="margin"></div>
</div>
<p class="comment">下面的DNS解析记录也可以手工在DNS服务商提供的管理平台添加。</p>
<!-- 节点DNS解析记录 -->
<h3>节点DNS解析记录 <span>&nbsp; ({{nodes.length}}个)</span></h3>
<p class="comment" v-if="nodes.length == 0">暂时没有需要设置的DNS记录。</p>
<table class="ui table selectable celled" v-if="nodes.length > 0">
<thead>
<tr>
<th>节点</th>
<th>子域名</th>
<th>记录类型</th>
<th>记录值</th>
<th>线路</th>
<th>状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="node in nodes">
<td><link-icon :href="'/clusters/cluster/node?clusterId=' + node.clusterId + '&nodeId=' + node.id">{{node.name}}</link-icon></td>
<td>
<span v-if="dnsInfo.dnsName.length > 0">{{dnsInfo.dnsName}}</span>
<link-red v-else @click.prevent="updateCluster(cluster.id)">没有设置</link-red>
</td>
<td>
<span v-if="node.ipAddr.indexOf(':') > -1">AAAA</span>
<span v-else>A</span>
</td>
<td>
<span v-if="node.ipAddr.length > 0">{{node.ipAddr}}</span>
<link-red title="点击设置" v-else @click.prevent="updateNode(node.clusterId, node.id, node.ipAddrId)">没有设置</link-red>
</td>
<td>
<span v-if="node.route.code.length > 0">{{node.route.name}}</span>
<link-red v-else title="点击设置" @click.prevent="updateNode(node.clusterId, node.id, node.ipAddrId)">没有设置</link-red>
</td>
<td>
<span v-if="node.isBackup" class="red">备用节点</span>
<span v-else-if="node.isOffline" class="red">已下线</span>
<div v-else="">
<span v-if="node.isInstalled">
<span class="green" v-if="node.isResolved">已解析</span>
<span v-else class="red">未解析</span>
</span>
<link-red :href="'/clusters/cluster/node/install?clusterId=' + cluster.id + '&nodeId=' + node.id" v-if="!node.isInstalled" title="节点未安装"><span class="red">未安装</span></link-red>
</div>
</td>
<td>
<link-popup @click.prevent="updateNode(node.clusterId, node.id, node.ipAddrId)">修改</link-popup>
</td>
</tr>
</table>
<!-- 网站解析记录 -->
<h3>网站解析记录 <span>&nbsp; ({{servers.length}}个)</span></h3>
<p class="comment" v-if="servers.length == 0">暂时没有需要设置的DNS记录。</p>
<table class="ui table selectable celled" v-if="servers.length > 0">
<thead>
<tr>
<th>网站</th>
<th>子域名</th>
<th>记录类型</th>
<th>记录值</th>
<th>状态</th>
</tr>
</thead>
<tr v-for="server in servers">
<td><link-icon :href="'/servers/server?serverId=' + server.id">{{server.name}}</link-icon> </td>
<td>{{server.dnsName}}</td>
<td>CNAME</td>
<td>
<span v-if="dnsInfo.domainName.length > 0"><var>{{dnsInfo.dnsName}}</var>.{{dnsInfo.domainName}}.</span>
<link-red title="点击设置" v-else @click.prevent="updateCluster(cluster.id)">没有设置</link-red>
</td>
<td>
<span class="green" v-if="server.isResolved">已解析</span>
<span v-else class="red">未解析</span>
</td>
</tr>
</table>
</div>

View File

@@ -0,0 +1,56 @@
Tea.context(function () {
this.updateCluster = function (clusterId) {
teaweb.popup("/dns/updateClusterPopup?clusterId=" + clusterId, {
height: "25em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.updateNode = function (clusterId, nodeId, ipAddrId) {
teaweb.popup("/dns/issues/updateNodePopup?clusterId=" + clusterId + "&nodeId=" + nodeId + "&ipAddrId=" + (ipAddrId ? ipAddrId : 0), {
width: "46em",
height: "26em",
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.isSyncing = false
this.syncCluster = function (clusterId) {
let that = this
teaweb.confirm("确定要执行数据同步吗?", function () {
that.isSyncing = true
that.$post("/dns/clusters/sync")
.params({clusterId: clusterId})
.done(function () {
that.isSyncing = false
that.dnsHasChanges = false
})
.success(function () {
teaweb.success("同步成功", function () {
teaweb.reload()
})
})
})
}
this.deleteTask = function (taskId) {
let that = this
teaweb.confirm("确定要删除这个任务吗?", function () {
that.$post("/dns/tasks/delete")
.params({
taskId: taskId
})
.success(function () {
teaweb.reload()
})
})
}
})

View File

@@ -16,14 +16,14 @@
<table class="ui table definition selectable">
<tr>
<td class="title color-border">禁止未绑定域名访问</td>
<td class="title">禁止未绑定域名访问</td>
<td>
<checkbox name="httpAllMatchDomainStrictly" v-model="config.httpAll.matchDomainStrictly"></checkbox>
<p class="comment">选中后表示禁止未在网站绑定的域名和IP访问。</p>
</td>
</tr>
<tr>
<tr v-show="config.httpAll.matchDomainStrictly">
<td class="color-border">处理未绑定域名方式</td>
<td>
<radio name="httpAllDomainMismatchActionCode" :v-value="'page'" v-model="httpAllDomainMismatchActionCode">显示提示页面</radio> &nbsp;
@@ -49,14 +49,14 @@
</tr>
<tr v-show="config.httpAll.matchDomainStrictly">
<td class="color-border">允许例外的域名</td>
<td>允许例外的域名</td>
<td>
<domains-box name="httpAllAllowMismatchDomainsJSON" :v-domains="config.httpAll.allowMismatchDomains"></domains-box>
<p class="comment">允许这些域名不经过绑定就可以直接访问网站。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly">
<td class="color-border">默认域名</td>
<td>默认域名</td>
<td>
<input type="text" name="httpAllDefaultDomain" v-model="config.httpAll.defaultDomain"/>
<p class="comment">例外域名或使用节点IP访问时使用的默认域名如果指定的域名在集群里已经绑定到某个网站则相当于直接访问该网站。</p>
@@ -70,7 +70,7 @@
<p class="comment">选中后表示允许直接使用节点IP访问网站。</p>
</td>
</tr>
<tr v-show="config.httpAll.matchDomainStrictly">
<tr v-show="config.httpAll.matchDomainStrictly && config.httpAll.allowNodeIP">
<td class="color-border">访问节点IP显示自定义内容</td>
<td>
<checkbox name="httpAllNodeIPShowPage" v-model="config.httpAll.nodeIPShowPage"></checkbox>

View File

@@ -1,87 +0,0 @@
{$layout "layout_popup"}
<h3>添加阈值</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="clusterId" :value="clusterId" />
<input type="hidden" name="nodeId" :value="nodeId"/>
<input type="hidden" name="sumMethod" value="avg"/>
<table class="ui table definition selectable">
<tr>
<td class="title">监控项 *</td>
<td>
<select class="ui dropdown auto-width" name="item" v-model="threshold.item" @change="changeItem">
<option v-for="item in items" :value="item.code">{{item.name}}</option>
</select>
<p class="comment">{{itemDescription}}</p>
</td>
</tr>
<tr>
<td>参数 *</td>
<td>
<select class="ui dropdown auto-width" name="param" v-model="threshold.param" @change="changeParam">
<option v-for="param in itemParams" :value="param.code">{{param.name}}</option>
</select>
<p class="comment">{{paramDescription}}</p>
</td>
</tr>
<tr>
<td>操作符 *</td>
<td>
<select class="ui dropdown auto-width" name="operator" v-model="threshold.operator">
<option v-for="operator in operators" :value="operator.code">{{operator.name}}</option>
</select>
</td>
</tr>
<tr>
<td>对比值</td>
<td>
<input type="text" name="value" style="width: 6em" maxlength="10"/>
</td>
</tr>
<tr>
<td>统计时间段 *</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="duration" value="5" style="width: 5em"/>
</div>
<div class="ui field">
分钟
<!-- TODO 将来支持更多时间范围 -->
<input type="hidden" name="durationUnit" value="minute"/>
</div>
</div>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>消息</td>
<td>
<textarea rows="2" maxlength="100" name="message"></textarea>
<p class="comment">触发阈值时的消息提示。</p>
</td>
</tr>
<tr>
<td>消息通知间隔</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="notifyDuration" value="10" style="width: 5em"/>
</div>
<div class="ui field">
分钟
</div>
</div>
<p class="comment">在此间隔内将不会重复发送跟此阈值相关的消息。</p>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -1,43 +0,0 @@
Tea.context(function () {
this.success = NotifyPopup
this.threshold = {
item: this.items[0].code,
param: "",
operator: this.operators[0].code
}
this.$delay(function () {
this.changeItem()
this.changeParam()
})
this.itemDescription = ""
this.itemParams = []
this.changeItem = function () {
let that = this
this.threshold.param = ""
this.items.forEach(function (v) {
if (v.code == that.threshold.item) {
that.itemDescription = v.description
that.itemParams = v.params
that.threshold.param = v.params[0].code
that.paramDescription = v.params[0].description
}
})
}
this.paramDescription = ""
this.changeParam = function () {
let that = this
this.items.forEach(function (v) {
if (v.code == that.threshold.item) {
v.params.forEach(function (param) {
if (param.code == that.threshold.param) {
that.paramDescription = param.description
}
})
}
})
}
})

View File

@@ -1,42 +0,0 @@
{$layout}
{$template "../menu"}
{$template "/left_menu_with_menu"}
<div class="right-box with-menu">
<first-menu>
<menu-item @click.prevent="createThreshold">[添加阈值]</menu-item>
</first-menu>
<p class="comment" v-if="thresholds.length == 0">暂时还没有设置阈值。</p>
<table class="ui table selectable celled" v-if="thresholds.length > 0">
<thead>
<tr>
<th>监控项</th>
<th>参数</th>
<th>操作符</th>
<th>对比值</th>
<th>统计时间段</th>
<th class="two wide">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="threshold in thresholds">
<td>{{threshold.itemName}}
<div v-if="threshold.node != null" style="margin-top: 0.3em">
<a :href="'/clusters/cluster/node/settings/thresholds?clusterId=' + clusterId + '&nodeId=' + threshold.node.id" class="ui label basic tiny" title="节点专属阈值设置"><span class="small">节点:{{threshold.node.name}}</span></a>
</div>
</td>
<td>{{threshold.paramName}}</td>
<td>{{threshold.operatorName}}</td>
<td>{{threshold.value}}</td>
<td>{{threshold.duration}}{{threshold.durationUnitName}}</td>
<td>
<label-on :v-is-on="threshold.isOn"></label-on>
</td>
<td>
<a href="" @click.prevent="updateThreshold(threshold.id)">修改</a> &nbsp;
<a href="" @click.prevent="deleteThreshold(threshold.id)">删除</a>
</td>
</tr>
</table>
</div>

View File

@@ -1,40 +0,0 @@
Tea.context(function () {
this.createThreshold = function () {
teaweb.popup(Tea.url(".createPopup", {
clusterId: this.clusterId
}), {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.updateThreshold = function (thresholdId) {
teaweb.popup(Tea.url(".updatePopup", {
thresholdId: thresholdId
}), {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteThreshold = function (thresholdId) {
let that = this
teaweb.confirm("确定要删除这个阈值吗?", function () {
that.$post(".delete")
.params({
thresholdId: thresholdId
})
.success(function () {
teaweb.success("删除成功", function () {
teaweb.reload()
})
})
})
}
})

View File

@@ -1,92 +0,0 @@
{$layout "layout_popup"}
<h3>修改阈值</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="thresholdId" :value="threshold.id"/>
<input type="hidden" name="sumMethod" value="avg"/>
<table class="ui table definition selectable">
<tr>
<td class="title">监控项 *</td>
<td>
<select class="ui dropdown auto-width" name="item" v-model="threshold.item" @change="changeItem">
<option v-for="item in items" :value="item.code">{{item.name}}</option>
</select>
<p class="comment">{{itemDescription}}</p>
</td>
</tr>
<tr>
<td>参数 *</td>
<td>
<select class="ui dropdown auto-width" name="param" v-model="threshold.param" @change="changeParam">
<option v-for="param in itemParams" :value="param.code">{{param.name}}</option>
</select>
<p class="comment">{{paramDescription}}</p>
</td>
</tr>
<tr>
<td>操作符 *</td>
<td>
<select class="ui dropdown auto-width" name="operator" v-model="threshold.operator">
<option v-for="operator in operators" :value="operator.code">{{operator.name}}</option>
</select>
</td>
</tr>
<tr>
<td>对比值</td>
<td>
<input type="text" name="value" style="width: 6em" maxlength="10" v-model="threshold.value"/>
</td>
</tr>
<tr>
<td>统计时间段 *</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="duration" value="5" style="width: 5em" v-model="threshold.duration"/>
</div>
<div class="ui field">
分钟
<!-- TODO 将来支持更多时间范围 -->
<input type="hidden" name="durationUnit" value="minute" v-model="threshold.durationUnit"/>
</div>
</div>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>消息</td>
<td>
<textarea rows="2" maxlength="100" name="message" v-model="threshold.message"></textarea>
<p class="comment">触发阈值时的消息提示。</p>
</td>
</tr>
<tr>
<td>消息通知间隔</td>
<td>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="notifyDuration" v-model="threshold.notifyDuration" value="10" style="width: 5em"/>
</div>
<div class="ui field">
分钟
</div>
</div>
<p class="comment">在此间隔内将不会重复发送跟此阈值相关的消息。</p>
</td>
</tr>
<tr>
<td>是否启用</td>
<td>
<checkbox name="isOn" value="1" v-model="threshold.isOn"></checkbox>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -1,48 +0,0 @@
Tea.context(function () {
this.success = NotifyPopup
this.$delay(function () {
this.initItem()
this.changeParam()
})
this.itemDescription = ""
this.itemParams = []
this.initItem = function () {
let that = this
this.items.forEach(function (v) {
if (v.code == that.threshold.item) {
that.itemDescription = v.description
that.itemParams = v.params
}
})
}
this.changeItem = function () {
let that = this
this.threshold.param = ""
this.items.forEach(function (v) {
if (v.code == that.threshold.item) {
that.itemDescription = v.description
that.itemParams = v.params
that.threshold.param = v.params[0].code
that.paramDescription = v.params[0].description
}
})
}
this.paramDescription = ""
this.changeParam = function () {
let that = this
this.items.forEach(function (v) {
if (v.code == that.threshold.item) {
v.params.forEach(function (param) {
if (param.code == that.threshold.param) {
that.paramDescription = param.description
}
})
}
})
}
})

View File

@@ -8,7 +8,7 @@
<td>名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus"/>
<p class="comment">起一个容易识别的名称。</p>
<p class="comment">为当前认证信息起一个容易识别的名称。</p>
</td>
</tr>
@@ -26,7 +26,7 @@
<tr>
<td>SSH用户名 *</td>
<td>
<input type="text" name="username" maxlength="100" value="root"/>
<input type="text" name="username" maxlength="100" value="root" v-model="username"/>
<p class="comment">SSH登录用户名。</p>
</td>
</tr>
@@ -42,7 +42,7 @@
<tr>
<td>SSH用户名 *</td>
<td>
<input type="text" name="username" maxlength="100" value="root"/>
<input type="text" name="username" maxlength="100" value="root" v-model="username"/>
<p class="comment">SSH登录用户名。</p>
</td>
</tr>
@@ -62,17 +62,18 @@
</tr>
</tbody>
<tr v-if="username != 'root'">
<td>执行sudo</td>
<td>
<checkbox name="su" checked="checked"></checkbox>
<p class="comment">非root的用户可以使用<code-label>sudo</code-label>获得更高权限来执行命令请确保当前用户已经加入到sudo分组中。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>执行sudo</td>
<td>
<checkbox name="su"></checkbox>
<p class="comment">非root的用户可以使用<code-label>sudo</code-label>获得更高权限来执行命令。</p>
</td>
</tr>
<tr>
<td>备注</td>
<td>

View File

@@ -1,5 +1,6 @@
Tea.context(function () {
this.method = "user";
this.username = "root"
this.success = NotifySuccess("保存成功", "/clusters/grants");
});

View File

@@ -7,6 +7,7 @@
<td>名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus" value=""/>
<p class="comment">为当前认证信息起一个容易识别的名称。</p>
</td>
</tr>
@@ -24,7 +25,7 @@
<tr>
<td>SSH用户名 *</td>
<td>
<input type="text" name="username" maxlength="100" value="root"/>
<input type="text" name="username" maxlength="100" value="root" v-model="username"/>
<p class="comment">SSH登录用户名。</p>
</td>
</tr>
@@ -40,7 +41,7 @@
<tr>
<td>SSH用户名 *</td>
<td>
<input type="text" name="username" maxlength="100" value="root"/>
<input type="text" name="username" maxlength="100" value="root" v-model="username"/>
<p class="comment">SSH登录用户名。</p>
</td>
</tr>
@@ -60,18 +61,19 @@
</tr>
</tbody>
<tr v-if="username != 'root'">
<td>执行sudo</td>
<td>
<checkbox name="su" checked="checked"></checkbox>
<p class="comment">非root的用户可以使用<code-label>sudo</code-label>获得更高权限来执行命令请确保当前用户已经加入到sudo分组中。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>执行sudo</td>
<td>
<checkbox name="su"></checkbox>
<p class="comment">非root的用户可以使用<code-label>sudo</code-label>获得更高权限来执行命令。</p>
</td>
</tr>
<tr>
<td>备注</td>
<td>

View File

@@ -1,5 +1,6 @@
Tea.context(function () {
this.method = "user";
this.username = "root"
this.success = NotifyPopup;
});

View File

@@ -9,7 +9,7 @@
<td>名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus" v-model="grant.name"/>
<p class="comment">起一个容易识别的名称。</p>
<p class="comment">为当前认证信息起一个容易识别的名称。</p>
</td>
</tr>
@@ -62,11 +62,11 @@
</td>
</tr>
</tbody>
<tr>
<tr v-if="grant.username != 'root'">
<td>执行sudo</td>
<td>
<checkbox name="su" v-model="grant.su"></checkbox>
<p class="comment">非root的用户可以使用<code-label>sudo</code-label>获得更高权限来执行命令。</p>
<p class="comment">非root的用户可以使用<code-label>sudo</code-label>获得更高权限来执行命令请确保当前用户已经加入到sudo分组中</p>
</td>
</tr>
<tr>

View File

@@ -9,6 +9,7 @@
<td>名称 *</td>
<td>
<input type="text" name="name" maxlength="100" ref="focus" value="" v-model="grant.name"/>
<p class="comment">为当前认证信息起一个容易识别的名称。</p>
</td>
</tr>
@@ -61,19 +62,19 @@
</td>
</tr>
</tbody>
<tr v-if="grant.username != 'root'">
<td>执行sudo</td>
<td>
<checkbox name="su" v-model="grant.su"></checkbox>
<p class="comment">非root的用户可以使用<code-label>sudo</code-label>获得更高权限来执行命令请确保当前用户已经加入到sudo分组中。</p>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>执行sudo</td>
<td>
<checkbox name="su" v-model="grant.su"></checkbox>
<p class="comment">非root的用户可以使用<code-label>sudo</code-label>获得更高权限来执行命令。</p>
</td>
</tr>
<tr>
<td>备注</td>
<td>

View File

@@ -1,7 +0,0 @@
<first-menu>
<!--<menu-item href="/clusters/monitors" code="index">任务</menu-item>-->
<!--<menu-item href="/clusters/monitors/logs" code="log">日志</menu-item>-->
<menu-item href="/clusters/monitors/reporters" code="reporter">终端</menu-item>
<menu-item href="/clusters/monitors/groups" code="group">分组</menu-item>
<menu-item href="/clusters/monitors/settings" code="setting">设置</menu-item>
</first-menu>

View File

@@ -1,15 +0,0 @@
{$layout "layout_popup"}
<h3>创建分组</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">分组名称</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus"/>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -1,16 +0,0 @@
{$layout "layout_popup"}
<h3>修改分组</h3>
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<input type="hidden" name="groupId" :value="group.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">分组名称</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus" v-model="group.name"/>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -1,23 +0,0 @@
{$layout}
{$template "../menu"}
<second-menu>
<menu-item @click.prevent="createGroup">[添加分组]</menu-item>
</second-menu>
<p class="comment" v-if="groups.length == 0">暂时还没有分组。</p>
<table class="ui table selectable celled" v-if="groups.length > 0">
<thead>
<tr>
<th>分组名称</th>
<th class="two wide">操作</th>
</tr>
</thead>
<tr v-for="group in groups">
<td>{{group.name}}</td>
<td>
<a href="" @click.prevent="updateGroup(group.id)">修改</a> &nbsp;
<a href="" @click.prevent="deleteGroup(group.id)">删除</a>
</td>
</tr>
</table>

View File

@@ -1,31 +0,0 @@
Tea.context(function () {
this.createGroup = function () {
teaweb.popup(".createPopup", {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.updateGroup = function (groupId) {
teaweb.popup(".group.updatePopup?groupId=" + groupId, {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteGroup = function (groupId) {
teaweb.confirm("确定要删除此分组吗?", function () {
this.$post(".group.delete")
.params({
groupId: groupId
})
.refresh()
})
}
})

View File

@@ -1,60 +0,0 @@
{$layout}
{$template "menu"}
<div class="margin"></div>
<form class="ui form" method="get" action="/clusters/monitors">
<div class="ui fields inline">
<div class="ui field">
集群:
</div>
<div class="ui field">
<select class="ui dropdown" name="clusterId" v-model="clusterId">
<option v-for="cluster in clusters" :value="cluster.id">{{cluster.name}}</option>
</select>
</div>
<div class="ui field">
<button class="ui button" type="submit">查看</button>
</div>
</div>
</form>
<p class="comment" v-if="tasks.length == 0">暂时还没有任务。</p>
<table class="ui table selectable celled" v-if="tasks.length > 0">
<thead>
<tr>
<th>节点</th>
<th>IP</th>
<th class="one wide">端口</th>
<th>综合延时</th>
<th>综合级别</th>
<th>综合连通率</th>
</tr>
</thead>
<tr v-for="task in tasks">
<td>
<span v-if="task.node.id > 0">{{task.node.name}}<link-icon :href="'/clusters/cluster/node?nodeId=' + task.node.id + '&clusterId=' + clusterId"></link-icon></span>
</td>
<td>
{{task.ip}}<link-icon :href="'/clusters/ip-addrs/addr?addrId=' + task.addr.id"></link-icon>
</td>
<td>
<span v-if="task.port > 0">{{task.port}}</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="task.costMs > 0" :class="task.color">{{task.costMs}}ms</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span :class="task.color">{{task.levelName}}</span>
</td>
<td>
<span v-if="task.connectivity > 0" :class="task.color">{{task.connectivity}}%</span>
<span v-else class="disabled">-</span>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -1,18 +0,0 @@
Tea.context(function () {
this.tasks.forEach(function (v) {
switch (v.level) {
case "good":
v.color = "green"
break
case "normal":
v.color = "blue"
break
case "bad":
v.color = "orange"
break
case "broken":
v.color = "red"
break
}
})
})

View File

@@ -1,2 +0,0 @@
{$layout}
{$template "menu"}

View File

@@ -1,47 +0,0 @@
{$layout "layout_popup"}
<h3>添加终端</h3>
<form class="ui form" data-tea-success="success" data-tea-action="$">
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">终端名称 *</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus"/>
</td>
</tr>
<tr>
<td>所属分组</td>
<td>
<report-node-groups-selector></report-node-groups-selector>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>区域</td>
<td>
<input type="text" name="location" maxlength="50"/>
<p class="comment">终端所在地域。</p>
</td>
</tr>
<tr>
<td>网络提供商</td>
<td>
<input type="text" name="isp" maxlength="50"/>
<p class="comment">终端所属网络供应商。</p>
</td>
</tr>
<tr>
<td>允许的终端IP</td>
<td>
<values-box name="allowIPs"></values-box>
<p class="comment">如果不为空则只有这些IP才能连接API。支持单个IP、CIDR格式的IP段和IP1-IP2这样的IP段。</p>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -1,77 +0,0 @@
{$layout}
{$template "../menu"}
<second-menu>
<menu-item @click.prevent="createReporter">[添加终端]</menu-item>
</second-menu>
<form class="ui form" method="get" action="/clusters/monitors/reporters">
<div class="ui fields inline">
<div class="ui field" v-if="groups.length > 0">
<select class="ui dropdown auto-width" name="groupId" v-model="groupId">
<option value="0">[全部分组]</option>
<option v-for="group in groups" :value="group.id">{{group.name}}</option>
</select>
</div>
<div class="ui field">
<input type="text" name="keyword" v-model="keyword" placeholder="名称、地域、IP..."/>
</div>
<div class="ui field">
<button class="ui button" type="submit">搜索</button>
&nbsp;
<a href="/clusters/monitors/reporters" v-if="groupId > 0 || keyword.length > 0">[清除条件]</a>
</div>
</div>
</form>
<p class="comment" v-if="reporters.length == 0">暂时还没有终端。</p>
<table class="ui table selectable celled" v-if="reporters.length > 0">
<thead>
<tr>
<th>终端名称</th>
<th>区域</th>
<th>网络提供商</th>
<th>当前IP</th>
<th class="one wide">状态</th>
<th class="two op">操作</th>
</tr>
</thead>
<tr v-for="reporter in reporters">
<td>
<a :href="Tea.url('.reporter', {reporterId: reporter.id})"><keyword :v-word="keyword">{{reporter.name}}</keyword></a>
<div v-if="reporter.groups.length > 0">
<grey-label v-for="group in reporter.groups">{{group.name}}</grey-label>
</div>
<div v-if="reporter.shouldUpgrade">
<span class="red small" title="需要升级">v{{reporter.status.buildVersion}} -&gt; v{{reporter.newVersion}}</span>
</div>
</td>
<td>
<span v-if="reporter.location.length > 0"><keyword :v-word="keyword">{{reporter.location}}</keyword></span>
<span v-else-if="reporter.status.location.length > 0" class="grey"><keyword :v-word="keyword">{{reporter.status.location}}</keyword></span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="reporter.isp.length > 0"><keyword :v-word="keyword">{{reporter.isp}}</keyword></span>
<span v-else-if="reporter.status.isp.length > 0" class="grey"><keyword :v-word="keyword">{{reporter.status.isp}}</keyword></span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="reporter.status.ip.length > 0"><keyword :v-word="keyword">{{reporter.status.ip}}</keyword></span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="!reporter.isOn" class="red">禁用</span>
<span v-else-if="!reporter.isActive" class="red">离线</span>
<span v-else class="green">在线</span>
</td>
<td>
<a :href="Tea.url('.reporter', {reporterId: reporter.id})">详情</a> &nbsp;
<a href="" @click.prevent="deleteReporter(reporter.id)">删除</a>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -1,25 +0,0 @@
Tea.context(function () {
this.createReporter = function () {
teaweb.popup(".createPopup", {
callback: function () {
teaweb.success("保存成功", function () {
teaweb.reload()
})
}
})
}
this.deleteReporter = function (reporterId) {
teaweb.confirm("确定要删除此终端吗?", function () {
this.$post(".reporter.delete")
.params({
reporterId: reporterId
})
.success(function () {
teaweb.success("删除成功", function () {
teaweb.reload()
})
})
})
}
})

View File

@@ -1,8 +0,0 @@
<first-menu>
<menu-item href="/clusters/monitors/reporters">所有终端</menu-item>
<span class="disabled item">|</span>
<menu-item :href="'/clusters/monitors/reporters/reporter?reporterId=' + reporter.id" code="reporter">"{{reporter.name}}"详情</menu-item>
<menu-item :href="'/clusters/monitors/reporters/reporter/results?reporterId=' + reporter.id" code="result">监控结果</menu-item>
<menu-item :href="'/clusters/monitors/reporters/reporter/logs?reporterId=' + reporter.id" code="log">运行日志</menu-item>
<menu-item :href="'/clusters/monitors/reporters/reporter/update?reporterId=' + reporter.id" code="update">修改</menu-item>
</first-menu>

View File

@@ -1,85 +0,0 @@
{$layout}
{$template "/code_editor"}
{$template "reporter_menu"}
<table class="ui table definition selectable">
<tr>
<td class="title">终端名称</td>
<td>
{{reporter.name}}
</td>
</tr>
<tr>
<td>状态</td>
<td>
<span v-if="!reporter.isOn" class="red">禁用</span>
<span v-else-if="!reporter.isActive" class="red">离线</span>
<span v-else class="green">在线</span>
</td>
</tr>
<tr>
<td>所属分组</td>
<td>
<div v-if="reporter.groups.length > 0">
<span v-for="group in reporter.groups" class="ui label basic tiny">{{group.name}}</span>
</div>
<span v-else class="disabled">全部分组</span>
</td>
</tr>
<tr>
<td>区域</td>
<td>
<span v-if="reporter.location.length > 0">{{reporter.location}}</span>
<span v-else-if="reporter.status.location.length > 0" class="grey">{{reporter.status.location}}</span>
<span v-else class="disabled">没有设置</span>
</td>
</tr>
<tr>
<td>网络提供商</td>
<td>
<span v-if="reporter.isp.length > 0">{{reporter.isp}}</span>
<span v-else-if="reporter.status.isp.length > 0" class="grey">{{reporter.status.isp}}</span>
<span v-else class="disabled">没有设置</span>
</td>
</tr>
<tr>
<td>允许的终端IP</td>
<td>
<div v-if="reporter.allowIPs != null && reporter.allowIPs.length > 0">
<span v-for="ip in reporter.allowIPs" class="ui label tiny basic">{{ip}}</span>
</div>
<span v-else class="disabled">没有设置</span>
</td>
</tr>
<tr v-if="reporter.status.buildVersion.length > 0">
<td>版本号</td>
<td>v{{reporter.status.buildVersion}}</td>
</tr>
</table>
<h4>安装信息</h4>
<table class="ui table definition selectable">
<tr>
<td class="title">节点ID</td>
<td>{{reporter.uniqueId}}</td>
</tr>
<tr>
<td>节点Secret</td>
<td>{{reporter.secret}}</td>
</tr>
<tr>
<td>配置文件</td>
<td>
configs/api_reporter.yaml&nbsp;
<download-link :v-element="'rpc-code'" :v-file="'api_reporter.yaml'">[下载]</download-link >
</td>
</tr>
<tr>
<td>配置内容</td>
<td>
<source-code-box id="rpc-code" type="text/yaml">rpc.endpoints: [ {{apiEndpoints}} ]
nodeId: "{{reporter.uniqueId}}"
secret: "{{reporter.secret}}"</source-code-box>
</td>
</tr>
</table>

View File

@@ -1,52 +0,0 @@
{$layout}
{$template "reporter_menu"}
{$template "/datepicker"}
<div class="margin"></div>
<form method="get" :action="Tea.url('$')" class="ui form" autocomplete="off">
<input type="hidden" name="reporterId" :value="reporter.id"/>
<div class="ui fields inline">
<div class="ui field">
<input type="text" name="dayFrom" placeholder="开始日期" v-model="dayFrom" value="" style="width:8em" id="day-from-picker"/>
</div>
<div class="ui field">
<input type="text" name="dayTo" placeholder="结束日期" v-model="dayTo" value="" style="width:8em" id="day-to-picker"/>
</div>
<div class="ui field">
<select class="ui dropdown" name="level" v-model="level">
<option value="">[级别]</option>
<option value="error">错误</option>
<option value="warning">警告</option>
<option value="info">信息</option>
<option value="success">成功</option>
</select>
</div>
<div class="ui field">
<input type="text" name="keyword" style="width:10em" v-model="keyword" placeholder="关键词"/>
</div>
<div class="ui field">
<button type="submit" class="ui button">查询</button>
</div>
<div class="ui field" v-if="dayFrom.length > 0 || dayTo.length > 0 || keyword.length > 0 || level.length > 0">
<a :href="Tea.url('.logs', { reporterId:reporter.id })">[清除条件]</a>
</div>
</div>
</form>
<p class="comment" v-if="logs.length == 0">暂时还没有日志。</p>
<table class="ui table selectable" v-if="logs.length > 0">
<thead>
<tr>
</tr>
</thead>
<tr v-for="log in logs">
<td>
<node-log-row :v-log="log" :v-keyword="keyword"></node-log-row>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -1,6 +0,0 @@
Tea.context(function () {
this.$delay(function () {
teaweb.datepicker("day-from-picker")
teaweb.datepicker("day-to-picker")
})
})

View File

@@ -1,53 +0,0 @@
{$layout}
{$template "reporter_menu"}
<div class="ui margin"></div>
<form class="ui form" action="/clusters/monitors/reporters/reporter/results">
<input type="hidden" name="reporterId" :value="reporter.id"/>
<div class="ui fields inline">
<div class="ui field">
<select class="ui dropdown" name="level" v-model="level">
<option value="">[级别]</option>
<option v-for="level in levels" :value="level.code">{{level.name}}</option>
</select>
</div>
<div class="ui field">
<button class="ui button" type="submit">搜索</button> &nbsp;
<a :href="Tea.url('$', {reporterId: reporter.id})" v-if="level.length > 0">[清除条件]</a>
</div>
</div>
</form>
<p class="comment" v-if="results.length == 0">暂时还没有监控结果。</p>
<table class="ui table celled selectable" v-if="results.length > 0">
<thead>
<tr>
<th class="two wide">类型</th>
<th class="two wide">检测时间</th>
<th class="four wide">对象</th>
<th class="one wide">级别</th>
<th class="two wide">耗时</th>
<th>错误信息</th>
</tr>
</thead>
<tr v-for="result in results">
<td>{{result.typeName}}</td>
<td>{{result.updatedTime}}</td>
<td>{{result.targetDesc}}
<span v-if="result.type == 'ipAddr'"><link-icon :href="'/clusters/ip-addrs/addr?addrId=' + result.targetId"></link-icon></span>
</td>
<td>
<span :class="result.color">{{result.levelName}}</span>
</td>
<td>
<span v-if="result.isOk" :class="result.color">{{result.costMs}}ms</span>
<span v-else class="disabled">-</span>
</td>
<td>
<span v-if="!result.isOk" class="red">{{result.error}}</span>
<span v-else class="disabled">-</span>
</td>
</tr>
</table>
<div class="page" v-html="page"></div>

View File

@@ -1,18 +0,0 @@
Tea.context(function () {
this.results.forEach(function (v) {
switch (v.level) {
case "good":
v.color = "green"
break
case "normal":
v.color = "blue"
break
case "bad":
v.color = "orange"
break
case "broken":
v.color = "red"
break
}
})
})

View File

@@ -1,54 +0,0 @@
{$layout}
{$template "reporter_menu"}
<form class="ui form" data-tea-success="success" data-tea-action="$">
<csrf-token></csrf-token>
<input type="hidden" name="reporterId" :value="reporter.id"/>
<table class="ui table definition selectable">
<tr>
<td class="title">终端名称 *</td>
<td>
<input type="text" name="name" maxlength="50" ref="focus" v-model="reporter.name"/>
</td>
</tr>
<tr>
<td>所属分组</td>
<td>
<report-node-groups-selector :v-group-ids="reporter.groupIds"></report-node-groups-selector>
</td>
</tr>
<tr>
<td colspan="2"><more-options-indicator></more-options-indicator></td>
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>区域</td>
<td>
<input type="text" name="location" maxlength="50" v-model="reporter.location"/>
<p class="comment">终端所在地域。</p>
</td>
</tr>
<tr>
<td>网络提供商</td>
<td>
<input type="text" name="isp" maxlength="50" v-model="reporter.isp"/>
<p class="comment">终端所属网络供应商。</p>
</td>
</tr>
<tr>
<td>允许的终端IP</td>
<td>
<values-box name="allowIPs" :values="reporter.allowIPs"></values-box>
<p class="comment">如果不为空则只有这些IP才能连接API。支持单个IP、CIDR格式的IP段和IP1-IP2这样的IP段。</p>
</td>
</tr>
<tr>
<td>是否启用</td>
<td>
<checkbox name="isOn" v-model="reporter.isOn"></checkbox>
</td>
</tr>
</tbody>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -1,3 +0,0 @@
Tea.context(function () {
this.success = NotifySuccess("保存成功", Tea.url(".", {reporterId: this.reporter.id}))
})

View File

@@ -1,26 +0,0 @@
{$layout}
{$template "../menu"}
<form class="ui form" data-tea-action="$" data-tea-success="success">
<csrf-token></csrf-token>
<table class="ui table definition selectable">
<tr>
<td class="title">提醒连通性阈值</td>
<td>
<div class="ui input right labeled">
<input type="text" maxlength="4" size="2" name="minNotifyConnectivity" v-model="setting.minNotifyConnectivity"/>
<span class="ui label">%</span>
</div>
<p class="comment">取值0到100当单个被监控对象连通性低于此值时不包含此值发送消息提醒。<span v-if="setting.minNotifyConnectivity == 100">100%表示只要有任何访问异常的情形都会发送提醒。</span></p>
</td>
</tr>
<tr>
<td>通知URL</td>
<td>
<input type="text" name="notifyWebHookURL" maxlength="1000" v-model="setting.notifyWebHookURL" placeholder="https://..."/>
<p class="comment">当达到连通性阈值时将监控对象的信息发送到此URL上。</p>
</td>
</tr>
</table>
<submit-btn></submit-btn>
</form>

View File

@@ -1,3 +0,0 @@
Tea.context(function () {
this.success = NotifyReloadSuccess("保存成功")
})

View File

@@ -84,7 +84,7 @@
</div>
<div class="ui column">
<h4>服务<link-icon href="/servers" v-if="dashboard.canGoServers"></link-icon></h4>
<h4>网站<link-icon href="/servers" v-if="dashboard.canGoServers"></link-icon></h4>
<div class="value"><span>{{dashboard.countServers}}</span></div>
</div>
@@ -94,30 +94,34 @@
</div>
</columns-grid>
<div class="ui divider" v-show="!isLoading"></div>
<chart-columns-grid>
<div class="ui column">
<div class="ui menu text blue" v-show="!isLoading">
<a href="" class="item" :class="{active: trafficTab == 'hourly'}" @click.prevent="selectTrafficTab('hourly')">24小时流量趋势</a>
<a href="" class="item" :class="{active: trafficTab == 'daily'}" @click.prevent="selectTrafficTab('daily')">15天流量趋势</a>
</div>
<div class="ui divider"></div>
<div class="ui menu tabular" v-show="!isLoading">
<a href="" class="item" :class="{active: trafficTab == 'hourly'}" @click.prevent="selectTrafficTab('hourly')">24小时流量趋势</a>
<a href="" class="item" :class="{active: trafficTab == 'daily'}" @click.prevent="selectTrafficTab('daily')">15天流量趋势</a>
</div>
<!-- 按小时统计 -->
<div class="chart-box" id="hourly-traffic-chart-box" v-show="trafficTab == 'hourly'"></div>
<!--小时统计 -->
<div class="chart-box" id="hourly-traffic-chart-box" v-show="trafficTab == 'hourly'"></div>
<!--统计 -->
<div class="chart-box" id="daily-traffic-chart-box" v-show="trafficTab == 'daily'"></div>
</div>
<!-- 按日统计 -->
<div class="chart-box" id="daily-traffic-chart-box" v-show="trafficTab == 'daily'"></div>
<div class="ui column">
<!-- 域名排行 -->
<h4 v-show="!isLoading">域名访问排行 <span>24小时</span></h4>
<div class="ui divider"></div>
<div class="chart-box" id="top-domains-chart"></div>
</div>
<!-- 域名排行 -->
<h4 v-show="!isLoading">域名访问排行 <span>24小时</span></h4>
<div class="chart-box" id="top-domains-chart"></div>
<!-- 指标 -->
<div class="ui divider" v-if="metricCharts.length > 0"></div>
<metric-board>
<!-- 指标 -->
<metric-chart v-for="chart in metricCharts"
:key="chart.id"
:v-chart="chart.chart"
:v-stats="chart.stats"
:v-item="chart.item">
:v-item="chart.item"
:v-column="true">
</metric-chart>
</metric-board>
</chart-columns-grid>

View File

@@ -13,7 +13,7 @@
<td><link-icon :href="'/clusters/cluster?clusterId=' + cluster.id">{{cluster.name}}</link-icon></td>
</tr>
<tr>
<td>子域名</td>
<td>DNS子域名</td>
<td>
<span v-if="dnsInfo.domainName.length > 0"><var>{{dnsInfo.dnsName}}</var>.{{dnsInfo.domainName}}</span>
<span v-else class="disabled">没有设置</span>

View File

@@ -18,7 +18,7 @@
</tr>
<tbody v-show="moreOptionsVisible">
<tr>
<td>是否启用</td>
<td>启用当前域名</td>
<td>
<div class="ui checkbox">
<input type="checkbox" name="isOn" value="1" v-model="domain.isOn"/>

View File

@@ -18,7 +18,7 @@
<option value="">[请选择]</option>
<option v-for="type in types" :value="type.code">{{type.name}}</option>
</select>
<p class="comment">{{typeDescription}} <span v-if="!teaIsPlus">购买商业版可获得更多厂商支持。</span></p>
<p class="comment" v-if="typeDescription.length > 0">{{typeDescription}} 系统会保留原有域名下的域名解析,请放心使用。<span v-if="!teaIsPlus">购买商业版可获得更多厂商支持。</span></p>
</td>
</tr>
<tr>
@@ -27,24 +27,47 @@
<!-- DNSPod -->
<tbody v-if="type == 'dnspod'">
<tr>
<tr>
<td>密钥类型 *</td>
<td>
<select class="ui dropdown auto-width" name="paramDNSPodAPIType" v-model="paramDNSPodAPIType">
<option value="tencentDNS">腾讯云API密钥</option>
<option value="dnsPodToken">DNSPod Token</option>
</select>
</td>
</tr>
<tr v-show="paramDNSPodAPIType == 'tencentDNS'">
<td>SecretId *</td>
<td>
<input type="text" name="paramDNSPodAccessKeyId" maxlength="100"/>
<p class="comment">在DNSPod控制台“账号中心--API密钥”中获取。</p>
</td>
</tr>
<tr v-show="paramDNSPodAPIType == 'tencentDNS'">
<td>SecretKey *</td>
<td>
<input type="text" name="paramDNSPodAccessKeySecret" maxlength="100"/>
<p class="comment">在DNSPod控制台“账号中心--API密钥”中获取。</p>
</td>
</tr>
<tr v-show="paramDNSPodAPIType == 'dnsPodToken'">
<td>密钥ID *</td>
<td>
<input type="text" name="paramId" maxlength="100"/>
<p class="comment">获取方法参考:<a href="https://docs.dnspod.cn/account/5f2d466de8320f1a740d9ff3/" target="_blank">文档</a> </p>
<input type="text" name="paramDNSPodId" maxlength="100" spellcheck="false"/>
<p class="comment">在DNSPod控制台“账号中心--API密钥--DNSPod Token”中获取。</p>
</td>
</tr>
<tr>
<tr v-show="paramDNSPodAPIType == 'dnsPodToken'">
<td>密钥Token *</td>
<td>
<input type="text" name="paramToken" maxlength="100"/>
<p class="comment">获取方法参考:<a href="https://docs.dnspod.cn/account/5f2d466de8320f1a740d9ff3/" target="_blank">文档</a> </p>
<input type="text" name="paramDNSPodToken" maxlength="100" spellcheck="false"/>
<p class="comment">在DNSPod控制台“账号中心--API密钥--DNSPod Token”中获取。</p>
</td>
</tr>
<tr>
<tr v-if="paramDNSPodAPIType == 'dnsPodToken'">
<td>区域</td>
<td>
<select class="ui dropdown auto-width" name="paramRegion">
<select class="ui dropdown auto-width" name="paramDNSPodRegion">
<option value="">中国站</option>
<option value="international">国际站</option>
</select>
@@ -57,21 +80,21 @@
<tr>
<td>AccessKeyId *</td>
<td>
<input type="text" name="paramAliDNSAccessKeyId" maxlength="100"/>
<input type="text" name="paramAliDNSAccessKeyId" maxlength="100" spellcheck="false"/>
<p class="comment">登录阿里云控制台 -- 在"访问控制"中创建和获取。</p>
</td>
</tr>
<tr>
<td>AccessKeySecret *</td>
<td>
<input type="text" name="paramAliDNSAccessKeySecret" maxlength="100"/>
<input type="text" name="paramAliDNSAccessKeySecret" maxlength="100" spellcheck="false"/>
<p class="comment">登录阿里云控制台 -- 在"访问控制"中创建和获取。</p>
</td>
</tr>
<tr>
<td>区域ID<optional-label></optional-label></td>
<td>
<input type="text" name="paramAliDNSRegionId" maxlength="100"/>
<input type="text" name="paramAliDNSRegionId" maxlength="100" spellcheck="false"/>
<p class="comment">阿里云产品所在区域代号,通常不需要填写。</p>
</td>
</tr>
@@ -82,21 +105,21 @@
<tr>
<td>AccessKeyId *</td>
<td>
<input type="text" name="paramHuaweiAccessKeyId" maxlength="100"/>
<input type="text" name="paramHuaweiAccessKeyId" maxlength="100" spellcheck="false"/>
<p class="comment">登录华为云控制台 -- 在"我的凭证 -- 访问密钥"中创建和获取。</p>
</td>
</tr>
<tr>
<td>AccessKeySecret *</td>
<td>
<input type="text" name="paramHuaweiAccessKeySecret" maxlength="100"/>
<input type="text" name="paramHuaweiAccessKeySecret" maxlength="100" spellcheck="false"/>
<p class="comment">登录华为云控制台 -- 在"我的凭证 -- 访问密钥"中创建和获取。</p>
</td>
</tr>
<tr>
<td>终端节点</td>
<td>
<input type="text" name="paramHuaweiEndpoint" maxlength="100"/>
<input type="text" name="paramHuaweiEndpoint" maxlength="100" spellcheck="false"/>
<p class="comment">选填项。可以填写终端节点Endpoint区域代号或者域名参考 <a href="https://developer.huaweicloud.com/endpoint?DNS" target="_blank">https://developer.huaweicloud.com/endpoint?DNS</a>(如果此链接失效,请到华为云开发者中心自行查找)。</p>
</td>
</tr>
@@ -107,14 +130,14 @@
<tr>
<td>API密钥 *</td>
<td>
<input type="text" name="paramCloudFlareAPIKey" maxlength="100"/>
<input type="text" name="paramCloudFlareAPIKey" maxlength="100" spellcheck="false"/>
<p class="comment">在个人资料中的"API令牌"--"API密钥"--"Global API Key"中获取。</p>
</td>
</tr>
<tr>
<td>账号邮箱 *</td>
<td>
<input type="text" name="paramCloudFlareEmail" maxlength="100"/>
<input type="text" name="paramCloudFlareEmail" maxlength="100" spellcheck="false"/>
<p class="comment">登录账号使用的邮箱。</p>
</td>
</tr>
@@ -125,14 +148,14 @@
<tr>
<td>Key *</td>
<td>
<input type="text" name="paramGoDaddyKey" maxlength="100"/>
<p class="comment">可以在GoDaddy<a href="https://developer.godaddy.com/keys" target="_blank">开发者中心</a>添加</p>
<input type="text" name="paramGoDaddyKey" maxlength="100" spellcheck="false"/>
<p class="comment">可以在GoDaddy<a href="https://developer.godaddy.com/keys" target="_blank">开发者中心</a>创建创建时Environment选择Production</p>
</td>
</tr>
<tr>
<td>Secret *</td>
<td>
<input type="text" name="paramGoDaddySecret" maxlength="100"/>
<input type="text" name="paramGoDaddySecret" maxlength="100" spellcheck="false"/>
</td>
</tr>
</tbody>
@@ -142,21 +165,21 @@
<tr>
<td>用户认证ID<em>auth-id</em></td>
<td>
<input type="text" name="paramClouDNSAuthId" maxlength="20"/>
<input type="text" name="paramClouDNSAuthId" maxlength="20" spellcheck="false"/>
<p class="comment">和子用户认证ID二选一。可以在ClouDNS<a href="https://www.cloudns.net/api-settings/" target="_blank">API设置页面</a>添加。</p>
</td>
</tr>
<tr>
<td>子用户认证ID<em>sub-auth-id</em></td>
<td>
<input type="text" name="paramClouDNSSubAuthId" maxlength="20"/>
<input type="text" name="paramClouDNSSubAuthId" maxlength="20" spellcheck="false"/>
<p class="comment">和用户认证ID二选一。可以在ClouDNS<a href="https://www.cloudns.net/api-settings/" target="_blank">API设置页面</a>添加。</p>
</td>
</tr>
<tr>
<td>认证密码 *<em>auth-password</em></td>
<td>
<input type="password" name="paramClouDNSAuthPassword" maxlength="100"/>
<input type="password" name="paramClouDNSAuthPassword" maxlength="100" spellcheck="false"/>
<p class="comment">用户或者子用户的认证密码。</p>
</td>
</tr>
@@ -167,14 +190,14 @@
<tr>
<td>API Key *</td>
<td>
<input type="text" name="paramDNSComKey" maxlength="100"/>
<input type="text" name="paramDNSComKey" maxlength="100" spellcheck="false"/>
<p class="comment">在DNS.COM控制台账号中心--API设置中创建和查看。</p>
</td>
</tr>
<tr>
<td>API Secret *</td>
<td>
<input type="text" name="paramDNSComSecret" maxlength="100"/>
<input type="text" name="paramDNSComSecret" maxlength="100" spellcheck="false"/>
<p class="comment">在DNS.COM控制台账号中心--API设置中创建和查看。</p>
</td>
</tr>
@@ -186,14 +209,91 @@
<td>API ID *</td>
<td>
<input type="text" name="paramDNSLaAPIId" maxlength="100"/>
<p class="comment">在DNS.LA控制台我的账户菜单--API设置中创建和查看。</p>
<p class="comment">在DNS.LA控制台--账户信息中查看。</p>
</td>
</tr>
<tr>
<td>API密钥 *</td>
<td>
<input type="text" name="paramDNSLaSecret" maxlength="100"/>
<p class="comment">在DNS.LA控制台我的账户菜单--API设置中创建和查看。</p>
<input type="text" name="paramDNSLaSecret" maxlength="100" spellcheck="false"/>
<p class="comment">在DNS.LA控制台--账户信息中查看。</p>
</td>
</tr>
</tbody>
<!-- VolcEngine -->
<tbody v-if="type == 'volcEngine'">
<tr>
<td>Access Key ID *</td>
<td>
<input type="text" name="paramVolcEngineAccessKeyId" maxlength="100" spellcheck="false"/>
<p class="comment">在火山引擎“访问控制--API访问密钥”中获取。</p>
</td>
</tr>
<tr>
<td>Secret Access Key *</td>
<td>
<input type="text" name="paramVolcEngineAccessKeySecret" maxlength="100" spellcheck="false"/>
</td>
</tr>
</tbody>
<!-- Amazon Route 53 -->
<tbody v-if="type == 'amazonRoute53'">
<tr>
<td>Access Key ID *</td>
<td>
<input type="text" name="paramAmazonRoute53AccessKeyId" maxlength="100" spellcheck="false"/>
</td>
</tr>
<tr>
<td>Secret Access Key *</td>
<td>
<input type="text" name="paramAmazonRoute53AccessKeySecret" maxlength="100" spellcheck="false"/>
</td>
</tr>
<tr>
<td>API区域</td>
<td>
<input type="text" name="paramAmazonRoute53Region" maxlength="100" spellcheck="false"/>
<p class="comment">通常不需要填写。</p>
</td>
</tr>
</tbody>
<!-- Microsoft Azure DNS -->
<tbody v-if="type == 'azureDNS'">
<tr>
<td>订阅ID <br/><em>(Subscription ID)</em> *</td>
<td>
<input type="text" name="paramAzureDNSSubscriptionId" maxlength="100" spellcheck="false"/>
<p class="comment">可以在订阅Subscriptions服务中查看。</p>
</td>
</tr>
<tr>
<td>目录(租户) ID <br/><em>(Directory Tenant ID)</em> *</td>
<td>
<input type="text" name="paramAzureDNSTenantId" maxlength="100" spellcheck="false"/>
<p class="comment">可以在应用注册App registrations中对应应用概述Overview中查看。</p>
</td>
</tr>
<tr>
<td>应用程序(客户端) ID <br/><em>(Client ID)</em> *</td>
<td><input type="text" name="paramAzureDNSClientId" maxlength="100" spellcheck="false"/>
<p class="comment">需要在应用注册App registrations中新注册应用程序获得。</p>
</td>
</tr>
<tr>
<td>客户端密码值 <br/><em>(Client Secret Value)</em> *</td>
<td><input type="text" name="paramAzureDNSClientSecret" maxlength="100" spellcheck="false"/>
<p class="comment">可以在应用注册App registrations中对应应用的“证书和密码Certificates &amp; secrets”--“客户端密码Client secrets”中创建和查看。</p>
</td>
</tr>
<tr>
<td>资源组 <br/><em>(Resource Group Name)</em> *</td>
<td>
<input type="text" name="paramAzureDNSResourceGroupName" maxlength="100" spellcheck="false"/>
<p class="comment">权限设置帮助你需要在对应资源组Resource group-- 访问控制Access control (IAM)-- 角色分配Role assignments中添加一个角色分配Role assignment其中作业职能角色Job function roles为"DNS 区域参与者(DNS Zone Contributor)"成员Members为应用注册App registrations中的应用程序application有时需要在选择成员select members界面搜索应用程序application名称才能看到</p>
</td>
</tr>
</tbody>

View File

@@ -14,4 +14,7 @@ Tea.context(function () {
this.typeDescription = ""
}
}
// DNSPod
this.paramDNSPodAPIType = "tencentDNS"
})

View File

@@ -21,15 +21,30 @@
<!-- DNSPod -->
<tbody v-if="provider.type == 'dnspod'">
<tr>
<tr>
<td class="color-border">密钥类型</td>
<td>
<span v-if="provider.apiParams.apiType == 'tencentDNS'">腾讯云API密钥</span>
<span v-else>DNSPod Token</span>
</td>
</tr>
<tr v-if="provider.apiParams.apiType == 'tencentDNS'">
<td class="color-border">SecretId</td>
<td>{{provider.apiParams.accessKeyId}}</td>
</tr>
<tr v-if="provider.apiParams.apiType == 'tencentDNS'">
<td class="color-border">SecretKey</td>
<td>{{provider.apiParams.accessKeySecret}}</td>
</tr>
<tr v-if="provider.apiParams.apiType != 'tencentDNS'">
<td class="color-border">密钥ID</td>
<td>{{provider.apiParams.id}}</td>
</tr>
<tr>
<tr v-if="provider.apiParams.apiType != 'tencentDNS'">
<td class="color-border">密钥Token</td>
<td>{{provider.apiParams.token}}</td>
</tr>
<tr>
<tr v-if="provider.apiParams.apiType != 'tencentDNS'">
<td class="color-border">区域</td>
<td>
<span v-if="provider.apiParams.region == 'international'">国际站</span>
@@ -145,6 +160,70 @@
</tr>
</tbody>
<!-- VolcEngine -->
<tbody v-if="provider.type == 'volcEngine'">
<tr>
<td class="color-border">Access Key ID</td>
<td>{{provider.apiParams.accessKeyId}}</td>
</tr>
<tr>
<td class="color-border">Secret Access Key</td>
<td>{{provider.apiParams.accessKeySecret}}</td>
</tr>
</tbody>
<!-- Amazon Route 53 -->
<tbody v-if="provider.type == 'amazonRoute53'">
<tr>
<td class="color-border">Access Key ID</td>
<td>{{provider.apiParams.accessKeyId}}</td>
</tr>
<tr>
<td class="color-border">Secret Access Key</td>
<td>{{provider.apiParams.accessKeySecret}}</td>
</tr>
<tr>
<td class="color-border">API区域</td>
<td>
<span v-if="provider.apiParams.region != null && provider.apiParams.region.length > 0">{{provider.apiParams.region}}</span>
<span v-else class="disabled">暂未设置</span>
</td>
</tr>
</tbody>
<tbody v-if="provider.type == 'azureDNS'">
<tr>
<td class="color-border">订阅ID <br/><em>(Subscription ID)</em></td>
<td>
{{provider.apiParams.subscriptionId}}
</td>
</tr>
<tr>
<td class="color-border">目录(租户) ID <br/><em>(Directory Tenant ID)</em></td>
<td>
{{provider.apiParams.tenantId}}
</td>
</tr>
<tr>
<td class="color-border">应用程序(客户端) ID <br/><em>(Client ID)</em></td>
<td>
{{provider.apiParams.clientId}}
</td>
</tr>
<tr>
<td class="color-border">客户端密码值 <br/><em>(Client Secret Value)</em></td>
<td>
{{provider.apiParams.clientSecret}}
</td>
</tr>
<tr>
<td class="color-border">资源组 <br/><em>(Resource Group Name)</em></td>
<td>
{{provider.apiParams.resourceGroupName}}
</td>
</tr>
</tbody>
<!-- Local EdgeDNS -->
<tbody v-if="provider.type == 'localEdgeDNS'">
<tr>

View File

@@ -26,24 +26,47 @@
<!-- DNSPod -->
<tbody v-if="provider.type == 'dnspod'">
<tr>
<tr>
<td>密钥类型 *</td>
<td>
<select class="ui dropdown auto-width" name="paramDNSPodAPIType" v-model="provider.params.apiType">
<option value="tencentDNS">腾讯云API密钥</option>
<option value="dnsPodToken">DNSPod Token</option>
</select>
</td>
</tr>
<tr v-show="provider.params.apiType == 'tencentDNS'">
<td>SecretId *</td>
<td>
<input type="text" name="paramDNSPodAccessKeyId" maxlength="100" v-model="provider.params.accessKeyId"/>
<p class="comment">在DNSPod控制台“账号中心--API密钥”中获取。</p>
</td>
</tr>
<tr v-show="provider.params.apiType == 'tencentDNS'">
<td>SecretKey *</td>
<td>
<input type="text" name="paramDNSPodAccessKeySecret" maxlength="100" v-model="provider.params.accessKeySecret"/>
<p class="comment">在DNSPod控制台“账号中心--API密钥”中获取。</p>
</td>
</tr>
<tr v-show="provider.params.apiType == null || provider.params.apiType.length == 0 || provider.params.apiType == 'dnsPodToken'">
<td>密钥ID *</td>
<td>
<input type="text" name="paramId" maxlength="100" v-model="provider.params.id"/>
<p class="comment">获取方法参考:<a href="https://docs.dnspod.cn/account/5f2d466de8320f1a740d9ff3/" target="_blank">文档</a> </p>
<input type="text" name="paramDNSPodId" maxlength="100" v-model="provider.params.id"/>
<p class="comment">在DNSPod控制台“账号中心--API密钥--DNSPod Token”中获取。</p>
</td>
</tr>
<tr>
<tr v-show="provider.params.apiType == null || provider.params.apiType.length == 0 || provider.params.apiType == 'dnsPodToken'">
<td>密钥Token *</td>
<td>
<input type="text" name="paramToken" maxlength="100" v-model="provider.params.token"/>
<p class="comment">获取方法参考:<a href="https://docs.dnspod.cn/account/5f2d466de8320f1a740d9ff3/" target="_blank">文档</a> </p>
<input type="text" name="paramDNSPodToken" maxlength="100" v-model="provider.params.token" spellcheck="false"/>
<p class="comment">在DNSPod控制台“账号中心--API密钥--DNSPod Token”中获取。</p>
</td>
</tr>
<tr>
<tr v-if="provider.params.apiType == null || provider.params.apiType.length == 0 || provider.params.apiType == 'dnsPodToken'">
<td>区域</td>
<td>
<select class="ui dropdown auto-width" name="paramRegion" v-model="provider.params.region">
<select class="ui dropdown auto-width" name="paramDNSPodRegion" v-model="provider.params.region">
<option value="">中国站</option>
<option value="international">国际站</option>
</select>
@@ -56,14 +79,14 @@
<tr>
<td>AccessKeyId *</td>
<td>
<input type="text" name="paramAliDNSAccessKeyId" maxlength="100" v-model="provider.params.accessKeyId"/>
<input type="text" name="paramAliDNSAccessKeyId" maxlength="100" v-model="provider.params.accessKeyId" spellcheck="false"/>
<p class="comment">登录阿里云控制台 -- 在"访问控制"中创建和获取。</p>
</td>
</tr>
<tr>
<td>AccessKeySecret *</td>
<td>
<input type="text" name="paramAliDNSAccessKeySecret" maxlength="100" v-model="provider.params.accessKeySecret"/>
<input type="text" name="paramAliDNSAccessKeySecret" maxlength="100" v-model="provider.params.accessKeySecret" spellcheck="false"/>
<p class="comment">登录阿里云控制台 -- 在"访问控制"中创建和获取。</p>
</td>
</tr>
@@ -81,21 +104,21 @@
<tr>
<td>AccessKeyId *</td>
<td>
<input type="text" name="paramHuaweiAccessKeyId" maxlength="100" v-model="provider.params.accessKeyId"/>
<input type="text" name="paramHuaweiAccessKeyId" maxlength="100" v-model="provider.params.accessKeyId" spellcheck="false"/>
<p class="comment">登录华为云控制台 -- 在"我的凭证 -- 访问密钥"中创建和获取。</p>
</td>
</tr>
<tr>
<td>AccessKeySecret *</td>
<td>
<input type="text" name="paramHuaweiAccessKeySecret" maxlength="100" v-model="provider.params.accessKeySecret"/>
<input type="text" name="paramHuaweiAccessKeySecret" maxlength="100" v-model="provider.params.accessKeySecret" spellcheck="false"/>
<p class="comment">登录华为云控制台 -- 在"我的凭证 -- 访问密钥"中创建和获取。</p>
</td>
</tr>
<tr>
<td>终端节点</td>
<td>
<input type="text" name="paramHuaweiEndpoint" maxlength="100" v-model="provider.params.endpoint"/>
<input type="text" name="paramHuaweiEndpoint" maxlength="100" v-model="provider.params.endpoint" spellcheck="false"/>
<p class="comment">选填项。可以填写终端节点Endpoint区域代号或者域名参考 <a href="https://developer.huaweicloud.com/endpoint?DNS" target="_blank">https://developer.huaweicloud.com/endpoint?DNS</a>(如果此链接失效,请到华为云开发者中心自行查找)。</p>
</td>
</tr>
@@ -107,14 +130,14 @@
<tr>
<td>API密钥 *</td>
<td>
<input type="text" name="paramCloudFlareAPIKey" maxlength="100" v-model="provider.params.apiKey"/>
<input type="text" name="paramCloudFlareAPIKey" maxlength="100" v-model="provider.params.apiKey" spellcheck="false"/>
<p class="comment">在个人资料中的"API令牌"--"API密钥"--"Global API Key"中获取。</p>
</td>
</tr>
<tr>
<td>账号邮箱 *</td>
<td>
<input type="text" name="paramCloudFlareEmail" maxlength="100" v-model="provider.params.email"/>
<input type="text" name="paramCloudFlareEmail" maxlength="100" v-model="provider.params.email" spellcheck="false"/>
<p class="comment">登录账号使用的邮箱。</p>
</td>
</tr>
@@ -125,14 +148,14 @@
<tr>
<td>Key *</td>
<td>
<input type="text" name="paramGoDaddyKey" maxlength="100" v-model="provider.params.key"/>
<p class="comment">可以在GoDaddy<a href="https://developer.godaddy.com/keys" target="_blank">开发者中心</a>添加</p>
<input type="text" name="paramGoDaddyKey" maxlength="100" v-model="provider.params.key" spellcheck="false"/>
<p class="comment">可以在GoDaddy<a href="https://developer.godaddy.com/keys" target="_blank">开发者中心</a>创建创建时Environment选择Production</p>
</td>
</tr>
<tr>
<td>Secret *</td>
<td>
<input type="text" name="paramGoDaddySecret" maxlength="100" v-model="provider.params.secret"/>
<input type="text" name="paramGoDaddySecret" maxlength="100" v-model="provider.params.secret" spellcheck="false"/>
</td>
</tr>
</tbody>
@@ -142,7 +165,7 @@
<tr>
<td>用户认证ID<em>auth-id</em></td>
<td>
<input type="text" name="paramClouDNSAuthId" maxlength="20" v-model="provider.params.authId"/>
<input type="text" name="paramClouDNSAuthId" maxlength="20" v-model="provider.params.authId" spellcheck="false"/>
<p class="comment">和子用户认证ID二选一。可以在ClouDNS<a href="https://www.cloudns.net/api-settings/" target="_blank">API设置页面</a>添加。</p>
</td>
</tr>
@@ -156,7 +179,7 @@
<tr>
<td>认证密码 *<em>auth-password</em></td>
<td>
<input type="password" name="paramClouDNSAuthPassword" maxlength="100" v-model="provider.params.authPassword"/>
<input type="password" name="paramClouDNSAuthPassword" maxlength="100" v-model="provider.params.authPassword" spellcheck="false"/>
<p class="comment">用户或者子用户的认证密码。</p>
</td>
</tr>
@@ -167,14 +190,14 @@
<tr>
<td>API Key *</td>
<td>
<input type="text" name="paramDNSComKey" maxlength="100" v-model="provider.params.key"/>
<input type="text" name="paramDNSComKey" maxlength="100" v-model="provider.params.key" spellcheck="false"/>
<p class="comment">在DNS.COM控制台账号中心--API设置中创建和查看。</p>
</td>
</tr>
<tr>
<td>API Secret *</td>
<td>
<input type="text" name="paramDNSComSecret" maxlength="100" v-model="provider.params.secret"/>
<input type="text" name="paramDNSComSecret" maxlength="100" v-model="provider.params.secret" spellcheck="false"/>
<p class="comment">在DNS.COM控制台账号中心--API设置中创建和查看。</p>
</td>
</tr>
@@ -185,15 +208,93 @@
<tr>
<td>API ID *</td>
<td>
<input type="text" name="paramDNSLaAPIId" maxlength="100" v-model="provider.params.apiId"/>
<p class="comment">在DNS.LA控制台我的账户菜单--API设置中创建和查看。</p>
<input type="text" name="paramDNSLaAPIId" maxlength="100" v-model="provider.params.apiId" spellcheck="false"/>
<p class="comment">在DNS.LA控制台--账户信息中查看。</p>
</td>
</tr>
<tr>
<td>API密钥 *</td>
<td>
<input type="text" name="paramDNSLaSecret" maxlength="100" v-model="provider.params.secret"/>
<p class="comment">在DNS.LA控制台我的账户菜单--API设置中创建和查看。</p>
<input type="text" name="paramDNSLaSecret" maxlength="100" v-model="provider.params.secret" spellcheck="false"/>
<p class="comment">在DNS.LA控制台--账户信息中查看。</p>
</td>
</tr>
</tbody>
<!-- VolcEngine -->
<tbody v-if="provider.type == 'volcEngine'">
<tr>
<td>Access Key ID *</td>
<td>
<input type="text" name="paramVolcEngineAccessKeyId" maxlength="100" v-model="provider.params.accessKeyId" spellcheck="false"/>
<p class="comment">在火山引擎“访问控制--API访问密钥”中获取。</p>
</td>
</tr>
<tr>
<td>Secret Access Key *</td>
<td>
<input type="text" name="paramVolcEngineAccessKeySecret" maxlength="100" v-model="provider.params.accessKeySecret" spellcheck="false"/>
</td>
</tr>
</tbody>
<!-- Amazon Route 53 -->
<tbody v-if="provider.type == 'amazonRoute53'">
<tr>
<td>Access Key ID *</td>
<td>
<input type="text" name="paramAmazonRoute53AccessKeyId" maxlength="100" v-model="provider.params.accessKeyId" spellcheck="false"/>
</td>
</tr>
<tr>
<td>Secret Access Key *</td>
<td>
<input type="text" name="paramAmazonRoute53AccessKeySecret" maxlength="100" v-model="provider.params.accessKeySecret" spellcheck="false"/>
</td>
</tr>
<tr>
<td>API区域</td>
<td>
<input type="text" name="paramAmazonRoute53Region" maxlength="100" v-model="provider.params.region" spellcheck="false"/>
<p class="comment">通常不需要填写。</p>
</td>
</tr>
</tbody>
<!-- Microsoft Azure DNS -->
<tbody v-if="provider.type == 'azureDNS'">
<tr>
<td>订阅ID <br/><em>(Subscription ID)</em> *</td>
<td>
<input type="text" name="paramAzureDNSSubscriptionId" maxlength="100" v-model="provider.params.subscriptionId" spellcheck="false"/>
<p class="comment">可以在订阅Subscriptions服务中查看。</p>
</td>
</tr>
<tr>
<td>目录(租户) ID <br/><em>(Directory Tenant ID)</em> *</td>
<td>
<input type="text" name="paramAzureDNSTenantId" maxlength="100" v-model="provider.params.tenantId" spellcheck="false"/>
<p class="comment">可以在应用注册App registrations中对应应用概述Overview中查看。</p>
</td>
</tr>
<tr>
<td>应用程序(客户端) ID <br/><em>(Client ID)</em> *</td>
<td><input type="text" name="paramAzureDNSClientId" maxlength="100" v-model="provider.params.clientId" spellcheck="false"/>
<p class="comment">需要在应用注册App registrations中新注册应用程序获得。</p>
</td>
</tr>
<tr>
<td>客户端密码值 <br/><em>(Client Secret Value)</em> *</td>
<td>
<input type="text" name="paramAzureDNSClientSecret" maxlength="100" v-model="provider.params.clientSecret" spellcheck="false"/>
<p class="comment">可以在应用注册App registrations中对应应用的“证书和密码Certificates &amp; secrets”--“客户端密码Client secrets”中创建和查看。</p>
</td>
</tr>
<tr>
<td>资源组 <br/><em>(Resource Group Name)</em> *</td>
<td>
<input type="text" name="paramAzureDNSResourceGroupName" maxlength="100" v-model="provider.params.resourceGroupName" spellcheck="false"/>
<p class="comment">权限设置帮助你需要在对应资源组Resource group-- 访问控制Access control (IAM)-- 角色分配Role assignments中添加一个角色分配Role assignment其中作业职能角色Job function roles为"DNS 区域参与者(DNS Zone Contributor)"成员Members为应用注册App registrations中的应用程序application有时需要在选择成员select members界面搜索应用程序application名称才能看到</p>
</td>
</tr>
</tbody>

View File

@@ -7,4 +7,9 @@ Tea.context(function () {
that.typeDescription = v.description
}
})
// DNSPod
if (this.provider.type == "dnspod" && this.provider.params != null && (this.provider.params.apiType == null || this.provider.params.apiType.length == 0)) {
this.provider.params.apiType = "dnsPodToken"
}
})

View File

@@ -66,7 +66,7 @@
<td>包含Ln节点</td>
<td>
<checkbox name="includingLnNodes" v-model="includingLnNodes"></checkbox>
<p class="comment">选中后表示域名解析中包含L2及以上级别节点。</p>
<p class="comment">选中后表示域名解析中包含L2及以上级别节点,也就意味着用户请求可能会被分配到这些节点</p>
</td>
</tr>
<tr>

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