管理端全部功能跑通
This commit is contained in:
@@ -94,6 +94,32 @@ function build() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# build edge-httpdns
|
||||||
|
HTTPDNS_ROOT=$ROOT"/../../EdgeHttpDNS"
|
||||||
|
if [ -d "$HTTPDNS_ROOT" ]; then
|
||||||
|
HTTPDNSNodeVersion=$(lookup-version "$ROOT""/../../EdgeHttpDNS/internal/const/const.go")
|
||||||
|
echo "building edge-httpdns v${HTTPDNSNodeVersion} ..."
|
||||||
|
EDGE_HTTPDNS_NODE_BUILD_SCRIPT=$ROOT"/../../EdgeHttpDNS/build/build.sh"
|
||||||
|
if [ ! -f "$EDGE_HTTPDNS_NODE_BUILD_SCRIPT" ]; then
|
||||||
|
echo "unable to find edge-httpdns build script 'EdgeHttpDNS/build/build.sh'"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
cd "$ROOT""/../../EdgeHttpDNS/build" || exit
|
||||||
|
echo "=============================="
|
||||||
|
architects=("amd64")
|
||||||
|
#architects=("amd64" "arm64")
|
||||||
|
for arch in "${architects[@]}"; do
|
||||||
|
# always rebuild to avoid reusing stale zip when version number is unchanged
|
||||||
|
./build.sh linux "$arch"
|
||||||
|
done
|
||||||
|
echo "=============================="
|
||||||
|
cd - || exit
|
||||||
|
|
||||||
|
for arch in "${architects[@]}"; do
|
||||||
|
cp "$ROOT""/../../EdgeHttpDNS/dist/edge-httpdns-linux-${arch}-v${HTTPDNSNodeVersion}.zip" "$ROOT"/deploy/edge-httpdns-linux-"${arch}"-v"${HTTPDNSNodeVersion}".zip
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# build sql
|
# build sql
|
||||||
if [ $TAG = "plus" ]; then
|
if [ $TAG = "plus" ]; then
|
||||||
echo "building sql ..."
|
echo "building sql ..."
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package clickhouse
|
package clickhouse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -12,13 +13,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client 通过 HTTP 接口执行只读查询(SELECT),返回 JSONEachRow 解析为 map 或结构体
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
cfg *Config
|
cfg *Config
|
||||||
httpCli *http.Client
|
httpCli *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient 使用共享配置创建客户端
|
|
||||||
func NewClient() *Client {
|
func NewClient() *Client {
|
||||||
cfg := SharedConfig()
|
cfg := SharedConfig()
|
||||||
transport := &http.Transport{}
|
transport := &http.Transport{}
|
||||||
@@ -28,6 +27,7 @@ func NewClient() *Client {
|
|||||||
ServerName: cfg.TLSServerName,
|
ServerName: cfg.TLSServerName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
httpCli: &http.Client{
|
httpCli: &http.Client{
|
||||||
@@ -37,21 +37,20 @@ func NewClient() *Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsConfigured 是否已配置
|
|
||||||
func (c *Client) IsConfigured() bool {
|
func (c *Client) IsConfigured() bool {
|
||||||
return c.cfg != nil && c.cfg.IsConfigured()
|
return c.cfg != nil && c.cfg.IsConfigured()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query 执行 SELECT,将每行 JSON 解析到 dest 切片;dest 元素类型需为 *struct 或 map
|
|
||||||
func (c *Client) Query(ctx context.Context, query string, dest interface{}) error {
|
func (c *Client) Query(ctx context.Context, query string, dest interface{}) error {
|
||||||
if !c.IsConfigured() {
|
if !c.IsConfigured() {
|
||||||
return fmt.Errorf("clickhouse: not configured")
|
return fmt.Errorf("clickhouse: not configured")
|
||||||
}
|
}
|
||||||
// 强制 JSONEachRow 便于解析
|
|
||||||
q := strings.TrimSpace(query)
|
q := strings.TrimSpace(query)
|
||||||
if !strings.HasSuffix(strings.ToUpper(q), "FORMAT JSONEACHROW") {
|
if !strings.HasSuffix(strings.ToUpper(q), "FORMAT JSONEACHROW") {
|
||||||
query = q + " FORMAT JSONEachRow"
|
query = q + " FORMAT JSONEachRow"
|
||||||
}
|
}
|
||||||
|
|
||||||
u := c.buildURL(query)
|
u := c.buildURL(query)
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -60,28 +59,32 @@ func (c *Client) Query(ctx context.Context, query string, dest interface{}) erro
|
|||||||
if c.cfg.User != "" || c.cfg.Password != "" {
|
if c.cfg.User != "" || c.cfg.Password != "" {
|
||||||
req.SetBasicAuth(c.cfg.User, c.cfg.Password)
|
req.SetBasicAuth(c.cfg.User, c.cfg.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.httpCli.Do(req)
|
resp, err := c.httpCli.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
body, _ := io.ReadAll(resp.Body)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
return fmt.Errorf("clickhouse HTTP %d: %s", resp.StatusCode, string(body))
|
return fmt.Errorf("clickhouse HTTP %d: %s", resp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
dec := json.NewDecoder(resp.Body)
|
dec := json.NewDecoder(resp.Body)
|
||||||
return decodeRows(dec, dest)
|
return decodeRows(dec, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryRow 执行仅返回一行的查询,将结果解析到 dest(*struct 或 *map)
|
|
||||||
func (c *Client) QueryRow(ctx context.Context, query string, dest interface{}) error {
|
func (c *Client) QueryRow(ctx context.Context, query string, dest interface{}) error {
|
||||||
if !c.IsConfigured() {
|
if !c.IsConfigured() {
|
||||||
return fmt.Errorf("clickhouse: not configured")
|
return fmt.Errorf("clickhouse: not configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
q := strings.TrimSpace(query)
|
q := strings.TrimSpace(query)
|
||||||
if !strings.HasSuffix(strings.ToUpper(q), "FORMAT JSONEACHROW") {
|
if !strings.HasSuffix(strings.ToUpper(q), "FORMAT JSONEACHROW") {
|
||||||
query = q + " FORMAT JSONEachRow"
|
query = q + " FORMAT JSONEachRow"
|
||||||
}
|
}
|
||||||
|
|
||||||
u := c.buildURL(query)
|
u := c.buildURL(query)
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -90,32 +93,109 @@ func (c *Client) QueryRow(ctx context.Context, query string, dest interface{}) e
|
|||||||
if c.cfg.User != "" || c.cfg.Password != "" {
|
if c.cfg.User != "" || c.cfg.Password != "" {
|
||||||
req.SetBasicAuth(c.cfg.User, c.cfg.Password)
|
req.SetBasicAuth(c.cfg.User, c.cfg.Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.httpCli.Do(req)
|
resp, err := c.httpCli.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
body, _ := io.ReadAll(resp.Body)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
return fmt.Errorf("clickhouse HTTP %d: %s", resp.StatusCode, string(body))
|
return fmt.Errorf("clickhouse HTTP %d: %s", resp.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
dec := json.NewDecoder(resp.Body)
|
dec := json.NewDecoder(resp.Body)
|
||||||
return decodeOneRow(dec, dest)
|
return decodeOneRow(dec, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) Execute(ctx context.Context, query string) error {
|
||||||
|
if !c.IsConfigured() {
|
||||||
|
return fmt.Errorf("clickhouse: not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
u := c.buildURL(strings.TrimSpace(query))
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c.cfg.User != "" || c.cfg.Password != "" {
|
||||||
|
req.SetBasicAuth(c.cfg.User, c.cfg.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.httpCli.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("clickhouse HTTP %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) InsertJSONEachRow(ctx context.Context, insertSQL string, rows []map[string]interface{}) error {
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !c.IsConfigured() {
|
||||||
|
return fmt.Errorf("clickhouse: not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := strings.TrimSpace(insertSQL)
|
||||||
|
if !strings.HasSuffix(strings.ToUpper(query), "FORMAT JSONEACHROW") {
|
||||||
|
query += " FORMAT JSONEachRow"
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload bytes.Buffer
|
||||||
|
for _, row := range rows {
|
||||||
|
if row == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(row)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
payload.Write(data)
|
||||||
|
payload.WriteByte('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
u := c.buildURL(query)
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, &payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
if c.cfg.User != "" || c.cfg.Password != "" {
|
||||||
|
req.SetBasicAuth(c.cfg.User, c.cfg.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.httpCli.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("clickhouse HTTP %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) buildURL(query string) string {
|
func (c *Client) buildURL(query string) string {
|
||||||
scheme := "http"
|
scheme := "http"
|
||||||
if c.cfg != nil && strings.EqualFold(c.cfg.Scheme, "https") {
|
if c.cfg != nil && strings.EqualFold(c.cfg.Scheme, "https") {
|
||||||
scheme = "https"
|
scheme = "https"
|
||||||
}
|
}
|
||||||
rawURL := fmt.Sprintf("%s://%s:%d/?query=%s&database=%s",
|
return fmt.Sprintf("%s://%s:%d/?query=%s&database=%s",
|
||||||
scheme, c.cfg.Host, c.cfg.Port, url.QueryEscape(query), url.QueryEscape(c.cfg.Database))
|
scheme, c.cfg.Host, c.cfg.Port, url.QueryEscape(query), url.QueryEscape(c.cfg.Database))
|
||||||
return rawURL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodeRows 将 JSONEachRow 流解析到 slice;元素类型须为 *struct 或 *[]map[string]interface{}
|
|
||||||
func decodeRows(dec *json.Decoder, dest interface{}) error {
|
func decodeRows(dec *json.Decoder, dest interface{}) error {
|
||||||
// dest 应为 *[]*SomeStruct 或 *[]map[string]interface{}
|
|
||||||
switch d := dest.(type) {
|
switch d := dest.(type) {
|
||||||
case *[]map[string]interface{}:
|
case *[]map[string]interface{}:
|
||||||
*d = (*d)[:0]
|
*d = (*d)[:0]
|
||||||
@@ -130,7 +210,7 @@ func decodeRows(dec *json.Decoder, dest interface{}) error {
|
|||||||
*d = append(*d, row)
|
*d = append(*d, row)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("clickhouse: unsupported dest type for Query (use *[]map[string]interface{} or implement decoder)")
|
return fmt.Errorf("clickhouse: unsupported dest type for Query (use *[]map[string]interface{})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
279
EdgeAPI/internal/clickhouse/httpdns_access_logs_store.go
Normal file
279
EdgeAPI/internal/clickhouse/httpdns_access_logs_store.go
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
package clickhouse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const httpDNSAccessLogsTable = "httpdns_access_logs_ingest"
|
||||||
|
|
||||||
|
type HTTPDNSAccessLogRow struct {
|
||||||
|
RequestId string
|
||||||
|
ClusterId int64
|
||||||
|
NodeId int64
|
||||||
|
AppId string
|
||||||
|
AppName string
|
||||||
|
Domain string
|
||||||
|
QType string
|
||||||
|
ClientIP string
|
||||||
|
ClientRegion string
|
||||||
|
Carrier string
|
||||||
|
SDKVersion string
|
||||||
|
OS string
|
||||||
|
ResultIPs string
|
||||||
|
Status string
|
||||||
|
ErrorCode string
|
||||||
|
CostMs int32
|
||||||
|
CreatedAt int64
|
||||||
|
Day string
|
||||||
|
Summary string
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPDNSAccessLogListFilter struct {
|
||||||
|
Day string
|
||||||
|
ClusterId int64
|
||||||
|
NodeId int64
|
||||||
|
AppId string
|
||||||
|
Domain string
|
||||||
|
Status string
|
||||||
|
Keyword string
|
||||||
|
Offset int64
|
||||||
|
Size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPDNSAccessLogsStore struct {
|
||||||
|
client *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPDNSAccessLogsStore() *HTTPDNSAccessLogsStore {
|
||||||
|
return &HTTPDNSAccessLogsStore{client: NewClient()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogsStore) Client() *Client {
|
||||||
|
return s.client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogsStore) Insert(ctx context.Context, logs []*pb.HTTPDNSAccessLog) error {
|
||||||
|
if len(logs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !s.client.IsConfigured() {
|
||||||
|
return fmt.Errorf("clickhouse: not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := make([]map[string]interface{}, 0, len(logs))
|
||||||
|
for _, item := range logs {
|
||||||
|
if item == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rows = append(rows, map[string]interface{}{
|
||||||
|
"request_id": item.GetRequestId(),
|
||||||
|
"cluster_id": item.GetClusterId(),
|
||||||
|
"node_id": item.GetNodeId(),
|
||||||
|
"app_id": item.GetAppId(),
|
||||||
|
"app_name": item.GetAppName(),
|
||||||
|
"domain": item.GetDomain(),
|
||||||
|
"qtype": item.GetQtype(),
|
||||||
|
"client_ip": item.GetClientIP(),
|
||||||
|
"client_region": item.GetClientRegion(),
|
||||||
|
"carrier": item.GetCarrier(),
|
||||||
|
"sdk_version": item.GetSdkVersion(),
|
||||||
|
"os": item.GetOs(),
|
||||||
|
"result_ips": item.GetResultIPs(),
|
||||||
|
"status": item.GetStatus(),
|
||||||
|
"error_code": item.GetErrorCode(),
|
||||||
|
"cost_ms": item.GetCostMs(),
|
||||||
|
"created_at": item.GetCreatedAt(),
|
||||||
|
"day": item.GetDay(),
|
||||||
|
"summary": item.GetSummary(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf("INSERT INTO %s (request_id, cluster_id, node_id, app_id, app_name, domain, qtype, client_ip, client_region, carrier, sdk_version, os, result_ips, status, error_code, cost_ms, created_at, day, summary)",
|
||||||
|
s.tableName())
|
||||||
|
return s.client.InsertJSONEachRow(ctx, query, rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogsStore) Count(ctx context.Context, f HTTPDNSAccessLogListFilter) (int64, error) {
|
||||||
|
if !s.client.IsConfigured() {
|
||||||
|
return 0, fmt.Errorf("clickhouse: not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
conditions := s.buildConditions(f)
|
||||||
|
query := fmt.Sprintf("SELECT count() AS count FROM %s", s.tableName())
|
||||||
|
if len(conditions) > 0 {
|
||||||
|
query += " WHERE " + strings.Join(conditions, " AND ")
|
||||||
|
}
|
||||||
|
|
||||||
|
row := map[string]interface{}{}
|
||||||
|
if err := s.client.QueryRow(ctx, query, &row); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return toInt64(row["count"]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogsStore) List(ctx context.Context, f HTTPDNSAccessLogListFilter) ([]*HTTPDNSAccessLogRow, error) {
|
||||||
|
if !s.client.IsConfigured() {
|
||||||
|
return nil, fmt.Errorf("clickhouse: not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
size := f.Size
|
||||||
|
if size <= 0 {
|
||||||
|
size = 20
|
||||||
|
}
|
||||||
|
if size > 1000 {
|
||||||
|
size = 1000
|
||||||
|
}
|
||||||
|
offset := f.Offset
|
||||||
|
if offset < 0 {
|
||||||
|
offset = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
conditions := s.buildConditions(f)
|
||||||
|
query := fmt.Sprintf("SELECT request_id, cluster_id, node_id, app_id, app_name, domain, qtype, client_ip, client_region, carrier, sdk_version, os, result_ips, status, error_code, cost_ms, created_at, day, summary FROM %s",
|
||||||
|
s.tableName())
|
||||||
|
if len(conditions) > 0 {
|
||||||
|
query += " WHERE " + strings.Join(conditions, " AND ")
|
||||||
|
}
|
||||||
|
query += " ORDER BY created_at DESC, request_id DESC"
|
||||||
|
query += fmt.Sprintf(" LIMIT %d OFFSET %d", size, offset)
|
||||||
|
|
||||||
|
rawRows := []map[string]interface{}{}
|
||||||
|
if err := s.client.Query(ctx, query, &rawRows); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]*HTTPDNSAccessLogRow, 0, len(rawRows))
|
||||||
|
for _, row := range rawRows {
|
||||||
|
result = append(result, &HTTPDNSAccessLogRow{
|
||||||
|
RequestId: toString(row["request_id"]),
|
||||||
|
ClusterId: toInt64(row["cluster_id"]),
|
||||||
|
NodeId: toInt64(row["node_id"]),
|
||||||
|
AppId: toString(row["app_id"]),
|
||||||
|
AppName: toString(row["app_name"]),
|
||||||
|
Domain: toString(row["domain"]),
|
||||||
|
QType: toString(row["qtype"]),
|
||||||
|
ClientIP: toString(row["client_ip"]),
|
||||||
|
ClientRegion: toString(row["client_region"]),
|
||||||
|
Carrier: toString(row["carrier"]),
|
||||||
|
SDKVersion: toString(row["sdk_version"]),
|
||||||
|
OS: toString(row["os"]),
|
||||||
|
ResultIPs: toString(row["result_ips"]),
|
||||||
|
Status: toString(row["status"]),
|
||||||
|
ErrorCode: toString(row["error_code"]),
|
||||||
|
CostMs: int32(toInt64(row["cost_ms"])),
|
||||||
|
CreatedAt: toInt64(row["created_at"]),
|
||||||
|
Day: toString(row["day"]),
|
||||||
|
Summary: toString(row["summary"]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HTTPDNSRowToPB(row *HTTPDNSAccessLogRow) *pb.HTTPDNSAccessLog {
|
||||||
|
if row == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &pb.HTTPDNSAccessLog{
|
||||||
|
RequestId: row.RequestId,
|
||||||
|
ClusterId: row.ClusterId,
|
||||||
|
NodeId: row.NodeId,
|
||||||
|
AppId: row.AppId,
|
||||||
|
AppName: row.AppName,
|
||||||
|
Domain: row.Domain,
|
||||||
|
Qtype: row.QType,
|
||||||
|
ClientIP: row.ClientIP,
|
||||||
|
ClientRegion: row.ClientRegion,
|
||||||
|
Carrier: row.Carrier,
|
||||||
|
SdkVersion: row.SDKVersion,
|
||||||
|
Os: row.OS,
|
||||||
|
ResultIPs: row.ResultIPs,
|
||||||
|
Status: row.Status,
|
||||||
|
ErrorCode: row.ErrorCode,
|
||||||
|
CostMs: row.CostMs,
|
||||||
|
CreatedAt: row.CreatedAt,
|
||||||
|
Day: row.Day,
|
||||||
|
Summary: row.Summary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogsStore) buildConditions(f HTTPDNSAccessLogListFilter) []string {
|
||||||
|
conditions := []string{}
|
||||||
|
if day := strings.TrimSpace(f.Day); day != "" {
|
||||||
|
conditions = append(conditions, "day = '"+escapeString(day)+"'")
|
||||||
|
}
|
||||||
|
if f.ClusterId > 0 {
|
||||||
|
conditions = append(conditions, "cluster_id = "+strconv.FormatInt(f.ClusterId, 10))
|
||||||
|
}
|
||||||
|
if f.NodeId > 0 {
|
||||||
|
conditions = append(conditions, "node_id = "+strconv.FormatInt(f.NodeId, 10))
|
||||||
|
}
|
||||||
|
if appID := strings.TrimSpace(f.AppId); appID != "" {
|
||||||
|
conditions = append(conditions, "app_id = '"+escapeString(appID)+"'")
|
||||||
|
}
|
||||||
|
if domain := strings.TrimSpace(f.Domain); domain != "" {
|
||||||
|
conditions = append(conditions, "domain = '"+escapeString(domain)+"'")
|
||||||
|
}
|
||||||
|
if status := strings.TrimSpace(f.Status); status != "" {
|
||||||
|
conditions = append(conditions, "status = '"+escapeString(status)+"'")
|
||||||
|
}
|
||||||
|
if keyword := strings.TrimSpace(f.Keyword); keyword != "" {
|
||||||
|
kw := escapeString(keyword)
|
||||||
|
conditions = append(conditions, "(summary LIKE '%"+kw+"%' OR app_name LIKE '%"+kw+"%' OR client_ip LIKE '%"+kw+"%' OR result_ips LIKE '%"+kw+"%')")
|
||||||
|
}
|
||||||
|
return conditions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogsStore) tableName() string {
|
||||||
|
if s.client != nil && s.client.cfg != nil && s.client.cfg.Database != "" && s.client.cfg.Database != "default" {
|
||||||
|
return quoteIdent(s.client.cfg.Database) + "." + quoteIdent(httpDNSAccessLogsTable)
|
||||||
|
}
|
||||||
|
return quoteIdent(httpDNSAccessLogsTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func toString(value interface{}) string {
|
||||||
|
if value == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
switch v := value.(type) {
|
||||||
|
case string:
|
||||||
|
return v
|
||||||
|
case json.Number:
|
||||||
|
return v.String()
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toInt64(value interface{}) int64 {
|
||||||
|
if value == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
switch v := value.(type) {
|
||||||
|
case int:
|
||||||
|
return int64(v)
|
||||||
|
case int32:
|
||||||
|
return int64(v)
|
||||||
|
case int64:
|
||||||
|
return v
|
||||||
|
case uint32:
|
||||||
|
return int64(v)
|
||||||
|
case uint64:
|
||||||
|
return int64(v)
|
||||||
|
case float64:
|
||||||
|
return int64(v)
|
||||||
|
case json.Number:
|
||||||
|
n, _ := v.Int64()
|
||||||
|
return n
|
||||||
|
case string:
|
||||||
|
n, _ := strconv.ParseInt(v, 10, 64)
|
||||||
|
return n
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package teaconst
|
package teaconst
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "1.4.7" //1.3.9
|
Version = "1.4.8" //1.3.9
|
||||||
|
|
||||||
ProductName = "Edge API"
|
ProductName = "Edge API"
|
||||||
ProcessName = "edge-api"
|
ProcessName = "edge-api"
|
||||||
@@ -17,6 +17,6 @@ const (
|
|||||||
|
|
||||||
// 其他节点版本号,用来检测是否有需要升级的节点
|
// 其他节点版本号,用来检测是否有需要升级的节点
|
||||||
|
|
||||||
NodeVersion = "1.4.7" //1.3.8.2
|
NodeVersion = "1.4.8" //1.3.8.2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
package teaconst
|
package teaconst
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DNSNodeVersion = "1.4.7" //1.3.8.2
|
DNSNodeVersion = "1.4.8" //1.3.8.2
|
||||||
UserNodeVersion = "1.4.7" //1.3.8.2
|
UserNodeVersion = "1.4.8" //1.3.8.2
|
||||||
ReportNodeVersion = "0.1.5"
|
ReportNodeVersion = "0.1.5"
|
||||||
|
|
||||||
DefaultMaxNodes int32 = 50
|
DefaultMaxNodes int32 = 50
|
||||||
|
|||||||
101
EdgeAPI/internal/db/models/httpdns_access_log_dao.go
Normal file
101
EdgeAPI/internal/db/models/httpdns_access_log_dao.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPDNSAccessLogDAO dbs.DAO
|
||||||
|
|
||||||
|
func NewHTTPDNSAccessLogDAO() *HTTPDNSAccessLogDAO {
|
||||||
|
return dbs.NewDAO(&HTTPDNSAccessLogDAO{
|
||||||
|
DAOObject: dbs.DAOObject{
|
||||||
|
DB: Tea.Env,
|
||||||
|
Table: "edgeHTTPDNSAccessLogs",
|
||||||
|
Model: new(HTTPDNSAccessLog),
|
||||||
|
PkName: "id",
|
||||||
|
},
|
||||||
|
}).(*HTTPDNSAccessLogDAO)
|
||||||
|
}
|
||||||
|
|
||||||
|
var SharedHTTPDNSAccessLogDAO *HTTPDNSAccessLogDAO
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dbs.OnReady(func() {
|
||||||
|
SharedHTTPDNSAccessLogDAO = NewHTTPDNSAccessLogDAO()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAccessLogDAO) CreateLog(tx *dbs.Tx, log *HTTPDNSAccessLog) error {
|
||||||
|
var op = NewHTTPDNSAccessLogOperator()
|
||||||
|
op.RequestId = log.RequestId
|
||||||
|
op.ClusterId = log.ClusterId
|
||||||
|
op.NodeId = log.NodeId
|
||||||
|
op.AppId = log.AppId
|
||||||
|
op.AppName = log.AppName
|
||||||
|
op.Domain = log.Domain
|
||||||
|
op.QType = log.QType
|
||||||
|
op.ClientIP = log.ClientIP
|
||||||
|
op.ClientRegion = log.ClientRegion
|
||||||
|
op.Carrier = log.Carrier
|
||||||
|
op.SDKVersion = log.SDKVersion
|
||||||
|
op.OS = log.OS
|
||||||
|
op.ResultIPs = log.ResultIPs
|
||||||
|
op.Status = log.Status
|
||||||
|
op.ErrorCode = log.ErrorCode
|
||||||
|
op.CostMs = log.CostMs
|
||||||
|
op.CreatedAt = log.CreatedAt
|
||||||
|
op.Day = log.Day
|
||||||
|
op.Summary = log.Summary
|
||||||
|
return this.Save(tx, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAccessLogDAO) BuildListQuery(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, domain string, status string, keyword string) *dbs.Query {
|
||||||
|
query := this.Query(tx).DescPk()
|
||||||
|
if len(day) > 0 {
|
||||||
|
query = query.Attr("day", day)
|
||||||
|
}
|
||||||
|
if clusterId > 0 {
|
||||||
|
query = query.Attr("clusterId", clusterId)
|
||||||
|
}
|
||||||
|
if nodeId > 0 {
|
||||||
|
query = query.Attr("nodeId", nodeId)
|
||||||
|
}
|
||||||
|
if len(appId) > 0 {
|
||||||
|
query = query.Attr("appId", appId)
|
||||||
|
}
|
||||||
|
if len(domain) > 0 {
|
||||||
|
query = query.Attr("domain", domain)
|
||||||
|
}
|
||||||
|
if len(status) > 0 {
|
||||||
|
query = query.Attr("status", status)
|
||||||
|
}
|
||||||
|
if len(keyword) > 0 {
|
||||||
|
query = query.Where("(summary LIKE :kw OR appName LIKE :kw OR clientIP LIKE :kw OR resultIPs LIKE :kw)").Param("kw", "%"+keyword+"%")
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAccessLogDAO) CountLogs(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, domain string, status string, keyword string) (int64, error) {
|
||||||
|
return this.BuildListQuery(tx, day, clusterId, nodeId, appId, domain, status, keyword).Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAccessLogDAO) ListLogs(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, domain string, status string, keyword string, offset int64, size int64) (result []*HTTPDNSAccessLog, err error) {
|
||||||
|
_, err = this.BuildListQuery(tx, day, clusterId, nodeId, appId, domain, status, keyword).
|
||||||
|
Offset(offset).
|
||||||
|
Limit(size).
|
||||||
|
Slice(&result).
|
||||||
|
FindAll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAccessLogDAO) DeleteLogsWithAppId(tx *dbs.Tx, appId string) error {
|
||||||
|
if len(appId) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err := this.Query(tx).
|
||||||
|
Attr("appId", appId).
|
||||||
|
Delete()
|
||||||
|
return err
|
||||||
|
}
|
||||||
52
EdgeAPI/internal/db/models/httpdns_access_log_model.go
Normal file
52
EdgeAPI/internal/db/models/httpdns_access_log_model.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// HTTPDNSAccessLog 访问日志
|
||||||
|
type HTTPDNSAccessLog struct {
|
||||||
|
Id uint64 `field:"id"` // ID
|
||||||
|
RequestId string `field:"requestId"` // 请求ID
|
||||||
|
ClusterId uint32 `field:"clusterId"` // 集群ID
|
||||||
|
NodeId uint32 `field:"nodeId"` // 节点ID
|
||||||
|
AppId string `field:"appId"` // AppID
|
||||||
|
AppName string `field:"appName"` // 应用名
|
||||||
|
Domain string `field:"domain"` // 域名
|
||||||
|
QType string `field:"qtype"` // 查询类型
|
||||||
|
ClientIP string `field:"clientIP"` // 客户端IP
|
||||||
|
ClientRegion string `field:"clientRegion"` // 客户端区域
|
||||||
|
Carrier string `field:"carrier"` // 运营商
|
||||||
|
SDKVersion string `field:"sdkVersion"` // SDK版本
|
||||||
|
OS string `field:"os"` // 系统
|
||||||
|
ResultIPs string `field:"resultIPs"` // 结果IP
|
||||||
|
Status string `field:"status"` // 状态
|
||||||
|
ErrorCode string `field:"errorCode"` // 错误码
|
||||||
|
CostMs int32 `field:"costMs"` // 耗时
|
||||||
|
CreatedAt uint64 `field:"createdAt"` // 创建时间
|
||||||
|
Day string `field:"day"` // YYYYMMDD
|
||||||
|
Summary string `field:"summary"` // 概要
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPDNSAccessLogOperator struct {
|
||||||
|
Id any // ID
|
||||||
|
RequestId any // 请求ID
|
||||||
|
ClusterId any // 集群ID
|
||||||
|
NodeId any // 节点ID
|
||||||
|
AppId any // AppID
|
||||||
|
AppName any // 应用名
|
||||||
|
Domain any // 域名
|
||||||
|
QType any // 查询类型
|
||||||
|
ClientIP any // 客户端IP
|
||||||
|
ClientRegion any // 客户端区域
|
||||||
|
Carrier any // 运营商
|
||||||
|
SDKVersion any // SDK版本
|
||||||
|
OS any // 系统
|
||||||
|
ResultIPs any // 结果IP
|
||||||
|
Status any // 状态
|
||||||
|
ErrorCode any // 错误码
|
||||||
|
CostMs any // 耗时
|
||||||
|
CreatedAt any // 创建时间
|
||||||
|
Day any // YYYYMMDD
|
||||||
|
Summary any // 概要
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPDNSAccessLogOperator() *HTTPDNSAccessLogOperator {
|
||||||
|
return &HTTPDNSAccessLogOperator{}
|
||||||
|
}
|
||||||
128
EdgeAPI/internal/db/models/httpdns_app_dao.go
Normal file
128
EdgeAPI/internal/db/models/httpdns_app_dao.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HTTPDNSAppStateEnabled = 1
|
||||||
|
HTTPDNSAppStateDisabled = 0
|
||||||
|
HTTPDNSSNIModeFixedHide = "fixed_hide"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPDNSAppDAO dbs.DAO
|
||||||
|
|
||||||
|
func NewHTTPDNSAppDAO() *HTTPDNSAppDAO {
|
||||||
|
return dbs.NewDAO(&HTTPDNSAppDAO{
|
||||||
|
DAOObject: dbs.DAOObject{
|
||||||
|
DB: Tea.Env,
|
||||||
|
Table: "edgeHTTPDNSApps",
|
||||||
|
Model: new(HTTPDNSApp),
|
||||||
|
PkName: "id",
|
||||||
|
},
|
||||||
|
}).(*HTTPDNSAppDAO)
|
||||||
|
}
|
||||||
|
|
||||||
|
var SharedHTTPDNSAppDAO *HTTPDNSAppDAO
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dbs.OnReady(func() {
|
||||||
|
SharedHTTPDNSAppDAO = NewHTTPDNSAppDAO()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppDAO) CreateApp(tx *dbs.Tx, name string, appId string, primaryClusterId int64, backupClusterId int64, isOn bool, userId int64) (int64, error) {
|
||||||
|
var op = NewHTTPDNSAppOperator()
|
||||||
|
op.Name = name
|
||||||
|
op.AppId = appId
|
||||||
|
op.PrimaryClusterId = primaryClusterId
|
||||||
|
op.BackupClusterId = backupClusterId
|
||||||
|
op.IsOn = isOn
|
||||||
|
op.UserId = userId
|
||||||
|
op.SNIMode = HTTPDNSSNIModeFixedHide
|
||||||
|
op.CreatedAt = time.Now().Unix()
|
||||||
|
op.UpdatedAt = time.Now().Unix()
|
||||||
|
op.State = HTTPDNSAppStateEnabled
|
||||||
|
err := this.Save(tx, op)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return types.Int64(op.Id), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppDAO) UpdateApp(tx *dbs.Tx, appDbId int64, name string, primaryClusterId int64, backupClusterId int64, isOn bool, userId int64) error {
|
||||||
|
var op = NewHTTPDNSAppOperator()
|
||||||
|
op.Id = appDbId
|
||||||
|
op.Name = name
|
||||||
|
op.PrimaryClusterId = primaryClusterId
|
||||||
|
op.BackupClusterId = backupClusterId
|
||||||
|
op.IsOn = isOn
|
||||||
|
op.UserId = userId
|
||||||
|
op.UpdatedAt = time.Now().Unix()
|
||||||
|
return this.Save(tx, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppDAO) DisableApp(tx *dbs.Tx, appDbId int64) error {
|
||||||
|
_, err := this.Query(tx).
|
||||||
|
Pk(appDbId).
|
||||||
|
Set("state", HTTPDNSAppStateDisabled).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppDAO) FindEnabledApp(tx *dbs.Tx, appDbId int64) (*HTTPDNSApp, error) {
|
||||||
|
one, err := this.Query(tx).
|
||||||
|
Pk(appDbId).
|
||||||
|
State(HTTPDNSAppStateEnabled).
|
||||||
|
Find()
|
||||||
|
if one == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return one.(*HTTPDNSApp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppDAO) FindEnabledAppWithAppId(tx *dbs.Tx, appId string) (*HTTPDNSApp, error) {
|
||||||
|
one, err := this.Query(tx).
|
||||||
|
State(HTTPDNSAppStateEnabled).
|
||||||
|
Attr("appId", appId).
|
||||||
|
Find()
|
||||||
|
if one == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return one.(*HTTPDNSApp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppDAO) ListEnabledApps(tx *dbs.Tx, offset int64, size int64, keyword string) (result []*HTTPDNSApp, err error) {
|
||||||
|
query := this.Query(tx).
|
||||||
|
State(HTTPDNSAppStateEnabled).
|
||||||
|
AscPk()
|
||||||
|
if len(keyword) > 0 {
|
||||||
|
query = query.Where("(name LIKE :kw OR appId LIKE :kw)").Param("kw", "%"+keyword+"%")
|
||||||
|
}
|
||||||
|
if size > 0 {
|
||||||
|
query = query.Offset(offset).Limit(size)
|
||||||
|
}
|
||||||
|
_, err = query.Slice(&result).FindAll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppDAO) CountEnabledApps(tx *dbs.Tx, keyword string) (int64, error) {
|
||||||
|
query := this.Query(tx).State(HTTPDNSAppStateEnabled)
|
||||||
|
if len(keyword) > 0 {
|
||||||
|
query = query.Where("(name LIKE :kw OR appId LIKE :kw)").Param("kw", "%"+keyword+"%")
|
||||||
|
}
|
||||||
|
return query.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppDAO) FindAllEnabledApps(tx *dbs.Tx) (result []*HTTPDNSApp, err error) {
|
||||||
|
_, err = this.Query(tx).
|
||||||
|
State(HTTPDNSAppStateEnabled).
|
||||||
|
AscPk().
|
||||||
|
Slice(&result).
|
||||||
|
FindAll()
|
||||||
|
return
|
||||||
|
}
|
||||||
36
EdgeAPI/internal/db/models/httpdns_app_model.go
Normal file
36
EdgeAPI/internal/db/models/httpdns_app_model.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// HTTPDNSApp maps to edgeHTTPDNSApps.
|
||||||
|
type HTTPDNSApp struct {
|
||||||
|
Id uint32 `field:"id"` // id
|
||||||
|
Name string `field:"name"` // app name
|
||||||
|
AppId string `field:"appId"` // external app id
|
||||||
|
IsOn bool `field:"isOn"` // enabled
|
||||||
|
PrimaryClusterId uint32 `field:"primaryClusterId"` // primary cluster id
|
||||||
|
BackupClusterId uint32 `field:"backupClusterId"` // backup cluster id
|
||||||
|
SNIMode string `field:"sniMode"` // sni mode
|
||||||
|
UserId int64 `field:"userId"` // owner user id
|
||||||
|
CreatedAt uint64 `field:"createdAt"` // created unix ts
|
||||||
|
UpdatedAt uint64 `field:"updatedAt"` // updated unix ts
|
||||||
|
State uint8 `field:"state"` // state
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPDNSAppOperator is used by DAO save/update.
|
||||||
|
type HTTPDNSAppOperator struct {
|
||||||
|
Id any // id
|
||||||
|
Name any // app name
|
||||||
|
AppId any // external app id
|
||||||
|
IsOn any // enabled
|
||||||
|
PrimaryClusterId any // primary cluster id
|
||||||
|
BackupClusterId any // backup cluster id
|
||||||
|
SNIMode any // sni mode
|
||||||
|
UserId any // owner user id
|
||||||
|
CreatedAt any // created unix ts
|
||||||
|
UpdatedAt any // updated unix ts
|
||||||
|
State any // state
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPDNSAppOperator() *HTTPDNSAppOperator {
|
||||||
|
return &HTTPDNSAppOperator{}
|
||||||
|
}
|
||||||
|
|
||||||
125
EdgeAPI/internal/db/models/httpdns_app_secret_dao.go
Normal file
125
EdgeAPI/internal/db/models/httpdns_app_secret_dao.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
"github.com/iwind/TeaGo/rands"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HTTPDNSAppSecretStateEnabled = 1
|
||||||
|
HTTPDNSAppSecretStateDisabled = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPDNSAppSecretDAO dbs.DAO
|
||||||
|
|
||||||
|
func NewHTTPDNSAppSecretDAO() *HTTPDNSAppSecretDAO {
|
||||||
|
return dbs.NewDAO(&HTTPDNSAppSecretDAO{
|
||||||
|
DAOObject: dbs.DAOObject{
|
||||||
|
DB: Tea.Env,
|
||||||
|
Table: "edgeHTTPDNSAppSecrets",
|
||||||
|
Model: new(HTTPDNSAppSecret),
|
||||||
|
PkName: "id",
|
||||||
|
},
|
||||||
|
}).(*HTTPDNSAppSecretDAO)
|
||||||
|
}
|
||||||
|
|
||||||
|
var SharedHTTPDNSAppSecretDAO *HTTPDNSAppSecretDAO
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dbs.OnReady(func() {
|
||||||
|
SharedHTTPDNSAppSecretDAO = NewHTTPDNSAppSecretDAO()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppSecretDAO) InitAppSecret(tx *dbs.Tx, appDbId int64, signEnabled bool) (string, uint64, error) {
|
||||||
|
signSecret := "ss_" + rands.HexString(12)
|
||||||
|
now := uint64(time.Now().Unix())
|
||||||
|
var op = NewHTTPDNSAppSecretOperator()
|
||||||
|
op.AppId = appDbId
|
||||||
|
op.SignEnabled = signEnabled
|
||||||
|
op.SignSecret = signSecret
|
||||||
|
op.SignUpdatedAt = now
|
||||||
|
op.UpdatedAt = now
|
||||||
|
op.State = HTTPDNSAppSecretStateEnabled
|
||||||
|
err := this.Save(tx, op)
|
||||||
|
return signSecret, now, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppSecretDAO) FindEnabledAppSecret(tx *dbs.Tx, appDbId int64) (*HTTPDNSAppSecret, error) {
|
||||||
|
one, err := this.Query(tx).
|
||||||
|
Attr("appId", appDbId).
|
||||||
|
State(HTTPDNSAppSecretStateEnabled).
|
||||||
|
Find()
|
||||||
|
if one == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return one.(*HTTPDNSAppSecret), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppSecretDAO) UpdateSignEnabled(tx *dbs.Tx, appDbId int64, signEnabled bool) error {
|
||||||
|
_, err := this.Query(tx).
|
||||||
|
Attr("appId", appDbId).
|
||||||
|
State(HTTPDNSAppSecretStateEnabled).
|
||||||
|
Set("signEnabled", signEnabled).
|
||||||
|
Set("updatedAt", time.Now().Unix()).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppSecretDAO) ResetSignSecret(tx *dbs.Tx, appDbId int64) (string, int64, error) {
|
||||||
|
signSecret := "ss_" + rands.HexString(12)
|
||||||
|
now := time.Now().Unix()
|
||||||
|
_, err := this.Query(tx).
|
||||||
|
Attr("appId", appDbId).
|
||||||
|
State(HTTPDNSAppSecretStateEnabled).
|
||||||
|
Set("signSecret", signSecret).
|
||||||
|
Set("signUpdatedAt", now).
|
||||||
|
Set("updatedAt", now).
|
||||||
|
Update()
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
return signSecret, now, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppSecretDAO) FindSignEnabled(tx *dbs.Tx, appDbId int64) (bool, error) {
|
||||||
|
one, err := this.FindEnabledAppSecret(tx, appDbId)
|
||||||
|
if err != nil || one == nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return one.SignEnabled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppSecretDAO) FindSignSecretWithAppDbId(tx *dbs.Tx, appDbId int64) (string, error) {
|
||||||
|
return this.Query(tx).
|
||||||
|
Attr("appId", appDbId).
|
||||||
|
State(HTTPDNSAppSecretStateEnabled).
|
||||||
|
Result("signSecret").
|
||||||
|
FindStringCol("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppSecretDAO) FindSignUpdatedAt(tx *dbs.Tx, appDbId int64) (int64, error) {
|
||||||
|
col, err := this.Query(tx).
|
||||||
|
Attr("appId", appDbId).
|
||||||
|
State(HTTPDNSAppSecretStateEnabled).
|
||||||
|
Result("signUpdatedAt").
|
||||||
|
FindCol(nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return types.Int64(col), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppSecretDAO) DisableAppSecret(tx *dbs.Tx, appDbId int64) error {
|
||||||
|
_, err := this.Query(tx).
|
||||||
|
Attr("appId", appDbId).
|
||||||
|
State(HTTPDNSAppSecretStateEnabled).
|
||||||
|
Set("state", HTTPDNSAppSecretStateDisabled).
|
||||||
|
Set("updatedAt", time.Now().Unix()).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
26
EdgeAPI/internal/db/models/httpdns_app_secret_model.go
Normal file
26
EdgeAPI/internal/db/models/httpdns_app_secret_model.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// HTTPDNSAppSecret 应用验签密钥配置
|
||||||
|
type HTTPDNSAppSecret struct {
|
||||||
|
Id uint32 `field:"id"` // ID
|
||||||
|
AppId uint32 `field:"appId"` // 应用DB ID
|
||||||
|
SignEnabled bool `field:"signEnabled"` // 是否启用验签
|
||||||
|
SignSecret string `field:"signSecret"` // 验签密钥(当前先明文存储)
|
||||||
|
SignUpdatedAt uint64 `field:"signUpdatedAt"` // 验签密钥更新时间
|
||||||
|
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
|
||||||
|
State uint8 `field:"state"` // 记录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPDNSAppSecretOperator struct {
|
||||||
|
Id any // ID
|
||||||
|
AppId any // 应用DB ID
|
||||||
|
SignEnabled any // 是否启用验签
|
||||||
|
SignSecret any // 验签密钥
|
||||||
|
SignUpdatedAt any // 验签密钥更新时间
|
||||||
|
UpdatedAt any // 修改时间
|
||||||
|
State any // 记录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPDNSAppSecretOperator() *HTTPDNSAppSecretOperator {
|
||||||
|
return &HTTPDNSAppSecretOperator{}
|
||||||
|
}
|
||||||
169
EdgeAPI/internal/db/models/httpdns_cluster_dao.go
Normal file
169
EdgeAPI/internal/db/models/httpdns_cluster_dao.go
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HTTPDNSClusterStateEnabled = 1
|
||||||
|
HTTPDNSClusterStateDisabled = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPDNSClusterDAO dbs.DAO
|
||||||
|
|
||||||
|
func NewHTTPDNSClusterDAO() *HTTPDNSClusterDAO {
|
||||||
|
return dbs.NewDAO(&HTTPDNSClusterDAO{
|
||||||
|
DAOObject: dbs.DAOObject{
|
||||||
|
DB: Tea.Env,
|
||||||
|
Table: "edgeHTTPDNSClusters",
|
||||||
|
Model: new(HTTPDNSCluster),
|
||||||
|
PkName: "id",
|
||||||
|
},
|
||||||
|
}).(*HTTPDNSClusterDAO)
|
||||||
|
}
|
||||||
|
|
||||||
|
var SharedHTTPDNSClusterDAO *HTTPDNSClusterDAO
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dbs.OnReady(func() {
|
||||||
|
SharedHTTPDNSClusterDAO = NewHTTPDNSClusterDAO()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterDAO) CreateCluster(tx *dbs.Tx, name string, serviceDomain string, defaultTTL int32, fallbackTimeoutMs int32, installDir string, tlsPolicyJSON []byte, isOn bool, isDefault bool) (int64, error) {
|
||||||
|
if isDefault {
|
||||||
|
err := this.Query(tx).
|
||||||
|
State(HTTPDNSClusterStateEnabled).
|
||||||
|
Set("isDefault", false).
|
||||||
|
UpdateQuickly()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var op = NewHTTPDNSClusterOperator()
|
||||||
|
op.Name = name
|
||||||
|
op.ServiceDomain = serviceDomain
|
||||||
|
op.DefaultTTL = defaultTTL
|
||||||
|
op.FallbackTimeoutMs = fallbackTimeoutMs
|
||||||
|
op.InstallDir = installDir
|
||||||
|
op.IsOn = isOn
|
||||||
|
op.IsDefault = isDefault
|
||||||
|
op.CreatedAt = time.Now().Unix()
|
||||||
|
op.UpdatedAt = time.Now().Unix()
|
||||||
|
op.State = HTTPDNSClusterStateEnabled
|
||||||
|
if len(tlsPolicyJSON) > 0 {
|
||||||
|
op.TLSPolicy = tlsPolicyJSON
|
||||||
|
}
|
||||||
|
err := this.Save(tx, op)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return types.Int64(op.Id), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterDAO) UpdateCluster(tx *dbs.Tx, clusterId int64, name string, serviceDomain string, defaultTTL int32, fallbackTimeoutMs int32, installDir string, tlsPolicyJSON []byte, isOn bool, isDefault bool) error {
|
||||||
|
if isDefault {
|
||||||
|
err := this.Query(tx).
|
||||||
|
State(HTTPDNSClusterStateEnabled).
|
||||||
|
Neq("id", clusterId).
|
||||||
|
Set("isDefault", false).
|
||||||
|
UpdateQuickly()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var op = NewHTTPDNSClusterOperator()
|
||||||
|
op.Id = clusterId
|
||||||
|
op.Name = name
|
||||||
|
op.ServiceDomain = serviceDomain
|
||||||
|
op.DefaultTTL = defaultTTL
|
||||||
|
op.FallbackTimeoutMs = fallbackTimeoutMs
|
||||||
|
op.InstallDir = installDir
|
||||||
|
op.IsOn = isOn
|
||||||
|
op.IsDefault = isDefault
|
||||||
|
op.UpdatedAt = time.Now().Unix()
|
||||||
|
if len(tlsPolicyJSON) > 0 {
|
||||||
|
op.TLSPolicy = tlsPolicyJSON
|
||||||
|
}
|
||||||
|
return this.Save(tx, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterDAO) DisableCluster(tx *dbs.Tx, clusterId int64) error {
|
||||||
|
_, err := this.Query(tx).
|
||||||
|
Pk(clusterId).
|
||||||
|
Set("state", HTTPDNSClusterStateDisabled).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterDAO) FindEnabledCluster(tx *dbs.Tx, clusterId int64) (*HTTPDNSCluster, error) {
|
||||||
|
one, err := this.Query(tx).
|
||||||
|
Pk(clusterId).
|
||||||
|
State(HTTPDNSClusterStateEnabled).
|
||||||
|
Find()
|
||||||
|
if one == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return one.(*HTTPDNSCluster), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterDAO) FindEnabledClusterName(tx *dbs.Tx, clusterId int64) (string, error) {
|
||||||
|
return this.Query(tx).
|
||||||
|
Pk(clusterId).
|
||||||
|
State(HTTPDNSClusterStateEnabled).
|
||||||
|
Result("name").
|
||||||
|
FindStringCol("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterDAO) ListEnabledClusters(tx *dbs.Tx, offset int64, size int64, keyword string) (result []*HTTPDNSCluster, err error) {
|
||||||
|
query := this.Query(tx).
|
||||||
|
State(HTTPDNSClusterStateEnabled).
|
||||||
|
AscPk()
|
||||||
|
if len(keyword) > 0 {
|
||||||
|
query = query.Where("(name LIKE :kw OR serviceDomain LIKE :kw)").Param("kw", "%"+keyword+"%")
|
||||||
|
}
|
||||||
|
if size > 0 {
|
||||||
|
query = query.Offset(offset).Limit(size)
|
||||||
|
}
|
||||||
|
_, err = query.Slice(&result).FindAll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterDAO) CountEnabledClusters(tx *dbs.Tx, keyword string) (int64, error) {
|
||||||
|
query := this.Query(tx).State(HTTPDNSClusterStateEnabled)
|
||||||
|
if len(keyword) > 0 {
|
||||||
|
query = query.Where("(name LIKE :kw OR serviceDomain LIKE :kw)").Param("kw", "%"+keyword+"%")
|
||||||
|
}
|
||||||
|
return query.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterDAO) FindAllEnabledClusters(tx *dbs.Tx) (result []*HTTPDNSCluster, err error) {
|
||||||
|
_, err = this.Query(tx).
|
||||||
|
State(HTTPDNSClusterStateEnabled).
|
||||||
|
AscPk().
|
||||||
|
Slice(&result).
|
||||||
|
FindAll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterDAO) UpdateDefaultCluster(tx *dbs.Tx, clusterId int64) error {
|
||||||
|
err := this.Query(tx).
|
||||||
|
State(HTTPDNSClusterStateEnabled).
|
||||||
|
Set("isDefault", false).
|
||||||
|
UpdateQuickly()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = this.Query(tx).
|
||||||
|
Pk(clusterId).
|
||||||
|
State(HTTPDNSClusterStateEnabled).
|
||||||
|
Set("isDefault", true).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
38
EdgeAPI/internal/db/models/httpdns_cluster_model.go
Normal file
38
EdgeAPI/internal/db/models/httpdns_cluster_model.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "github.com/iwind/TeaGo/dbs"
|
||||||
|
|
||||||
|
// HTTPDNSCluster HTTPDNS集群
|
||||||
|
type HTTPDNSCluster struct {
|
||||||
|
Id uint32 `field:"id"` // ID
|
||||||
|
Name string `field:"name"` // 集群名称
|
||||||
|
IsOn bool `field:"isOn"` // 是否启用
|
||||||
|
IsDefault bool `field:"isDefault"` // 默认集群
|
||||||
|
ServiceDomain string `field:"serviceDomain"` // 服务域名
|
||||||
|
DefaultTTL int32 `field:"defaultTTL"` // 默认TTL
|
||||||
|
FallbackTimeoutMs int32 `field:"fallbackTimeoutMs"` // 降级超时
|
||||||
|
InstallDir string `field:"installDir"` // 安装目录
|
||||||
|
TLSPolicy dbs.JSON `field:"tlsPolicy"` // TLS策略
|
||||||
|
CreatedAt uint64 `field:"createdAt"` // 创建时间
|
||||||
|
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
|
||||||
|
State uint8 `field:"state"` // 记录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPDNSClusterOperator struct {
|
||||||
|
Id any // ID
|
||||||
|
Name any // 集群名称
|
||||||
|
IsOn any // 是否启用
|
||||||
|
IsDefault any // 默认集群
|
||||||
|
ServiceDomain any // 服务域名
|
||||||
|
DefaultTTL any // 默认TTL
|
||||||
|
FallbackTimeoutMs any // 降级超时
|
||||||
|
InstallDir any // 安装目录
|
||||||
|
TLSPolicy any // TLS策略
|
||||||
|
CreatedAt any // 创建时间
|
||||||
|
UpdatedAt any // 修改时间
|
||||||
|
State any // 记录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPDNSClusterOperator() *HTTPDNSClusterOperator {
|
||||||
|
return &HTTPDNSClusterOperator{}
|
||||||
|
}
|
||||||
143
EdgeAPI/internal/db/models/httpdns_custom_rule_dao.go
Normal file
143
EdgeAPI/internal/db/models/httpdns_custom_rule_dao.go
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HTTPDNSCustomRuleStateEnabled = 1
|
||||||
|
HTTPDNSCustomRuleStateDisabled = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPDNSCustomRuleDAO dbs.DAO
|
||||||
|
|
||||||
|
func NewHTTPDNSCustomRuleDAO() *HTTPDNSCustomRuleDAO {
|
||||||
|
return dbs.NewDAO(&HTTPDNSCustomRuleDAO{
|
||||||
|
DAOObject: dbs.DAOObject{
|
||||||
|
DB: Tea.Env,
|
||||||
|
Table: "edgeHTTPDNSCustomRules",
|
||||||
|
Model: new(HTTPDNSCustomRule),
|
||||||
|
PkName: "id",
|
||||||
|
},
|
||||||
|
}).(*HTTPDNSCustomRuleDAO)
|
||||||
|
}
|
||||||
|
|
||||||
|
var SharedHTTPDNSCustomRuleDAO *HTTPDNSCustomRuleDAO
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dbs.OnReady(func() {
|
||||||
|
SharedHTTPDNSCustomRuleDAO = NewHTTPDNSCustomRuleDAO()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSCustomRuleDAO) CreateRule(tx *dbs.Tx, rule *HTTPDNSCustomRule) (int64, error) {
|
||||||
|
var op = NewHTTPDNSCustomRuleOperator()
|
||||||
|
op.AppId = rule.AppId
|
||||||
|
op.DomainId = rule.DomainId
|
||||||
|
op.RuleName = rule.RuleName
|
||||||
|
op.LineScope = rule.LineScope
|
||||||
|
op.LineCarrier = rule.LineCarrier
|
||||||
|
op.LineRegion = rule.LineRegion
|
||||||
|
op.LineProvince = rule.LineProvince
|
||||||
|
op.LineContinent = rule.LineContinent
|
||||||
|
op.LineCountry = rule.LineCountry
|
||||||
|
op.TTL = rule.TTL
|
||||||
|
op.IsOn = rule.IsOn
|
||||||
|
op.Priority = rule.Priority
|
||||||
|
op.UpdatedAt = time.Now().Unix()
|
||||||
|
op.State = HTTPDNSCustomRuleStateEnabled
|
||||||
|
err := this.Save(tx, op)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return types.Int64(op.Id), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSCustomRuleDAO) UpdateRule(tx *dbs.Tx, rule *HTTPDNSCustomRule) error {
|
||||||
|
var op = NewHTTPDNSCustomRuleOperator()
|
||||||
|
op.Id = rule.Id
|
||||||
|
op.RuleName = rule.RuleName
|
||||||
|
op.LineScope = rule.LineScope
|
||||||
|
op.LineCarrier = rule.LineCarrier
|
||||||
|
op.LineRegion = rule.LineRegion
|
||||||
|
op.LineProvince = rule.LineProvince
|
||||||
|
op.LineContinent = rule.LineContinent
|
||||||
|
op.LineCountry = rule.LineCountry
|
||||||
|
op.TTL = rule.TTL
|
||||||
|
op.IsOn = rule.IsOn
|
||||||
|
op.Priority = rule.Priority
|
||||||
|
op.UpdatedAt = time.Now().Unix()
|
||||||
|
return this.Save(tx, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSCustomRuleDAO) DisableRule(tx *dbs.Tx, ruleId int64) error {
|
||||||
|
_, err := this.Query(tx).
|
||||||
|
Pk(ruleId).
|
||||||
|
Set("state", HTTPDNSCustomRuleStateDisabled).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSCustomRuleDAO) DisableRulesWithAppId(tx *dbs.Tx, appDbId int64) error {
|
||||||
|
_, err := this.Query(tx).
|
||||||
|
Attr("appId", appDbId).
|
||||||
|
State(HTTPDNSCustomRuleStateEnabled).
|
||||||
|
Set("state", HTTPDNSCustomRuleStateDisabled).
|
||||||
|
Set("updatedAt", time.Now().Unix()).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSCustomRuleDAO) UpdateRuleStatus(tx *dbs.Tx, ruleId int64, isOn bool) error {
|
||||||
|
_, err := this.Query(tx).
|
||||||
|
Pk(ruleId).
|
||||||
|
State(HTTPDNSCustomRuleStateEnabled).
|
||||||
|
Set("isOn", isOn).
|
||||||
|
Set("updatedAt", time.Now().Unix()).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSCustomRuleDAO) FindEnabledRule(tx *dbs.Tx, ruleId int64) (*HTTPDNSCustomRule, error) {
|
||||||
|
one, err := this.Query(tx).
|
||||||
|
Pk(ruleId).
|
||||||
|
State(HTTPDNSCustomRuleStateEnabled).
|
||||||
|
Find()
|
||||||
|
if one == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return one.(*HTTPDNSCustomRule), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSCustomRuleDAO) ListEnabledRulesWithDomainId(tx *dbs.Tx, domainId int64) (result []*HTTPDNSCustomRule, err error) {
|
||||||
|
_, err = this.Query(tx).
|
||||||
|
State(HTTPDNSCustomRuleStateEnabled).
|
||||||
|
Attr("domainId", domainId).
|
||||||
|
Asc("priority").
|
||||||
|
AscPk().
|
||||||
|
Slice(&result).
|
||||||
|
FindAll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSCustomRuleDAO) ListEnabledRulesWithAppId(tx *dbs.Tx, appDbId int64) (result []*HTTPDNSCustomRule, err error) {
|
||||||
|
_, err = this.Query(tx).
|
||||||
|
State(HTTPDNSCustomRuleStateEnabled).
|
||||||
|
Attr("appId", appDbId).
|
||||||
|
Asc("priority").
|
||||||
|
AscPk().
|
||||||
|
Slice(&result).
|
||||||
|
FindAll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSCustomRuleDAO) CountEnabledRulesWithDomainId(tx *dbs.Tx, domainId int64) (int64, error) {
|
||||||
|
return this.Query(tx).
|
||||||
|
State(HTTPDNSCustomRuleStateEnabled).
|
||||||
|
Attr("domainId", domainId).
|
||||||
|
Count()
|
||||||
|
}
|
||||||
42
EdgeAPI/internal/db/models/httpdns_custom_rule_model.go
Normal file
42
EdgeAPI/internal/db/models/httpdns_custom_rule_model.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// HTTPDNSCustomRule 自定义解析规则
|
||||||
|
type HTTPDNSCustomRule struct {
|
||||||
|
Id uint32 `field:"id"` // ID
|
||||||
|
AppId uint32 `field:"appId"` // 应用DB ID
|
||||||
|
DomainId uint32 `field:"domainId"` // 域名ID
|
||||||
|
RuleName string `field:"ruleName"` // 规则名称
|
||||||
|
LineScope string `field:"lineScope"` // 线路范围
|
||||||
|
LineCarrier string `field:"lineCarrier"` // 运营商
|
||||||
|
LineRegion string `field:"lineRegion"` // 区域
|
||||||
|
LineProvince string `field:"lineProvince"` // 省份
|
||||||
|
LineContinent string `field:"lineContinent"` // 大洲
|
||||||
|
LineCountry string `field:"lineCountry"` // 国家
|
||||||
|
TTL int32 `field:"ttl"` // TTL
|
||||||
|
IsOn bool `field:"isOn"` // 启用状态
|
||||||
|
Priority int32 `field:"priority"` // 优先级
|
||||||
|
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
|
||||||
|
State uint8 `field:"state"` // 记录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPDNSCustomRuleOperator struct {
|
||||||
|
Id any // ID
|
||||||
|
AppId any // 应用DB ID
|
||||||
|
DomainId any // 域名ID
|
||||||
|
RuleName any // 规则名称
|
||||||
|
LineScope any // 线路范围
|
||||||
|
LineCarrier any // 运营商
|
||||||
|
LineRegion any // 区域
|
||||||
|
LineProvince any // 省份
|
||||||
|
LineContinent any // 大洲
|
||||||
|
LineCountry any // 国家
|
||||||
|
TTL any // TTL
|
||||||
|
IsOn any // 启用状态
|
||||||
|
Priority any // 优先级
|
||||||
|
UpdatedAt any // 修改时间
|
||||||
|
State any // 记录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPDNSCustomRuleOperator() *HTTPDNSCustomRuleOperator {
|
||||||
|
return &HTTPDNSCustomRuleOperator{}
|
||||||
|
}
|
||||||
69
EdgeAPI/internal/db/models/httpdns_custom_rule_record_dao.go
Normal file
69
EdgeAPI/internal/db/models/httpdns_custom_rule_record_dao.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HTTPDNSCustomRuleRecordStateEnabled = 1
|
||||||
|
HTTPDNSCustomRuleRecordStateDisabled = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPDNSCustomRuleRecordDAO dbs.DAO
|
||||||
|
|
||||||
|
func NewHTTPDNSCustomRuleRecordDAO() *HTTPDNSCustomRuleRecordDAO {
|
||||||
|
return dbs.NewDAO(&HTTPDNSCustomRuleRecordDAO{
|
||||||
|
DAOObject: dbs.DAOObject{
|
||||||
|
DB: Tea.Env,
|
||||||
|
Table: "edgeHTTPDNSCustomRuleRecords",
|
||||||
|
Model: new(HTTPDNSCustomRuleRecord),
|
||||||
|
PkName: "id",
|
||||||
|
},
|
||||||
|
}).(*HTTPDNSCustomRuleRecordDAO)
|
||||||
|
}
|
||||||
|
|
||||||
|
var SharedHTTPDNSCustomRuleRecordDAO *HTTPDNSCustomRuleRecordDAO
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dbs.OnReady(func() {
|
||||||
|
SharedHTTPDNSCustomRuleRecordDAO = NewHTTPDNSCustomRuleRecordDAO()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSCustomRuleRecordDAO) CreateRecord(tx *dbs.Tx, ruleId int64, recordType string, recordValue string, weight int32, sort int32) (int64, error) {
|
||||||
|
var op = NewHTTPDNSCustomRuleRecordOperator()
|
||||||
|
op.RuleId = ruleId
|
||||||
|
op.RecordType = recordType
|
||||||
|
op.RecordValue = recordValue
|
||||||
|
op.Weight = weight
|
||||||
|
op.Sort = sort
|
||||||
|
op.State = HTTPDNSCustomRuleRecordStateEnabled
|
||||||
|
err := this.Save(tx, op)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return types.Int64(op.Id), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSCustomRuleRecordDAO) DisableRecordsWithRuleId(tx *dbs.Tx, ruleId int64) error {
|
||||||
|
_, err := this.Query(tx).
|
||||||
|
Attr("ruleId", ruleId).
|
||||||
|
State(HTTPDNSCustomRuleRecordStateEnabled).
|
||||||
|
Set("state", HTTPDNSCustomRuleRecordStateDisabled).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSCustomRuleRecordDAO) ListEnabledRecordsWithRuleId(tx *dbs.Tx, ruleId int64) (result []*HTTPDNSCustomRuleRecord, err error) {
|
||||||
|
_, err = this.Query(tx).
|
||||||
|
State(HTTPDNSCustomRuleRecordStateEnabled).
|
||||||
|
Attr("ruleId", ruleId).
|
||||||
|
Asc("sort").
|
||||||
|
AscPk().
|
||||||
|
Slice(&result).
|
||||||
|
FindAll()
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// HTTPDNSCustomRuleRecord 自定义规则记录值
|
||||||
|
type HTTPDNSCustomRuleRecord struct {
|
||||||
|
Id uint32 `field:"id"` // ID
|
||||||
|
RuleId uint32 `field:"ruleId"` // 规则ID
|
||||||
|
RecordType string `field:"recordType"` // 记录类型
|
||||||
|
RecordValue string `field:"recordValue"` // 记录值
|
||||||
|
Weight int32 `field:"weight"` // 权重
|
||||||
|
Sort int32 `field:"sort"` // 顺序
|
||||||
|
State uint8 `field:"state"` // 记录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPDNSCustomRuleRecordOperator struct {
|
||||||
|
Id any // ID
|
||||||
|
RuleId any // 规则ID
|
||||||
|
RecordType any // 记录类型
|
||||||
|
RecordValue any // 记录值
|
||||||
|
Weight any // 权重
|
||||||
|
Sort any // 顺序
|
||||||
|
State any // 记录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPDNSCustomRuleRecordOperator() *HTTPDNSCustomRuleRecordOperator {
|
||||||
|
return &HTTPDNSCustomRuleRecordOperator{}
|
||||||
|
}
|
||||||
115
EdgeAPI/internal/db/models/httpdns_domain_dao.go
Normal file
115
EdgeAPI/internal/db/models/httpdns_domain_dao.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HTTPDNSDomainStateEnabled = 1
|
||||||
|
HTTPDNSDomainStateDisabled = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPDNSDomainDAO dbs.DAO
|
||||||
|
|
||||||
|
func NewHTTPDNSDomainDAO() *HTTPDNSDomainDAO {
|
||||||
|
return dbs.NewDAO(&HTTPDNSDomainDAO{
|
||||||
|
DAOObject: dbs.DAOObject{
|
||||||
|
DB: Tea.Env,
|
||||||
|
Table: "edgeHTTPDNSDomains",
|
||||||
|
Model: new(HTTPDNSDomain),
|
||||||
|
PkName: "id",
|
||||||
|
},
|
||||||
|
}).(*HTTPDNSDomainDAO)
|
||||||
|
}
|
||||||
|
|
||||||
|
var SharedHTTPDNSDomainDAO *HTTPDNSDomainDAO
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dbs.OnReady(func() {
|
||||||
|
SharedHTTPDNSDomainDAO = NewHTTPDNSDomainDAO()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSDomainDAO) CreateDomain(tx *dbs.Tx, appDbId int64, domain string, isOn bool) (int64, error) {
|
||||||
|
domain = strings.ToLower(strings.TrimSpace(domain))
|
||||||
|
var op = NewHTTPDNSDomainOperator()
|
||||||
|
op.AppId = appDbId
|
||||||
|
op.Domain = domain
|
||||||
|
op.IsOn = isOn
|
||||||
|
op.CreatedAt = time.Now().Unix()
|
||||||
|
op.UpdatedAt = time.Now().Unix()
|
||||||
|
op.State = HTTPDNSDomainStateEnabled
|
||||||
|
err := this.Save(tx, op)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return types.Int64(op.Id), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSDomainDAO) DisableDomain(tx *dbs.Tx, domainId int64) error {
|
||||||
|
_, err := this.Query(tx).
|
||||||
|
Pk(domainId).
|
||||||
|
Set("state", HTTPDNSDomainStateDisabled).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSDomainDAO) DisableDomainsWithAppId(tx *dbs.Tx, appDbId int64) error {
|
||||||
|
_, err := this.Query(tx).
|
||||||
|
Attr("appId", appDbId).
|
||||||
|
State(HTTPDNSDomainStateEnabled).
|
||||||
|
Set("state", HTTPDNSDomainStateDisabled).
|
||||||
|
Set("updatedAt", time.Now().Unix()).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSDomainDAO) UpdateDomainStatus(tx *dbs.Tx, domainId int64, isOn bool) error {
|
||||||
|
_, err := this.Query(tx).
|
||||||
|
Pk(domainId).
|
||||||
|
State(HTTPDNSDomainStateEnabled).
|
||||||
|
Set("isOn", isOn).
|
||||||
|
Set("updatedAt", time.Now().Unix()).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSDomainDAO) FindEnabledDomain(tx *dbs.Tx, domainId int64) (*HTTPDNSDomain, error) {
|
||||||
|
one, err := this.Query(tx).
|
||||||
|
Pk(domainId).
|
||||||
|
State(HTTPDNSDomainStateEnabled).
|
||||||
|
Find()
|
||||||
|
if one == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return one.(*HTTPDNSDomain), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSDomainDAO) FindEnabledDomainWithAppAndName(tx *dbs.Tx, appDbId int64, domain string) (*HTTPDNSDomain, error) {
|
||||||
|
one, err := this.Query(tx).
|
||||||
|
State(HTTPDNSDomainStateEnabled).
|
||||||
|
Attr("appId", appDbId).
|
||||||
|
Attr("domain", strings.ToLower(strings.TrimSpace(domain))).
|
||||||
|
Find()
|
||||||
|
if one == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return one.(*HTTPDNSDomain), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSDomainDAO) ListEnabledDomainsWithAppId(tx *dbs.Tx, appDbId int64, keyword string) (result []*HTTPDNSDomain, err error) {
|
||||||
|
query := this.Query(tx).
|
||||||
|
State(HTTPDNSDomainStateEnabled).
|
||||||
|
Attr("appId", appDbId).
|
||||||
|
AscPk()
|
||||||
|
if len(keyword) > 0 {
|
||||||
|
query = query.Where("domain LIKE :kw").Param("kw", "%"+keyword+"%")
|
||||||
|
}
|
||||||
|
_, err = query.Slice(&result).FindAll()
|
||||||
|
return
|
||||||
|
}
|
||||||
26
EdgeAPI/internal/db/models/httpdns_domain_model.go
Normal file
26
EdgeAPI/internal/db/models/httpdns_domain_model.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// HTTPDNSDomain 应用绑定域名
|
||||||
|
type HTTPDNSDomain struct {
|
||||||
|
Id uint32 `field:"id"` // ID
|
||||||
|
AppId uint32 `field:"appId"` // 应用DB ID
|
||||||
|
Domain string `field:"domain"` // 业务域名
|
||||||
|
IsOn bool `field:"isOn"` // 是否启用
|
||||||
|
CreatedAt uint64 `field:"createdAt"` // 创建时间
|
||||||
|
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
|
||||||
|
State uint8 `field:"state"` // 记录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPDNSDomainOperator struct {
|
||||||
|
Id any // ID
|
||||||
|
AppId any // 应用DB ID
|
||||||
|
Domain any // 业务域名
|
||||||
|
IsOn any // 是否启用
|
||||||
|
CreatedAt any // 创建时间
|
||||||
|
UpdatedAt any // 修改时间
|
||||||
|
State any // 记录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPDNSDomainOperator() *HTTPDNSDomainOperator {
|
||||||
|
return &HTTPDNSDomainOperator{}
|
||||||
|
}
|
||||||
273
EdgeAPI/internal/db/models/httpdns_node_dao.go
Normal file
273
EdgeAPI/internal/db/models/httpdns_node_dao.go
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
"github.com/iwind/TeaGo/rands"
|
||||||
|
"github.com/iwind/TeaGo/types"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HTTPDNSNodeStateEnabled = 1 // 已启用
|
||||||
|
HTTPDNSNodeStateDisabled = 0 // 已禁用
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPDNSNodeDAO dbs.DAO
|
||||||
|
|
||||||
|
func NewHTTPDNSNodeDAO() *HTTPDNSNodeDAO {
|
||||||
|
return dbs.NewDAO(&HTTPDNSNodeDAO{
|
||||||
|
DAOObject: dbs.DAOObject{
|
||||||
|
DB: Tea.Env,
|
||||||
|
Table: "edgeHTTPDNSNodes",
|
||||||
|
Model: new(HTTPDNSNode),
|
||||||
|
PkName: "id",
|
||||||
|
},
|
||||||
|
}).(*HTTPDNSNodeDAO)
|
||||||
|
}
|
||||||
|
|
||||||
|
var SharedHTTPDNSNodeDAO *HTTPDNSNodeDAO
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dbs.OnReady(func() {
|
||||||
|
SharedHTTPDNSNodeDAO = NewHTTPDNSNodeDAO()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindEnabledNodeIdWithUniqueId 根据唯一ID获取启用中的HTTPDNS节点ID
|
||||||
|
func (this *HTTPDNSNodeDAO) FindEnabledNodeIdWithUniqueId(tx *dbs.Tx, uniqueId string) (int64, error) {
|
||||||
|
return this.Query(tx).
|
||||||
|
Attr("uniqueId", uniqueId).
|
||||||
|
Attr("state", HTTPDNSNodeStateEnabled).
|
||||||
|
ResultPk().
|
||||||
|
FindInt64Col(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNode 创建节点
|
||||||
|
func (this *HTTPDNSNodeDAO) CreateNode(tx *dbs.Tx, clusterId int64, name string, installDir string, isOn bool) (int64, error) {
|
||||||
|
uniqueId := rands.HexString(32)
|
||||||
|
secret := rands.String(32)
|
||||||
|
err := SharedApiTokenDAO.CreateAPIToken(tx, uniqueId, secret, nodeconfigs.NodeRoleHTTPDNS)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var op = NewHTTPDNSNodeOperator()
|
||||||
|
op.ClusterId = clusterId
|
||||||
|
op.Name = name
|
||||||
|
op.IsOn = isOn
|
||||||
|
op.IsUp = false
|
||||||
|
op.IsInstalled = false
|
||||||
|
op.IsActive = false
|
||||||
|
op.UniqueId = uniqueId
|
||||||
|
op.Secret = secret
|
||||||
|
op.InstallDir = installDir
|
||||||
|
op.CreatedAt = time.Now().Unix()
|
||||||
|
op.UpdatedAt = time.Now().Unix()
|
||||||
|
op.State = HTTPDNSNodeStateEnabled
|
||||||
|
err = this.Save(tx, op)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return types.Int64(op.Id), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNode 更新节点
|
||||||
|
func (this *HTTPDNSNodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, name string, installDir string, isOn bool) error {
|
||||||
|
var op = NewHTTPDNSNodeOperator()
|
||||||
|
op.Id = nodeId
|
||||||
|
op.Name = name
|
||||||
|
op.InstallDir = installDir
|
||||||
|
op.IsOn = isOn
|
||||||
|
op.UpdatedAt = time.Now().Unix()
|
||||||
|
return this.Save(tx, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableNode 禁用节点
|
||||||
|
func (this *HTTPDNSNodeDAO) DisableNode(tx *dbs.Tx, nodeId int64) error {
|
||||||
|
node, err := this.FindEnabledNode(tx, nodeId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = this.Query(tx).
|
||||||
|
Pk(nodeId).
|
||||||
|
Set("state", HTTPDNSNodeStateDisabled).
|
||||||
|
Update()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = SharedApiTokenDAO.Query(tx).
|
||||||
|
Attr("nodeId", node.UniqueId).
|
||||||
|
Attr("role", nodeconfigs.NodeRoleHTTPDNS).
|
||||||
|
Set("state", ApiTokenStateDisabled).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindEnabledNode 查找启用节点
|
||||||
|
func (this *HTTPDNSNodeDAO) FindEnabledNode(tx *dbs.Tx, nodeId int64) (*HTTPDNSNode, error) {
|
||||||
|
one, err := this.Query(tx).
|
||||||
|
Pk(nodeId).
|
||||||
|
Attr("state", HTTPDNSNodeStateEnabled).
|
||||||
|
Find()
|
||||||
|
if one == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return one.(*HTTPDNSNode), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindNodeClusterId 查询节点所属集群ID
|
||||||
|
func (this *HTTPDNSNodeDAO) FindNodeClusterId(tx *dbs.Tx, nodeId int64) (int64, error) {
|
||||||
|
return this.Query(tx).
|
||||||
|
Pk(nodeId).
|
||||||
|
Attr("state", HTTPDNSNodeStateEnabled).
|
||||||
|
Result("clusterId").
|
||||||
|
FindInt64Col(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEnabledNodes 列出节点
|
||||||
|
func (this *HTTPDNSNodeDAO) ListEnabledNodes(tx *dbs.Tx, clusterId int64) (result []*HTTPDNSNode, err error) {
|
||||||
|
query := this.Query(tx).
|
||||||
|
State(HTTPDNSNodeStateEnabled).
|
||||||
|
AscPk()
|
||||||
|
if clusterId > 0 {
|
||||||
|
query = query.Attr("clusterId", clusterId)
|
||||||
|
}
|
||||||
|
_, err = query.Slice(&result).FindAll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNodeStatus 更新节点状态
|
||||||
|
func (this *HTTPDNSNodeDAO) UpdateNodeStatus(tx *dbs.Tx, nodeId int64, isUp bool, isInstalled bool, isActive bool, statusJSON []byte, installStatusJSON []byte) error {
|
||||||
|
var op = NewHTTPDNSNodeOperator()
|
||||||
|
op.Id = nodeId
|
||||||
|
op.IsUp = isUp
|
||||||
|
op.IsInstalled = isInstalled
|
||||||
|
op.IsActive = isActive
|
||||||
|
op.UpdatedAt = time.Now().Unix()
|
||||||
|
if len(statusJSON) > 0 {
|
||||||
|
op.Status = statusJSON
|
||||||
|
}
|
||||||
|
if len(installStatusJSON) > 0 {
|
||||||
|
mergedStatusJSON, mergeErr := this.mergeInstallStatusJSON(tx, nodeId, installStatusJSON)
|
||||||
|
if mergeErr != nil {
|
||||||
|
return mergeErr
|
||||||
|
}
|
||||||
|
op.InstallStatus = mergedStatusJSON
|
||||||
|
}
|
||||||
|
return this.Save(tx, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNodeInstallStatus 更新节点安装状态
|
||||||
|
func (this *HTTPDNSNodeDAO) UpdateNodeInstallStatus(tx *dbs.Tx, nodeId int64, installStatus *NodeInstallStatus) error {
|
||||||
|
if installStatus == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read existing installStatus to preserve custom fields like 'ssh' and 'ipAddr'
|
||||||
|
raw, err := this.Query(tx).Pk(nodeId).Result("installStatus").FindBytesCol()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var m = map[string]interface{}{}
|
||||||
|
if len(raw) > 0 {
|
||||||
|
_ = json.Unmarshal(raw, &m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overlay standard install status fields
|
||||||
|
statusData, err := json.Marshal(installStatus)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var newStatusMap = map[string]interface{}{}
|
||||||
|
_ = json.Unmarshal(statusData, &newStatusMap)
|
||||||
|
|
||||||
|
for k, v := range newStatusMap {
|
||||||
|
m[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-marshal the merged map
|
||||||
|
mergedData, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = this.Query(tx).
|
||||||
|
Pk(nodeId).
|
||||||
|
Set("installStatus", mergedData).
|
||||||
|
Set("updatedAt", time.Now().Unix()).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSNodeDAO) mergeInstallStatusJSON(tx *dbs.Tx, nodeId int64, patch []byte) ([]byte, error) {
|
||||||
|
if len(patch) == 0 {
|
||||||
|
return patch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := this.Query(tx).Pk(nodeId).Result("installStatus").FindBytesCol()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
merged := map[string]interface{}{}
|
||||||
|
if len(raw) > 0 {
|
||||||
|
_ = json.Unmarshal(raw, &merged)
|
||||||
|
}
|
||||||
|
patchMap := map[string]interface{}{}
|
||||||
|
if len(patch) > 0 {
|
||||||
|
_ = json.Unmarshal(patch, &patchMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range patchMap {
|
||||||
|
merged[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(merged)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindNodeInstallStatus 读取节点安装状态
|
||||||
|
func (this *HTTPDNSNodeDAO) FindNodeInstallStatus(tx *dbs.Tx, nodeId int64) (*NodeInstallStatus, error) {
|
||||||
|
raw, err := this.Query(tx).
|
||||||
|
Pk(nodeId).
|
||||||
|
State(HTTPDNSNodeStateEnabled).
|
||||||
|
Result("installStatus").
|
||||||
|
FindBytesCol()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(raw) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
installStatus := &NodeInstallStatus{}
|
||||||
|
err = json.Unmarshal(raw, installStatus)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return installStatus, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateNodeIsInstalled 更新节点安装状态位
|
||||||
|
func (this *HTTPDNSNodeDAO) UpdateNodeIsInstalled(tx *dbs.Tx, nodeId int64, isInstalled bool) error {
|
||||||
|
_, err := this.Query(tx).
|
||||||
|
Pk(nodeId).
|
||||||
|
State(HTTPDNSNodeStateEnabled).
|
||||||
|
Set("isInstalled", isInstalled).
|
||||||
|
Set("updatedAt", time.Now().Unix()).
|
||||||
|
Update()
|
||||||
|
return err
|
||||||
|
}
|
||||||
44
EdgeAPI/internal/db/models/httpdns_node_model.go
Normal file
44
EdgeAPI/internal/db/models/httpdns_node_model.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "github.com/iwind/TeaGo/dbs"
|
||||||
|
|
||||||
|
// HTTPDNSNode HTTPDNS节点
|
||||||
|
type HTTPDNSNode struct {
|
||||||
|
Id uint32 `field:"id"` // ID
|
||||||
|
ClusterId uint32 `field:"clusterId"` // 集群ID
|
||||||
|
Name string `field:"name"` // 节点名称
|
||||||
|
IsOn bool `field:"isOn"` // 是否启用
|
||||||
|
IsUp bool `field:"isUp"` // 是否在线
|
||||||
|
IsInstalled bool `field:"isInstalled"` // 是否已安装
|
||||||
|
IsActive bool `field:"isActive"` // 是否活跃
|
||||||
|
UniqueId string `field:"uniqueId"` // 节点唯一ID
|
||||||
|
Secret string `field:"secret"` // 节点密钥
|
||||||
|
InstallDir string `field:"installDir"` // 安装目录
|
||||||
|
Status dbs.JSON `field:"status"` // 运行状态快照
|
||||||
|
InstallStatus dbs.JSON `field:"installStatus"` // 安装状态
|
||||||
|
CreatedAt uint64 `field:"createdAt"` // 创建时间
|
||||||
|
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
|
||||||
|
State uint8 `field:"state"` // 记录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPDNSNodeOperator struct {
|
||||||
|
Id any // ID
|
||||||
|
ClusterId any // 集群ID
|
||||||
|
Name any // 节点名称
|
||||||
|
IsOn any // 是否启用
|
||||||
|
IsUp any // 是否在线
|
||||||
|
IsInstalled any // 是否已安装
|
||||||
|
IsActive any // 是否活跃
|
||||||
|
UniqueId any // 节点唯一ID
|
||||||
|
Secret any // 节点密钥
|
||||||
|
InstallDir any // 安装目录
|
||||||
|
Status any // 运行状态快照
|
||||||
|
InstallStatus any // 安装状态
|
||||||
|
CreatedAt any // 创建时间
|
||||||
|
UpdatedAt any // 修改时间
|
||||||
|
State any // 记录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPDNSNodeOperator() *HTTPDNSNodeOperator {
|
||||||
|
return &HTTPDNSNodeOperator{}
|
||||||
|
}
|
||||||
108
EdgeAPI/internal/db/models/httpdns_runtime_log_dao.go
Normal file
108
EdgeAPI/internal/db/models/httpdns_runtime_log_dao.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPDNSRuntimeLogDAO dbs.DAO
|
||||||
|
|
||||||
|
func NewHTTPDNSRuntimeLogDAO() *HTTPDNSRuntimeLogDAO {
|
||||||
|
return dbs.NewDAO(&HTTPDNSRuntimeLogDAO{
|
||||||
|
DAOObject: dbs.DAOObject{
|
||||||
|
DB: Tea.Env,
|
||||||
|
Table: "edgeHTTPDNSRuntimeLogs",
|
||||||
|
Model: new(HTTPDNSRuntimeLog),
|
||||||
|
PkName: "id",
|
||||||
|
},
|
||||||
|
}).(*HTTPDNSRuntimeLogDAO)
|
||||||
|
}
|
||||||
|
|
||||||
|
var SharedHTTPDNSRuntimeLogDAO *HTTPDNSRuntimeLogDAO
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dbs.OnReady(func() {
|
||||||
|
SharedHTTPDNSRuntimeLogDAO = NewHTTPDNSRuntimeLogDAO()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSRuntimeLogDAO) CreateLog(tx *dbs.Tx, log *HTTPDNSRuntimeLog) error {
|
||||||
|
lastLog, err := this.Query(tx).
|
||||||
|
Result("id", "clusterId", "nodeId", "level", "type", "module", "description", "createdAt").
|
||||||
|
DescPk().
|
||||||
|
Find()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if lastLog != nil {
|
||||||
|
nodeLog := lastLog.(*HTTPDNSRuntimeLog)
|
||||||
|
if nodeLog.ClusterId == log.ClusterId &&
|
||||||
|
nodeLog.NodeId == log.NodeId &&
|
||||||
|
nodeLog.Level == log.Level &&
|
||||||
|
nodeLog.Type == log.Type &&
|
||||||
|
nodeLog.Module == log.Module &&
|
||||||
|
nodeLog.Description == log.Description &&
|
||||||
|
time.Now().Unix()-int64(nodeLog.CreatedAt) < 1800 {
|
||||||
|
|
||||||
|
count := log.Count
|
||||||
|
if count <= 0 {
|
||||||
|
count = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.Query(tx).
|
||||||
|
Pk(nodeLog.Id).
|
||||||
|
Set("count", dbs.SQL("count+"+strconv.FormatInt(count, 10))).
|
||||||
|
UpdateQuickly()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var op = NewHTTPDNSRuntimeLogOperator()
|
||||||
|
op.ClusterId = log.ClusterId
|
||||||
|
op.NodeId = log.NodeId
|
||||||
|
op.Level = log.Level
|
||||||
|
op.Type = log.Type
|
||||||
|
op.Module = log.Module
|
||||||
|
op.Description = log.Description
|
||||||
|
op.Count = log.Count
|
||||||
|
op.RequestId = log.RequestId
|
||||||
|
op.CreatedAt = log.CreatedAt
|
||||||
|
op.Day = log.Day
|
||||||
|
return this.Save(tx, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSRuntimeLogDAO) BuildListQuery(tx *dbs.Tx, day string, clusterId int64, nodeId int64, level string, keyword string) *dbs.Query {
|
||||||
|
query := this.Query(tx).DescPk()
|
||||||
|
if len(day) > 0 {
|
||||||
|
query = query.Attr("day", day)
|
||||||
|
}
|
||||||
|
if clusterId > 0 {
|
||||||
|
query = query.Attr("clusterId", clusterId)
|
||||||
|
}
|
||||||
|
if nodeId > 0 {
|
||||||
|
query = query.Attr("nodeId", nodeId)
|
||||||
|
}
|
||||||
|
if len(level) > 0 {
|
||||||
|
query = query.Attr("level", level)
|
||||||
|
}
|
||||||
|
if len(keyword) > 0 {
|
||||||
|
query = query.Where("(type LIKE :kw OR module LIKE :kw OR description LIKE :kw OR requestId LIKE :kw)").Param("kw", "%"+keyword+"%")
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSRuntimeLogDAO) CountLogs(tx *dbs.Tx, day string, clusterId int64, nodeId int64, level string, keyword string) (int64, error) {
|
||||||
|
return this.BuildListQuery(tx, day, clusterId, nodeId, level, keyword).Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSRuntimeLogDAO) ListLogs(tx *dbs.Tx, day string, clusterId int64, nodeId int64, level string, keyword string, offset int64, size int64) (result []*HTTPDNSRuntimeLog, err error) {
|
||||||
|
_, err = this.BuildListQuery(tx, day, clusterId, nodeId, level, keyword).
|
||||||
|
Offset(offset).
|
||||||
|
Limit(size).
|
||||||
|
Slice(&result).
|
||||||
|
FindAll()
|
||||||
|
return
|
||||||
|
}
|
||||||
34
EdgeAPI/internal/db/models/httpdns_runtime_log_model.go
Normal file
34
EdgeAPI/internal/db/models/httpdns_runtime_log_model.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// HTTPDNSRuntimeLog 运行日志
|
||||||
|
type HTTPDNSRuntimeLog struct {
|
||||||
|
Id uint64 `field:"id"` // ID
|
||||||
|
ClusterId uint32 `field:"clusterId"` // 集群ID
|
||||||
|
NodeId uint32 `field:"nodeId"` // 节点ID
|
||||||
|
Level string `field:"level"` // 级别
|
||||||
|
Type string `field:"type"` // 类型
|
||||||
|
Module string `field:"module"` // 模块
|
||||||
|
Description string `field:"description"` // 详情
|
||||||
|
Count int64 `field:"count"` // 次数
|
||||||
|
RequestId string `field:"requestId"` // 请求ID
|
||||||
|
CreatedAt uint64 `field:"createdAt"` // 创建时间
|
||||||
|
Day string `field:"day"` // YYYYMMDD
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPDNSRuntimeLogOperator struct {
|
||||||
|
Id any // ID
|
||||||
|
ClusterId any // 集群ID
|
||||||
|
NodeId any // 节点ID
|
||||||
|
Level any // 级别
|
||||||
|
Type any // 类型
|
||||||
|
Module any // 模块
|
||||||
|
Description any // 详情
|
||||||
|
Count any // 次数
|
||||||
|
RequestId any // 请求ID
|
||||||
|
CreatedAt any // 创建时间
|
||||||
|
Day any // YYYYMMDD
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPDNSRuntimeLogOperator() *HTTPDNSRuntimeLogOperator {
|
||||||
|
return &HTTPDNSRuntimeLogOperator{}
|
||||||
|
}
|
||||||
@@ -15,35 +15,41 @@ import (
|
|||||||
type NodeTaskType = string
|
type NodeTaskType = string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// CDN相关
|
// CDN鐩稿叧
|
||||||
|
|
||||||
NodeTaskTypeConfigChanged NodeTaskType = "configChanged" // 节点整体配置变化
|
NodeTaskTypeConfigChanged NodeTaskType = "configChanged" // 鑺傜偣鏁翠綋閰嶇疆鍙樺寲
|
||||||
NodeTaskTypeDDosProtectionChanged NodeTaskType = "ddosProtectionChanged" // 节点DDoS配置变更
|
NodeTaskTypeDDosProtectionChanged NodeTaskType = "ddosProtectionChanged" // 鑺傜偣DDoS閰嶇疆鍙樻洿
|
||||||
NodeTaskTypeGlobalServerConfigChanged NodeTaskType = "globalServerConfigChanged" // 全局服务设置变化
|
NodeTaskTypeGlobalServerConfigChanged NodeTaskType = "globalServerConfigChanged" // 鍏ㄥ眬鏈嶅姟璁剧疆鍙樺寲
|
||||||
NodeTaskTypeIPListDeleted NodeTaskType = "ipListDeleted" // IPList被删除
|
NodeTaskTypeIPListDeleted NodeTaskType = "ipListDeleted" // IPList琚垹闄?
|
||||||
NodeTaskTypeIPItemChanged NodeTaskType = "ipItemChanged" // IP条目变更
|
NodeTaskTypeIPItemChanged NodeTaskType = "ipItemChanged" // IP鏉$洰鍙樻洿
|
||||||
NodeTaskTypeNodeVersionChanged NodeTaskType = "nodeVersionChanged" // 节点版本变化
|
NodeTaskTypeNodeVersionChanged NodeTaskType = "nodeVersionChanged" // 鑺傜偣鐗堟湰鍙樺寲
|
||||||
NodeTaskTypeScriptsChanged NodeTaskType = "scriptsChanged" // 脚本配置变化
|
NodeTaskTypeScriptsChanged NodeTaskType = "scriptsChanged" // 鑴氭湰閰嶇疆鍙樺寲
|
||||||
NodeTaskTypeNodeLevelChanged NodeTaskType = "nodeLevelChanged" // 节点级别变化
|
NodeTaskTypeNodeLevelChanged NodeTaskType = "nodeLevelChanged" // 鑺傜偣绾у埆鍙樺寲
|
||||||
NodeTaskTypeUserServersStateChanged NodeTaskType = "userServersStateChanged" // 用户服务状态变化
|
NodeTaskTypeUserServersStateChanged NodeTaskType = "userServersStateChanged" // 鐢ㄦ埛鏈嶅姟鐘舵€佸彉鍖?
|
||||||
NodeTaskTypeUAMPolicyChanged NodeTaskType = "uamPolicyChanged" // UAM策略变化
|
NodeTaskTypeUAMPolicyChanged NodeTaskType = "uamPolicyChanged" // UAM绛栫暐鍙樺寲
|
||||||
NodeTaskTypeHTTPPagesPolicyChanged NodeTaskType = "httpPagesPolicyChanged" // 自定义页面变化
|
NodeTaskTypeHTTPPagesPolicyChanged NodeTaskType = "httpPagesPolicyChanged" // 鑷畾涔夐〉闈㈠彉鍖?
|
||||||
NodeTaskTypeHTTPCCPolicyChanged NodeTaskType = "httpCCPolicyChanged" // CC策略变化
|
NodeTaskTypeHTTPCCPolicyChanged NodeTaskType = "httpCCPolicyChanged" // CC绛栫暐鍙樺寲
|
||||||
NodeTaskTypeHTTP3PolicyChanged NodeTaskType = "http3PolicyChanged" // HTTP3策略变化
|
NodeTaskTypeHTTP3PolicyChanged NodeTaskType = "http3PolicyChanged" // HTTP3绛栫暐鍙樺寲
|
||||||
NodeTaskTypeNetworkSecurityPolicyChanged NodeTaskType = "networkSecurityPolicyChanged" // 网络安全策略变化
|
NodeTaskTypeNetworkSecurityPolicyChanged NodeTaskType = "networkSecurityPolicyChanged" // 缃戠粶瀹夊叏绛栫暐鍙樺寲
|
||||||
NodeTaskTypeWebPPolicyChanged NodeTaskType = "webPPolicyChanged" // WebP策略变化
|
NodeTaskTypeWebPPolicyChanged NodeTaskType = "webPPolicyChanged" // WebP绛栫暐鍙樺寲
|
||||||
NodeTaskTypeUpdatingServers NodeTaskType = "updatingServers" // 更新一组服务
|
NodeTaskTypeUpdatingServers NodeTaskType = "updatingServers" // 鏇存柊涓€缁勬湇鍔?
|
||||||
NodeTaskTypeTOAChanged NodeTaskType = "toaChanged" // TOA配置变化
|
NodeTaskTypeTOAChanged NodeTaskType = "toaChanged" // TOA閰嶇疆鍙樺寲
|
||||||
NodeTaskTypePlanChanged NodeTaskType = "planChanged" // 套餐变化
|
NodeTaskTypePlanChanged NodeTaskType = "planChanged" // 濂楅鍙樺寲
|
||||||
|
|
||||||
// NS相关
|
// NS鐩稿叧
|
||||||
|
|
||||||
NSNodeTaskTypeConfigChanged NodeTaskType = "nsConfigChanged"
|
NSNodeTaskTypeConfigChanged NodeTaskType = "nsConfigChanged"
|
||||||
NSNodeTaskTypeDomainChanged NodeTaskType = "nsDomainChanged"
|
NSNodeTaskTypeDomainChanged NodeTaskType = "nsDomainChanged"
|
||||||
NSNodeTaskTypeRecordChanged NodeTaskType = "nsRecordChanged"
|
NSNodeTaskTypeRecordChanged NodeTaskType = "nsRecordChanged"
|
||||||
NSNodeTaskTypeRouteChanged NodeTaskType = "nsRouteChanged"
|
NSNodeTaskTypeRouteChanged NodeTaskType = "nsRouteChanged"
|
||||||
NSNodeTaskTypeKeyChanged NodeTaskType = "nsKeyChanged"
|
NSNodeTaskTypeKeyChanged NodeTaskType = "nsKeyChanged"
|
||||||
NSNodeTaskTypeDDosProtectionChanged NodeTaskType = "nsDDoSProtectionChanged" // 节点DDoS配置变更
|
NSNodeTaskTypeDDosProtectionChanged NodeTaskType = "nsDDoSProtectionChanged" // 鑺傜偣DDoS閰嶇疆鍙樻洿
|
||||||
|
// HTTPDNS相关
|
||||||
|
HTTPDNSNodeTaskTypeConfigChanged NodeTaskType = "httpdnsConfigChanged"
|
||||||
|
HTTPDNSNodeTaskTypeAppChanged NodeTaskType = "httpdnsAppChanged"
|
||||||
|
HTTPDNSNodeTaskTypeDomainChanged NodeTaskType = "httpdnsDomainChanged"
|
||||||
|
HTTPDNSNodeTaskTypeRuleChanged NodeTaskType = "httpdnsRuleChanged"
|
||||||
|
HTTPDNSNodeTaskTypeTLSChanged NodeTaskType = "httpdnsTLSChanged"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NodeTaskDAO dbs.DAO
|
type NodeTaskDAO dbs.DAO
|
||||||
@@ -67,15 +73,15 @@ func init() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateNodeTask 创建单个节点任务
|
// CreateNodeTask 鍒涘缓鍗曚釜鑺傜偣浠诲姟
|
||||||
func (this *NodeTaskDAO) CreateNodeTask(tx *dbs.Tx, role string, clusterId int64, nodeId int64, userId int64, serverId int64, taskType NodeTaskType) error {
|
func (this *NodeTaskDAO) CreateNodeTask(tx *dbs.Tx, role string, clusterId int64, nodeId int64, userId int64, serverId int64, taskType NodeTaskType) error {
|
||||||
if clusterId <= 0 || nodeId <= 0 {
|
if clusterId <= 0 || nodeId <= 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var uniqueId = role + "@" + types.String(nodeId) + "@node@" + types.String(serverId) + "@" + taskType
|
var uniqueId = role + "@" + types.String(nodeId) + "@node@" + types.String(serverId) + "@" + taskType
|
||||||
|
|
||||||
// 用户信息
|
// 鐢ㄦ埛淇℃伅
|
||||||
// 没有直接加入到 uniqueId 中,是为了兼容以前的字段值
|
// 娌℃湁鐩存帴鍔犲叆鍒?uniqueId 涓紝鏄负浜嗗吋瀹逛互鍓嶇殑瀛楁鍊?
|
||||||
if userId > 0 {
|
if userId > 0 {
|
||||||
uniqueId += "@" + types.String(userId)
|
uniqueId += "@" + types.String(userId)
|
||||||
}
|
}
|
||||||
@@ -113,7 +119,7 @@ func (this *NodeTaskDAO) CreateNodeTask(tx *dbs.Tx, role string, clusterId int64
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateClusterTask 创建集群任务
|
// CreateClusterTask 鍒涘缓闆嗙兢浠诲姟
|
||||||
func (this *NodeTaskDAO) CreateClusterTask(tx *dbs.Tx, role string, clusterId int64, userId int64, serverId int64, taskType NodeTaskType) error {
|
func (this *NodeTaskDAO) CreateClusterTask(tx *dbs.Tx, role string, clusterId int64, userId int64, serverId int64, taskType NodeTaskType) error {
|
||||||
if clusterId <= 0 {
|
if clusterId <= 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -121,8 +127,8 @@ func (this *NodeTaskDAO) CreateClusterTask(tx *dbs.Tx, role string, clusterId in
|
|||||||
|
|
||||||
var uniqueId = role + "@" + types.String(clusterId) + "@" + types.String(serverId) + "@cluster@" + taskType
|
var uniqueId = role + "@" + types.String(clusterId) + "@" + types.String(serverId) + "@cluster@" + taskType
|
||||||
|
|
||||||
// 用户信息
|
// 鐢ㄦ埛淇℃伅
|
||||||
// 没有直接加入到 uniqueId 中,是为了兼容以前的字段值
|
// 娌℃湁鐩存帴鍔犲叆鍒?uniqueId 涓紝鏄负浜嗗吋瀹逛互鍓嶇殑瀛楁鍊?
|
||||||
if userId > 0 {
|
if userId > 0 {
|
||||||
uniqueId += "@" + types.String(userId)
|
uniqueId += "@" + types.String(userId)
|
||||||
}
|
}
|
||||||
@@ -155,7 +161,7 @@ func (this *NodeTaskDAO) CreateClusterTask(tx *dbs.Tx, role string, clusterId in
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractNodeClusterTask 分解边缘节点集群任务
|
// ExtractNodeClusterTask 鍒嗚В杈圭紭鑺傜偣闆嗙兢浠诲姟
|
||||||
func (this *NodeTaskDAO) ExtractNodeClusterTask(tx *dbs.Tx, clusterId int64, userId int64, serverId int64, taskType NodeTaskType) error {
|
func (this *NodeTaskDAO) ExtractNodeClusterTask(tx *dbs.Tx, clusterId int64, userId int64, serverId int64, taskType NodeTaskType) error {
|
||||||
nodeIds, err := SharedNodeDAO.FindAllNodeIdsMatch(tx, clusterId, true, configutils.BoolStateYes)
|
nodeIds, err := SharedNodeDAO.FindAllNodeIdsMatch(tx, clusterId, true, configutils.BoolStateYes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -193,7 +199,7 @@ func (this *NodeTaskDAO) ExtractNodeClusterTask(tx *dbs.Tx, clusterId int64, use
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractAllClusterTasks 分解所有集群任务
|
// ExtractAllClusterTasks 鍒嗚В鎵€鏈夐泦缇や换鍔?
|
||||||
func (this *NodeTaskDAO) ExtractAllClusterTasks(tx *dbs.Tx, role string) error {
|
func (this *NodeTaskDAO) ExtractAllClusterTasks(tx *dbs.Tx, role string) error {
|
||||||
ones, err := this.Query(tx).
|
ones, err := this.Query(tx).
|
||||||
Attr("role", role).
|
Attr("role", role).
|
||||||
@@ -216,12 +222,17 @@ func (this *NodeTaskDAO) ExtractAllClusterTasks(tx *dbs.Tx, role string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case nodeconfigs.NodeRoleHTTPDNS:
|
||||||
|
err = this.ExtractHTTPDNSClusterTask(tx, clusterId, one.(*NodeTask).Type)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAllClusterTasks 删除集群所有相关任务
|
// DeleteAllClusterTasks 鍒犻櫎闆嗙兢鎵€鏈夌浉鍏充换鍔?
|
||||||
func (this *NodeTaskDAO) DeleteAllClusterTasks(tx *dbs.Tx, role string, clusterId int64) error {
|
func (this *NodeTaskDAO) DeleteAllClusterTasks(tx *dbs.Tx, role string, clusterId int64) error {
|
||||||
_, err := this.Query(tx).
|
_, err := this.Query(tx).
|
||||||
Attr("role", role).
|
Attr("role", role).
|
||||||
@@ -230,7 +241,7 @@ func (this *NodeTaskDAO) DeleteAllClusterTasks(tx *dbs.Tx, role string, clusterI
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteNodeTasks 删除节点相关任务
|
// DeleteNodeTasks 鍒犻櫎鑺傜偣鐩稿叧浠诲姟
|
||||||
func (this *NodeTaskDAO) DeleteNodeTasks(tx *dbs.Tx, role string, nodeId int64) error {
|
func (this *NodeTaskDAO) DeleteNodeTasks(tx *dbs.Tx, role string, nodeId int64) error {
|
||||||
_, err := this.Query(tx).
|
_, err := this.Query(tx).
|
||||||
Attr("role", role).
|
Attr("role", role).
|
||||||
@@ -239,13 +250,13 @@ func (this *NodeTaskDAO) DeleteNodeTasks(tx *dbs.Tx, role string, nodeId int64)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAllNodeTasks 删除所有节点相关任务
|
// DeleteAllNodeTasks 鍒犻櫎鎵€鏈夎妭鐐圭浉鍏充换鍔?
|
||||||
func (this *NodeTaskDAO) DeleteAllNodeTasks(tx *dbs.Tx) error {
|
func (this *NodeTaskDAO) DeleteAllNodeTasks(tx *dbs.Tx) error {
|
||||||
return this.Query(tx).
|
return this.Query(tx).
|
||||||
DeleteQuickly()
|
DeleteQuickly()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindDoingNodeTasks 查询一个节点的所有任务
|
// FindDoingNodeTasks 鏌ヨ涓€涓妭鐐圭殑鎵€鏈変换鍔?
|
||||||
func (this *NodeTaskDAO) FindDoingNodeTasks(tx *dbs.Tx, role string, nodeId int64, version int64) (result []*NodeTask, err error) {
|
func (this *NodeTaskDAO) FindDoingNodeTasks(tx *dbs.Tx, role string, nodeId int64, version int64) (result []*NodeTask, err error) {
|
||||||
if nodeId <= 0 {
|
if nodeId <= 0 {
|
||||||
return
|
return
|
||||||
@@ -256,10 +267,10 @@ func (this *NodeTaskDAO) FindDoingNodeTasks(tx *dbs.Tx, role string, nodeId int6
|
|||||||
UseIndex("nodeId").
|
UseIndex("nodeId").
|
||||||
Asc("version")
|
Asc("version")
|
||||||
if version > 0 {
|
if version > 0 {
|
||||||
query.Lt("LENGTH(version)", 19) // 兼容以往版本
|
query.Lt("LENGTH(version)", 19) // 鍏煎浠ュ線鐗堟湰
|
||||||
query.Gt("version", version)
|
query.Gt("version", version)
|
||||||
} else {
|
} else {
|
||||||
// 第一次访问时只取当前正在执行的或者执行失败的
|
// 绗竴娆¤闂椂鍙彇褰撳墠姝e湪鎵ц鐨勬垨鑰呮墽琛屽け璐ョ殑
|
||||||
query.Where("(isDone=0 OR (isDone=1 AND isOk=0))")
|
query.Where("(isDone=0 OR (isDone=1 AND isOk=0))")
|
||||||
}
|
}
|
||||||
_, err = query.
|
_, err = query.
|
||||||
@@ -268,10 +279,10 @@ func (this *NodeTaskDAO) FindDoingNodeTasks(tx *dbs.Tx, role string, nodeId int6
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateNodeTaskDone 修改节点任务的完成状态
|
// UpdateNodeTaskDone 淇敼鑺傜偣浠诲姟鐨勫畬鎴愮姸鎬?
|
||||||
func (this *NodeTaskDAO) UpdateNodeTaskDone(tx *dbs.Tx, taskId int64, isOk bool, errorMessage string) error {
|
func (this *NodeTaskDAO) UpdateNodeTaskDone(tx *dbs.Tx, taskId int64, isOk bool, errorMessage string) error {
|
||||||
if isOk {
|
if isOk {
|
||||||
// 特殊任务删除
|
// 鐗规畩浠诲姟鍒犻櫎
|
||||||
taskType, err := this.Query(tx).
|
taskType, err := this.Query(tx).
|
||||||
Pk(taskId).
|
Pk(taskId).
|
||||||
Result("type").
|
Result("type").
|
||||||
@@ -286,7 +297,7 @@ func (this *NodeTaskDAO) UpdateNodeTaskDone(tx *dbs.Tx, taskId int64, isOk bool,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其他任务标记为完成
|
// 鍏朵粬浠诲姟鏍囪涓哄畬鎴?
|
||||||
var query = this.Query(tx).
|
var query = this.Query(tx).
|
||||||
Pk(taskId)
|
Pk(taskId)
|
||||||
if !isOk {
|
if !isOk {
|
||||||
@@ -305,7 +316,7 @@ func (this *NodeTaskDAO) UpdateNodeTaskDone(tx *dbs.Tx, taskId int64, isOk bool,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindAllDoingTaskClusterIds 查找正在更新的集群IDs
|
// FindAllDoingTaskClusterIds 鏌ユ壘姝e湪鏇存柊鐨勯泦缇Ds
|
||||||
func (this *NodeTaskDAO) FindAllDoingTaskClusterIds(tx *dbs.Tx, role string) ([]int64, error) {
|
func (this *NodeTaskDAO) FindAllDoingTaskClusterIds(tx *dbs.Tx, role string) ([]int64, error) {
|
||||||
ones, _, err := this.Query(tx).
|
ones, _, err := this.Query(tx).
|
||||||
Result("DISTINCT(clusterId) AS clusterId").
|
Result("DISTINCT(clusterId) AS clusterId").
|
||||||
@@ -322,7 +333,7 @@ func (this *NodeTaskDAO) FindAllDoingTaskClusterIds(tx *dbs.Tx, role string) ([]
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindAllDoingNodeTasksWithClusterId 查询某个集群下所有的任务
|
// FindAllDoingNodeTasksWithClusterId 鏌ヨ鏌愪釜闆嗙兢涓嬫墍鏈夌殑浠诲姟
|
||||||
func (this *NodeTaskDAO) FindAllDoingNodeTasksWithClusterId(tx *dbs.Tx, role string, clusterId int64) (result []*NodeTask, err error) {
|
func (this *NodeTaskDAO) FindAllDoingNodeTasksWithClusterId(tx *dbs.Tx, role string, clusterId int64) (result []*NodeTask, err error) {
|
||||||
_, err = this.Query(tx).
|
_, err = this.Query(tx).
|
||||||
Attr("role", role).
|
Attr("role", role).
|
||||||
@@ -337,7 +348,7 @@ func (this *NodeTaskDAO) FindAllDoingNodeTasksWithClusterId(tx *dbs.Tx, role str
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindAllDoingNodeIds 查询有任务的节点IDs
|
// FindAllDoingNodeIds 鏌ヨ鏈変换鍔$殑鑺傜偣IDs
|
||||||
func (this *NodeTaskDAO) FindAllDoingNodeIds(tx *dbs.Tx, role string) ([]int64, error) {
|
func (this *NodeTaskDAO) FindAllDoingNodeIds(tx *dbs.Tx, role string) ([]int64, error) {
|
||||||
ones, err := this.Query(tx).
|
ones, err := this.Query(tx).
|
||||||
Result("DISTINCT(nodeId) AS nodeId").
|
Result("DISTINCT(nodeId) AS nodeId").
|
||||||
@@ -356,7 +367,7 @@ func (this *NodeTaskDAO) FindAllDoingNodeIds(tx *dbs.Tx, role string) ([]int64,
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExistsDoingNodeTasks 检查是否有正在执行的任务
|
// ExistsDoingNodeTasks 妫€鏌ユ槸鍚︽湁姝e湪鎵ц鐨勪换鍔?
|
||||||
func (this *NodeTaskDAO) ExistsDoingNodeTasks(tx *dbs.Tx, role string, excludeTypes []NodeTaskType) (bool, error) {
|
func (this *NodeTaskDAO) ExistsDoingNodeTasks(tx *dbs.Tx, role string, excludeTypes []NodeTaskType) (bool, error) {
|
||||||
var query = this.Query(tx).
|
var query = this.Query(tx).
|
||||||
Attr("role", role).
|
Attr("role", role).
|
||||||
@@ -370,7 +381,7 @@ func (this *NodeTaskDAO) ExistsDoingNodeTasks(tx *dbs.Tx, role string, excludeTy
|
|||||||
return query.Exist()
|
return query.Exist()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExistsErrorNodeTasks 是否有错误的任务
|
// ExistsErrorNodeTasks 鏄惁鏈夐敊璇殑浠诲姟
|
||||||
func (this *NodeTaskDAO) ExistsErrorNodeTasks(tx *dbs.Tx, role string, excludeTypes []NodeTaskType) (bool, error) {
|
func (this *NodeTaskDAO) ExistsErrorNodeTasks(tx *dbs.Tx, role string, excludeTypes []NodeTaskType) (bool, error) {
|
||||||
var query = this.Query(tx).
|
var query = this.Query(tx).
|
||||||
Attr("role", role).
|
Attr("role", role).
|
||||||
@@ -383,7 +394,7 @@ func (this *NodeTaskDAO) ExistsErrorNodeTasks(tx *dbs.Tx, role string, excludeTy
|
|||||||
return query.Exist()
|
return query.Exist()
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteNodeTask 删除任务
|
// DeleteNodeTask 鍒犻櫎浠诲姟
|
||||||
func (this *NodeTaskDAO) DeleteNodeTask(tx *dbs.Tx, taskId int64) error {
|
func (this *NodeTaskDAO) DeleteNodeTask(tx *dbs.Tx, taskId int64) error {
|
||||||
_, err := this.Query(tx).
|
_, err := this.Query(tx).
|
||||||
Pk(taskId).
|
Pk(taskId).
|
||||||
@@ -391,7 +402,7 @@ func (this *NodeTaskDAO) DeleteNodeTask(tx *dbs.Tx, taskId int64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountDoingNodeTasks 计算正在执行的任务
|
// CountDoingNodeTasks 璁$畻姝e湪鎵ц鐨勪换鍔?
|
||||||
func (this *NodeTaskDAO) CountDoingNodeTasks(tx *dbs.Tx, role string) (int64, error) {
|
func (this *NodeTaskDAO) CountDoingNodeTasks(tx *dbs.Tx, role string) (int64, error) {
|
||||||
return this.Query(tx).
|
return this.Query(tx).
|
||||||
Attr("isDone", 0).
|
Attr("isDone", 0).
|
||||||
@@ -400,7 +411,7 @@ func (this *NodeTaskDAO) CountDoingNodeTasks(tx *dbs.Tx, role string) (int64, er
|
|||||||
Count()
|
Count()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindNotifyingNodeTasks 查找需要通知的任务
|
// FindNotifyingNodeTasks 鏌ユ壘闇€瑕侀€氱煡鐨勪换鍔?
|
||||||
func (this *NodeTaskDAO) FindNotifyingNodeTasks(tx *dbs.Tx, role string, size int64) (result []*NodeTask, err error) {
|
func (this *NodeTaskDAO) FindNotifyingNodeTasks(tx *dbs.Tx, role string, size int64) (result []*NodeTask, err error) {
|
||||||
_, err = this.Query(tx).
|
_, err = this.Query(tx).
|
||||||
Attr("role", role).
|
Attr("role", role).
|
||||||
@@ -413,7 +424,7 @@ func (this *NodeTaskDAO) FindNotifyingNodeTasks(tx *dbs.Tx, role string, size in
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateTasksNotified 设置任务已通知
|
// UpdateTasksNotified 璁剧疆浠诲姟宸查€氱煡
|
||||||
func (this *NodeTaskDAO) UpdateTasksNotified(tx *dbs.Tx, taskIds []int64) error {
|
func (this *NodeTaskDAO) UpdateTasksNotified(tx *dbs.Tx, taskIds []int64) error {
|
||||||
if len(taskIds) == 0 {
|
if len(taskIds) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -430,7 +441,7 @@ func (this *NodeTaskDAO) UpdateTasksNotified(tx *dbs.Tx, taskIds []int64) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成一个版本号
|
// 鐢熸垚涓€涓増鏈彿
|
||||||
func (this *NodeTaskDAO) increaseVersion(tx *dbs.Tx) (version int64, err error) {
|
func (this *NodeTaskDAO) increaseVersion(tx *dbs.Tx) (version int64, err error) {
|
||||||
return SharedSysLockerDAO.Increase(tx, "NODE_TASK_VERSION", 0)
|
return SharedSysLockerDAO.Increase(tx, "NODE_TASK_VERSION", 0)
|
||||||
}
|
}
|
||||||
|
|||||||
47
EdgeAPI/internal/db/models/node_task_dao_httpdns.go
Normal file
47
EdgeAPI/internal/db/models/node_task_dao_httpdns.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtractHTTPDNSClusterTask 分解HTTPDNS节点集群任务
|
||||||
|
func (this *NodeTaskDAO) ExtractHTTPDNSClusterTask(tx *dbs.Tx, clusterId int64, taskType NodeTaskType) error {
|
||||||
|
nodes, err := SharedHTTPDNSNodeDAO.ListEnabledNodes(tx, clusterId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = this.Query(tx).
|
||||||
|
Attr("role", nodeconfigs.NodeRoleHTTPDNS).
|
||||||
|
Attr("clusterId", clusterId).
|
||||||
|
Gt("nodeId", 0).
|
||||||
|
Attr("type", taskType).
|
||||||
|
Delete()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, node := range nodes {
|
||||||
|
if !node.IsOn {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = this.CreateNodeTask(tx, nodeconfigs.NodeRoleHTTPDNS, clusterId, int64(node.Id), 0, 0, taskType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = this.Query(tx).
|
||||||
|
Attr("role", nodeconfigs.NodeRoleHTTPDNS).
|
||||||
|
Attr("clusterId", clusterId).
|
||||||
|
Attr("nodeId", 0).
|
||||||
|
Attr("type", taskType).
|
||||||
|
Delete()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -343,6 +343,8 @@ func mapNodeRole(role nodeconfigs.NodeRole) (string, error) {
|
|||||||
return fluentBitRoleNode, nil
|
return fluentBitRoleNode, nil
|
||||||
case nodeconfigs.NodeRoleDNS:
|
case nodeconfigs.NodeRoleDNS:
|
||||||
return fluentBitRoleDNS, nil
|
return fluentBitRoleDNS, nil
|
||||||
|
case nodeconfigs.NodeRoleHTTPDNS:
|
||||||
|
return fluentBitRoleDNS, nil
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("unsupported fluent-bit role '%s'", role)
|
return "", fmt.Errorf("unsupported fluent-bit role '%s'", role)
|
||||||
}
|
}
|
||||||
|
|||||||
230
EdgeAPI/internal/installers/installer_httpdns_node.go
Normal file
230
EdgeAPI/internal/installers/installer_httpdns_node.go
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
package installers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPDNSNodeInstaller struct {
|
||||||
|
BaseInstaller
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *HTTPDNSNodeInstaller) Install(dir string, params interface{}, installStatus *models.NodeInstallStatus) error {
|
||||||
|
if params == nil {
|
||||||
|
return errors.New("'params' required for node installation")
|
||||||
|
}
|
||||||
|
nodeParams, ok := params.(*NodeParams)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("'params' should be *NodeParams")
|
||||||
|
}
|
||||||
|
err := nodeParams.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("params validation: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
installRootDir, appDir := resolveHTTPDNSInstallPaths(dir)
|
||||||
|
|
||||||
|
_, err = i.client.Stat(installRootDir)
|
||||||
|
if err != nil {
|
||||||
|
err = i.client.MkdirAll(installRootDir)
|
||||||
|
if err != nil {
|
||||||
|
installStatus.ErrorCode = "CREATE_ROOT_DIRECTORY_FAILED"
|
||||||
|
return fmt.Errorf("create directory '%s' failed: %w", installRootDir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env, err := i.InstallHelper(installRootDir, nodeconfigs.NodeRoleHTTPDNS)
|
||||||
|
if err != nil {
|
||||||
|
installStatus.ErrorCode = "INSTALL_HELPER_FAILED"
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filePrefix := "edge-httpdns-" + env.OS + "-" + env.Arch
|
||||||
|
zipFile, err := i.LookupLatestInstallerForTarget(filePrefix, env)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(zipFile) == 0 {
|
||||||
|
return errors.New("can not find installer file for " + env.OS + "/" + env.Arch + ", expected '" + filePrefix + "-v*.zip' or distro-specific '" + filePrefix + "-{ubuntu22.04|amzn2023}-v*.zip'")
|
||||||
|
}
|
||||||
|
|
||||||
|
targetZip, err := i.copyZipToRemote(installRootDir, zipFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !nodeParams.IsUpgrading {
|
||||||
|
_, stderr, testErr := i.client.Exec(env.HelperPath + " -cmd=test")
|
||||||
|
if testErr != nil {
|
||||||
|
return fmt.Errorf("test failed: %w", testErr)
|
||||||
|
}
|
||||||
|
if len(stderr) > 0 {
|
||||||
|
return errors.New("test failed: " + stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exePath := appDir + "/bin/edge-httpdns"
|
||||||
|
if nodeParams.IsUpgrading {
|
||||||
|
_, err = i.client.Stat(exePath)
|
||||||
|
if err == nil {
|
||||||
|
_, _, _ = i.client.Exec(exePath + " stop")
|
||||||
|
removeErr := i.client.Remove(exePath)
|
||||||
|
if removeErr != nil && removeErr != os.ErrNotExist {
|
||||||
|
return fmt.Errorf("remove old file failed: %w", removeErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, stderr, err := i.client.Exec(env.HelperPath + " -cmd=unzip -zip=\"" + targetZip + "\" -target=\"" + installRootDir + "\"")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(stderr) > 0 {
|
||||||
|
return errors.New("unzip installer failed: " + stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
certFile := appDir + "/configs/tls/server.crt"
|
||||||
|
keyFile := appDir + "/configs/tls/server.key"
|
||||||
|
err = i.writeTLSCertificate(certFile, keyFile, nodeParams.TLSCertData, nodeParams.TLSKeyData)
|
||||||
|
if err != nil {
|
||||||
|
installStatus.ErrorCode = "WRITE_TLS_CERT_FAILED"
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
configFile := appDir + "/configs/api_httpdns.yaml"
|
||||||
|
if i.client.sudo {
|
||||||
|
_, _, _ = i.client.Exec("chown " + i.client.User() + " " + filepath.Dir(configFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
configData := []byte(`rpc.endpoints: [ ${endpoints} ]
|
||||||
|
nodeId: "${nodeId}"
|
||||||
|
secret: "${nodeSecret}"
|
||||||
|
|
||||||
|
https.listenAddr: ":443"
|
||||||
|
https.cert: "${certFile}"
|
||||||
|
https.key: "${keyFile}"`)
|
||||||
|
certFileClean := strings.ReplaceAll(certFile, "\\", "/")
|
||||||
|
keyFileClean := strings.ReplaceAll(keyFile, "\\", "/")
|
||||||
|
|
||||||
|
configData = bytes.ReplaceAll(configData, []byte("${endpoints}"), []byte(nodeParams.QuoteEndpoints()))
|
||||||
|
configData = bytes.ReplaceAll(configData, []byte("${nodeId}"), []byte(nodeParams.NodeId))
|
||||||
|
configData = bytes.ReplaceAll(configData, []byte("${nodeSecret}"), []byte(nodeParams.Secret))
|
||||||
|
configData = bytes.ReplaceAll(configData, []byte("${certFile}"), []byte(certFileClean))
|
||||||
|
configData = bytes.ReplaceAll(configData, []byte("${keyFile}"), []byte(keyFileClean))
|
||||||
|
|
||||||
|
_, err = i.client.WriteFile(configFile, configData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("write '%s': %w", configFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.SetupFluentBit(nodeconfigs.NodeRoleHTTPDNS)
|
||||||
|
if err != nil {
|
||||||
|
installStatus.ErrorCode = "SETUP_FLUENT_BIT_FAILED"
|
||||||
|
return fmt.Errorf("setup fluent-bit failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
startCmdPrefix := "cd " + shQuote(appDir+"/configs") + " && ../bin/edge-httpdns "
|
||||||
|
|
||||||
|
stdout, stderr, err := i.client.Exec(startCmdPrefix + "test")
|
||||||
|
if err != nil {
|
||||||
|
installStatus.ErrorCode = "TEST_FAILED"
|
||||||
|
return fmt.Errorf("test edge-httpdns failed: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
||||||
|
}
|
||||||
|
if len(stderr) > 0 {
|
||||||
|
if regexp.MustCompile(`(?i)rpc`).MatchString(stderr) || regexp.MustCompile(`(?i)rpc`).MatchString(stdout) {
|
||||||
|
installStatus.ErrorCode = "RPC_TEST_FAILED"
|
||||||
|
}
|
||||||
|
return errors.New("test edge-httpdns failed, stdout: " + stdout + ", stderr: " + stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout, stderr, err = i.client.Exec(startCmdPrefix + "start")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("start edge-httpdns failed: %w, stdout: %s, stderr: %s", err, stdout, stderr)
|
||||||
|
}
|
||||||
|
if len(stderr) > 0 {
|
||||||
|
return errors.New("start edge-httpdns failed, stdout: " + stdout + ", stderr: " + stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveHTTPDNSInstallPaths(rawDir string) (installRootDir string, appDir string) {
|
||||||
|
dir := strings.TrimSpace(rawDir)
|
||||||
|
dir = strings.TrimRight(dir, "/")
|
||||||
|
if len(dir) == 0 {
|
||||||
|
return rawDir, rawDir + "/edge-httpdns"
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(dir, "/edge-httpdns") {
|
||||||
|
root := strings.TrimSuffix(dir, "/edge-httpdns")
|
||||||
|
if len(root) == 0 {
|
||||||
|
root = "/"
|
||||||
|
}
|
||||||
|
return root, dir
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir, dir + "/edge-httpdns"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *HTTPDNSNodeInstaller) copyZipToRemote(dir string, zipFile string) (string, error) {
|
||||||
|
targetZip := ""
|
||||||
|
var firstCopyErr error
|
||||||
|
zipName := filepath.Base(zipFile)
|
||||||
|
for _, candidate := range []string{
|
||||||
|
dir + "/" + zipName,
|
||||||
|
i.client.UserHome() + "/" + zipName,
|
||||||
|
"/tmp/" + zipName,
|
||||||
|
} {
|
||||||
|
err := i.client.Copy(zipFile, candidate, 0777)
|
||||||
|
if err != nil {
|
||||||
|
if firstCopyErr == nil {
|
||||||
|
firstCopyErr = err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
targetZip = candidate
|
||||||
|
firstCopyErr = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if firstCopyErr != nil {
|
||||||
|
return "", fmt.Errorf("upload httpdns file failed: %w", firstCopyErr)
|
||||||
|
}
|
||||||
|
return targetZip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *HTTPDNSNodeInstaller) writeTLSCertificate(certFile string, keyFile string, certData []byte, keyData []byte) error {
|
||||||
|
if len(certData) == 0 || len(keyData) == 0 {
|
||||||
|
return errors.New("cluster tls certificate is empty")
|
||||||
|
}
|
||||||
|
certDir := filepath.Dir(certFile)
|
||||||
|
_, stderr, err := i.client.Exec("mkdir -p " + shQuote(certDir))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create tls directory failed: %w, stderr: %s", err, stderr)
|
||||||
|
}
|
||||||
|
if i.client.sudo {
|
||||||
|
_, _, _ = i.client.Exec("chown " + i.client.User() + " " + shQuote(certDir))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = i.client.WriteFile(certFile, certData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("write cert file failed: %w", err)
|
||||||
|
}
|
||||||
|
_, err = i.client.WriteFile(keyFile, keyData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("write key file failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, stderr, err = i.client.Exec("chmod 0644 " + shQuote(certFile) + " && chmod 0600 " + shQuote(keyFile))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("chmod tls files failed: %w, stderr: %s", err, stderr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ type NodeParams struct {
|
|||||||
Endpoints []string
|
Endpoints []string
|
||||||
NodeId string
|
NodeId string
|
||||||
Secret string
|
Secret string
|
||||||
|
TLSCertData []byte
|
||||||
|
TLSKeyData []byte
|
||||||
IsUpgrading bool // 是否为升级
|
IsUpgrading bool // 是否为升级
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
280
EdgeAPI/internal/installers/queue_httpdns_node.go
Normal file
280
EdgeAPI/internal/installers/queue_httpdns_node.go
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
package installers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/goman"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/utils"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||||
|
"github.com/iwind/TeaGo/logs"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sharedHTTPDNSNodeQueue = NewHTTPDNSNodeQueue()
|
||||||
|
|
||||||
|
type HTTPDNSNodeQueue struct{}
|
||||||
|
|
||||||
|
func NewHTTPDNSNodeQueue() *HTTPDNSNodeQueue {
|
||||||
|
return &HTTPDNSNodeQueue{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SharedHTTPDNSNodeQueue() *HTTPDNSNodeQueue {
|
||||||
|
return sharedHTTPDNSNodeQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallNodeProcess 鍦ㄧ嚎瀹夎 HTTPDNS 鑺傜偣娴佺▼鎺у埗
|
||||||
|
func (q *HTTPDNSNodeQueue) InstallNodeProcess(nodeId int64, isUpgrading bool) error {
|
||||||
|
installStatus := models.NewNodeInstallStatus()
|
||||||
|
installStatus.IsRunning = true
|
||||||
|
installStatus.IsFinished = false
|
||||||
|
installStatus.IsOk = false
|
||||||
|
installStatus.Error = ""
|
||||||
|
installStatus.ErrorCode = ""
|
||||||
|
installStatus.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
err := models.SharedHTTPDNSNodeDAO.UpdateNodeInstallStatus(nil, nodeId, installStatus)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker := utils.NewTicker(3 * time.Second)
|
||||||
|
goman.New(func() {
|
||||||
|
for ticker.Wait() {
|
||||||
|
installStatus.UpdatedAt = time.Now().Unix()
|
||||||
|
updateErr := models.SharedHTTPDNSNodeDAO.UpdateNodeInstallStatus(nil, nodeId, installStatus)
|
||||||
|
if updateErr != nil {
|
||||||
|
logs.Println("[HTTPDNS_INSTALL]" + updateErr.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
err = q.InstallNode(nodeId, installStatus, isUpgrading)
|
||||||
|
|
||||||
|
installStatus.IsRunning = false
|
||||||
|
installStatus.IsFinished = true
|
||||||
|
if err != nil {
|
||||||
|
installStatus.IsOk = false
|
||||||
|
installStatus.Error = err.Error()
|
||||||
|
} else {
|
||||||
|
installStatus.IsOk = true
|
||||||
|
}
|
||||||
|
installStatus.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
updateErr := models.SharedHTTPDNSNodeDAO.UpdateNodeInstallStatus(nil, nodeId, installStatus)
|
||||||
|
if updateErr != nil {
|
||||||
|
return updateErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if installStatus.IsOk {
|
||||||
|
return models.SharedHTTPDNSNodeDAO.UpdateNodeIsInstalled(nil, nodeId, true)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallNode 鍦ㄧ嚎瀹夎 HTTPDNS 鑺傜偣
|
||||||
|
func (q *HTTPDNSNodeQueue) InstallNode(nodeId int64, installStatus *models.NodeInstallStatus, isUpgrading bool) error {
|
||||||
|
node, err := models.SharedHTTPDNSNodeDAO.FindEnabledNode(nil, nodeId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if node == nil {
|
||||||
|
return errors.New("can not find node, ID '" + numberutils.FormatInt64(nodeId) + "'")
|
||||||
|
}
|
||||||
|
cluster, err := models.SharedHTTPDNSClusterDAO.FindEnabledCluster(nil, int64(node.ClusterId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if cluster == nil {
|
||||||
|
return errors.New("can not find cluster")
|
||||||
|
}
|
||||||
|
|
||||||
|
sshHost, sshPort, grantId, err := q.parseSSHInfo(node)
|
||||||
|
if err != nil {
|
||||||
|
installStatus.ErrorCode = "EMPTY_SSH"
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
grant, err := models.SharedNodeGrantDAO.FindEnabledNodeGrant(nil, grantId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if grant == nil {
|
||||||
|
installStatus.ErrorCode = "EMPTY_GRANT"
|
||||||
|
return errors.New("can not find user grant with id '" + numberutils.FormatInt64(grantId) + "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
apiNodes, err := models.SharedAPINodeDAO.FindAllEnabledAndOnAPINodes(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(apiNodes) == 0 {
|
||||||
|
return errors.New("no available api nodes")
|
||||||
|
}
|
||||||
|
|
||||||
|
apiEndpoints := make([]string, 0, 8)
|
||||||
|
for _, apiNode := range apiNodes {
|
||||||
|
addrConfigs, decodeErr := apiNode.DecodeAccessAddrs()
|
||||||
|
if decodeErr != nil {
|
||||||
|
return fmt.Errorf("decode api node access addresses failed: %w", decodeErr)
|
||||||
|
}
|
||||||
|
for _, addrConfig := range addrConfigs {
|
||||||
|
apiEndpoints = append(apiEndpoints, addrConfig.FullAddresses()...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(apiEndpoints) == 0 {
|
||||||
|
return errors.New("no available api endpoints")
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsCertData, tlsKeyData, err := q.resolveClusterTLSCertPair(cluster)
|
||||||
|
if err != nil {
|
||||||
|
installStatus.ErrorCode = "EMPTY_TLS_CERT"
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
params := &NodeParams{
|
||||||
|
Endpoints: apiEndpoints,
|
||||||
|
NodeId: node.UniqueId,
|
||||||
|
Secret: node.Secret,
|
||||||
|
TLSCertData: tlsCertData,
|
||||||
|
TLSKeyData: tlsKeyData,
|
||||||
|
IsUpgrading: isUpgrading,
|
||||||
|
}
|
||||||
|
|
||||||
|
installer := &HTTPDNSNodeInstaller{}
|
||||||
|
err = installer.Login(&Credentials{
|
||||||
|
Host: sshHost,
|
||||||
|
Port: sshPort,
|
||||||
|
Username: grant.Username,
|
||||||
|
Password: grant.Password,
|
||||||
|
PrivateKey: grant.PrivateKey,
|
||||||
|
Passphrase: grant.Passphrase,
|
||||||
|
Method: grant.Method,
|
||||||
|
Sudo: grant.Su == 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
installStatus.ErrorCode = "SSH_LOGIN_FAILED"
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = installer.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
installDir := node.InstallDir
|
||||||
|
if len(installDir) == 0 {
|
||||||
|
if cluster != nil && len(cluster.InstallDir) > 0 {
|
||||||
|
installDir = cluster.InstallDir
|
||||||
|
}
|
||||||
|
if len(installDir) == 0 {
|
||||||
|
installDir = installer.client.UserHome() + "/edge-httpdns"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return installer.Install(installDir, params, installStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *HTTPDNSNodeQueue) resolveClusterTLSCertPair(cluster *models.HTTPDNSCluster) ([]byte, []byte, error) {
|
||||||
|
if cluster == nil {
|
||||||
|
return nil, nil, errors.New("cluster not found")
|
||||||
|
}
|
||||||
|
if len(cluster.TLSPolicy) == 0 {
|
||||||
|
return nil, nil, errors.New("cluster tls policy is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig := map[string]json.RawMessage{}
|
||||||
|
if err := json.Unmarshal(cluster.TLSPolicy, &tlsConfig); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("decode cluster tls policy failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sslPolicyData := tlsConfig["sslPolicy"]
|
||||||
|
if len(sslPolicyData) == 0 {
|
||||||
|
// Compatible with old data where TLSPolicy stores SSLPolicy directly.
|
||||||
|
sslPolicyData = json.RawMessage(cluster.TLSPolicy)
|
||||||
|
}
|
||||||
|
sslPolicy := &sslconfigs.SSLPolicy{}
|
||||||
|
if err := json.Unmarshal(sslPolicyData, sslPolicy); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("decode ssl policy failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cert := range sslPolicy.Certs {
|
||||||
|
if cert == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(cert.CertData) > 0 && len(cert.KeyData) > 0 {
|
||||||
|
return cert.CertData, cert.KeyData, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, certRef := range sslPolicy.CertRefs {
|
||||||
|
if certRef == nil || certRef.CertId <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
certConfig, err := models.SharedSSLCertDAO.ComposeCertConfig(nil, certRef.CertId, false, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("load ssl cert %d failed: %w", certRef.CertId, err)
|
||||||
|
}
|
||||||
|
if certConfig == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(certConfig.CertData) > 0 && len(certConfig.KeyData) > 0 {
|
||||||
|
return certConfig.CertData, certConfig.KeyData, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sslPolicy.Id > 0 {
|
||||||
|
policyConfig, err := models.SharedSSLPolicyDAO.ComposePolicyConfig(nil, sslPolicy.Id, false, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("load ssl policy %d failed: %w", sslPolicy.Id, err)
|
||||||
|
}
|
||||||
|
if policyConfig != nil {
|
||||||
|
for _, cert := range policyConfig.Certs {
|
||||||
|
if cert == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(cert.CertData) > 0 && len(cert.KeyData) > 0 {
|
||||||
|
return cert.CertData, cert.KeyData, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil, errors.New("cluster tls certificate is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *HTTPDNSNodeQueue) parseSSHInfo(node *models.HTTPDNSNode) (string, int, int64, error) {
|
||||||
|
if node == nil {
|
||||||
|
return "", 0, 0, errors.New("node should not be nil")
|
||||||
|
}
|
||||||
|
if len(node.InstallStatus) == 0 {
|
||||||
|
return "", 0, 0, errors.New("ssh config should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
statusMap := maps.Map{}
|
||||||
|
err := json.Unmarshal(node.InstallStatus, &statusMap)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, 0, errors.New("invalid install status data")
|
||||||
|
}
|
||||||
|
sshMap := statusMap.GetMap("ssh")
|
||||||
|
if sshMap == nil {
|
||||||
|
return "", 0, 0, errors.New("ssh config should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
host := sshMap.GetString("host")
|
||||||
|
port := sshMap.GetInt("port")
|
||||||
|
grantId := sshMap.GetInt64("grantId")
|
||||||
|
if len(host) == 0 {
|
||||||
|
return "", 0, 0, errors.New("ssh host should not be empty")
|
||||||
|
}
|
||||||
|
if port <= 0 {
|
||||||
|
port = 22
|
||||||
|
}
|
||||||
|
if grantId <= 0 {
|
||||||
|
return "", 0, 0, errors.New("grant id should not be empty")
|
||||||
|
}
|
||||||
|
return host, port, grantId, nil
|
||||||
|
}
|
||||||
@@ -144,6 +144,17 @@ func (this *APINode) Start() {
|
|||||||
this.processTableNames()
|
this.processTableNames()
|
||||||
dbs.NotifyReady()
|
dbs.NotifyReady()
|
||||||
|
|
||||||
|
// 自动确保 ClickHouse 日志表存在(不阻断主流程)
|
||||||
|
this.setProgress("CLICKHOUSE", "正在检查 ClickHouse 日志表")
|
||||||
|
logs.Println("[API_NODE]ensuring clickhouse tables ...")
|
||||||
|
err = setup.EnsureClickHouseTables()
|
||||||
|
if err != nil {
|
||||||
|
logs.Println("[API_NODE]WARNING: ensure clickhouse tables failed: " + err.Error())
|
||||||
|
remotelogs.Error("API_NODE", "ensure clickhouse tables failed: "+err.Error())
|
||||||
|
} else {
|
||||||
|
logs.Println("[API_NODE]ensure clickhouse tables done")
|
||||||
|
}
|
||||||
|
|
||||||
// 设置时区
|
// 设置时区
|
||||||
this.setProgress("TIMEZONE", "正在设置时区")
|
this.setProgress("TIMEZONE", "正在设置时区")
|
||||||
this.setupTimeZone()
|
this.setupTimeZone()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package nodes
|
|||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/clients"
|
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/clients"
|
||||||
|
httpdnsservices "github.com/TeaOSLab/EdgeAPI/internal/rpc/services/httpdns"
|
||||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/users"
|
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/users"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@@ -425,6 +426,46 @@ func (this *APINode) registerServices(server *grpc.Server) {
|
|||||||
pb.RegisterDNSTaskServiceServer(server, instance)
|
pb.RegisterDNSTaskServiceServer(server, instance)
|
||||||
this.rest(instance)
|
this.rest(instance)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSClusterService{}).(*httpdnsservices.HTTPDNSClusterService)
|
||||||
|
pb.RegisterHTTPDNSClusterServiceServer(server, instance)
|
||||||
|
this.rest(instance)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSNodeService{}).(*httpdnsservices.HTTPDNSNodeService)
|
||||||
|
pb.RegisterHTTPDNSNodeServiceServer(server, instance)
|
||||||
|
this.rest(instance)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSAppService{}).(*httpdnsservices.HTTPDNSAppService)
|
||||||
|
pb.RegisterHTTPDNSAppServiceServer(server, instance)
|
||||||
|
this.rest(instance)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSDomainService{}).(*httpdnsservices.HTTPDNSDomainService)
|
||||||
|
pb.RegisterHTTPDNSDomainServiceServer(server, instance)
|
||||||
|
this.rest(instance)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSRuleService{}).(*httpdnsservices.HTTPDNSRuleService)
|
||||||
|
pb.RegisterHTTPDNSRuleServiceServer(server, instance)
|
||||||
|
this.rest(instance)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSAccessLogService{}).(*httpdnsservices.HTTPDNSAccessLogService)
|
||||||
|
pb.RegisterHTTPDNSAccessLogServiceServer(server, instance)
|
||||||
|
this.rest(instance)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSRuntimeLogService{}).(*httpdnsservices.HTTPDNSRuntimeLogService)
|
||||||
|
pb.RegisterHTTPDNSRuntimeLogServiceServer(server, instance)
|
||||||
|
this.rest(instance)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSSandboxService{}).(*httpdnsservices.HTTPDNSSandboxService)
|
||||||
|
pb.RegisterHTTPDNSSandboxServiceServer(server, instance)
|
||||||
|
this.rest(instance)
|
||||||
|
}
|
||||||
{
|
{
|
||||||
var instance = this.serviceInstance(&services.NodeClusterFirewallActionService{}).(*services.NodeClusterFirewallActionService)
|
var instance = this.serviceInstance(&services.NodeClusterFirewallActionService{}).(*services.NodeClusterFirewallActionService)
|
||||||
pb.RegisterNodeClusterFirewallActionServiceServer(server, instance)
|
pb.RegisterNodeClusterFirewallActionServiceServer(server, instance)
|
||||||
|
|||||||
125
EdgeAPI/internal/rpc/services/httpdns/converters.go
Normal file
125
EdgeAPI/internal/rpc/services/httpdns/converters.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package httpdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func toPBCluster(cluster *models.HTTPDNSCluster) *pb.HTTPDNSCluster {
|
||||||
|
if cluster == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &pb.HTTPDNSCluster{
|
||||||
|
Id: int64(cluster.Id),
|
||||||
|
IsOn: cluster.IsOn,
|
||||||
|
IsDefault: cluster.IsDefault,
|
||||||
|
Name: cluster.Name,
|
||||||
|
ServiceDomain: cluster.ServiceDomain,
|
||||||
|
DefaultTTL: cluster.DefaultTTL,
|
||||||
|
FallbackTimeoutMs: cluster.FallbackTimeoutMs,
|
||||||
|
InstallDir: cluster.InstallDir,
|
||||||
|
TlsPolicyJSON: cluster.TLSPolicy,
|
||||||
|
CreatedAt: int64(cluster.CreatedAt),
|
||||||
|
UpdatedAt: int64(cluster.UpdatedAt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPBNode(node *models.HTTPDNSNode) *pb.HTTPDNSNode {
|
||||||
|
if node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &pb.HTTPDNSNode{
|
||||||
|
Id: int64(node.Id),
|
||||||
|
ClusterId: int64(node.ClusterId),
|
||||||
|
Name: node.Name,
|
||||||
|
IsOn: node.IsOn,
|
||||||
|
IsUp: node.IsUp,
|
||||||
|
IsInstalled: node.IsInstalled,
|
||||||
|
IsActive: node.IsActive,
|
||||||
|
UniqueId: node.UniqueId,
|
||||||
|
Secret: node.Secret,
|
||||||
|
InstallDir: node.InstallDir,
|
||||||
|
StatusJSON: node.Status,
|
||||||
|
InstallStatusJSON: node.InstallStatus,
|
||||||
|
CreatedAt: int64(node.CreatedAt),
|
||||||
|
UpdatedAt: int64(node.UpdatedAt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPBApp(app *models.HTTPDNSApp, secret *models.HTTPDNSAppSecret) *pb.HTTPDNSApp {
|
||||||
|
if app == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var signEnabled bool
|
||||||
|
var signSecret string
|
||||||
|
var signUpdatedAt int64
|
||||||
|
if secret != nil {
|
||||||
|
signEnabled = secret.SignEnabled
|
||||||
|
signSecret = secret.SignSecret
|
||||||
|
signUpdatedAt = int64(secret.SignUpdatedAt)
|
||||||
|
}
|
||||||
|
return &pb.HTTPDNSApp{
|
||||||
|
Id: int64(app.Id),
|
||||||
|
Name: app.Name,
|
||||||
|
AppId: app.AppId,
|
||||||
|
IsOn: app.IsOn,
|
||||||
|
PrimaryClusterId: int64(app.PrimaryClusterId),
|
||||||
|
BackupClusterId: int64(app.BackupClusterId),
|
||||||
|
SniMode: app.SNIMode,
|
||||||
|
SignEnabled: signEnabled,
|
||||||
|
SignSecret: signSecret,
|
||||||
|
SignUpdatedAt: signUpdatedAt,
|
||||||
|
CreatedAt: int64(app.CreatedAt),
|
||||||
|
UpdatedAt: int64(app.UpdatedAt),
|
||||||
|
UserId: int64(app.UserId),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPBDomain(domain *models.HTTPDNSDomain, ruleCount int64) *pb.HTTPDNSDomain {
|
||||||
|
if domain == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &pb.HTTPDNSDomain{
|
||||||
|
Id: int64(domain.Id),
|
||||||
|
AppId: int64(domain.AppId),
|
||||||
|
Domain: domain.Domain,
|
||||||
|
IsOn: domain.IsOn,
|
||||||
|
CreatedAt: int64(domain.CreatedAt),
|
||||||
|
UpdatedAt: int64(domain.UpdatedAt),
|
||||||
|
RuleCount: ruleCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPBRule(rule *models.HTTPDNSCustomRule, records []*models.HTTPDNSCustomRuleRecord) *pb.HTTPDNSCustomRule {
|
||||||
|
if rule == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var pbRecords []*pb.HTTPDNSRuleRecord
|
||||||
|
for _, record := range records {
|
||||||
|
pbRecords = append(pbRecords, &pb.HTTPDNSRuleRecord{
|
||||||
|
Id: int64(record.Id),
|
||||||
|
RuleId: int64(record.RuleId),
|
||||||
|
RecordType: record.RecordType,
|
||||||
|
RecordValue: record.RecordValue,
|
||||||
|
Weight: record.Weight,
|
||||||
|
Sort: record.Sort,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return &pb.HTTPDNSCustomRule{
|
||||||
|
Id: int64(rule.Id),
|
||||||
|
AppId: int64(rule.AppId),
|
||||||
|
DomainId: int64(rule.DomainId),
|
||||||
|
RuleName: rule.RuleName,
|
||||||
|
LineScope: rule.LineScope,
|
||||||
|
LineCarrier: rule.LineCarrier,
|
||||||
|
LineRegion: rule.LineRegion,
|
||||||
|
LineProvince: rule.LineProvince,
|
||||||
|
LineContinent: rule.LineContinent,
|
||||||
|
LineCountry: rule.LineCountry,
|
||||||
|
Ttl: rule.TTL,
|
||||||
|
IsOn: rule.IsOn,
|
||||||
|
Priority: rule.Priority,
|
||||||
|
UpdatedAt: int64(rule.UpdatedAt),
|
||||||
|
Records: pbRecords,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
package httpdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/clickhouse"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPDNSAccessLogService struct {
|
||||||
|
services.BaseService
|
||||||
|
pb.UnimplementedHTTPDNSAccessLogServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogService) CreateHTTPDNSAccessLogs(ctx context.Context, req *pb.CreateHTTPDNSAccessLogsRequest) (*pb.CreateHTTPDNSAccessLogsResponse, error) {
|
||||||
|
nodeIdInContext, err := s.ValidateHTTPDNSNode(ctx)
|
||||||
|
if err != nil {
|
||||||
|
_, err = s.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.GetLogs()) == 0 {
|
||||||
|
return &pb.CreateHTTPDNSAccessLogsResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqlLogs := make([]*models.HTTPDNSAccessLog, 0, len(req.GetLogs()))
|
||||||
|
chLogs := make([]*pb.HTTPDNSAccessLog, 0, len(req.GetLogs()))
|
||||||
|
seen := map[string]struct{}{}
|
||||||
|
for _, item := range req.GetLogs() {
|
||||||
|
if item == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nodeId := item.GetNodeId()
|
||||||
|
// When called by HTTPDNS node, trust node id parsed from RPC token.
|
||||||
|
if nodeIdInContext > 0 {
|
||||||
|
nodeId = nodeIdInContext
|
||||||
|
}
|
||||||
|
clusterId := item.GetClusterId()
|
||||||
|
if clusterId <= 0 && nodeId > 0 {
|
||||||
|
clusterId, _ = models.SharedHTTPDNSNodeDAO.FindNodeClusterId(s.NullTx(), nodeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := item.GetRequestId() + "#" + strconv.FormatInt(nodeId, 10)
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
|
||||||
|
createdAt := item.GetCreatedAt()
|
||||||
|
if createdAt <= 0 {
|
||||||
|
createdAt = time.Now().Unix()
|
||||||
|
}
|
||||||
|
day := item.GetDay()
|
||||||
|
if len(day) == 0 {
|
||||||
|
day = timeutil.Format("Ymd")
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqlLogs = append(mysqlLogs, &models.HTTPDNSAccessLog{
|
||||||
|
RequestId: item.GetRequestId(),
|
||||||
|
ClusterId: uint32(clusterId),
|
||||||
|
NodeId: uint32(nodeId),
|
||||||
|
AppId: item.GetAppId(),
|
||||||
|
AppName: item.GetAppName(),
|
||||||
|
Domain: item.GetDomain(),
|
||||||
|
QType: item.GetQtype(),
|
||||||
|
ClientIP: item.GetClientIP(),
|
||||||
|
ClientRegion: item.GetClientRegion(),
|
||||||
|
Carrier: item.GetCarrier(),
|
||||||
|
SDKVersion: item.GetSdkVersion(),
|
||||||
|
OS: item.GetOs(),
|
||||||
|
ResultIPs: item.GetResultIPs(),
|
||||||
|
Status: item.GetStatus(),
|
||||||
|
ErrorCode: item.GetErrorCode(),
|
||||||
|
CostMs: item.GetCostMs(),
|
||||||
|
CreatedAt: uint64(createdAt),
|
||||||
|
Day: day,
|
||||||
|
Summary: item.GetSummary(),
|
||||||
|
})
|
||||||
|
|
||||||
|
chLogs = append(chLogs, &pb.HTTPDNSAccessLog{
|
||||||
|
RequestId: item.GetRequestId(),
|
||||||
|
ClusterId: clusterId,
|
||||||
|
NodeId: nodeId,
|
||||||
|
AppId: item.GetAppId(),
|
||||||
|
AppName: item.GetAppName(),
|
||||||
|
Domain: item.GetDomain(),
|
||||||
|
Qtype: item.GetQtype(),
|
||||||
|
ClientIP: item.GetClientIP(),
|
||||||
|
ClientRegion: item.GetClientRegion(),
|
||||||
|
Carrier: item.GetCarrier(),
|
||||||
|
SdkVersion: item.GetSdkVersion(),
|
||||||
|
Os: item.GetOs(),
|
||||||
|
ResultIPs: item.GetResultIPs(),
|
||||||
|
Status: item.GetStatus(),
|
||||||
|
ErrorCode: item.GetErrorCode(),
|
||||||
|
CostMs: item.GetCostMs(),
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
Day: day,
|
||||||
|
Summary: item.GetSummary(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.canWriteHTTPDNSAccessLogsToMySQL() {
|
||||||
|
for _, item := range mysqlLogs {
|
||||||
|
err = models.SharedHTTPDNSAccessLogDAO.CreateLog(s.NullTx(), item)
|
||||||
|
if err != nil {
|
||||||
|
if models.CheckSQLDuplicateErr(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store := clickhouse.NewHTTPDNSAccessLogsStore()
|
||||||
|
if s.canWriteHTTPDNSAccessLogsToClickHouse() && store.Client().IsConfigured() && len(chLogs) > 0 {
|
||||||
|
err = store.Insert(ctx, chLogs)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[HTTPDNS_ACCESS_LOG]write clickhouse failed, keep mysql success:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.CreateHTTPDNSAccessLogsResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogService) ListHTTPDNSAccessLogs(ctx context.Context, req *pb.ListHTTPDNSAccessLogsRequest) (*pb.ListHTTPDNSAccessLogsResponse, error) {
|
||||||
|
_, _, err := s.ValidateAdminAndUser(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
store := clickhouse.NewHTTPDNSAccessLogsStore()
|
||||||
|
canReadFromClickHouse := s.shouldReadHTTPDNSAccessLogsFromClickHouse() && store.Client().IsConfigured()
|
||||||
|
canReadFromMySQL := s.shouldReadHTTPDNSAccessLogsFromMySQL()
|
||||||
|
if canReadFromClickHouse {
|
||||||
|
resp, listErr := s.listFromClickHouse(ctx, store, req)
|
||||||
|
if listErr == nil {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
log.Println("[HTTPDNS_ACCESS_LOG]read clickhouse failed, fallback mysql:", listErr.Error())
|
||||||
|
if !canReadFromMySQL {
|
||||||
|
return nil, listErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !canReadFromMySQL {
|
||||||
|
return &pb.ListHTTPDNSAccessLogsResponse{
|
||||||
|
Logs: []*pb.HTTPDNSAccessLog{},
|
||||||
|
Total: 0,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := models.SharedHTTPDNSAccessLogDAO.CountLogs(s.NullTx(), req.GetDay(), req.GetClusterId(), req.GetNodeId(), req.GetAppId(), req.GetDomain(), req.GetStatus(), req.GetKeyword())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logs, err := models.SharedHTTPDNSAccessLogDAO.ListLogs(s.NullTx(), req.GetDay(), req.GetClusterId(), req.GetNodeId(), req.GetAppId(), req.GetDomain(), req.GetStatus(), req.GetKeyword(), req.GetOffset(), req.GetSize())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]*pb.HTTPDNSAccessLog, 0, len(logs))
|
||||||
|
for _, item := range logs {
|
||||||
|
if item == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
clusterName, _ := models.SharedHTTPDNSClusterDAO.FindEnabledClusterName(s.NullTx(), int64(item.ClusterId))
|
||||||
|
nodeName := ""
|
||||||
|
node, _ := models.SharedHTTPDNSNodeDAO.FindEnabledNode(s.NullTx(), int64(item.NodeId))
|
||||||
|
if node != nil {
|
||||||
|
nodeName = node.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, &pb.HTTPDNSAccessLog{
|
||||||
|
Id: int64(item.Id),
|
||||||
|
RequestId: item.RequestId,
|
||||||
|
ClusterId: int64(item.ClusterId),
|
||||||
|
NodeId: int64(item.NodeId),
|
||||||
|
AppId: item.AppId,
|
||||||
|
AppName: item.AppName,
|
||||||
|
Domain: item.Domain,
|
||||||
|
Qtype: item.QType,
|
||||||
|
ClientIP: item.ClientIP,
|
||||||
|
ClientRegion: item.ClientRegion,
|
||||||
|
Carrier: item.Carrier,
|
||||||
|
SdkVersion: item.SDKVersion,
|
||||||
|
Os: item.OS,
|
||||||
|
ResultIPs: item.ResultIPs,
|
||||||
|
Status: item.Status,
|
||||||
|
ErrorCode: item.ErrorCode,
|
||||||
|
CostMs: item.CostMs,
|
||||||
|
CreatedAt: int64(item.CreatedAt),
|
||||||
|
Day: item.Day,
|
||||||
|
Summary: item.Summary,
|
||||||
|
NodeName: nodeName,
|
||||||
|
ClusterName: clusterName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.ListHTTPDNSAccessLogsResponse{
|
||||||
|
Logs: result,
|
||||||
|
Total: total,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogService) listFromClickHouse(ctx context.Context, store *clickhouse.HTTPDNSAccessLogsStore, req *pb.ListHTTPDNSAccessLogsRequest) (*pb.ListHTTPDNSAccessLogsResponse, error) {
|
||||||
|
filter := clickhouse.HTTPDNSAccessLogListFilter{
|
||||||
|
Day: req.GetDay(),
|
||||||
|
ClusterId: req.GetClusterId(),
|
||||||
|
NodeId: req.GetNodeId(),
|
||||||
|
AppId: req.GetAppId(),
|
||||||
|
Domain: req.GetDomain(),
|
||||||
|
Status: req.GetStatus(),
|
||||||
|
Keyword: req.GetKeyword(),
|
||||||
|
Offset: req.GetOffset(),
|
||||||
|
Size: req.GetSize(),
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := store.Count(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rows, err := store.List(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]*pb.HTTPDNSAccessLog, 0, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
item := clickhouse.HTTPDNSRowToPB(row)
|
||||||
|
if item == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
clusterName, _ := models.SharedHTTPDNSClusterDAO.FindEnabledClusterName(s.NullTx(), item.GetClusterId())
|
||||||
|
nodeName := ""
|
||||||
|
node, _ := models.SharedHTTPDNSNodeDAO.FindEnabledNode(s.NullTx(), item.GetNodeId())
|
||||||
|
if node != nil {
|
||||||
|
nodeName = node.Name
|
||||||
|
}
|
||||||
|
item.ClusterName = clusterName
|
||||||
|
item.NodeName = nodeName
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.ListHTTPDNSAccessLogsResponse{
|
||||||
|
Logs: result,
|
||||||
|
Total: total,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//go:build !plus
|
||||||
|
|
||||||
|
package httpdns
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogService) canWriteHTTPDNSAccessLogsToMySQL() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogService) canWriteHTTPDNSAccessLogsToClickHouse() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogService) shouldReadHTTPDNSAccessLogsFromClickHouse() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogService) shouldReadHTTPDNSAccessLogsFromMySQL() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
//go:build plus
|
||||||
|
|
||||||
|
package httpdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
httpDNSAccessLogWriteTargetsLocker sync.RWMutex
|
||||||
|
httpDNSAccessLogWriteTargetsCache = &serverconfigs.AccessLogWriteTargets{
|
||||||
|
File: true,
|
||||||
|
MySQL: true,
|
||||||
|
ClickHouse: false,
|
||||||
|
}
|
||||||
|
httpDNSAccessLogWriteTargetsExpireAt int64
|
||||||
|
)
|
||||||
|
|
||||||
|
const httpDNSAccessLogWriteTargetsCacheTTL = 10 * time.Second
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogService) canWriteHTTPDNSAccessLogsToMySQL() bool {
|
||||||
|
targets := s.readHTTPDNSAccessLogWriteTargets()
|
||||||
|
if targets == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return targets.MySQL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogService) canWriteHTTPDNSAccessLogsToClickHouse() bool {
|
||||||
|
targets := s.readHTTPDNSAccessLogWriteTargets()
|
||||||
|
if targets == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return targets.ClickHouse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogService) shouldReadHTTPDNSAccessLogsFromClickHouse() bool {
|
||||||
|
targets := s.readHTTPDNSAccessLogWriteTargets()
|
||||||
|
if targets == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return targets.ClickHouse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogService) shouldReadHTTPDNSAccessLogsFromMySQL() bool {
|
||||||
|
targets := s.readHTTPDNSAccessLogWriteTargets()
|
||||||
|
if targets == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return targets.MySQL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogService) readHTTPDNSAccessLogWriteTargets() *serverconfigs.AccessLogWriteTargets {
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
httpDNSAccessLogWriteTargetsLocker.RLock()
|
||||||
|
if now < httpDNSAccessLogWriteTargetsExpireAt && httpDNSAccessLogWriteTargetsCache != nil {
|
||||||
|
targets := *httpDNSAccessLogWriteTargetsCache
|
||||||
|
httpDNSAccessLogWriteTargetsLocker.RUnlock()
|
||||||
|
return &targets
|
||||||
|
}
|
||||||
|
httpDNSAccessLogWriteTargetsLocker.RUnlock()
|
||||||
|
|
||||||
|
httpDNSAccessLogWriteTargetsLocker.Lock()
|
||||||
|
defer httpDNSAccessLogWriteTargetsLocker.Unlock()
|
||||||
|
|
||||||
|
// double-check
|
||||||
|
now = time.Now().Unix()
|
||||||
|
if now < httpDNSAccessLogWriteTargetsExpireAt && httpDNSAccessLogWriteTargetsCache != nil {
|
||||||
|
targets := *httpDNSAccessLogWriteTargetsCache
|
||||||
|
return &targets
|
||||||
|
}
|
||||||
|
|
||||||
|
targets := s.loadHTTPDNSAccessLogWriteTargetsFromPolicy()
|
||||||
|
if targets == nil {
|
||||||
|
targets = &serverconfigs.AccessLogWriteTargets{
|
||||||
|
File: true,
|
||||||
|
MySQL: true,
|
||||||
|
ClickHouse: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
httpDNSAccessLogWriteTargetsCache = targets
|
||||||
|
httpDNSAccessLogWriteTargetsExpireAt = time.Now().Add(httpDNSAccessLogWriteTargetsCacheTTL).Unix()
|
||||||
|
|
||||||
|
copyTargets := *targets
|
||||||
|
return ©Targets
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HTTPDNSAccessLogService) loadHTTPDNSAccessLogWriteTargetsFromPolicy() *serverconfigs.AccessLogWriteTargets {
|
||||||
|
tx := s.NullTx()
|
||||||
|
publicPolicyId, err := models.SharedHTTPAccessLogPolicyDAO.FindCurrentPublicPolicyId(tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[HTTPDNS_ACCESS_LOG]load public access log policy failed:", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if publicPolicyId <= 0 {
|
||||||
|
return &serverconfigs.AccessLogWriteTargets{
|
||||||
|
File: true,
|
||||||
|
MySQL: true,
|
||||||
|
ClickHouse: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
policy, err := models.SharedHTTPAccessLogPolicyDAO.FindEnabledHTTPAccessLogPolicy(tx, publicPolicyId)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[HTTPDNS_ACCESS_LOG]load access log policy detail failed:", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if policy == nil {
|
||||||
|
return &serverconfigs.AccessLogWriteTargets{
|
||||||
|
File: true,
|
||||||
|
MySQL: true,
|
||||||
|
ClickHouse: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverconfigs.ParseWriteTargetsFromPolicy(policy.WriteTargets, policy.Type, policy.DisableDefaultDB)
|
||||||
|
}
|
||||||
246
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_app.go
Normal file
246
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_app.go
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
package httpdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPDNSAppService HTTPDNS应用服务
|
||||||
|
type HTTPDNSAppService struct {
|
||||||
|
services.BaseService
|
||||||
|
pb.UnimplementedHTTPDNSAppServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppService) CreateHTTPDNSApp(ctx context.Context, req *pb.CreateHTTPDNSAppRequest) (*pb.CreateHTTPDNSAppResponse, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(req.Name) == 0 || len(req.AppId) == 0 {
|
||||||
|
return nil, errors.New("required 'name' and 'appId'")
|
||||||
|
}
|
||||||
|
if req.PrimaryClusterId <= 0 {
|
||||||
|
return nil, errors.New("required 'primaryClusterId'")
|
||||||
|
}
|
||||||
|
var appDbId int64
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
exists, err := models.SharedHTTPDNSAppDAO.FindEnabledAppWithAppId(tx, strings.TrimSpace(req.AppId))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists != nil {
|
||||||
|
return errors.New("appId already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
appDbId, err = models.SharedHTTPDNSAppDAO.CreateApp(tx, req.Name, strings.TrimSpace(req.AppId), req.PrimaryClusterId, req.BackupClusterId, req.IsOn, req.UserId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _, err = models.SharedHTTPDNSAppSecretDAO.InitAppSecret(tx, appDbId, req.SignEnabled)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSAppTasksByAppDbId(tx, appDbId, models.HTTPDNSNodeTaskTypeAppChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.CreateHTTPDNSAppResponse{AppDbId: appDbId}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppService) UpdateHTTPDNSApp(ctx context.Context, req *pb.UpdateHTTPDNSAppRequest) (*pb.RPCSuccess, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
oldApp, err := models.SharedHTTPDNSAppDAO.FindEnabledApp(tx, req.AppDbId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if oldApp == nil {
|
||||||
|
return errors.New("app not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = models.SharedHTTPDNSAppDAO.UpdateApp(tx, req.AppDbId, req.Name, req.PrimaryClusterId, req.BackupClusterId, req.IsOn, req.UserId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = notifyHTTPDNSAppTasksByApp(tx, oldApp, models.HTTPDNSNodeTaskTypeAppChanged)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSAppTasksByAppDbId(tx, req.AppDbId, models.HTTPDNSNodeTaskTypeAppChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppService) DeleteHTTPDNSApp(ctx context.Context, req *pb.DeleteHTTPDNSAppRequest) (*pb.RPCSuccess, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
app, err := models.SharedHTTPDNSAppDAO.FindEnabledApp(tx, req.AppDbId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if app == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) 先停用规则记录
|
||||||
|
rules, err := models.SharedHTTPDNSCustomRuleDAO.ListEnabledRulesWithAppId(tx, req.AppDbId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, rule := range rules {
|
||||||
|
err = models.SharedHTTPDNSCustomRuleRecordDAO.DisableRecordsWithRuleId(tx, int64(rule.Id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 停用规则、域名、密钥
|
||||||
|
err = models.SharedHTTPDNSCustomRuleDAO.DisableRulesWithAppId(tx, req.AppDbId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = models.SharedHTTPDNSDomainDAO.DisableDomainsWithAppId(tx, req.AppDbId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = models.SharedHTTPDNSAppSecretDAO.DisableAppSecret(tx, req.AppDbId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) 删除该应用的 MySQL 访问日志,避免残留
|
||||||
|
err = models.SharedHTTPDNSAccessLogDAO.DeleteLogsWithAppId(tx, app.AppId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) 最后停用应用
|
||||||
|
err = models.SharedHTTPDNSAppDAO.DisableApp(tx, req.AppDbId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return notifyHTTPDNSAppTasksByApp(tx, app, models.HTTPDNSNodeTaskTypeAppChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppService) FindHTTPDNSApp(ctx context.Context, req *pb.FindHTTPDNSAppRequest) (*pb.FindHTTPDNSAppResponse, error) {
|
||||||
|
_, _, err := this.ValidateAdminAndUser(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
app, err := models.SharedHTTPDNSAppDAO.FindEnabledApp(this.NullTx(), req.AppDbId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
secret, err := models.SharedHTTPDNSAppSecretDAO.FindEnabledAppSecret(this.NullTx(), req.AppDbId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.FindHTTPDNSAppResponse{App: toPBApp(app, secret)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppService) ListHTTPDNSApps(ctx context.Context, req *pb.ListHTTPDNSAppsRequest) (*pb.ListHTTPDNSAppsResponse, error) {
|
||||||
|
_, _, err := this.ValidateAdminAndUser(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
apps, err := models.SharedHTTPDNSAppDAO.ListEnabledApps(this.NullTx(), req.Offset, req.Size, req.Keyword)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var pbApps []*pb.HTTPDNSApp
|
||||||
|
for _, app := range apps {
|
||||||
|
secret, err := models.SharedHTTPDNSAppSecretDAO.FindEnabledAppSecret(this.NullTx(), int64(app.Id))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pbApps = append(pbApps, toPBApp(app, secret))
|
||||||
|
}
|
||||||
|
return &pb.ListHTTPDNSAppsResponse{Apps: pbApps}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppService) FindAllHTTPDNSApps(ctx context.Context, req *pb.FindAllHTTPDNSAppsRequest) (*pb.FindAllHTTPDNSAppsResponse, error) {
|
||||||
|
_, _, validateErr := this.ValidateAdminAndUser(ctx, true)
|
||||||
|
if validateErr != nil {
|
||||||
|
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
|
||||||
|
return nil, validateErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apps, err := models.SharedHTTPDNSAppDAO.FindAllEnabledApps(this.NullTx())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var pbApps []*pb.HTTPDNSApp
|
||||||
|
for _, app := range apps {
|
||||||
|
secret, err := models.SharedHTTPDNSAppSecretDAO.FindEnabledAppSecret(this.NullTx(), int64(app.Id))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pbApps = append(pbApps, toPBApp(app, secret))
|
||||||
|
}
|
||||||
|
return &pb.FindAllHTTPDNSAppsResponse{Apps: pbApps}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppService) UpdateHTTPDNSAppSignEnabled(ctx context.Context, req *pb.UpdateHTTPDNSAppSignEnabledRequest) (*pb.RPCSuccess, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
err := models.SharedHTTPDNSAppSecretDAO.UpdateSignEnabled(tx, req.AppDbId, req.SignEnabled)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSAppTasksByAppDbId(tx, req.AppDbId, models.HTTPDNSNodeTaskTypeAppChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSAppService) ResetHTTPDNSAppSignSecret(ctx context.Context, req *pb.ResetHTTPDNSAppSignSecretRequest) (*pb.ResetHTTPDNSAppSignSecretResponse, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var signSecret string
|
||||||
|
var updatedAt int64
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
var err error
|
||||||
|
signSecret, updatedAt, err = models.SharedHTTPDNSAppSecretDAO.ResetSignSecret(tx, req.AppDbId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSAppTasksByAppDbId(tx, req.AppDbId, models.HTTPDNSNodeTaskTypeAppChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.ResetHTTPDNSAppSignSecretResponse{
|
||||||
|
SignSecret: signSecret,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
170
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_cluster.go
Normal file
170
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_cluster.go
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
package httpdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPDNSClusterService HTTPDNS集群服务
|
||||||
|
type HTTPDNSClusterService struct {
|
||||||
|
services.BaseService
|
||||||
|
pb.UnimplementedHTTPDNSClusterServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterService) CreateHTTPDNSCluster(ctx context.Context, req *pb.CreateHTTPDNSClusterRequest) (*pb.CreateHTTPDNSClusterResponse, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(req.Name) == 0 {
|
||||||
|
return nil, errors.New("required 'name'")
|
||||||
|
}
|
||||||
|
var clusterId int64
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
clusterId, err = models.SharedHTTPDNSClusterDAO.CreateCluster(tx, req.Name, req.ServiceDomain, req.DefaultTTL, req.FallbackTimeoutMs, req.InstallDir, req.TlsPolicyJSON, req.IsOn, req.IsDefault)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSClusterTask(tx, clusterId, models.HTTPDNSNodeTaskTypeConfigChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.CreateHTTPDNSClusterResponse{ClusterId: clusterId}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterService) UpdateHTTPDNSCluster(ctx context.Context, req *pb.UpdateHTTPDNSClusterRequest) (*pb.RPCSuccess, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
err = models.SharedHTTPDNSClusterDAO.UpdateCluster(tx, req.ClusterId, req.Name, req.ServiceDomain, req.DefaultTTL, req.FallbackTimeoutMs, req.InstallDir, req.TlsPolicyJSON, req.IsOn, req.IsDefault)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
taskType := models.HTTPDNSNodeTaskTypeConfigChanged
|
||||||
|
if len(req.TlsPolicyJSON) > 0 {
|
||||||
|
taskType = models.HTTPDNSNodeTaskTypeTLSChanged
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSClusterTask(tx, req.ClusterId, taskType)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterService) DeleteHTTPDNSCluster(ctx context.Context, req *pb.DeleteHTTPDNSClusterRequest) (*pb.RPCSuccess, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
err = models.SharedHTTPDNSClusterDAO.DisableCluster(tx, req.ClusterId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSClusterTask(tx, req.ClusterId, models.HTTPDNSNodeTaskTypeConfigChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterService) FindHTTPDNSCluster(ctx context.Context, req *pb.FindHTTPDNSClusterRequest) (*pb.FindHTTPDNSClusterResponse, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cluster, err := models.SharedHTTPDNSClusterDAO.FindEnabledCluster(this.NullTx(), req.ClusterId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.FindHTTPDNSClusterResponse{Cluster: toPBCluster(cluster)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterService) ListHTTPDNSClusters(ctx context.Context, req *pb.ListHTTPDNSClustersRequest) (*pb.ListHTTPDNSClustersResponse, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clusters, err := models.SharedHTTPDNSClusterDAO.ListEnabledClusters(this.NullTx(), req.Offset, req.Size, req.Keyword)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var pbClusters []*pb.HTTPDNSCluster
|
||||||
|
for _, cluster := range clusters {
|
||||||
|
pbClusters = append(pbClusters, toPBCluster(cluster))
|
||||||
|
}
|
||||||
|
return &pb.ListHTTPDNSClustersResponse{Clusters: pbClusters}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterService) FindAllHTTPDNSClusters(ctx context.Context, req *pb.FindAllHTTPDNSClustersRequest) (*pb.FindAllHTTPDNSClustersResponse, error) {
|
||||||
|
_, _, validateErr := this.ValidateAdminAndUser(ctx, true)
|
||||||
|
if validateErr != nil {
|
||||||
|
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
|
||||||
|
return nil, validateErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clusters, err := models.SharedHTTPDNSClusterDAO.FindAllEnabledClusters(this.NullTx())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var pbClusters []*pb.HTTPDNSCluster
|
||||||
|
for _, cluster := range clusters {
|
||||||
|
pbClusters = append(pbClusters, toPBCluster(cluster))
|
||||||
|
}
|
||||||
|
return &pb.FindAllHTTPDNSClustersResponse{Clusters: pbClusters}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterService) UpdateHTTPDNSClusterDefault(ctx context.Context, req *pb.UpdateHTTPDNSClusterDefaultRequest) (*pb.RPCSuccess, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
err = models.SharedHTTPDNSClusterDAO.UpdateDefaultCluster(tx, req.ClusterId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
clusters, err := models.SharedHTTPDNSClusterDAO.FindAllEnabledClusters(tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cluster := range clusters {
|
||||||
|
err = notifyHTTPDNSClusterTask(tx, int64(cluster.Id), models.HTTPDNSNodeTaskTypeConfigChanged)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSClusterService) ListHTTPDNSNodesWithClusterId(ctx context.Context, req *pb.ListHTTPDNSNodesWithClusterIdRequest) (*pb.ListHTTPDNSNodesWithClusterIdResponse, error) {
|
||||||
|
_, _, err := this.ValidateAdminAndUser(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodes, err := models.SharedHTTPDNSNodeDAO.ListEnabledNodes(this.NullTx(), req.ClusterId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var pbNodes []*pb.HTTPDNSNode
|
||||||
|
for _, node := range nodes {
|
||||||
|
pbNodes = append(pbNodes, toPBNode(node))
|
||||||
|
}
|
||||||
|
return &pb.ListHTTPDNSNodesWithClusterIdResponse{Nodes: pbNodes}, nil
|
||||||
|
}
|
||||||
112
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_domain.go
Normal file
112
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_domain.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package httpdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPDNSDomainService HTTPDNS域名服务
|
||||||
|
type HTTPDNSDomainService struct {
|
||||||
|
services.BaseService
|
||||||
|
pb.UnimplementedHTTPDNSDomainServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSDomainService) CreateHTTPDNSDomain(ctx context.Context, req *pb.CreateHTTPDNSDomainRequest) (*pb.CreateHTTPDNSDomainResponse, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if req.AppDbId <= 0 || len(req.Domain) == 0 {
|
||||||
|
return nil, errors.New("required 'appDbId' and 'domain'")
|
||||||
|
}
|
||||||
|
var domainId int64
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
domainId, err = models.SharedHTTPDNSDomainDAO.CreateDomain(tx, req.AppDbId, req.Domain, req.IsOn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSAppTasksByAppDbId(tx, req.AppDbId, models.HTTPDNSNodeTaskTypeDomainChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.CreateHTTPDNSDomainResponse{DomainId: domainId}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSDomainService) DeleteHTTPDNSDomain(ctx context.Context, req *pb.DeleteHTTPDNSDomainRequest) (*pb.RPCSuccess, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
domain, err := models.SharedHTTPDNSDomainDAO.FindEnabledDomain(tx, req.DomainId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if domain == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = models.SharedHTTPDNSDomainDAO.DisableDomain(tx, req.DomainId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(domain.AppId), models.HTTPDNSNodeTaskTypeDomainChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSDomainService) UpdateHTTPDNSDomainStatus(ctx context.Context, req *pb.UpdateHTTPDNSDomainStatusRequest) (*pb.RPCSuccess, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
domain, err := models.SharedHTTPDNSDomainDAO.FindEnabledDomain(tx, req.DomainId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if domain == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = models.SharedHTTPDNSDomainDAO.UpdateDomainStatus(tx, req.DomainId, req.IsOn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(domain.AppId), models.HTTPDNSNodeTaskTypeDomainChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSDomainService) ListHTTPDNSDomainsWithAppId(ctx context.Context, req *pb.ListHTTPDNSDomainsWithAppIdRequest) (*pb.ListHTTPDNSDomainsWithAppIdResponse, error) {
|
||||||
|
_, _, validateErr := this.ValidateAdminAndUser(ctx, true)
|
||||||
|
if validateErr != nil {
|
||||||
|
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
|
||||||
|
return nil, validateErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
domains, err := models.SharedHTTPDNSDomainDAO.ListEnabledDomainsWithAppId(this.NullTx(), req.AppDbId, req.Keyword)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var pbDomains []*pb.HTTPDNSDomain
|
||||||
|
for _, domain := range domains {
|
||||||
|
ruleCount, err := models.SharedHTTPDNSCustomRuleDAO.CountEnabledRulesWithDomainId(this.NullTx(), int64(domain.Id))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pbDomains = append(pbDomains, toPBDomain(domain, ruleCount))
|
||||||
|
}
|
||||||
|
return &pb.ListHTTPDNSDomainsWithAppIdResponse{Domains: pbDomains}, nil
|
||||||
|
}
|
||||||
185
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_node.go
Normal file
185
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_node.go
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
package httpdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/goman"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/installers"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
"github.com/iwind/TeaGo/logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPDNSNodeService HTTPDNS节点服务
|
||||||
|
type HTTPDNSNodeService struct {
|
||||||
|
services.BaseService
|
||||||
|
pb.UnimplementedHTTPDNSNodeServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSNodeService) CreateHTTPDNSNode(ctx context.Context, req *pb.CreateHTTPDNSNodeRequest) (*pb.CreateHTTPDNSNodeResponse, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if req.ClusterId <= 0 {
|
||||||
|
return nil, errors.New("required 'clusterId'")
|
||||||
|
}
|
||||||
|
var nodeId int64
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
nodeId, err = models.SharedHTTPDNSNodeDAO.CreateNode(tx, req.ClusterId, req.Name, req.InstallDir, req.IsOn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSClusterTask(tx, req.ClusterId, models.HTTPDNSNodeTaskTypeConfigChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.CreateHTTPDNSNodeResponse{NodeId: nodeId}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSNodeService) UpdateHTTPDNSNode(ctx context.Context, req *pb.UpdateHTTPDNSNodeRequest) (*pb.RPCSuccess, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
node, err := models.SharedHTTPDNSNodeDAO.FindEnabledNode(tx, req.NodeId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if node == nil {
|
||||||
|
return errors.New("node not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = models.SharedHTTPDNSNodeDAO.UpdateNode(tx, req.NodeId, req.Name, req.InstallDir, req.IsOn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSClusterTask(tx, int64(node.ClusterId), models.HTTPDNSNodeTaskTypeConfigChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSNodeService) DeleteHTTPDNSNode(ctx context.Context, req *pb.DeleteHTTPDNSNodeRequest) (*pb.RPCSuccess, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
node, err := models.SharedHTTPDNSNodeDAO.FindEnabledNode(tx, req.NodeId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = models.SharedHTTPDNSNodeDAO.DisableNode(tx, req.NodeId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSClusterTask(tx, int64(node.ClusterId), models.HTTPDNSNodeTaskTypeConfigChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSNodeService) FindHTTPDNSNode(ctx context.Context, req *pb.FindHTTPDNSNodeRequest) (*pb.FindHTTPDNSNodeResponse, error) {
|
||||||
|
nodeId := req.NodeId
|
||||||
|
if nodeId <= 0 {
|
||||||
|
parsedNodeId, nodeErr := this.ValidateHTTPDNSNode(ctx)
|
||||||
|
if nodeErr != nil {
|
||||||
|
return nil, errors.New("invalid 'nodeId'")
|
||||||
|
}
|
||||||
|
nodeId = parsedNodeId
|
||||||
|
} else {
|
||||||
|
_, _, validateErr := this.ValidateAdminAndUser(ctx, true)
|
||||||
|
if validateErr != nil {
|
||||||
|
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
|
||||||
|
return nil, validateErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node, err := models.SharedHTTPDNSNodeDAO.FindEnabledNode(this.NullTx(), nodeId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.FindHTTPDNSNodeResponse{Node: toPBNode(node)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSNodeService) ListHTTPDNSNodes(ctx context.Context, req *pb.ListHTTPDNSNodesRequest) (*pb.ListHTTPDNSNodesResponse, error) {
|
||||||
|
_, _, err := this.ValidateAdminAndUser(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodes, err := models.SharedHTTPDNSNodeDAO.ListEnabledNodes(this.NullTx(), req.ClusterId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var pbNodes []*pb.HTTPDNSNode
|
||||||
|
for _, node := range nodes {
|
||||||
|
pbNodes = append(pbNodes, toPBNode(node))
|
||||||
|
}
|
||||||
|
return &pb.ListHTTPDNSNodesResponse{Nodes: pbNodes}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSNodeService) UpdateHTTPDNSNodeStatus(ctx context.Context, req *pb.UpdateHTTPDNSNodeStatusRequest) (*pb.RPCSuccess, error) {
|
||||||
|
nodeId := req.GetNodeId()
|
||||||
|
isAdminCaller := false
|
||||||
|
if nodeId > 0 {
|
||||||
|
if _, adminErr := this.ValidateAdmin(ctx); adminErr == nil {
|
||||||
|
isAdminCaller = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isAdminCaller {
|
||||||
|
if nodeId <= 0 {
|
||||||
|
parsedNodeId, err := this.ValidateHTTPDNSNode(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodeId = parsedNodeId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nodeId <= 0 {
|
||||||
|
return nil, errors.New("invalid 'nodeId'")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := models.SharedHTTPDNSNodeDAO.UpdateNodeStatus(this.NullTx(), nodeId, req.GetIsUp(), req.GetIsInstalled(), req.GetIsActive(), req.GetStatusJSON(), req.GetInstallStatusJSON())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAdminCaller && shouldTriggerHTTPDNSInstall(req.GetInstallStatusJSON()) {
|
||||||
|
goman.New(func() {
|
||||||
|
installErr := installers.SharedHTTPDNSNodeQueue().InstallNodeProcess(nodeId, false)
|
||||||
|
if installErr != nil {
|
||||||
|
logs.Println("[RPC][HTTPDNS]install node failed:", installErr.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldTriggerHTTPDNSInstall(installStatusJSON []byte) bool {
|
||||||
|
if len(installStatusJSON) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
installStatus := &models.NodeInstallStatus{}
|
||||||
|
err := json.Unmarshal(installStatusJSON, installStatus)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return installStatus.IsRunning && !installStatus.IsFinished
|
||||||
|
}
|
||||||
193
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_rule.go
Normal file
193
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_rule.go
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
package httpdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPDNSRuleService HTTPDNS规则服务
|
||||||
|
type HTTPDNSRuleService struct {
|
||||||
|
services.BaseService
|
||||||
|
pb.UnimplementedHTTPDNSRuleServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSRuleService) CreateHTTPDNSCustomRule(ctx context.Context, req *pb.CreateHTTPDNSCustomRuleRequest) (*pb.CreateHTTPDNSCustomRuleResponse, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if req.Rule == nil {
|
||||||
|
return nil, errors.New("required 'rule'")
|
||||||
|
}
|
||||||
|
var ruleId int64
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
rule := &models.HTTPDNSCustomRule{
|
||||||
|
AppId: uint32(req.Rule.AppId),
|
||||||
|
DomainId: uint32(req.Rule.DomainId),
|
||||||
|
RuleName: req.Rule.RuleName,
|
||||||
|
LineScope: req.Rule.LineScope,
|
||||||
|
LineCarrier: req.Rule.LineCarrier,
|
||||||
|
LineRegion: req.Rule.LineRegion,
|
||||||
|
LineProvince: req.Rule.LineProvince,
|
||||||
|
LineContinent: req.Rule.LineContinent,
|
||||||
|
LineCountry: req.Rule.LineCountry,
|
||||||
|
TTL: req.Rule.Ttl,
|
||||||
|
IsOn: req.Rule.IsOn,
|
||||||
|
Priority: req.Rule.Priority,
|
||||||
|
}
|
||||||
|
ruleId, err = models.SharedHTTPDNSCustomRuleDAO.CreateRule(tx, rule)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, record := range req.Rule.Records {
|
||||||
|
_, err := models.SharedHTTPDNSCustomRuleRecordDAO.CreateRecord(tx, ruleId, record.RecordType, record.RecordValue, record.Weight, record.Sort)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSAppTasksByAppDbId(tx, req.Rule.AppId, models.HTTPDNSNodeTaskTypeRuleChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.CreateHTTPDNSCustomRuleResponse{RuleId: ruleId}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSRuleService) UpdateHTTPDNSCustomRule(ctx context.Context, req *pb.UpdateHTTPDNSCustomRuleRequest) (*pb.RPCSuccess, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if req.Rule == nil || req.Rule.Id <= 0 {
|
||||||
|
return nil, errors.New("invalid 'rule.id'")
|
||||||
|
}
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
oldRule, err := models.SharedHTTPDNSCustomRuleDAO.FindEnabledRule(tx, req.Rule.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if oldRule == nil {
|
||||||
|
return errors.New("rule not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
rule := &models.HTTPDNSCustomRule{
|
||||||
|
Id: uint32(req.Rule.Id),
|
||||||
|
RuleName: req.Rule.RuleName,
|
||||||
|
LineScope: req.Rule.LineScope,
|
||||||
|
LineCarrier: req.Rule.LineCarrier,
|
||||||
|
LineRegion: req.Rule.LineRegion,
|
||||||
|
LineProvince: req.Rule.LineProvince,
|
||||||
|
LineContinent: req.Rule.LineContinent,
|
||||||
|
LineCountry: req.Rule.LineCountry,
|
||||||
|
TTL: req.Rule.Ttl,
|
||||||
|
IsOn: req.Rule.IsOn,
|
||||||
|
Priority: req.Rule.Priority,
|
||||||
|
}
|
||||||
|
err = models.SharedHTTPDNSCustomRuleDAO.UpdateRule(tx, rule)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = models.SharedHTTPDNSCustomRuleRecordDAO.DisableRecordsWithRuleId(tx, req.Rule.Id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, record := range req.Rule.Records {
|
||||||
|
_, err := models.SharedHTTPDNSCustomRuleRecordDAO.CreateRecord(tx, req.Rule.Id, record.RecordType, record.RecordValue, record.Weight, record.Sort)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = notifyHTTPDNSAppTasksByAppDbId(tx, int64(oldRule.AppId), models.HTTPDNSNodeTaskTypeRuleChanged)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetAppDbId := req.Rule.AppId
|
||||||
|
if targetAppDbId <= 0 {
|
||||||
|
targetAppDbId = int64(oldRule.AppId)
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSAppTasksByAppDbId(tx, targetAppDbId, models.HTTPDNSNodeTaskTypeRuleChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSRuleService) DeleteHTTPDNSCustomRule(ctx context.Context, req *pb.DeleteHTTPDNSCustomRuleRequest) (*pb.RPCSuccess, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
rule, err := models.SharedHTTPDNSCustomRuleDAO.FindEnabledRule(tx, req.RuleId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rule == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = models.SharedHTTPDNSCustomRuleDAO.DisableRule(tx, req.RuleId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(rule.AppId), models.HTTPDNSNodeTaskTypeRuleChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSRuleService) UpdateHTTPDNSCustomRuleStatus(ctx context.Context, req *pb.UpdateHTTPDNSCustomRuleStatusRequest) (*pb.RPCSuccess, error) {
|
||||||
|
_, err := this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||||
|
rule, err := models.SharedHTTPDNSCustomRuleDAO.FindEnabledRule(tx, req.RuleId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rule == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = models.SharedHTTPDNSCustomRuleDAO.UpdateRuleStatus(tx, req.RuleId, req.IsOn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(rule.AppId), models.HTTPDNSNodeTaskTypeRuleChanged)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSRuleService) ListHTTPDNSCustomRulesWithDomainId(ctx context.Context, req *pb.ListHTTPDNSCustomRulesWithDomainIdRequest) (*pb.ListHTTPDNSCustomRulesWithDomainIdResponse, error) {
|
||||||
|
_, _, validateErr := this.ValidateAdminAndUser(ctx, true)
|
||||||
|
if validateErr != nil {
|
||||||
|
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
|
||||||
|
return nil, validateErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rules, err := models.SharedHTTPDNSCustomRuleDAO.ListEnabledRulesWithDomainId(this.NullTx(), req.DomainId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var pbRules []*pb.HTTPDNSCustomRule
|
||||||
|
for _, rule := range rules {
|
||||||
|
records, err := models.SharedHTTPDNSCustomRuleRecordDAO.ListEnabledRecordsWithRuleId(this.NullTx(), int64(rule.Id))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pbRules = append(pbRules, toPBRule(rule, records))
|
||||||
|
}
|
||||||
|
return &pb.ListHTTPDNSCustomRulesWithDomainIdResponse{Rules: pbRules}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package httpdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPDNSRuntimeLogService HTTPDNS运行日志服务
|
||||||
|
type HTTPDNSRuntimeLogService struct {
|
||||||
|
services.BaseService
|
||||||
|
pb.UnimplementedHTTPDNSRuntimeLogServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSRuntimeLogService) CreateHTTPDNSRuntimeLogs(ctx context.Context, req *pb.CreateHTTPDNSRuntimeLogsRequest) (*pb.CreateHTTPDNSRuntimeLogsResponse, error) {
|
||||||
|
nodeIdInContext, err := this.ValidateHTTPDNSNode(ctx)
|
||||||
|
if err != nil {
|
||||||
|
_, err = this.ValidateAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, item := range req.Logs {
|
||||||
|
createdAt := item.CreatedAt
|
||||||
|
if createdAt <= 0 {
|
||||||
|
createdAt = time.Now().Unix()
|
||||||
|
}
|
||||||
|
day := item.Day
|
||||||
|
if len(day) == 0 {
|
||||||
|
day = timeutil.Format("Ymd")
|
||||||
|
}
|
||||||
|
nodeId := item.NodeId
|
||||||
|
// When called by HTTPDNS node, trust node id parsed from RPC token.
|
||||||
|
if nodeIdInContext > 0 {
|
||||||
|
nodeId = nodeIdInContext
|
||||||
|
}
|
||||||
|
|
||||||
|
clusterId := item.ClusterId
|
||||||
|
if clusterId <= 0 && nodeId > 0 {
|
||||||
|
clusterId, _ = models.SharedHTTPDNSNodeDAO.FindNodeClusterId(this.NullTx(), nodeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
log := &models.HTTPDNSRuntimeLog{
|
||||||
|
ClusterId: uint32(clusterId),
|
||||||
|
NodeId: uint32(nodeId),
|
||||||
|
Level: item.Level,
|
||||||
|
Type: item.Type,
|
||||||
|
Module: item.Module,
|
||||||
|
Description: item.Description,
|
||||||
|
Count: item.Count,
|
||||||
|
RequestId: item.RequestId,
|
||||||
|
CreatedAt: uint64(createdAt),
|
||||||
|
Day: day,
|
||||||
|
}
|
||||||
|
err := models.SharedHTTPDNSRuntimeLogDAO.CreateLog(this.NullTx(), log)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &pb.CreateHTTPDNSRuntimeLogsResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSRuntimeLogService) ListHTTPDNSRuntimeLogs(ctx context.Context, req *pb.ListHTTPDNSRuntimeLogsRequest) (*pb.ListHTTPDNSRuntimeLogsResponse, error) {
|
||||||
|
_, _, err := this.ValidateAdminAndUser(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
total, err := models.SharedHTTPDNSRuntimeLogDAO.CountLogs(this.NullTx(), req.Day, req.ClusterId, req.NodeId, req.Level, req.Keyword)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logs, err := models.SharedHTTPDNSRuntimeLogDAO.ListLogs(this.NullTx(), req.Day, req.ClusterId, req.NodeId, req.Level, req.Keyword, req.Offset, req.Size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var pbLogs []*pb.HTTPDNSRuntimeLog
|
||||||
|
for _, item := range logs {
|
||||||
|
clusterName, _ := models.SharedHTTPDNSClusterDAO.FindEnabledClusterName(this.NullTx(), int64(item.ClusterId))
|
||||||
|
nodeName := ""
|
||||||
|
node, _ := models.SharedHTTPDNSNodeDAO.FindEnabledNode(this.NullTx(), int64(item.NodeId))
|
||||||
|
if node != nil {
|
||||||
|
nodeName = node.Name
|
||||||
|
}
|
||||||
|
pbLogs = append(pbLogs, &pb.HTTPDNSRuntimeLog{
|
||||||
|
Id: int64(item.Id),
|
||||||
|
ClusterId: int64(item.ClusterId),
|
||||||
|
NodeId: int64(item.NodeId),
|
||||||
|
Level: item.Level,
|
||||||
|
Type: item.Type,
|
||||||
|
Module: item.Module,
|
||||||
|
Description: item.Description,
|
||||||
|
Count: item.Count,
|
||||||
|
RequestId: item.RequestId,
|
||||||
|
CreatedAt: int64(item.CreatedAt),
|
||||||
|
Day: item.Day,
|
||||||
|
ClusterName: clusterName,
|
||||||
|
NodeName: nodeName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return &pb.ListHTTPDNSRuntimeLogsResponse{
|
||||||
|
Logs: pbLogs,
|
||||||
|
Total: total,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
252
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_sandbox.go
Normal file
252
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_sandbox.go
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
package httpdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/rands"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPDNSSandboxService HTTPDNS解析测试服务
|
||||||
|
type HTTPDNSSandboxService struct {
|
||||||
|
services.BaseService
|
||||||
|
pb.UnimplementedHTTPDNSSandboxServiceServer
|
||||||
|
}
|
||||||
|
|
||||||
|
// nodeResolveResponse 节点返回的 JSON 结构(对齐 EdgeHttpDNS resolve_server.go)
|
||||||
|
type nodeResolveResponse struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
RequestID string `json:"requestId"`
|
||||||
|
Data *nodeResolveData `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeResolveData struct {
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
QType string `json:"qtype"`
|
||||||
|
TTL int32 `json:"ttl"`
|
||||||
|
Records []*nodeResolveRecord `json:"records"`
|
||||||
|
Client *nodeClientInfo `json:"client"`
|
||||||
|
Summary string `json:"summary"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeResolveRecord struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Weight int32 `json:"weight"`
|
||||||
|
Line string `json:"line"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeClientInfo struct {
|
||||||
|
IP string `json:"ip"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
Carrier string `json:"carrier"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *HTTPDNSSandboxService) TestHTTPDNSResolve(ctx context.Context, req *pb.TestHTTPDNSResolveRequest) (*pb.TestHTTPDNSResolveResponse, error) {
|
||||||
|
_, _, err := this.ValidateAdminAndUser(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.AppId) == 0 || len(req.Domain) == 0 {
|
||||||
|
return nil, errors.New("appId 和 domain 不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
app, err := models.SharedHTTPDNSAppDAO.FindEnabledAppWithAppId(this.NullTx(), req.AppId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if app == nil || !app.IsOn {
|
||||||
|
return &pb.TestHTTPDNSResolveResponse{
|
||||||
|
Code: "APP_NOT_FOUND_OR_DISABLED",
|
||||||
|
Message: "找不到指定的应用,或该应用已下线",
|
||||||
|
RequestId: "rid-" + rands.HexString(12),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
if req.ClusterId > 0 && req.ClusterId != int64(app.PrimaryClusterId) && req.ClusterId != int64(app.BackupClusterId) {
|
||||||
|
return &pb.TestHTTPDNSResolveResponse{
|
||||||
|
Code: "APP_CLUSTER_MISMATCH",
|
||||||
|
Message: "当前应用未绑定到该集群 (主集群: " + strconv.FormatInt(int64(app.PrimaryClusterId), 10) + ", 备用集群: " + strconv.FormatInt(int64(app.BackupClusterId), 10) + ")",
|
||||||
|
RequestId: "rid-" + rands.HexString(12),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
qtype := strings.ToUpper(strings.TrimSpace(req.Qtype))
|
||||||
|
if qtype == "" {
|
||||||
|
qtype = "A"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取集群服务域名
|
||||||
|
clusterId := req.ClusterId
|
||||||
|
if clusterId <= 0 {
|
||||||
|
clusterId = int64(app.PrimaryClusterId)
|
||||||
|
}
|
||||||
|
cluster, err := models.SharedHTTPDNSClusterDAO.FindEnabledCluster(this.NullTx(), clusterId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cluster == nil {
|
||||||
|
return &pb.TestHTTPDNSResolveResponse{
|
||||||
|
Code: "CLUSTER_NOT_FOUND",
|
||||||
|
Message: "找不到指定的集群",
|
||||||
|
RequestId: "rid-" + rands.HexString(12),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceDomain := strings.TrimSpace(cluster.ServiceDomain)
|
||||||
|
if len(serviceDomain) == 0 {
|
||||||
|
return &pb.TestHTTPDNSResolveResponse{
|
||||||
|
Code: "NO_SERVICE_DOMAIN",
|
||||||
|
Message: "该集群未配置服务域名",
|
||||||
|
RequestId: "rid-" + rands.HexString(12),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造请求转发到 EdgeHttpDNS 节点
|
||||||
|
secret, err := models.SharedHTTPDNSAppSecretDAO.FindEnabledAppSecret(this.NullTx(), int64(app.Id))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
query := url.Values{}
|
||||||
|
query.Set("appId", req.AppId)
|
||||||
|
query.Set("dn", req.Domain)
|
||||||
|
query.Set("qtype", qtype)
|
||||||
|
if len(req.ClientIP) > 0 {
|
||||||
|
query.Set("cip", req.ClientIP)
|
||||||
|
}
|
||||||
|
if len(req.Sid) > 0 {
|
||||||
|
query.Set("sid", req.Sid)
|
||||||
|
}
|
||||||
|
if len(req.SdkVersion) > 0 {
|
||||||
|
query.Set("sdk_version", req.SdkVersion)
|
||||||
|
}
|
||||||
|
if len(req.Os) > 0 {
|
||||||
|
query.Set("os", req.Os)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用开启验签时,沙盒自动生成签名参数,避免测试请求被拒绝
|
||||||
|
if secret != nil && secret.SignEnabled {
|
||||||
|
signSecret := strings.TrimSpace(secret.SignSecret)
|
||||||
|
if len(signSecret) == 0 {
|
||||||
|
return &pb.TestHTTPDNSResolveResponse{
|
||||||
|
Code: "SIGN_INVALID",
|
||||||
|
Message: "应用开启了请求验签,但未配置有效加签 Secret",
|
||||||
|
RequestId: "rid-" + rands.HexString(12),
|
||||||
|
Domain: req.Domain,
|
||||||
|
Qtype: qtype,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := strconv.FormatInt(time.Now().Unix()+300, 10)
|
||||||
|
nonce := "sandbox-" + rands.HexString(16)
|
||||||
|
sign := buildSandboxResolveSign(signSecret, req.AppId, req.Domain, qtype, exp, nonce)
|
||||||
|
|
||||||
|
query.Set("exp", exp)
|
||||||
|
query.Set("nonce", nonce)
|
||||||
|
query.Set("sign", sign)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveURL := "https://" + serviceDomain + "/resolve?" + query.Encode()
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true, // 沙盒测试环境允许自签名证书
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, resolveURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("构建请求失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := httpClient.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return &pb.TestHTTPDNSResolveResponse{
|
||||||
|
Code: "NODE_UNREACHABLE",
|
||||||
|
Message: "无法连接到 HTTPDNS 节点: " + err.Error(),
|
||||||
|
RequestId: "rid-" + rands.HexString(12),
|
||||||
|
Domain: req.Domain,
|
||||||
|
Qtype: qtype,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(io.LimitReader(resp.Body, 64*1024))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("读取节点响应失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析节点返回的 JSON
|
||||||
|
var nodeResp nodeResolveResponse
|
||||||
|
if err := json.Unmarshal(body, &nodeResp); err != nil {
|
||||||
|
return &pb.TestHTTPDNSResolveResponse{
|
||||||
|
Code: "PARSE_ERROR",
|
||||||
|
Message: "解析节点返回数据失败: " + err.Error(),
|
||||||
|
RequestId: "rid-" + rands.HexString(12),
|
||||||
|
Domain: req.Domain,
|
||||||
|
Qtype: qtype,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 映射节点响应到 protobuf 响应
|
||||||
|
pbResp := &pb.TestHTTPDNSResolveResponse{
|
||||||
|
Code: nodeResp.Code,
|
||||||
|
Message: nodeResp.Message,
|
||||||
|
RequestId: nodeResp.RequestID,
|
||||||
|
Domain: req.Domain,
|
||||||
|
Qtype: qtype,
|
||||||
|
}
|
||||||
|
|
||||||
|
if nodeResp.Data != nil {
|
||||||
|
pbResp.Ttl = nodeResp.Data.TTL
|
||||||
|
pbResp.Summary = nodeResp.Data.Summary
|
||||||
|
|
||||||
|
if nodeResp.Data.Client != nil {
|
||||||
|
pbResp.ClientIP = nodeResp.Data.Client.IP
|
||||||
|
pbResp.ClientRegion = nodeResp.Data.Client.Region
|
||||||
|
pbResp.ClientCarrier = nodeResp.Data.Client.Carrier
|
||||||
|
pbResp.ClientCountry = nodeResp.Data.Client.Country
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rec := range nodeResp.Data.Records {
|
||||||
|
pbResp.Records = append(pbResp.Records, &pb.HTTPDNSResolveRecord{
|
||||||
|
Type: rec.Type,
|
||||||
|
Ip: rec.IP,
|
||||||
|
Ttl: nodeResp.Data.TTL,
|
||||||
|
Weight: rec.Weight,
|
||||||
|
Line: rec.Line,
|
||||||
|
Region: rec.Region,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pbResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildSandboxResolveSign(signSecret string, appID string, domain string, qtype string, exp string, nonce string) string {
|
||||||
|
raw := strings.TrimSpace(appID) + "|" + strings.ToLower(strings.TrimSpace(domain)) + "|" + strings.ToUpper(strings.TrimSpace(qtype)) + "|" + strings.TrimSpace(exp) + "|" + strings.TrimSpace(nonce)
|
||||||
|
mac := hmac.New(sha256.New, []byte(strings.TrimSpace(signSecret)))
|
||||||
|
_, _ = mac.Write([]byte(raw))
|
||||||
|
return hex.EncodeToString(mac.Sum(nil))
|
||||||
|
}
|
||||||
49
EdgeAPI/internal/rpc/services/httpdns/task_notify.go
Normal file
49
EdgeAPI/internal/rpc/services/httpdns/task_notify.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package httpdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
|
"github.com/iwind/TeaGo/dbs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func notifyHTTPDNSClusterTask(tx *dbs.Tx, clusterId int64, taskType models.NodeTaskType) error {
|
||||||
|
if clusterId <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return models.SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleHTTPDNS, clusterId, 0, 0, taskType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func notifyHTTPDNSAppTasksByApp(tx *dbs.Tx, app *models.HTTPDNSApp, taskType models.NodeTaskType) error {
|
||||||
|
if app == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
primaryClusterId := int64(app.PrimaryClusterId)
|
||||||
|
backupClusterId := int64(app.BackupClusterId)
|
||||||
|
|
||||||
|
err := notifyHTTPDNSClusterTask(tx, primaryClusterId, taskType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if backupClusterId > 0 && backupClusterId != primaryClusterId {
|
||||||
|
err = notifyHTTPDNSClusterTask(tx, backupClusterId, taskType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func notifyHTTPDNSAppTasksByAppDbId(tx *dbs.Tx, appDbId int64, taskType models.NodeTaskType) error {
|
||||||
|
if appDbId <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
app, err := models.SharedHTTPDNSAppDAO.FindEnabledApp(tx, appDbId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return notifyHTTPDNSAppTasksByApp(tx, app, taskType)
|
||||||
|
}
|
||||||
@@ -83,6 +83,12 @@ func (this *BaseService) ValidateNSNode(ctx context.Context) (nodeId int64, err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidateHTTPDNSNode 校验HTTPDNS节点
|
||||||
|
func (this *BaseService) ValidateHTTPDNSNode(ctx context.Context) (nodeId int64, err error) {
|
||||||
|
_, _, nodeId, err = rpcutils.ValidateRequest(ctx, rpcutils.UserTypeHTTPDNS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateUserNode 校验用户节点
|
// ValidateUserNode 校验用户节点
|
||||||
func (this *BaseService) ValidateUserNode(ctx context.Context, canRest bool) (userId int64, err error) {
|
func (this *BaseService) ValidateUserNode(ctx context.Context, canRest bool) (userId int64, err error) {
|
||||||
// 不允许REST调用
|
// 不允许REST调用
|
||||||
@@ -105,7 +111,7 @@ func (this *BaseService) ValidateAuthorityNode(ctx context.Context) (nodeId int6
|
|||||||
func (this *BaseService) ValidateNodeId(ctx context.Context, roles ...rpcutils.UserType) (role rpcutils.UserType, nodeIntId int64, err error) {
|
func (this *BaseService) ValidateNodeId(ctx context.Context, roles ...rpcutils.UserType) (role rpcutils.UserType, nodeIntId int64, err error) {
|
||||||
// 默认包含大部分节点
|
// 默认包含大部分节点
|
||||||
if len(roles) == 0 {
|
if len(roles) == 0 {
|
||||||
roles = []rpcutils.UserType{rpcutils.UserTypeNode, rpcutils.UserTypeCluster, rpcutils.UserTypeAdmin, rpcutils.UserTypeUser, rpcutils.UserTypeDNS, rpcutils.UserTypeReport, rpcutils.UserTypeLog, rpcutils.UserTypeAPI}
|
roles = []rpcutils.UserType{rpcutils.UserTypeNode, rpcutils.UserTypeCluster, rpcutils.UserTypeAdmin, rpcutils.UserTypeUser, rpcutils.UserTypeDNS, rpcutils.UserTypeHTTPDNS, rpcutils.UserTypeReport, rpcutils.UserTypeLog, rpcutils.UserTypeAPI}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
@@ -191,6 +197,8 @@ func (this *BaseService) ValidateNodeId(ctx context.Context, roles ...rpcutils.U
|
|||||||
nodeIntId = 0
|
nodeIntId = 0
|
||||||
case rpcutils.UserTypeDNS:
|
case rpcutils.UserTypeDNS:
|
||||||
nodeIntId, err = models.SharedNSNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
|
nodeIntId, err = models.SharedNSNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
|
||||||
|
case rpcutils.UserTypeHTTPDNS:
|
||||||
|
nodeIntId, err = models.SharedHTTPDNSNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
|
||||||
case rpcutils.UserTypeReport:
|
case rpcutils.UserTypeReport:
|
||||||
nodeIntId, err = models.SharedReportNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
|
nodeIntId, err = models.SharedReportNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
|
||||||
case rpcutils.UserTypeAuthority:
|
case rpcutils.UserTypeAuthority:
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ type NodeTaskService struct {
|
|||||||
|
|
||||||
// FindNodeTasks 获取单节点同步任务
|
// FindNodeTasks 获取单节点同步任务
|
||||||
func (this *NodeTaskService) FindNodeTasks(ctx context.Context, req *pb.FindNodeTasksRequest) (*pb.FindNodeTasksResponse, error) {
|
func (this *NodeTaskService) FindNodeTasks(ctx context.Context, req *pb.FindNodeTasksRequest) (*pb.FindNodeTasksResponse, error) {
|
||||||
nodeType, nodeId, err := this.ValidateNodeId(ctx, rpcutils.UserTypeNode, rpcutils.UserTypeDNS)
|
nodeType, nodeId, err := this.ValidateNodeId(ctx, rpcutils.UserTypeNode, rpcutils.UserTypeDNS, rpcutils.UserTypeHTTPDNS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ func (this *NodeTaskService) FindNodeTasks(ctx context.Context, req *pb.FindNode
|
|||||||
|
|
||||||
// ReportNodeTaskDone 报告同步任务结果
|
// ReportNodeTaskDone 报告同步任务结果
|
||||||
func (this *NodeTaskService) ReportNodeTaskDone(ctx context.Context, req *pb.ReportNodeTaskDoneRequest) (*pb.RPCSuccess, error) {
|
func (this *NodeTaskService) ReportNodeTaskDone(ctx context.Context, req *pb.ReportNodeTaskDoneRequest) (*pb.RPCSuccess, error) {
|
||||||
_, _, err := this.ValidateNodeId(ctx, rpcutils.UserTypeNode, rpcutils.UserTypeDNS)
|
_, _, err := this.ValidateNodeId(ctx, rpcutils.UserTypeNode, rpcutils.UserTypeDNS, rpcutils.UserTypeHTTPDNS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const (
|
|||||||
UserTypeCluster = "cluster"
|
UserTypeCluster = "cluster"
|
||||||
UserTypeStat = "stat"
|
UserTypeStat = "stat"
|
||||||
UserTypeDNS = "dns"
|
UserTypeDNS = "dns"
|
||||||
|
UserTypeHTTPDNS = "httpdns"
|
||||||
UserTypeLog = "log"
|
UserTypeLog = "log"
|
||||||
UserTypeAPI = "api"
|
UserTypeAPI = "api"
|
||||||
UserTypeAuthority = "authority"
|
UserTypeAuthority = "authority"
|
||||||
|
|||||||
@@ -142,6 +142,16 @@ func ValidateRequest(ctx context.Context, userTypes ...UserType) (userType UserT
|
|||||||
return UserTypeUser, 0, 0, errors.New("context: not found node with id '" + nodeId + "'")
|
return UserTypeUser, 0, 0, errors.New("context: not found node with id '" + nodeId + "'")
|
||||||
}
|
}
|
||||||
resultNodeId = nodeIntId
|
resultNodeId = nodeIntId
|
||||||
|
case UserTypeHTTPDNS:
|
||||||
|
nodeIntId, err := models.SharedHTTPDNSNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
|
||||||
|
if err != nil {
|
||||||
|
return UserTypeHTTPDNS, nodeIntId, 0, errors.New("context: " + err.Error())
|
||||||
|
}
|
||||||
|
if nodeIntId <= 0 {
|
||||||
|
return UserTypeHTTPDNS, nodeIntId, 0, errors.New("context: not found node with id '" + nodeId + "'")
|
||||||
|
}
|
||||||
|
nodeUserId = nodeIntId
|
||||||
|
resultNodeId = nodeIntId
|
||||||
}
|
}
|
||||||
|
|
||||||
if nodeUserId > 0 {
|
if nodeUserId > 0 {
|
||||||
|
|||||||
@@ -171,6 +171,16 @@ func ValidateRequest(ctx context.Context, userTypes ...UserType) (userType UserT
|
|||||||
}
|
}
|
||||||
nodeUserId = nodeIntId
|
nodeUserId = nodeIntId
|
||||||
resultNodeId = nodeIntId
|
resultNodeId = nodeIntId
|
||||||
|
case UserTypeHTTPDNS:
|
||||||
|
nodeIntId, err := models.SharedHTTPDNSNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
|
||||||
|
if err != nil {
|
||||||
|
return UserTypeHTTPDNS, nodeIntId, 0, errors.New("context: " + err.Error())
|
||||||
|
}
|
||||||
|
if nodeIntId <= 0 {
|
||||||
|
return UserTypeHTTPDNS, nodeIntId, 0, errors.New("context: not found node with id '" + nodeId + "'")
|
||||||
|
}
|
||||||
|
nodeUserId = nodeIntId
|
||||||
|
resultNodeId = nodeIntId
|
||||||
case UserTypeReport:
|
case UserTypeReport:
|
||||||
nodeIntId, err := models.SharedReportNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
|
nodeIntId, err := models.SharedReportNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
132
EdgeAPI/internal/setup/clickhouse_upgrade.go
Normal file
132
EdgeAPI/internal/setup/clickhouse_upgrade.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/TeaOSLab/EdgeAPI/internal/clickhouse"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnsureClickHouseTables 自动确保日志相关 ClickHouse 表存在。
|
||||||
|
// 仅做 CREATE TABLE IF NOT EXISTS,不会覆盖已有表结构。
|
||||||
|
func EnsureClickHouseTables() error {
|
||||||
|
client := clickhouse.NewClient()
|
||||||
|
if !client.IsConfigured() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
sqls := []string{
|
||||||
|
`CREATE TABLE IF NOT EXISTS logs_ingest
|
||||||
|
(
|
||||||
|
timestamp DateTime CODEC(DoubleDelta, ZSTD(1)),
|
||||||
|
node_id UInt64,
|
||||||
|
cluster_id UInt64,
|
||||||
|
server_id UInt64,
|
||||||
|
host LowCardinality(String),
|
||||||
|
ip String,
|
||||||
|
method LowCardinality(String),
|
||||||
|
path String CODEC(ZSTD(1)),
|
||||||
|
status UInt16,
|
||||||
|
bytes_in UInt64 CODEC(Delta, ZSTD(1)),
|
||||||
|
bytes_out UInt64 CODEC(Delta, ZSTD(1)),
|
||||||
|
cost_ms UInt32 CODEC(Delta, ZSTD(1)),
|
||||||
|
ua String CODEC(ZSTD(1)),
|
||||||
|
referer String CODEC(ZSTD(1)),
|
||||||
|
log_type LowCardinality(String),
|
||||||
|
trace_id String,
|
||||||
|
firewall_policy_id UInt64 DEFAULT 0,
|
||||||
|
firewall_rule_group_id UInt64 DEFAULT 0,
|
||||||
|
firewall_rule_set_id UInt64 DEFAULT 0,
|
||||||
|
firewall_rule_id UInt64 DEFAULT 0,
|
||||||
|
request_headers String CODEC(ZSTD(3)) DEFAULT '',
|
||||||
|
request_body String CODEC(ZSTD(3)) DEFAULT '',
|
||||||
|
response_headers String CODEC(ZSTD(3)) DEFAULT '',
|
||||||
|
response_body String CODEC(ZSTD(3)) DEFAULT '',
|
||||||
|
INDEX idx_trace_id trace_id TYPE bloom_filter(0.01) GRANULARITY 4,
|
||||||
|
INDEX idx_ip ip TYPE bloom_filter(0.01) GRANULARITY 4,
|
||||||
|
INDEX idx_host host TYPE tokenbf_v1(10240, 3, 0) GRANULARITY 4,
|
||||||
|
INDEX idx_fw_policy firewall_policy_id TYPE minmax GRANULARITY 4,
|
||||||
|
INDEX idx_status status TYPE minmax GRANULARITY 4
|
||||||
|
)
|
||||||
|
ENGINE = MergeTree
|
||||||
|
PARTITION BY toYYYYMMDD(timestamp)
|
||||||
|
ORDER BY (timestamp, node_id, server_id, trace_id)
|
||||||
|
SETTINGS index_granularity = 8192`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS dns_logs_ingest
|
||||||
|
(
|
||||||
|
timestamp DateTime CODEC(DoubleDelta, ZSTD(1)),
|
||||||
|
request_id String,
|
||||||
|
node_id UInt64,
|
||||||
|
cluster_id UInt64,
|
||||||
|
domain_id UInt64,
|
||||||
|
record_id UInt64,
|
||||||
|
remote_addr String,
|
||||||
|
question_name String,
|
||||||
|
question_type LowCardinality(String),
|
||||||
|
record_name String,
|
||||||
|
record_type LowCardinality(String),
|
||||||
|
record_value String,
|
||||||
|
networking LowCardinality(String),
|
||||||
|
is_recursive UInt8,
|
||||||
|
error String CODEC(ZSTD(1)),
|
||||||
|
ns_route_codes Array(String),
|
||||||
|
content_json String CODEC(ZSTD(3)) DEFAULT '',
|
||||||
|
INDEX idx_request_id request_id TYPE bloom_filter(0.01) GRANULARITY 4,
|
||||||
|
INDEX idx_remote_addr remote_addr TYPE bloom_filter(0.01) GRANULARITY 4,
|
||||||
|
INDEX idx_question_name question_name TYPE tokenbf_v1(10240, 3, 0) GRANULARITY 4,
|
||||||
|
INDEX idx_domain_id domain_id TYPE minmax GRANULARITY 4
|
||||||
|
)
|
||||||
|
ENGINE = MergeTree
|
||||||
|
PARTITION BY toYYYYMMDD(timestamp)
|
||||||
|
ORDER BY (timestamp, request_id, node_id)
|
||||||
|
SETTINGS index_granularity = 8192`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS httpdns_access_logs_ingest
|
||||||
|
(
|
||||||
|
request_id String,
|
||||||
|
cluster_id UInt64,
|
||||||
|
node_id UInt64,
|
||||||
|
app_id String,
|
||||||
|
app_name String,
|
||||||
|
domain String,
|
||||||
|
qtype LowCardinality(String),
|
||||||
|
client_ip String,
|
||||||
|
client_region String,
|
||||||
|
carrier String,
|
||||||
|
sdk_version String,
|
||||||
|
os LowCardinality(String),
|
||||||
|
result_ips String,
|
||||||
|
status LowCardinality(String),
|
||||||
|
error_code String,
|
||||||
|
cost_ms UInt32,
|
||||||
|
created_at UInt64,
|
||||||
|
day String,
|
||||||
|
summary String CODEC(ZSTD(1)),
|
||||||
|
INDEX idx_request_id request_id TYPE bloom_filter(0.01) GRANULARITY 4,
|
||||||
|
INDEX idx_cluster_id cluster_id TYPE minmax GRANULARITY 4,
|
||||||
|
INDEX idx_node_id node_id TYPE minmax GRANULARITY 4,
|
||||||
|
INDEX idx_app_id app_id TYPE tokenbf_v1(10240, 3, 0) GRANULARITY 4,
|
||||||
|
INDEX idx_domain domain TYPE tokenbf_v1(10240, 3, 0) GRANULARITY 4,
|
||||||
|
INDEX idx_status status TYPE minmax GRANULARITY 4
|
||||||
|
)
|
||||||
|
ENGINE = MergeTree
|
||||||
|
PARTITION BY day
|
||||||
|
ORDER BY (day, created_at, request_id, node_id)
|
||||||
|
SETTINGS index_granularity = 8192`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sql := range sqls {
|
||||||
|
stmt := strings.TrimSpace(sql)
|
||||||
|
if len(stmt) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := client.Execute(ctx, stmt); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -110,6 +110,9 @@ var upgradeFuncs = []*upgradeVersion{
|
|||||||
{
|
{
|
||||||
"1.4.4", upgradeV1_4_4,
|
"1.4.4", upgradeV1_4_4,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"1.4.8", upgradeV1_4_8,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpgradeSQLData 升级SQL数据
|
// UpgradeSQLData 升级SQL数据
|
||||||
@@ -269,7 +272,7 @@ func upgradeV0_0_10(db *dbs.DB) error {
|
|||||||
|
|
||||||
// v0.2.5
|
// v0.2.5
|
||||||
func upgradeV0_2_5(db *dbs.DB) error {
|
func upgradeV0_2_5(db *dbs.DB) error {
|
||||||
// 更新用户
|
// 鏇存柊鐢ㄦ埛
|
||||||
_, err := db.Exec("UPDATE edgeUsers SET day=FROM_UNIXTIME(createdAt,'%Y%m%d') WHERE day IS NULL OR LENGTH(day)=0")
|
_, err := db.Exec("UPDATE edgeUsers SET day=FROM_UNIXTIME(createdAt,'%Y%m%d') WHERE day IS NULL OR LENGTH(day)=0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -342,11 +345,10 @@ func upgradeV0_3_0(db *dbs.DB) error {
|
|||||||
|
|
||||||
// v0.3.1
|
// v0.3.1
|
||||||
func upgradeV0_3_1(db *dbs.DB) error {
|
func upgradeV0_3_1(db *dbs.DB) error {
|
||||||
// 清空域名统计,已使用分表代替
|
// 娓呯┖鍩熷悕缁熻锛屽凡浣跨敤鍒嗚〃浠f浛
|
||||||
// 因为可能有权限问题,所以我们忽略错误
|
// 鍥犱负鍙兘鏈夋潈闄愰棶棰橈紝鎵€浠ユ垜浠拷鐣ラ敊璇? _, _ = db.Exec("TRUNCATE table edgeServerDomainHourlyStats")
|
||||||
_, _ = db.Exec("TRUNCATE table edgeServerDomainHourlyStats")
|
|
||||||
|
|
||||||
// 升级APIToken
|
// 鍗囩骇APIToken
|
||||||
ones, _, err := db.FindOnes("SELECT uniqueId,secret FROM edgeNodeClusters")
|
ones, _, err := db.FindOnes("SELECT uniqueId,secret FROM edgeNodeClusters")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -374,7 +376,7 @@ func upgradeV0_3_2(db *dbs.DB) error {
|
|||||||
// gzip => compression
|
// gzip => compression
|
||||||
|
|
||||||
type HTTPGzipRef struct {
|
type HTTPGzipRef struct {
|
||||||
IsPrior bool `yaml:"isPrior" json:"isPrior"` // 是否覆盖
|
IsPrior bool `yaml:"isPrior" json:"isPrior"` // 鏄惁瑕嗙洊
|
||||||
IsOn bool `yaml:"isOn" json:"isOn"` // 是否开启
|
IsOn bool `yaml:"isOn" json:"isOn"` // 是否开启
|
||||||
GzipId int64 `yaml:"gzipId" json:"gzipId"` // 使用的配置ID
|
GzipId int64 `yaml:"gzipId" json:"gzipId"` // 使用的配置ID
|
||||||
}
|
}
|
||||||
@@ -458,7 +460,7 @@ func upgradeV0_3_2(db *dbs.DB) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新服务端口
|
// 鏇存柊鏈嶅姟绔彛
|
||||||
var serverDAO = models.NewServerDAO()
|
var serverDAO = models.NewServerDAO()
|
||||||
ones, err := serverDAO.Query(nil).
|
ones, err := serverDAO.Query(nil).
|
||||||
ResultPk().
|
ResultPk().
|
||||||
@@ -479,14 +481,14 @@ func upgradeV0_3_2(db *dbs.DB) error {
|
|||||||
|
|
||||||
// v0.3.3
|
// v0.3.3
|
||||||
func upgradeV0_3_3(db *dbs.DB) error {
|
func upgradeV0_3_3(db *dbs.DB) error {
|
||||||
// 升级CC请求数Code
|
// 鍗囩骇CC璇锋眰鏁癈ode
|
||||||
_, err := db.Exec("UPDATE edgeHTTPFirewallRuleSets SET code='8002' WHERE name='CC请求数' AND code='8001'")
|
_, err := db.Exec("UPDATE edgeHTTPFirewallRuleSets SET code='8002' WHERE name='CC璇锋眰鏁? AND code='8001'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除节点
|
// 娓呴櫎鑺傜偣
|
||||||
// 删除7天以前的info日志
|
// 鍒犻櫎7澶╀互鍓嶇殑info鏃ュ織
|
||||||
err = models.NewNodeLogDAO().DeleteExpiredLogsWithLevel(nil, "info", 7)
|
err = models.NewNodeLogDAO().DeleteExpiredLogsWithLevel(nil, "info", 7)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -497,13 +499,13 @@ func upgradeV0_3_3(db *dbs.DB) error {
|
|||||||
|
|
||||||
// v0.3.7
|
// v0.3.7
|
||||||
func upgradeV0_3_7(db *dbs.DB) error {
|
func upgradeV0_3_7(db *dbs.DB) error {
|
||||||
// 修改所有edgeNodeGrants中的su为0
|
// 淇敼鎵€鏈塭dgeNodeGrants涓殑su涓?
|
||||||
_, err := db.Exec("UPDATE edgeNodeGrants SET su=0 WHERE su=1")
|
_, err := db.Exec("UPDATE edgeNodeGrants SET su=0 WHERE su=1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// WAF预置分组
|
// WAF棰勭疆鍒嗙粍
|
||||||
_, err = db.Exec("UPDATE edgeHTTPFirewallRuleGroups SET isTemplate=1 WHERE LENGTH(code)>0")
|
_, err = db.Exec("UPDATE edgeHTTPFirewallRuleGroups SET isTemplate=1 WHERE LENGTH(code)>0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -514,7 +516,7 @@ func upgradeV0_3_7(db *dbs.DB) error {
|
|||||||
|
|
||||||
// v0.4.0
|
// v0.4.0
|
||||||
func upgradeV0_4_0(db *dbs.DB) error {
|
func upgradeV0_4_0(db *dbs.DB) error {
|
||||||
// 升级SYN Flood配置
|
// 鍗囩骇SYN Flood閰嶇疆
|
||||||
synFloodJSON, err := json.Marshal(firewallconfigs.NewSYNFloodConfig())
|
synFloodJSON, err := json.Marshal(firewallconfigs.NewSYNFloodConfig())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_, err := db.Exec("UPDATE edgeHTTPFirewallPolicies SET synFlood=? WHERE synFlood IS NULL AND state=1", string(synFloodJSON))
|
_, err := db.Exec("UPDATE edgeHTTPFirewallPolicies SET synFlood=? WHERE synFlood IS NULL AND state=1", string(synFloodJSON))
|
||||||
@@ -528,13 +530,13 @@ func upgradeV0_4_0(db *dbs.DB) error {
|
|||||||
|
|
||||||
// v0.4.1
|
// v0.4.1
|
||||||
func upgradeV0_4_1(db *dbs.DB) error {
|
func upgradeV0_4_1(db *dbs.DB) error {
|
||||||
// 升级 servers.lastUserPlanId
|
// 鍗囩骇 servers.lastUserPlanId
|
||||||
_, err := db.Exec("UPDATE edgeServers SET lastUserPlanId=userPlanId WHERE userPlanId>0")
|
_, err := db.Exec("UPDATE edgeServers SET lastUserPlanId=userPlanId WHERE userPlanId>0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行域名统计清理
|
// 鎵ц鍩熷悕缁熻娓呯悊
|
||||||
err = stats.NewServerDomainHourlyStatDAO().CleanDays(nil, 7)
|
err = stats.NewServerDomainHourlyStatDAO().CleanDays(nil, 7)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -545,7 +547,7 @@ func upgradeV0_4_1(db *dbs.DB) error {
|
|||||||
|
|
||||||
// v0.4.5
|
// v0.4.5
|
||||||
func upgradeV0_4_5(db *dbs.DB) error {
|
func upgradeV0_4_5(db *dbs.DB) error {
|
||||||
// 升级访问日志自动分表
|
// 鍗囩骇璁块棶鏃ュ織鑷姩鍒嗚〃
|
||||||
{
|
{
|
||||||
var dao = models.NewSysSettingDAO()
|
var dao = models.NewSysSettingDAO()
|
||||||
valueJSON, err := dao.ReadSetting(nil, systemconfigs.SettingCodeAccessLogQueue)
|
valueJSON, err := dao.ReadSetting(nil, systemconfigs.SettingCodeAccessLogQueue)
|
||||||
@@ -569,7 +571,7 @@ func upgradeV0_4_5(db *dbs.DB) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 升级一个防SQL注入规则
|
// 鍗囩骇涓€涓槻SQL娉ㄥ叆瑙勫垯
|
||||||
{
|
{
|
||||||
ones, _, err := db.FindOnes(`SELECT id FROM edgeHTTPFirewallRules WHERE value=?`, "(updatexml|extractvalue|ascii|ord|char|chr|count|concat|rand|floor|substr|length|len|user|database|benchmark|analyse)\\s*\\(")
|
ones, _, err := db.FindOnes(`SELECT id FROM edgeHTTPFirewallRules WHERE value=?`, "(updatexml|extractvalue|ascii|ord|char|chr|count|concat|rand|floor|substr|length|len|user|database|benchmark|analyse)\\s*\\(")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -589,7 +591,7 @@ func upgradeV0_4_5(db *dbs.DB) error {
|
|||||||
|
|
||||||
// v0.4.7
|
// v0.4.7
|
||||||
func upgradeV0_4_7(db *dbs.DB) error {
|
func upgradeV0_4_7(db *dbs.DB) error {
|
||||||
// 升级 edgeServers 中的 plainServerNames
|
// 鍗囩骇 edgeServers 涓殑 plainServerNames
|
||||||
{
|
{
|
||||||
ones, _, err := db.FindOnes("SELECT id,serverNames FROM edgeServers WHERE state=1")
|
ones, _, err := db.FindOnes("SELECT id,serverNames FROM edgeServers WHERE state=1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -621,7 +623,7 @@ func upgradeV0_4_7(db *dbs.DB) error {
|
|||||||
|
|
||||||
// v0.4.8
|
// v0.4.8
|
||||||
func upgradeV0_4_8(db *dbs.DB) error {
|
func upgradeV0_4_8(db *dbs.DB) error {
|
||||||
// 设置edgeIPLists中的serverId
|
// 璁剧疆edgeIPLists涓殑serverId
|
||||||
{
|
{
|
||||||
firewallPolicyOnes, _, err := db.FindOnes("SELECT inbound,serverId FROM edgeHTTPFirewallPolicies WHERE serverId>0")
|
firewallPolicyOnes, _, err := db.FindOnes("SELECT inbound,serverId FROM edgeHTTPFirewallPolicies WHERE serverId>0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -673,7 +675,7 @@ func upgradeV0_4_8(db *dbs.DB) error {
|
|||||||
|
|
||||||
// v0.4.11
|
// v0.4.11
|
||||||
func upgradeV0_4_11(db *dbs.DB) error {
|
func upgradeV0_4_11(db *dbs.DB) error {
|
||||||
// 升级ns端口
|
// 鍗囩骇ns绔彛
|
||||||
{
|
{
|
||||||
// TCP
|
// TCP
|
||||||
{
|
{
|
||||||
@@ -752,16 +754,16 @@ func upgradeV1_2_1(db *dbs.DB) error {
|
|||||||
func upgradeV1_2_10(db *dbs.DB) error {
|
func upgradeV1_2_10(db *dbs.DB) error {
|
||||||
{
|
{
|
||||||
type OldGlobalConfig struct {
|
type OldGlobalConfig struct {
|
||||||
// HTTP & HTTPS相关配置
|
// HTTP & HTTPS鐩稿叧閰嶇疆
|
||||||
HTTPAll struct {
|
HTTPAll struct {
|
||||||
DomainAuditingIsOn bool `yaml:"domainAuditingIsOn" json:"domainAuditingIsOn"` // 域名是否需要审核
|
DomainAuditingIsOn bool `yaml:"domainAuditingIsOn" json:"domainAuditingIsOn"` // 域名是否需要审核
|
||||||
DomainAuditingPrompt string `yaml:"domainAuditingPrompt" json:"domainAuditingPrompt"` // 域名审核的提示
|
DomainAuditingPrompt string `yaml:"domainAuditingPrompt" json:"domainAuditingPrompt"` // 域名审核提示
|
||||||
} `yaml:"httpAll" json:"httpAll"`
|
} `yaml:"httpAll" json:"httpAll"`
|
||||||
|
|
||||||
TCPAll struct {
|
TCPAll struct {
|
||||||
PortRangeMin int `yaml:"portRangeMin" json:"portRangeMin"` // 最小端口
|
PortRangeMin int `yaml:"portRangeMin" json:"portRangeMin"` // 最小端口
|
||||||
PortRangeMax int `yaml:"portRangeMax" json:"portRangeMax"` // 最大端口
|
PortRangeMax int `yaml:"portRangeMax" json:"portRangeMax"` // 最大端口
|
||||||
DenyPorts []int `yaml:"denyPorts" json:"denyPorts"` // 禁止使用的端口
|
DenyPorts []int `yaml:"denyPorts" json:"denyPorts"` // 禁止端口
|
||||||
} `yaml:"tcpAll" json:"tcpAll"`
|
} `yaml:"tcpAll" json:"tcpAll"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1253,3 +1255,30 @@ func upgradeV1_4_4(db *dbs.DB) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1.4.8
|
||||||
|
func upgradeV1_4_8(db *dbs.DB) error {
|
||||||
|
return createHTTPDNSTables(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createHTTPDNSTables(db *dbs.DB) error {
|
||||||
|
sqls := []string{
|
||||||
|
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSClusters` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`isOn` tinyint unsigned DEFAULT '1',`isDefault` tinyint unsigned DEFAULT '0',`serviceDomain` varchar(255) DEFAULT NULL,`defaultTTL` int unsigned DEFAULT '30',`fallbackTimeoutMs` int unsigned DEFAULT '300',`installDir` varchar(255) DEFAULT '/opt/edge-httpdns',`tlsPolicy` json DEFAULT NULL,`createdAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),KEY `name` (`name`),KEY `isDefault` (`isDefault`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS集群配置表(默认TTL、回退超时、服务域名等)'",
|
||||||
|
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSNodes` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`clusterId` bigint unsigned DEFAULT '0',`name` varchar(255) DEFAULT NULL,`isOn` tinyint unsigned DEFAULT '1',`isUp` tinyint unsigned DEFAULT '0',`isInstalled` tinyint unsigned DEFAULT '0',`isActive` tinyint unsigned DEFAULT '0',`uniqueId` varchar(64) DEFAULT NULL,`secret` varchar(64) DEFAULT NULL,`installDir` varchar(255) DEFAULT '/opt/edge-httpdns',`status` json DEFAULT NULL,`installStatus` json DEFAULT NULL,`createdAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),UNIQUE KEY `uniqueId` (`uniqueId`),KEY `clusterId` (`clusterId`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS节点表(节点基础信息与运行状态)'",
|
||||||
|
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSApps` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`appId` varchar(64) DEFAULT NULL,`isOn` tinyint unsigned DEFAULT '1',`primaryClusterId` bigint unsigned DEFAULT '0',`backupClusterId` bigint unsigned DEFAULT '0',`sniMode` varchar(64) DEFAULT 'fixed_hide',`userId` bigint unsigned DEFAULT '0',`createdAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),UNIQUE KEY `appId` (`appId`),KEY `name` (`name`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS应用表(应用与主备集群绑定关系)'",
|
||||||
|
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSAppSecrets` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`appId` bigint unsigned DEFAULT '0',`signEnabled` tinyint unsigned DEFAULT '0',`signSecret` varchar(255) DEFAULT NULL,`signUpdatedAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),UNIQUE KEY `appId` (`appId`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS应用密钥表(请求验签开关与加签Secret)'",
|
||||||
|
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSDomains` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`appId` bigint unsigned DEFAULT '0',`domain` varchar(255) DEFAULT NULL,`isOn` tinyint unsigned DEFAULT '1',`createdAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),UNIQUE KEY `appId_domain` (`appId`,`domain`),KEY `domain` (`domain`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS应用域名表(应用绑定的业务域名)'",
|
||||||
|
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSCustomRules` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`appId` bigint unsigned DEFAULT '0',`domainId` bigint unsigned DEFAULT '0',`ruleName` varchar(255) DEFAULT NULL,`lineScope` varchar(64) DEFAULT NULL,`lineCarrier` varchar(64) DEFAULT NULL,`lineRegion` varchar(64) DEFAULT NULL,`lineProvince` varchar(64) DEFAULT NULL,`lineContinent` varchar(64) DEFAULT NULL,`lineCountry` varchar(128) DEFAULT NULL,`ttl` int unsigned DEFAULT '30',`isOn` tinyint unsigned DEFAULT '1',`priority` int unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),KEY `domainId_isOn_priority` (`domainId`,`isOn`,`priority`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS自定义解析规则表(按线路/地域匹配)'",
|
||||||
|
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSCustomRuleRecords` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`ruleId` bigint unsigned DEFAULT '0',`recordType` varchar(32) DEFAULT NULL,`recordValue` varchar(255) DEFAULT NULL,`weight` int unsigned DEFAULT '0',`sort` int unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),KEY `ruleId` (`ruleId`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS自定义规则记录值表(A/AAAA及权重)'",
|
||||||
|
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSAccessLogs` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`requestId` varchar(128) DEFAULT NULL,`clusterId` bigint unsigned DEFAULT '0',`nodeId` bigint unsigned DEFAULT '0',`appId` varchar(64) DEFAULT NULL,`appName` varchar(255) DEFAULT NULL,`domain` varchar(255) DEFAULT NULL,`qtype` varchar(16) DEFAULT NULL,`clientIP` varchar(64) DEFAULT NULL,`clientRegion` varchar(255) DEFAULT NULL,`carrier` varchar(128) DEFAULT NULL,`sdkVersion` varchar(64) DEFAULT NULL,`os` varchar(64) DEFAULT NULL,`resultIPs` text,`status` varchar(32) DEFAULT NULL,`errorCode` varchar(64) DEFAULT NULL,`costMs` int unsigned DEFAULT '0',`createdAt` bigint unsigned DEFAULT '0',`day` varchar(8) DEFAULT NULL,`summary` text,PRIMARY KEY (`id`),UNIQUE KEY `requestId_nodeId` (`requestId`,`nodeId`),KEY `day_cluster_node_domain_status_createdAt` (`day`,`clusterId`,`nodeId`,`domain`,`status`,`createdAt`),KEY `appId` (`appId`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS访问日志表(解析请求与结果)'",
|
||||||
|
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSRuntimeLogs` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`clusterId` bigint unsigned DEFAULT '0',`nodeId` bigint unsigned DEFAULT '0',`level` varchar(32) DEFAULT NULL,`type` varchar(64) DEFAULT NULL,`module` varchar(64) DEFAULT NULL,`description` text,`count` bigint unsigned DEFAULT '1',`requestId` varchar(128) DEFAULT NULL,`createdAt` bigint unsigned DEFAULT '0',`day` varchar(8) DEFAULT NULL,PRIMARY KEY (`id`),KEY `day_cluster_node_level_createdAt` (`day`,`clusterId`,`nodeId`,`level`,`createdAt`),KEY `requestId` (`requestId`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS运行日志表(节点运行与异常日志)'",
|
||||||
|
}
|
||||||
|
for _, sql := range sqls {
|
||||||
|
if _, err := db.Exec(sql); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func (this *NodeTaskExtractor) Loop() error {
|
|||||||
|
|
||||||
// 这里不解锁,是为了让任务N秒钟之内只运行一次
|
// 这里不解锁,是为了让任务N秒钟之内只运行一次
|
||||||
|
|
||||||
for _, role := range []string{nodeconfigs.NodeRoleNode, nodeconfigs.NodeRoleDNS} {
|
for _, role := range []string{nodeconfigs.NodeRoleNode, nodeconfigs.NodeRoleDNS, nodeconfigs.NodeRoleHTTPDNS} {
|
||||||
err := models.SharedNodeTaskDAO.ExtractAllClusterTasks(nil, role)
|
err := models.SharedNodeTaskDAO.ExtractAllClusterTasks(nil, role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package teaconst
|
package teaconst
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "1.4.7" //1.3.9
|
Version = "1.4.8" //1.3.9
|
||||||
|
|
||||||
APINodeVersion = "1.4.7" //1.3.9
|
APINodeVersion = "1.4.8" //1.3.9
|
||||||
|
|
||||||
ProductName = "Edge Admin"
|
ProductName = "Edge Admin"
|
||||||
ProcessName = "edge-admin"
|
ProcessName = "edge-admin"
|
||||||
|
|||||||
@@ -349,6 +349,38 @@ func (this *RPCClient) DNSTaskRPC() pb.DNSTaskServiceClient {
|
|||||||
return pb.NewDNSTaskServiceClient(this.pickConn())
|
return pb.NewDNSTaskServiceClient(this.pickConn())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) HTTPDNSClusterRPC() pb.HTTPDNSClusterServiceClient {
|
||||||
|
return pb.NewHTTPDNSClusterServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) HTTPDNSNodeRPC() pb.HTTPDNSNodeServiceClient {
|
||||||
|
return pb.NewHTTPDNSNodeServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) HTTPDNSAppRPC() pb.HTTPDNSAppServiceClient {
|
||||||
|
return pb.NewHTTPDNSAppServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) HTTPDNSDomainRPC() pb.HTTPDNSDomainServiceClient {
|
||||||
|
return pb.NewHTTPDNSDomainServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) HTTPDNSRuleRPC() pb.HTTPDNSRuleServiceClient {
|
||||||
|
return pb.NewHTTPDNSRuleServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) HTTPDNSAccessLogRPC() pb.HTTPDNSAccessLogServiceClient {
|
||||||
|
return pb.NewHTTPDNSAccessLogServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) HTTPDNSRuntimeLogRPC() pb.HTTPDNSRuntimeLogServiceClient {
|
||||||
|
return pb.NewHTTPDNSRuntimeLogServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *RPCClient) HTTPDNSSandboxRPC() pb.HTTPDNSSandboxServiceClient {
|
||||||
|
return pb.NewHTTPDNSSandboxServiceClient(this.pickConn())
|
||||||
|
}
|
||||||
|
|
||||||
func (this *RPCClient) ACMEUserRPC() pb.ACMEUserServiceClient {
|
func (this *RPCClient) ACMEUserRPC() pb.ACMEUserServiceClient {
|
||||||
return pb.NewACMEUserServiceClient(this.pickConn())
|
return pb.NewACMEUserServiceClient(this.pickConn())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ func (this *AppAction) Init() {
|
|||||||
func (this *AppAction) RunGet(params struct {
|
func (this *AppAction) RunGet(params struct {
|
||||||
AppId int64
|
AppId int64
|
||||||
}) {
|
}) {
|
||||||
app := pickApp(params.AppId)
|
app, err := findAppMap(this.Parent(), params.AppId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
this.RedirectURL("/httpdns/apps/domains?appId=" + strconv.FormatInt(app.GetInt64("id"), 10))
|
this.RedirectURL("/httpdns/apps/domains?appId=" + strconv.FormatInt(app.GetInt64("id"), 10))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import (
|
|||||||
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/policies"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AppSettingsAction struct {
|
type AppSettingsAction struct {
|
||||||
@@ -22,33 +23,45 @@ func (this *AppSettingsAction) RunGet(params struct {
|
|||||||
Section string
|
Section string
|
||||||
}) {
|
}) {
|
||||||
httpdnsutils.AddLeftMenu(this.Parent())
|
httpdnsutils.AddLeftMenu(this.Parent())
|
||||||
app := pickApp(params.AppId)
|
|
||||||
|
|
||||||
// 顶部 tabbar
|
app, err := findAppMap(this.Parent(), params.AppId)
|
||||||
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "settings")
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), app.GetInt64("id"), "settings")
|
||||||
|
|
||||||
// 当前选中的 section
|
|
||||||
section := params.Section
|
section := params.Section
|
||||||
if len(section) == 0 {
|
if len(section) == 0 {
|
||||||
section = "basic"
|
section = "basic"
|
||||||
}
|
}
|
||||||
this.Data["activeSection"] = section
|
this.Data["activeSection"] = section
|
||||||
appIdStr := strconv.FormatInt(params.AppId, 10)
|
|
||||||
this.Data["leftMenuItems"] = []map[string]interface{}{
|
appIDStr := strconv.FormatInt(app.GetInt64("id"), 10)
|
||||||
|
this.Data["leftMenuItems"] = []maps.Map{
|
||||||
{
|
{
|
||||||
"name": "基础配置",
|
"name": "基础配置",
|
||||||
"url": "/httpdns/apps/app/settings?appId=" + appIdStr + "§ion=basic",
|
"url": "/httpdns/apps/app/settings?appId=" + appIDStr + "§ion=basic",
|
||||||
"isActive": section == "basic",
|
"isActive": section == "basic",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "认证与密钥",
|
"name": "认证与密钥",
|
||||||
"url": "/httpdns/apps/app/settings?appId=" + appIdStr + "§ion=auth",
|
"url": "/httpdns/apps/app/settings?appId=" + appIDStr + "§ion=auth",
|
||||||
"isActive": section == "auth",
|
"isActive": section == "auth",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
settings := loadAppSettings(app)
|
settings := maps.Map{
|
||||||
this.Data["clusters"] = policies.LoadAvailableDeployClusters()
|
"appId": app.GetString("appId"),
|
||||||
|
"appStatus": app.GetBool("isOn"),
|
||||||
|
"primaryClusterId": app.GetInt64("primaryClusterId"),
|
||||||
|
"backupClusterId": app.GetInt64("backupClusterId"),
|
||||||
|
"signEnabled": app.GetBool("signEnabled"),
|
||||||
|
"signSecretPlain": app.GetString("signSecretPlain"),
|
||||||
|
"signSecretMasked": app.GetString("signSecretMasked"),
|
||||||
|
"signSecretUpdatedAt": app.GetString("signSecretUpdated"),
|
||||||
|
}
|
||||||
this.Data["app"] = app
|
this.Data["app"] = app
|
||||||
this.Data["settings"] = settings
|
this.Data["settings"] = settings
|
||||||
this.Show()
|
this.Show()
|
||||||
@@ -58,35 +71,36 @@ func (this *AppSettingsAction) RunPost(params struct {
|
|||||||
AppId int64
|
AppId int64
|
||||||
|
|
||||||
AppStatus bool
|
AppStatus bool
|
||||||
PrimaryClusterId int64
|
|
||||||
BackupClusterId int64
|
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
CSRF *actionutils.CSRF
|
CSRF *actionutils.CSRF
|
||||||
}) {
|
}) {
|
||||||
params.Must.Field("appId", params.AppId).Gt(0, "please select app")
|
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
||||||
params.Must.Field("primaryClusterId", params.PrimaryClusterId).Gt(0, "please select primary cluster")
|
|
||||||
if params.BackupClusterId > 0 && params.BackupClusterId == params.PrimaryClusterId {
|
appResp, err := this.RPC().HTTPDNSAppRPC().FindHTTPDNSApp(this.AdminContext(), &pb.FindHTTPDNSAppRequest{
|
||||||
this.FailField("backupClusterId", "backup cluster must be different from primary cluster")
|
AppDbId: params.AppId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if appResp.GetApp() == nil {
|
||||||
|
this.Fail("找不到对应的应用")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
app := pickApp(params.AppId)
|
_, err = this.RPC().HTTPDNSAppRPC().UpdateHTTPDNSApp(this.AdminContext(), &pb.UpdateHTTPDNSAppRequest{
|
||||||
settings := loadAppSettings(app)
|
AppDbId: params.AppId,
|
||||||
settings["appStatus"] = params.AppStatus
|
Name: appResp.GetApp().GetName(),
|
||||||
settings["primaryClusterId"] = params.PrimaryClusterId
|
PrimaryClusterId: appResp.GetApp().GetPrimaryClusterId(),
|
||||||
settings["backupClusterId"] = params.BackupClusterId
|
BackupClusterId: appResp.GetApp().GetBackupClusterId(),
|
||||||
|
IsOn: params.AppStatus,
|
||||||
|
UserId: appResp.GetApp().GetUserId(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// SNI strategy is fixed to level2 empty.
|
|
||||||
settings["sniPolicy"] = "level2"
|
|
||||||
settings["level2Mode"] = "empty"
|
|
||||||
settings["publicSniDomain"] = ""
|
|
||||||
settings["echFallbackPolicy"] = "level2"
|
|
||||||
settings["ecsMode"] = "off"
|
|
||||||
settings["ecsIPv4Prefix"] = 24
|
|
||||||
settings["ecsIPv6Prefix"] = 56
|
|
||||||
settings["pinningMode"] = "off"
|
|
||||||
settings["sanMode"] = "off"
|
|
||||||
|
|
||||||
saveAppSettings(app.GetInt64("id"), settings)
|
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package apps
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,11 +14,16 @@ func (this *AppSettingsResetSignSecretAction) RunPost(params struct {
|
|||||||
AppId int64
|
AppId int64
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
CSRF *actionutils.CSRF
|
|
||||||
}) {
|
}) {
|
||||||
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
||||||
|
|
||||||
app := pickApp(params.AppId)
|
_, err := this.RPC().HTTPDNSAppRPC().ResetHTTPDNSAppSignSecret(this.AdminContext(), &pb.ResetHTTPDNSAppSignSecretRequest{
|
||||||
resetSignSecret(app)
|
AppDbId: params.AppId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package apps
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,14 +15,17 @@ func (this *AppSettingsToggleSignEnabledAction) RunPost(params struct {
|
|||||||
IsOn int
|
IsOn int
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
CSRF *actionutils.CSRF
|
|
||||||
}) {
|
}) {
|
||||||
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
||||||
|
|
||||||
app := pickApp(params.AppId)
|
_, err := this.RPC().HTTPDNSAppRPC().UpdateHTTPDNSAppSignEnabled(this.AdminContext(), &pb.UpdateHTTPDNSAppSignEnabledRequest{
|
||||||
settings := loadAppSettings(app)
|
AppDbId: params.AppId,
|
||||||
settings["signEnabled"] = params.IsOn == 1
|
SignEnabled: params.IsOn == 1,
|
||||||
saveAppSettings(app.GetInt64("id"), settings)
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,223 +0,0 @@
|
|||||||
package apps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/iwind/TeaGo/maps"
|
|
||||||
)
|
|
||||||
|
|
||||||
var appSettingsStore = struct {
|
|
||||||
sync.RWMutex
|
|
||||||
data map[int64]maps.Map
|
|
||||||
}{
|
|
||||||
data: map[int64]maps.Map{},
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultAppSettings(app maps.Map) maps.Map {
|
|
||||||
signSecretPlain := randomPlainSecret("ss")
|
|
||||||
return maps.Map{
|
|
||||||
"appId": app.GetString("appId"),
|
|
||||||
"primaryClusterId": app.GetInt64("clusterId"),
|
|
||||||
"backupClusterId": int64(0),
|
|
||||||
"signSecretPlain": signSecretPlain,
|
|
||||||
"signSecretMasked": maskSecret(signSecretPlain),
|
|
||||||
"signSecretUpdatedAt": "2026-02-20 12:30:00",
|
|
||||||
"appStatus": app.GetBool("isOn"),
|
|
||||||
"defaultTTL": 30,
|
|
||||||
"fallbackTimeoutMs": 300,
|
|
||||||
"sniPolicy": "level2",
|
|
||||||
"level2Mode": "empty",
|
|
||||||
"publicSniDomain": "",
|
|
||||||
"echFallbackPolicy": "level2",
|
|
||||||
"ecsMode": "off",
|
|
||||||
"ecsIPv4Prefix": 24,
|
|
||||||
"ecsIPv6Prefix": 56,
|
|
||||||
"pinningMode": "off",
|
|
||||||
"sanMode": "off",
|
|
||||||
"signEnabled": true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloneSettings(settings maps.Map) maps.Map {
|
|
||||||
return maps.Map{
|
|
||||||
"appId": settings.GetString("appId"),
|
|
||||||
"primaryClusterId": settings.GetInt64("primaryClusterId"),
|
|
||||||
"backupClusterId": settings.GetInt64("backupClusterId"),
|
|
||||||
"signSecretPlain": settings.GetString("signSecretPlain"),
|
|
||||||
"signSecretMasked": settings.GetString("signSecretMasked"),
|
|
||||||
"signSecretUpdatedAt": settings.GetString("signSecretUpdatedAt"),
|
|
||||||
"appStatus": settings.GetBool("appStatus"),
|
|
||||||
"defaultTTL": settings.GetInt("defaultTTL"),
|
|
||||||
"fallbackTimeoutMs": settings.GetInt("fallbackTimeoutMs"),
|
|
||||||
"sniPolicy": settings.GetString("sniPolicy"),
|
|
||||||
"level2Mode": settings.GetString("level2Mode"),
|
|
||||||
"publicSniDomain": settings.GetString("publicSniDomain"),
|
|
||||||
"echFallbackPolicy": settings.GetString("echFallbackPolicy"),
|
|
||||||
"ecsMode": settings.GetString("ecsMode"),
|
|
||||||
"ecsIPv4Prefix": settings.GetInt("ecsIPv4Prefix"),
|
|
||||||
"ecsIPv6Prefix": settings.GetInt("ecsIPv6Prefix"),
|
|
||||||
"pinningMode": settings.GetString("pinningMode"),
|
|
||||||
"sanMode": settings.GetString("sanMode"),
|
|
||||||
"signEnabled": settings.GetBool("signEnabled"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadAppSettings(app maps.Map) maps.Map {
|
|
||||||
appId := app.GetInt64("id")
|
|
||||||
appSettingsStore.RLock()
|
|
||||||
settings, ok := appSettingsStore.data[appId]
|
|
||||||
appSettingsStore.RUnlock()
|
|
||||||
if ok {
|
|
||||||
if ensureSettingsFields(settings) {
|
|
||||||
saveAppSettings(appId, settings)
|
|
||||||
}
|
|
||||||
return cloneSettings(settings)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings = defaultAppSettings(app)
|
|
||||||
saveAppSettings(appId, settings)
|
|
||||||
return cloneSettings(settings)
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveAppSettings(appId int64, settings maps.Map) {
|
|
||||||
appSettingsStore.Lock()
|
|
||||||
appSettingsStore.data[appId] = cloneSettings(settings)
|
|
||||||
appSettingsStore.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteAppSettings(appId int64) {
|
|
||||||
appSettingsStore.Lock()
|
|
||||||
delete(appSettingsStore.data, appId)
|
|
||||||
appSettingsStore.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func resetSignSecret(app maps.Map) maps.Map {
|
|
||||||
settings := loadAppSettings(app)
|
|
||||||
signSecretPlain := randomPlainSecret("ss")
|
|
||||||
settings["signSecretPlain"] = signSecretPlain
|
|
||||||
settings["signSecretMasked"] = maskSecret(signSecretPlain)
|
|
||||||
settings["signSecretUpdatedAt"] = nowDateTime()
|
|
||||||
saveAppSettings(app.GetInt64("id"), settings)
|
|
||||||
return settings
|
|
||||||
}
|
|
||||||
|
|
||||||
func nowDateTime() string {
|
|
||||||
return time.Now().Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
func randomPlainSecret(prefix string) string {
|
|
||||||
suffix := time.Now().UnixNano() & 0xffff
|
|
||||||
return fmt.Sprintf("%s_%016x", prefix, suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func maskSecret(secret string) string {
|
|
||||||
if len(secret) < 4 {
|
|
||||||
return "******"
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix := ""
|
|
||||||
for i := 0; i < len(secret); i++ {
|
|
||||||
if secret[i] == '_' {
|
|
||||||
prefix = secret[:i+1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(prefix) == 0 {
|
|
||||||
prefix = secret[:2]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(secret) <= 8 {
|
|
||||||
return prefix + "xxxx"
|
|
||||||
}
|
|
||||||
return prefix + "xxxxxxxx" + secret[len(secret)-4:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureSettingsFields(settings maps.Map) bool {
|
|
||||||
changed := false
|
|
||||||
|
|
||||||
if settings.GetInt64("primaryClusterId") <= 0 {
|
|
||||||
settings["primaryClusterId"] = int64(1)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
if settings.GetInt64("backupClusterId") < 0 {
|
|
||||||
settings["backupClusterId"] = int64(0)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
if settings.GetInt64("backupClusterId") > 0 && settings.GetInt64("backupClusterId") == settings.GetInt64("primaryClusterId") {
|
|
||||||
settings["backupClusterId"] = int64(0)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
signSecretPlain := settings.GetString("signSecretPlain")
|
|
||||||
if len(signSecretPlain) == 0 {
|
|
||||||
signSecretPlain = randomPlainSecret("ss")
|
|
||||||
settings["signSecretPlain"] = signSecretPlain
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
if len(settings.GetString("signSecretMasked")) == 0 {
|
|
||||||
settings["signSecretMasked"] = maskSecret(signSecretPlain)
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
if len(settings.GetString("signSecretUpdatedAt")) == 0 {
|
|
||||||
settings["signSecretUpdatedAt"] = nowDateTime()
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(settings.GetString("sniPolicy")) == 0 {
|
|
||||||
settings["sniPolicy"] = "level2"
|
|
||||||
changed = true
|
|
||||||
} else if settings.GetString("sniPolicy") != "level2" {
|
|
||||||
settings["sniPolicy"] = "level2"
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
if settings.GetString("level2Mode") != "empty" {
|
|
||||||
settings["level2Mode"] = "empty"
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
if len(settings.GetString("publicSniDomain")) > 0 {
|
|
||||||
settings["publicSniDomain"] = ""
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
if len(settings.GetString("echFallbackPolicy")) == 0 {
|
|
||||||
settings["echFallbackPolicy"] = "level2"
|
|
||||||
changed = true
|
|
||||||
} else if settings.GetString("echFallbackPolicy") != "level2" {
|
|
||||||
settings["echFallbackPolicy"] = "level2"
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
if settings.GetString("ecsMode") != "off" {
|
|
||||||
settings["ecsMode"] = "off"
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
if settings.GetInt("ecsIPv4Prefix") <= 0 {
|
|
||||||
settings["ecsIPv4Prefix"] = 24
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
if settings.GetInt("ecsIPv6Prefix") <= 0 {
|
|
||||||
settings["ecsIPv6Prefix"] = 56
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
if settings.GetString("pinningMode") != "off" {
|
|
||||||
settings["pinningMode"] = "off"
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
if settings.GetString("sanMode") != "off" {
|
|
||||||
settings["sanMode"] = "off"
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return changed
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadAppSettingsByAppID exposes app settings for other httpdns sub-modules
|
|
||||||
// such as sandbox mock responses.
|
|
||||||
func LoadAppSettingsByAppID(appID string) maps.Map {
|
|
||||||
for _, app := range mockApps() {
|
|
||||||
if app.GetString("appId") == appID {
|
|
||||||
return loadAppSettings(app)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package apps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/actions"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *CreateAction) Init() {
|
||||||
|
this.Nav("", "", "create")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *CreateAction) RunGet(params struct{}) {
|
||||||
|
clusterResp, err := this.RPC().HTTPDNSClusterRPC().FindAllHTTPDNSClusters(this.AdminContext(), &pb.FindAllHTTPDNSClustersRequest{})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clusters := make([]maps.Map, 0, len(clusterResp.GetClusters()))
|
||||||
|
for _, cluster := range clusterResp.GetClusters() {
|
||||||
|
clusters = append(clusters, maps.Map{
|
||||||
|
"id": cluster.GetId(),
|
||||||
|
"name": cluster.GetName(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["clusters"] = clusters
|
||||||
|
|
||||||
|
defaultPrimaryClusterId := int64(0)
|
||||||
|
for _, cluster := range clusterResp.GetClusters() {
|
||||||
|
if cluster.GetIsDefault() {
|
||||||
|
defaultPrimaryClusterId = cluster.GetId()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if defaultPrimaryClusterId <= 0 && len(clusters) > 0 {
|
||||||
|
defaultPrimaryClusterId = clusters[0].GetInt64("id")
|
||||||
|
}
|
||||||
|
this.Data["defaultPrimaryClusterId"] = defaultPrimaryClusterId
|
||||||
|
|
||||||
|
defaultBackupClusterId := int64(0)
|
||||||
|
for _, cluster := range clusters {
|
||||||
|
clusterId := cluster.GetInt64("id")
|
||||||
|
if clusterId > 0 && clusterId != defaultPrimaryClusterId {
|
||||||
|
defaultBackupClusterId = clusterId
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.Data["defaultBackupClusterId"] = defaultBackupClusterId
|
||||||
|
|
||||||
|
this.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *CreateAction) RunPost(params struct {
|
||||||
|
Name string
|
||||||
|
PrimaryClusterId int64
|
||||||
|
BackupClusterId int64
|
||||||
|
UserId int64
|
||||||
|
|
||||||
|
Must *actions.Must
|
||||||
|
CSRF *actionutils.CSRF
|
||||||
|
}) {
|
||||||
|
params.Must.Field("name", params.Name).Require("请输入应用名称")
|
||||||
|
params.Must.Field("primaryClusterId", params.PrimaryClusterId).Gt(0, "请输入主服务集群")
|
||||||
|
if params.BackupClusterId > 0 && params.BackupClusterId == params.PrimaryClusterId {
|
||||||
|
this.FailField("backupClusterId", "备用服务集群必须和主服务集群不一致")
|
||||||
|
}
|
||||||
|
|
||||||
|
createResp, err := this.RPC().HTTPDNSAppRPC().CreateHTTPDNSApp(this.AdminContext(), &pb.CreateHTTPDNSAppRequest{
|
||||||
|
Name: params.Name,
|
||||||
|
AppId: "app" + strconv.FormatInt(time.Now().UnixNano()%1_000_000_000_000, 36),
|
||||||
|
PrimaryClusterId: params.PrimaryClusterId,
|
||||||
|
BackupClusterId: params.BackupClusterId,
|
||||||
|
IsOn: true,
|
||||||
|
SignEnabled: true,
|
||||||
|
UserId: params.UserId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Data["appId"] = createResp.GetAppDbId()
|
||||||
|
this.Success()
|
||||||
|
}
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package apps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/policies"
|
|
||||||
"github.com/iwind/TeaGo/actions"
|
|
||||||
"github.com/iwind/TeaGo/maps"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CreatePopupAction struct {
|
|
||||||
actionutils.ParentAction
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *CreatePopupAction) Init() {
|
|
||||||
this.Nav("", "", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *CreatePopupAction) RunGet(params struct{}) {
|
|
||||||
clusters := policies.LoadAvailableDeployClusters()
|
|
||||||
this.Data["clusters"] = clusters
|
|
||||||
|
|
||||||
defaultPrimaryClusterId := policies.LoadDefaultClusterID()
|
|
||||||
if defaultPrimaryClusterId <= 0 && len(clusters) > 0 {
|
|
||||||
defaultPrimaryClusterId = clusters[0].GetInt64("id")
|
|
||||||
}
|
|
||||||
this.Data["defaultPrimaryClusterId"] = defaultPrimaryClusterId
|
|
||||||
|
|
||||||
defaultBackupClusterId := int64(0)
|
|
||||||
for _, cluster := range clusters {
|
|
||||||
clusterId := cluster.GetInt64("id")
|
|
||||||
if clusterId > 0 && clusterId != defaultPrimaryClusterId {
|
|
||||||
defaultBackupClusterId = clusterId
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.Data["defaultBackupClusterId"] = defaultBackupClusterId
|
|
||||||
|
|
||||||
// Mock users for dropdown
|
|
||||||
this.Data["users"] = []maps.Map{
|
|
||||||
{"id": int64(1), "name": "User A", "username": "zhangsan"},
|
|
||||||
{"id": int64(2), "name": "User B", "username": "lisi"},
|
|
||||||
{"id": int64(3), "name": "User C", "username": "wangwu"},
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Show()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *CreatePopupAction) RunPost(params struct {
|
|
||||||
Name string
|
|
||||||
PrimaryClusterId int64
|
|
||||||
BackupClusterId int64
|
|
||||||
UserId int64
|
|
||||||
|
|
||||||
Must *actions.Must
|
|
||||||
CSRF *actionutils.CSRF
|
|
||||||
}) {
|
|
||||||
params.Must.Field("name", params.Name).Require("please input app name")
|
|
||||||
params.Must.Field("primaryClusterId", params.PrimaryClusterId).Gt(0, "please select primary cluster")
|
|
||||||
if params.BackupClusterId > 0 && params.BackupClusterId == params.PrimaryClusterId {
|
|
||||||
this.FailField("backupClusterId", "backup cluster must be different from primary cluster")
|
|
||||||
}
|
|
||||||
this.Success()
|
|
||||||
}
|
|
||||||
@@ -20,26 +20,33 @@ func (this *CustomRecordsAction) RunGet(params struct {
|
|||||||
}) {
|
}) {
|
||||||
httpdnsutils.AddLeftMenu(this.Parent())
|
httpdnsutils.AddLeftMenu(this.Parent())
|
||||||
|
|
||||||
app := pickApp(params.AppId)
|
app, err := findAppMap(this.Parent(), params.AppId)
|
||||||
// 自定义解析属于域名管理子页,顶部沿用应用 tabbar(高亮域名列表)
|
if err != nil {
|
||||||
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "domains")
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), app.GetInt64("id"), "domains")
|
||||||
|
|
||||||
domains := mockDomains(app.GetInt64("id"))
|
domains, err := listDomainMaps(this.Parent(), app.GetInt64("id"), "")
|
||||||
domain := pickDomainFromDomains(domains, params.DomainId)
|
if err != nil {
|
||||||
domainName := domain.GetString("name")
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
domain := findDomainMap(domains, params.DomainId)
|
||||||
|
|
||||||
records := make([]maps.Map, 0)
|
records := make([]maps.Map, 0)
|
||||||
for _, record := range loadCustomRecords(app.GetInt64("id")) {
|
if domain.GetInt64("id") > 0 {
|
||||||
if len(domainName) > 0 && record.GetString("domain") != domainName {
|
records, err = listCustomRuleMaps(this.Parent(), domain.GetInt64("id"))
|
||||||
continue
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
records = append(records, record)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, record := range records {
|
for _, record := range records {
|
||||||
|
record["domain"] = domain.GetString("name")
|
||||||
record["lineText"] = buildLineText(record)
|
record["lineText"] = buildLineText(record)
|
||||||
record["recordValueText"] = buildRecordValueText(record)
|
record["recordValueText"] = buildRecordValueText(record)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.Data["app"] = app
|
this.Data["app"] = app
|
||||||
this.Data["domain"] = domain
|
this.Data["domain"] = domain
|
||||||
@@ -47,17 +54,3 @@ func (this *CustomRecordsAction) RunGet(params struct {
|
|||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
func pickDomainFromDomains(domains []maps.Map, domainID int64) maps.Map {
|
|
||||||
if len(domains) == 0 {
|
|
||||||
return maps.Map{}
|
|
||||||
}
|
|
||||||
if domainID <= 0 {
|
|
||||||
return domains[0]
|
|
||||||
}
|
|
||||||
for _, domain := range domains {
|
|
||||||
if domain.GetInt64("id") == domainID {
|
|
||||||
return domain
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return domains[0]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
)
|
)
|
||||||
@@ -23,11 +24,18 @@ func (this *CustomRecordsCreatePopupAction) RunGet(params struct {
|
|||||||
DomainId int64
|
DomainId int64
|
||||||
RecordId int64
|
RecordId int64
|
||||||
}) {
|
}) {
|
||||||
app := pickApp(params.AppId)
|
app, err := findAppMap(this.Parent(), params.AppId)
|
||||||
this.Data["app"] = app
|
if err != nil {
|
||||||
domains := mockDomains(app.GetInt64("id"))
|
this.ErrorPage(err)
|
||||||
domain := pickDomainFromDomains(domains, params.DomainId)
|
return
|
||||||
this.Data["domain"] = domain
|
}
|
||||||
|
|
||||||
|
domains, err := listDomainMaps(this.Parent(), app.GetInt64("id"), "")
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
domain := findDomainMap(domains, params.DomainId)
|
||||||
|
|
||||||
record := maps.Map{
|
record := maps.Map{
|
||||||
"id": int64(0),
|
"id": int64(0),
|
||||||
@@ -45,77 +53,35 @@ func (this *CustomRecordsCreatePopupAction) RunGet(params struct {
|
|||||||
"recordItemsJson": `[{"type":"A","value":"","weight":100}]`,
|
"recordItemsJson": `[{"type":"A","value":"","weight":100}]`,
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.RecordId > 0 {
|
if params.RecordId > 0 && domain.GetInt64("id") > 0 {
|
||||||
existing := findCustomRecord(app.GetInt64("id"), params.RecordId)
|
rules, err := listCustomRuleMaps(this.Parent(), domain.GetInt64("id"))
|
||||||
if len(existing) > 0 {
|
if err != nil {
|
||||||
record["id"] = existing.GetInt64("id")
|
this.ErrorPage(err)
|
||||||
if len(record.GetString("domain")) == 0 {
|
return
|
||||||
record["domain"] = existing.GetString("domain")
|
|
||||||
}
|
}
|
||||||
|
for _, rule := range rules {
|
||||||
record["lineScope"] = strings.TrimSpace(existing.GetString("lineScope"))
|
if rule.GetInt64("id") != params.RecordId {
|
||||||
record["lineCarrier"] = strings.TrimSpace(existing.GetString("lineCarrier"))
|
continue
|
||||||
record["lineRegion"] = strings.TrimSpace(existing.GetString("lineRegion"))
|
|
||||||
record["lineProvince"] = strings.TrimSpace(existing.GetString("lineProvince"))
|
|
||||||
record["lineContinent"] = strings.TrimSpace(existing.GetString("lineContinent"))
|
|
||||||
record["lineCountry"] = strings.TrimSpace(existing.GetString("lineCountry"))
|
|
||||||
record["ruleName"] = existing.GetString("ruleName")
|
|
||||||
record["weightEnabled"] = existing.GetBool("weightEnabled")
|
|
||||||
record["ttl"] = existing.GetInt("ttl")
|
|
||||||
record["isOn"] = existing.GetBool("isOn")
|
|
||||||
|
|
||||||
recordItems := make([]maps.Map, 0)
|
|
||||||
recordType := strings.ToUpper(strings.TrimSpace(existing.GetString("recordType")))
|
|
||||||
values, _ := existing["recordValues"].([]maps.Map)
|
|
||||||
for _, item := range values {
|
|
||||||
itemType := strings.ToUpper(strings.TrimSpace(item.GetString("type")))
|
|
||||||
if len(itemType) == 0 {
|
|
||||||
itemType = recordType
|
|
||||||
}
|
}
|
||||||
if itemType != "A" && itemType != "AAAA" {
|
record["id"] = rule.GetInt64("id")
|
||||||
itemType = "A"
|
record["domain"] = domain.GetString("name")
|
||||||
}
|
record["lineScope"] = rule.GetString("lineScope")
|
||||||
|
record["lineCarrier"] = defaultLineField(rule.GetString("lineCarrier"))
|
||||||
recordItems = append(recordItems, maps.Map{
|
record["lineRegion"] = defaultLineField(rule.GetString("lineRegion"))
|
||||||
"type": itemType,
|
record["lineProvince"] = defaultLineField(rule.GetString("lineProvince"))
|
||||||
"value": strings.TrimSpace(item.GetString("value")),
|
record["lineContinent"] = defaultLineField(rule.GetString("lineContinent"))
|
||||||
"weight": item.GetInt("weight"),
|
record["lineCountry"] = defaultLineField(rule.GetString("lineCountry"))
|
||||||
})
|
record["ruleName"] = rule.GetString("ruleName")
|
||||||
}
|
record["weightEnabled"] = rule.GetBool("weightEnabled")
|
||||||
if len(recordItems) == 0 {
|
record["ttl"] = rule.GetInt("ttl")
|
||||||
recordItems = append(recordItems, maps.Map{
|
record["isOn"] = rule.GetBool("isOn")
|
||||||
"type": "A",
|
record["recordItemsJson"] = marshalJSON(rule["recordValues"], "[]")
|
||||||
"value": "",
|
break
|
||||||
"weight": 100,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
record["recordItemsJson"] = marshalJSON(recordItems, "[]")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if record.GetString("lineScope") != "china" && record.GetString("lineScope") != "overseas" {
|
this.Data["app"] = app
|
||||||
if len(strings.TrimSpace(record.GetString("lineContinent"))) > 0 || len(strings.TrimSpace(record.GetString("lineCountry"))) > 0 {
|
this.Data["domain"] = domain
|
||||||
record["lineScope"] = "overseas"
|
|
||||||
} else {
|
|
||||||
record["lineScope"] = "china"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(record.GetString("lineCarrier")) == 0 {
|
|
||||||
record["lineCarrier"] = "默认"
|
|
||||||
}
|
|
||||||
if len(record.GetString("lineRegion")) == 0 {
|
|
||||||
record["lineRegion"] = "默认"
|
|
||||||
}
|
|
||||||
if len(record.GetString("lineProvince")) == 0 {
|
|
||||||
record["lineProvince"] = "默认"
|
|
||||||
}
|
|
||||||
if len(record.GetString("lineContinent")) == 0 {
|
|
||||||
record["lineContinent"] = "默认"
|
|
||||||
}
|
|
||||||
if len(record.GetString("lineCountry")) == 0 {
|
|
||||||
record["lineCountry"] = "默认"
|
|
||||||
}
|
|
||||||
|
|
||||||
this.Data["record"] = record
|
this.Data["record"] = record
|
||||||
this.Data["isEditing"] = params.RecordId > 0
|
this.Data["isEditing"] = params.RecordId > 0
|
||||||
this.Show()
|
this.Show()
|
||||||
@@ -138,40 +104,26 @@ func (this *CustomRecordsCreatePopupAction) RunPost(params struct {
|
|||||||
RuleName string
|
RuleName string
|
||||||
RecordItemsJSON string
|
RecordItemsJSON string
|
||||||
WeightEnabled bool
|
WeightEnabled bool
|
||||||
TTL int
|
Ttl int
|
||||||
IsOn bool
|
IsOn bool
|
||||||
|
|
||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
CSRF *actionutils.CSRF
|
CSRF *actionutils.CSRF
|
||||||
}) {
|
}) {
|
||||||
params.Must.Field("appId", params.AppId).Gt(0, "please select app")
|
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
||||||
|
params.Must.Field("domainId", params.DomainId).Gt(0, "请选择所属域名")
|
||||||
|
|
||||||
params.Domain = strings.TrimSpace(params.Domain)
|
|
||||||
params.LineScope = strings.ToLower(strings.TrimSpace(params.LineScope))
|
params.LineScope = strings.ToLower(strings.TrimSpace(params.LineScope))
|
||||||
params.RuleName = strings.TrimSpace(params.RuleName)
|
|
||||||
params.RecordItemsJSON = strings.TrimSpace(params.RecordItemsJSON)
|
|
||||||
|
|
||||||
domain := maps.Map{}
|
|
||||||
if params.DomainId > 0 {
|
|
||||||
domain = pickDomainFromDomains(mockDomains(params.AppId), params.DomainId)
|
|
||||||
}
|
|
||||||
if len(domain) > 0 {
|
|
||||||
params.Domain = strings.TrimSpace(domain.GetString("name"))
|
|
||||||
}
|
|
||||||
if len(params.Domain) == 0 {
|
|
||||||
this.Fail("please select domain")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if params.LineScope != "china" && params.LineScope != "overseas" {
|
if params.LineScope != "china" && params.LineScope != "overseas" {
|
||||||
params.LineScope = "china"
|
params.LineScope = "china"
|
||||||
}
|
}
|
||||||
|
params.RuleName = strings.TrimSpace(params.RuleName)
|
||||||
if len(params.RuleName) == 0 {
|
if len(params.RuleName) == 0 {
|
||||||
this.Fail("please input rule name")
|
this.Fail("请输入规则名称")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if params.TTL <= 0 || params.TTL > 86400 {
|
if params.Ttl <= 0 || params.Ttl > 86400 {
|
||||||
this.Fail("ttl should be in 1-86400")
|
this.Fail("TTL值必须在 1-86400 范围内")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,11 +133,11 @@ func (this *CustomRecordsCreatePopupAction) RunPost(params struct {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(recordValues) == 0 {
|
if len(recordValues) == 0 {
|
||||||
this.Fail("please input record values")
|
this.Fail("请输入解析记录值")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(recordValues) > 10 {
|
if len(recordValues) > 10 {
|
||||||
this.Fail("record values should be <= 10")
|
this.Fail("单个规则最多只能添加 10 条解析记录")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +161,6 @@ func (this *CustomRecordsCreatePopupAction) RunPost(params struct {
|
|||||||
if len(lineCountry) == 0 {
|
if len(lineCountry) == 0 {
|
||||||
lineCountry = "默认"
|
lineCountry = "默认"
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.LineScope == "overseas" {
|
if params.LineScope == "overseas" {
|
||||||
lineCarrier = ""
|
lineCarrier = ""
|
||||||
lineRegion = ""
|
lineRegion = ""
|
||||||
@@ -219,40 +170,57 @@ func (this *CustomRecordsCreatePopupAction) RunPost(params struct {
|
|||||||
lineCountry = ""
|
lineCountry = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
recordType := recordValues[0].GetString("type")
|
records := make([]*pb.HTTPDNSRuleRecord, 0, len(recordValues))
|
||||||
if len(recordType) == 0 {
|
for i, item := range recordValues {
|
||||||
recordType = "A"
|
records = append(records, &pb.HTTPDNSRuleRecord{
|
||||||
|
Id: 0,
|
||||||
|
RuleId: 0,
|
||||||
|
RecordType: item.GetString("type"),
|
||||||
|
RecordValue: item.GetString("value"),
|
||||||
|
Weight: int32(item.GetInt("weight")),
|
||||||
|
Sort: int32(i + 1),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
saveCustomRecord(params.AppId, maps.Map{
|
rule := &pb.HTTPDNSCustomRule{
|
||||||
"id": params.RecordId,
|
Id: params.RecordId,
|
||||||
"domain": params.Domain,
|
AppId: params.AppId,
|
||||||
"lineScope": params.LineScope,
|
DomainId: params.DomainId,
|
||||||
"lineCarrier": lineCarrier,
|
RuleName: params.RuleName,
|
||||||
"lineRegion": lineRegion,
|
LineScope: params.LineScope,
|
||||||
"lineProvince": lineProvince,
|
LineCarrier: lineCarrier,
|
||||||
"lineContinent": lineContinent,
|
LineRegion: lineRegion,
|
||||||
"lineCountry": lineCountry,
|
LineProvince: lineProvince,
|
||||||
"ruleName": params.RuleName,
|
LineContinent: lineContinent,
|
||||||
"sdnsParams": []maps.Map{},
|
LineCountry: lineCountry,
|
||||||
"recordType": recordType,
|
Ttl: int32(params.Ttl),
|
||||||
"recordValues": recordValues,
|
IsOn: params.IsOn,
|
||||||
"weightEnabled": params.WeightEnabled,
|
Priority: 100,
|
||||||
"ttl": params.TTL,
|
Records: records,
|
||||||
"isOn": params.IsOn,
|
}
|
||||||
})
|
|
||||||
|
if params.RecordId > 0 {
|
||||||
|
err = updateCustomRule(this.Parent(), rule)
|
||||||
|
} else {
|
||||||
|
_, err = createCustomRule(this.Parent(), rule)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRecordItemsJSON(raw string, weightEnabled bool) ([]maps.Map, error) {
|
func parseRecordItemsJSON(raw string, weightEnabled bool) ([]maps.Map, error) {
|
||||||
|
raw = strings.TrimSpace(raw)
|
||||||
if len(raw) == 0 {
|
if len(raw) == 0 {
|
||||||
return []maps.Map{}, nil
|
return []maps.Map{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
list := []maps.Map{}
|
list := []maps.Map{}
|
||||||
if err := json.Unmarshal([]byte(raw), &list); err != nil {
|
if err := json.Unmarshal([]byte(raw), &list); err != nil {
|
||||||
return nil, fmt.Errorf("record items json is invalid")
|
return nil, fmt.Errorf("解析记录格式不正确")
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]maps.Map, 0, len(list))
|
result := make([]maps.Map, 0, len(list))
|
||||||
@@ -263,10 +231,10 @@ func parseRecordItemsJSON(raw string, weightEnabled bool) ([]maps.Map, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if recordType != "A" && recordType != "AAAA" {
|
if recordType != "A" && recordType != "AAAA" {
|
||||||
return nil, fmt.Errorf("record type should be A or AAAA")
|
return nil, fmt.Errorf("记录类型只能是 A 或 AAAA")
|
||||||
}
|
}
|
||||||
if len(recordValue) == 0 {
|
if len(recordValue) == 0 {
|
||||||
return nil, fmt.Errorf("record value should not be empty")
|
return nil, fmt.Errorf("记录值不能为空")
|
||||||
}
|
}
|
||||||
|
|
||||||
weight := item.GetInt("weight")
|
weight := item.GetInt("weight")
|
||||||
@@ -274,7 +242,7 @@ func parseRecordItemsJSON(raw string, weightEnabled bool) ([]maps.Map, error) {
|
|||||||
weight = 100
|
weight = 100
|
||||||
}
|
}
|
||||||
if weight < 1 || weight > 100 {
|
if weight < 1 || weight > 100 {
|
||||||
return nil, fmt.Errorf("weight should be in 1-100")
|
return nil, fmt.Errorf("权重值必须在 1-100 之间")
|
||||||
}
|
}
|
||||||
|
|
||||||
result = append(result, maps.Map{
|
result = append(result, maps.Map{
|
||||||
@@ -283,7 +251,6 @@ func parseRecordItemsJSON(raw string, weightEnabled bool) ([]maps.Map, error) {
|
|||||||
"weight": weight,
|
"weight": weight,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,12 @@ func (this *CustomRecordsDeleteAction) RunPost(params struct {
|
|||||||
AppId int64
|
AppId int64
|
||||||
RecordId int64
|
RecordId int64
|
||||||
}) {
|
}) {
|
||||||
if params.AppId > 0 && params.RecordId > 0 {
|
if params.RecordId > 0 {
|
||||||
deleteCustomRecord(params.AppId, params.RecordId)
|
err := deleteCustomRule(this.Parent(), params.RecordId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,12 @@ func (this *CustomRecordsToggleAction) RunPost(params struct {
|
|||||||
RecordId int64
|
RecordId int64
|
||||||
IsOn bool
|
IsOn bool
|
||||||
}) {
|
}) {
|
||||||
if params.AppId > 0 && params.RecordId > 0 {
|
if params.RecordId > 0 {
|
||||||
toggleCustomRecord(params.AppId, params.RecordId, params.IsOn)
|
err := toggleCustomRule(this.Parent(), params.RecordId, params.IsOn)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,244 +0,0 @@
|
|||||||
package apps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/iwind/TeaGo/maps"
|
|
||||||
)
|
|
||||||
|
|
||||||
var customRecordStore = struct {
|
|
||||||
sync.RWMutex
|
|
||||||
nextID int64
|
|
||||||
data map[int64][]maps.Map
|
|
||||||
}{
|
|
||||||
nextID: 1000,
|
|
||||||
data: map[int64][]maps.Map{
|
|
||||||
1: {
|
|
||||||
{
|
|
||||||
"id": int64(1001),
|
|
||||||
"domain": "api.business.com",
|
|
||||||
"lineScope": "china",
|
|
||||||
"lineCarrier": "电信",
|
|
||||||
"lineRegion": "华东",
|
|
||||||
"lineProvince": "上海",
|
|
||||||
"ruleName": "上海电信灰度-v2",
|
|
||||||
"sdnsParams": []maps.Map{},
|
|
||||||
"recordType": "A",
|
|
||||||
"recordValues": []maps.Map{{"type": "A", "value": "1.1.1.10", "weight": 100}},
|
|
||||||
"weightEnabled": false,
|
|
||||||
"ttl": 30,
|
|
||||||
"isOn": true,
|
|
||||||
"updatedAt": "2026-02-23 10:20:00",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadCustomRecords(appID int64) []maps.Map {
|
|
||||||
customRecordStore.RLock()
|
|
||||||
defer customRecordStore.RUnlock()
|
|
||||||
|
|
||||||
records := customRecordStore.data[appID]
|
|
||||||
result := make([]maps.Map, 0, len(records))
|
|
||||||
for _, record := range records {
|
|
||||||
result = append(result, cloneCustomRecord(record))
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func countCustomRecordsByDomain(appID int64, domain string) int {
|
|
||||||
domain = strings.ToLower(strings.TrimSpace(domain))
|
|
||||||
if len(domain) == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
customRecordStore.RLock()
|
|
||||||
defer customRecordStore.RUnlock()
|
|
||||||
|
|
||||||
count := 0
|
|
||||||
for _, record := range customRecordStore.data[appID] {
|
|
||||||
if strings.ToLower(strings.TrimSpace(record.GetString("domain"))) == domain {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
func findCustomRecord(appID int64, recordID int64) maps.Map {
|
|
||||||
for _, record := range loadCustomRecords(appID) {
|
|
||||||
if record.GetInt64("id") == recordID {
|
|
||||||
return record
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return maps.Map{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveCustomRecord(appID int64, record maps.Map) maps.Map {
|
|
||||||
customRecordStore.Lock()
|
|
||||||
defer customRecordStore.Unlock()
|
|
||||||
|
|
||||||
if appID <= 0 {
|
|
||||||
return maps.Map{}
|
|
||||||
}
|
|
||||||
|
|
||||||
record = cloneCustomRecord(record)
|
|
||||||
recordID := record.GetInt64("id")
|
|
||||||
if recordID <= 0 {
|
|
||||||
customRecordStore.nextID++
|
|
||||||
recordID = customRecordStore.nextID
|
|
||||||
record["id"] = recordID
|
|
||||||
}
|
|
||||||
record["updatedAt"] = nowCustomRecordTime()
|
|
||||||
|
|
||||||
records := customRecordStore.data[appID]
|
|
||||||
found := false
|
|
||||||
for i, oldRecord := range records {
|
|
||||||
if oldRecord.GetInt64("id") == recordID {
|
|
||||||
records[i] = cloneCustomRecord(record)
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
records = append(records, cloneCustomRecord(record))
|
|
||||||
}
|
|
||||||
customRecordStore.data[appID] = records
|
|
||||||
|
|
||||||
return cloneCustomRecord(record)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteCustomRecord(appID int64, recordID int64) {
|
|
||||||
customRecordStore.Lock()
|
|
||||||
defer customRecordStore.Unlock()
|
|
||||||
|
|
||||||
records := customRecordStore.data[appID]
|
|
||||||
if len(records) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filtered := make([]maps.Map, 0, len(records))
|
|
||||||
for _, record := range records {
|
|
||||||
if record.GetInt64("id") == recordID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
filtered = append(filtered, record)
|
|
||||||
}
|
|
||||||
customRecordStore.data[appID] = filtered
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteCustomRecordsByApp(appID int64) {
|
|
||||||
customRecordStore.Lock()
|
|
||||||
defer customRecordStore.Unlock()
|
|
||||||
delete(customRecordStore.data, appID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toggleCustomRecord(appID int64, recordID int64, isOn bool) {
|
|
||||||
customRecordStore.Lock()
|
|
||||||
defer customRecordStore.Unlock()
|
|
||||||
|
|
||||||
records := customRecordStore.data[appID]
|
|
||||||
for i, record := range records {
|
|
||||||
if record.GetInt64("id") == recordID {
|
|
||||||
record["isOn"] = isOn
|
|
||||||
record["updatedAt"] = nowCustomRecordTime()
|
|
||||||
records[i] = record
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
customRecordStore.data[appID] = records
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloneCustomRecord(src maps.Map) maps.Map {
|
|
||||||
dst := maps.Map{}
|
|
||||||
for k, v := range src {
|
|
||||||
switch k {
|
|
||||||
case "sdnsParams", "recordValues":
|
|
||||||
if list, ok := v.([]maps.Map); ok {
|
|
||||||
cloned := make([]maps.Map, 0, len(list))
|
|
||||||
for _, item := range list {
|
|
||||||
m := maps.Map{}
|
|
||||||
for k2, v2 := range item {
|
|
||||||
m[k2] = v2
|
|
||||||
}
|
|
||||||
cloned = append(cloned, m)
|
|
||||||
}
|
|
||||||
dst[k] = cloned
|
|
||||||
} else {
|
|
||||||
dst[k] = []maps.Map{}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
dst[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
func nowCustomRecordTime() string {
|
|
||||||
return time.Now().Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildLineText(record maps.Map) string {
|
|
||||||
parts := []string{}
|
|
||||||
if strings.TrimSpace(record.GetString("lineScope")) == "overseas" {
|
|
||||||
parts = append(parts,
|
|
||||||
strings.TrimSpace(record.GetString("lineContinent")),
|
|
||||||
strings.TrimSpace(record.GetString("lineCountry")),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
parts = append(parts,
|
|
||||||
strings.TrimSpace(record.GetString("lineCarrier")),
|
|
||||||
strings.TrimSpace(record.GetString("lineRegion")),
|
|
||||||
strings.TrimSpace(record.GetString("lineProvince")),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
finalParts := make([]string, 0, len(parts))
|
|
||||||
for _, part := range parts {
|
|
||||||
if len(part) == 0 || part == "默认" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
finalParts = append(finalParts, part)
|
|
||||||
}
|
|
||||||
if len(finalParts) == 0 {
|
|
||||||
return "默认"
|
|
||||||
}
|
|
||||||
return strings.Join(finalParts, " / ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildRecordValueText(record maps.Map) string {
|
|
||||||
values, ok := record["recordValues"].([]maps.Map)
|
|
||||||
if !ok || len(values) == 0 {
|
|
||||||
return "-"
|
|
||||||
}
|
|
||||||
|
|
||||||
weightEnabled := record.GetBool("weightEnabled")
|
|
||||||
defaultType := strings.ToUpper(strings.TrimSpace(record.GetString("recordType")))
|
|
||||||
parts := make([]string, 0, len(values))
|
|
||||||
for _, item := range values {
|
|
||||||
value := strings.TrimSpace(item.GetString("value"))
|
|
||||||
if len(value) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
recordType := strings.ToUpper(strings.TrimSpace(item.GetString("type")))
|
|
||||||
if len(recordType) == 0 {
|
|
||||||
recordType = defaultType
|
|
||||||
}
|
|
||||||
if recordType != "A" && recordType != "AAAA" {
|
|
||||||
recordType = "A"
|
|
||||||
}
|
|
||||||
part := recordType + " " + value
|
|
||||||
if weightEnabled {
|
|
||||||
part += "(" + strconv.Itoa(item.GetInt("weight")) + ")"
|
|
||||||
} else {
|
|
||||||
// no extra suffix
|
|
||||||
}
|
|
||||||
parts = append(parts, part)
|
|
||||||
}
|
|
||||||
if len(parts) == 0 {
|
|
||||||
return "-"
|
|
||||||
}
|
|
||||||
return strings.Join(parts, ", ")
|
|
||||||
}
|
|
||||||
@@ -17,10 +17,19 @@ func (this *DeleteAction) RunGet(params struct {
|
|||||||
AppId int64
|
AppId int64
|
||||||
}) {
|
}) {
|
||||||
httpdnsutils.AddLeftMenu(this.Parent())
|
httpdnsutils.AddLeftMenu(this.Parent())
|
||||||
app := pickApp(params.AppId)
|
app, err := findAppMap(this.Parent(), params.AppId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), app.GetInt64("id"), "delete")
|
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), app.GetInt64("id"), "delete")
|
||||||
this.Data["app"] = app
|
this.Data["app"] = app
|
||||||
this.Data["domainCount"] = len(mockDomains(app.GetInt64("id")))
|
domains, err := listDomainMaps(this.Parent(), app.GetInt64("id"), "")
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.Data["domainCount"] = len(domains)
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,9 +37,10 @@ func (this *DeleteAction) RunPost(params struct {
|
|||||||
AppId int64
|
AppId int64
|
||||||
}) {
|
}) {
|
||||||
if params.AppId > 0 {
|
if params.AppId > 0 {
|
||||||
if deleteApp(params.AppId) {
|
err := deleteAppByID(this.Parent(), params.AppId)
|
||||||
deleteAppSettings(params.AppId)
|
if err != nil {
|
||||||
deleteCustomRecordsByApp(params.AppId)
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,15 +17,19 @@ func (this *DomainsAction) RunGet(params struct {
|
|||||||
AppId int64
|
AppId int64
|
||||||
}) {
|
}) {
|
||||||
httpdnsutils.AddLeftMenu(this.Parent())
|
httpdnsutils.AddLeftMenu(this.Parent())
|
||||||
app := pickApp(params.AppId)
|
app, err := findAppMap(this.Parent(), params.AppId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 构建顶部 tabbar
|
// 构建顶部 tabbar
|
||||||
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "domains")
|
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "domains")
|
||||||
|
|
||||||
domains := mockDomains(app.GetInt64("id"))
|
domains, err := listDomainMaps(this.Parent(), app.GetInt64("id"), "")
|
||||||
for _, domain := range domains {
|
if err != nil {
|
||||||
domainName := domain.GetString("name")
|
this.ErrorPage(err)
|
||||||
domain["customRecordCount"] = countCustomRecordsByDomain(app.GetInt64("id"), domainName)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Data["app"] = app
|
this.Data["app"] = app
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ func (this *DomainsCreatePopupAction) Init() {
|
|||||||
func (this *DomainsCreatePopupAction) RunGet(params struct {
|
func (this *DomainsCreatePopupAction) RunGet(params struct {
|
||||||
AppId int64
|
AppId int64
|
||||||
}) {
|
}) {
|
||||||
this.Data["app"] = pickApp(params.AppId)
|
app, err := findAppMap(this.Parent(), params.AppId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.Data["app"] = app
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +32,13 @@ func (this *DomainsCreatePopupAction) RunPost(params struct {
|
|||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
CSRF *actionutils.CSRF
|
CSRF *actionutils.CSRF
|
||||||
}) {
|
}) {
|
||||||
params.Must.Field("domain", params.Domain).Require("please input domain")
|
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
||||||
|
params.Must.Field("domain", params.Domain).Require("请输入域名")
|
||||||
|
|
||||||
|
err := createDomain(this.Parent(), params.AppId, params.Domain)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ type DomainsDeleteAction struct {
|
|||||||
func (this *DomainsDeleteAction) RunPost(params struct {
|
func (this *DomainsDeleteAction) RunPost(params struct {
|
||||||
DomainId int64
|
DomainId int64
|
||||||
}) {
|
}) {
|
||||||
_ = params.DomainId
|
if params.DomainId > 0 {
|
||||||
|
err := deleteDomain(this.Parent(), params.DomainId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package apps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func maskSecret(secret string) string {
|
||||||
|
secret = strings.TrimSpace(secret)
|
||||||
|
if len(secret) < 4 {
|
||||||
|
return "******"
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := ""
|
||||||
|
for i := 0; i < len(secret); i++ {
|
||||||
|
if secret[i] == '_' {
|
||||||
|
prefix = secret[:i+1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(prefix) == 0 {
|
||||||
|
prefix = secret[:2]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(secret) <= 8 {
|
||||||
|
return prefix + "xxxx"
|
||||||
|
}
|
||||||
|
return prefix + "xxxxxxxx" + secret[len(secret)-4:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildLineText(record maps.Map) string {
|
||||||
|
parts := []string{}
|
||||||
|
if strings.TrimSpace(record.GetString("lineScope")) == "overseas" {
|
||||||
|
parts = append(parts,
|
||||||
|
strings.TrimSpace(record.GetString("lineContinent")),
|
||||||
|
strings.TrimSpace(record.GetString("lineCountry")),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
parts = append(parts,
|
||||||
|
strings.TrimSpace(record.GetString("lineCarrier")),
|
||||||
|
strings.TrimSpace(record.GetString("lineRegion")),
|
||||||
|
strings.TrimSpace(record.GetString("lineProvince")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
finalParts := make([]string, 0, len(parts))
|
||||||
|
for _, part := range parts {
|
||||||
|
if len(part) == 0 || part == "默认" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
finalParts = append(finalParts, part)
|
||||||
|
}
|
||||||
|
if len(finalParts) == 0 {
|
||||||
|
return "默认"
|
||||||
|
}
|
||||||
|
return strings.Join(finalParts, " / ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildRecordValueText(record maps.Map) string {
|
||||||
|
values, ok := record["recordValues"].([]maps.Map)
|
||||||
|
if !ok || len(values) == 0 {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
weightEnabled := record.GetBool("weightEnabled")
|
||||||
|
defaultType := strings.ToUpper(strings.TrimSpace(record.GetString("recordType")))
|
||||||
|
parts := make([]string, 0, len(values))
|
||||||
|
for _, item := range values {
|
||||||
|
value := strings.TrimSpace(item.GetString("value"))
|
||||||
|
if len(value) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
recordType := strings.ToUpper(strings.TrimSpace(item.GetString("type")))
|
||||||
|
if len(recordType) == 0 {
|
||||||
|
recordType = defaultType
|
||||||
|
}
|
||||||
|
if recordType != "A" && recordType != "AAAA" {
|
||||||
|
recordType = "A"
|
||||||
|
}
|
||||||
|
part := recordType + " " + value
|
||||||
|
if weightEnabled {
|
||||||
|
part += "(" + strconv.Itoa(item.GetInt("weight")) + ")"
|
||||||
|
}
|
||||||
|
parts = append(parts, part)
|
||||||
|
}
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ", ")
|
||||||
|
}
|
||||||
@@ -18,6 +18,11 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
}) {
|
}) {
|
||||||
httpdnsutils.AddLeftMenu(this.Parent())
|
httpdnsutils.AddLeftMenu(this.Parent())
|
||||||
this.Data["keyword"] = params.Keyword
|
this.Data["keyword"] = params.Keyword
|
||||||
this.Data["apps"] = filterApps(params.Keyword, "", "", "")
|
apps, err := listAppMaps(this.Parent(), params.Keyword)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.Data["apps"] = apps
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,17 @@ func init() {
|
|||||||
Prefix("/httpdns/apps").
|
Prefix("/httpdns/apps").
|
||||||
Get("", new(IndexAction)).
|
Get("", new(IndexAction)).
|
||||||
Get("/app", new(AppAction)).
|
Get("/app", new(AppAction)).
|
||||||
Get("/sdk", new(SDKAction)).
|
Get("/sdk", new(SdkAction)).
|
||||||
|
GetPost("/sdk/upload", new(SdkUploadAction)).
|
||||||
|
Post("/sdk/upload/delete", new(SdkUploadDeleteAction)).
|
||||||
|
Get("/sdk/download", new(SdkDownloadAction)).
|
||||||
|
Get("/sdk/doc", new(SdkDocAction)).
|
||||||
GetPost("/app/settings", new(AppSettingsAction)).
|
GetPost("/app/settings", new(AppSettingsAction)).
|
||||||
Post("/app/settings/toggleSignEnabled", new(AppSettingsToggleSignEnabledAction)).
|
Post("/app/settings/toggleSignEnabled", new(AppSettingsToggleSignEnabledAction)).
|
||||||
Post("/app/settings/resetSignSecret", new(AppSettingsResetSignSecretAction)).
|
Post("/app/settings/resetSignSecret", new(AppSettingsResetSignSecretAction)).
|
||||||
Get("/domains", new(DomainsAction)).
|
Get("/domains", new(DomainsAction)).
|
||||||
Get("/customRecords", new(CustomRecordsAction)).
|
Get("/customRecords", new(CustomRecordsAction)).
|
||||||
GetPost("/createPopup", new(CreatePopupAction)).
|
GetPost("/create", new(CreateAction)).
|
||||||
GetPost("/delete", new(DeleteAction)).
|
GetPost("/delete", new(DeleteAction)).
|
||||||
GetPost("/domains/createPopup", new(DomainsCreatePopupAction)).
|
GetPost("/domains/createPopup", new(DomainsCreatePopupAction)).
|
||||||
Post("/domains/delete", new(DomainsDeleteAction)).
|
Post("/domains/delete", new(DomainsDeleteAction)).
|
||||||
|
|||||||
@@ -1,190 +0,0 @@
|
|||||||
package apps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/iwind/TeaGo/maps"
|
|
||||||
)
|
|
||||||
|
|
||||||
var appStore = struct {
|
|
||||||
sync.RWMutex
|
|
||||||
data []maps.Map
|
|
||||||
}{
|
|
||||||
data: defaultMockApps(),
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultMockApps() []maps.Map {
|
|
||||||
return []maps.Map{
|
|
||||||
{
|
|
||||||
"id": int64(1),
|
|
||||||
"name": "\u4e3b\u7ad9\u79fb\u52a8\u4e1a\u52a1",
|
|
||||||
"appId": "ab12xc34s2",
|
|
||||||
"clusterId": int64(1),
|
|
||||||
"domainCount": 3,
|
|
||||||
"isOn": true,
|
|
||||||
"authStatus": "enabled",
|
|
||||||
"ecsMode": "auto",
|
|
||||||
"pinningMode": "report",
|
|
||||||
"sanMode": "strict",
|
|
||||||
"riskLevel": "medium",
|
|
||||||
"riskSummary": "Pinning \u5904\u4e8e\u89c2\u5bdf\u6a21\u5f0f",
|
|
||||||
"secretVersion": "v2026.02.20",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": int64(2),
|
|
||||||
"name": "\u89c6\u9891\u7f51\u5173\u4e1a\u52a1",
|
|
||||||
"appId": "vd8992ksm1",
|
|
||||||
"clusterId": int64(2),
|
|
||||||
"domainCount": 1,
|
|
||||||
"isOn": true,
|
|
||||||
"authStatus": "enabled",
|
|
||||||
"ecsMode": "custom",
|
|
||||||
"pinningMode": "enforce",
|
|
||||||
"sanMode": "strict",
|
|
||||||
"riskLevel": "low",
|
|
||||||
"riskSummary": "\u5df2\u542f\u7528\u5f3a\u6821\u9a8c",
|
|
||||||
"secretVersion": "v2026.02.18",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": int64(3),
|
|
||||||
"name": "\u6d77\u5916\u7070\u5ea6\u6d4b\u8bd5",
|
|
||||||
"appId": "ov7711hkq9",
|
|
||||||
"clusterId": int64(1),
|
|
||||||
"domainCount": 2,
|
|
||||||
"isOn": false,
|
|
||||||
"authStatus": "disabled",
|
|
||||||
"ecsMode": "off",
|
|
||||||
"pinningMode": "off",
|
|
||||||
"sanMode": "report",
|
|
||||||
"riskLevel": "high",
|
|
||||||
"riskSummary": "\u5e94\u7528\u5173\u95ed\u4e14\u8bc1\u4e66\u7b56\u7565\u504f\u5f31",
|
|
||||||
"secretVersion": "v2026.01.30",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloneMap(src maps.Map) maps.Map {
|
|
||||||
dst := maps.Map{}
|
|
||||||
for k, v := range src {
|
|
||||||
dst[k] = v
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloneApps(apps []maps.Map) []maps.Map {
|
|
||||||
result := make([]maps.Map, 0, len(apps))
|
|
||||||
for _, app := range apps {
|
|
||||||
result = append(result, cloneMap(app))
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockApps() []maps.Map {
|
|
||||||
appStore.RLock()
|
|
||||||
defer appStore.RUnlock()
|
|
||||||
return cloneApps(appStore.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteApp(appID int64) bool {
|
|
||||||
if appID <= 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
appStore.Lock()
|
|
||||||
defer appStore.Unlock()
|
|
||||||
|
|
||||||
found := false
|
|
||||||
filtered := make([]maps.Map, 0, len(appStore.data))
|
|
||||||
for _, app := range appStore.data {
|
|
||||||
if app.GetInt64("id") == appID {
|
|
||||||
found = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
filtered = append(filtered, app)
|
|
||||||
}
|
|
||||||
if found {
|
|
||||||
appStore.data = filtered
|
|
||||||
}
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterApps(keyword string, riskLevel string, ecsMode string, pinningMode string) []maps.Map {
|
|
||||||
all := mockApps()
|
|
||||||
if len(keyword) == 0 && len(riskLevel) == 0 && len(ecsMode) == 0 && len(pinningMode) == 0 {
|
|
||||||
return all
|
|
||||||
}
|
|
||||||
|
|
||||||
keyword = strings.ToLower(strings.TrimSpace(keyword))
|
|
||||||
result := make([]maps.Map, 0)
|
|
||||||
for _, app := range all {
|
|
||||||
if len(keyword) > 0 {
|
|
||||||
name := strings.ToLower(app.GetString("name"))
|
|
||||||
appID := strings.ToLower(app.GetString("appId"))
|
|
||||||
if !strings.Contains(name, keyword) && !strings.Contains(appID, keyword) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(riskLevel) > 0 && app.GetString("riskLevel") != riskLevel {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(ecsMode) > 0 && app.GetString("ecsMode") != ecsMode {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(pinningMode) > 0 && app.GetString("pinningMode") != pinningMode {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result = append(result, app)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func pickApp(appID int64) maps.Map {
|
|
||||||
apps := mockApps()
|
|
||||||
if len(apps) == 0 {
|
|
||||||
return maps.Map{
|
|
||||||
"id": int64(0),
|
|
||||||
"name": "",
|
|
||||||
"appId": "",
|
|
||||||
"clusterId": int64(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if appID <= 0 {
|
|
||||||
return apps[0]
|
|
||||||
}
|
|
||||||
for _, app := range apps {
|
|
||||||
if app.GetInt64("id") == appID {
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return apps[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockDomains(appID int64) []maps.Map {
|
|
||||||
_ = appID
|
|
||||||
return []maps.Map{
|
|
||||||
{
|
|
||||||
"id": int64(101),
|
|
||||||
"name": "api.business.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": int64(102),
|
|
||||||
"name": "payment.business.com",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pickDomain(domainID int64) maps.Map {
|
|
||||||
domains := mockDomains(0)
|
|
||||||
if domainID <= 0 {
|
|
||||||
return domains[0]
|
|
||||||
}
|
|
||||||
for _, domain := range domains {
|
|
||||||
if domain.GetInt64("id") == domainID {
|
|
||||||
return domain
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return domains[0]
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package apps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
|
||||||
"github.com/iwind/TeaGo/actions"
|
|
||||||
"github.com/iwind/TeaGo/maps"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PoliciesAction struct {
|
|
||||||
actionutils.ParentAction
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *PoliciesAction) Init() {
|
|
||||||
this.Nav("httpdns", "app", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *PoliciesAction) RunGet(params struct{}) {
|
|
||||||
httpdnsutils.AddLeftMenu(this.Parent())
|
|
||||||
this.Data["policies"] = loadGlobalPolicies()
|
|
||||||
this.Show()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *PoliciesAction) RunPost(params struct {
|
|
||||||
DefaultTTL int
|
|
||||||
DefaultSniPolicy string
|
|
||||||
DefaultFallbackMs int
|
|
||||||
ECSMode string
|
|
||||||
ECSIPv4Prefix int
|
|
||||||
ECSIPv6Prefix int
|
|
||||||
PinningMode string
|
|
||||||
SANMode string
|
|
||||||
|
|
||||||
Must *actions.Must
|
|
||||||
CSRF *actionutils.CSRF
|
|
||||||
}) {
|
|
||||||
params.Must.Field("defaultTTL", params.DefaultTTL).Gt(0, "默认 TTL 需要大于 0")
|
|
||||||
params.Must.Field("defaultFallbackMs", params.DefaultFallbackMs).Gt(0, "默认超时需要大于 0")
|
|
||||||
|
|
||||||
if params.DefaultTTL > 86400 {
|
|
||||||
this.Fail("默认 TTL 不能超过 86400 秒")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if params.DefaultFallbackMs > 10000 {
|
|
||||||
this.Fail("默认超时不能超过 10000 毫秒")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if params.DefaultSniPolicy != "level1" && params.DefaultSniPolicy != "level2" && params.DefaultSniPolicy != "level3" {
|
|
||||||
this.Fail("默认 SNI 等级不正确")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if params.ECSMode != "off" && params.ECSMode != "auto" && params.ECSMode != "custom" {
|
|
||||||
this.Fail("ECS 模式不正确")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if params.ECSIPv4Prefix < 0 || params.ECSIPv4Prefix > 32 {
|
|
||||||
this.Fail("IPv4 掩码范围是 0-32")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if params.ECSIPv6Prefix < 0 || params.ECSIPv6Prefix > 128 {
|
|
||||||
this.Fail("IPv6 掩码范围是 0-128")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if params.PinningMode != "off" && params.PinningMode != "report" && params.PinningMode != "enforce" {
|
|
||||||
this.Fail("Pinning 策略不正确")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if params.SANMode != "off" && params.SANMode != "report" && params.SANMode != "strict" {
|
|
||||||
this.Fail("SAN 策略不正确")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
saveGlobalPolicies(maps.Map{
|
|
||||||
"defaultTTL": params.DefaultTTL,
|
|
||||||
"defaultSniPolicy": params.DefaultSniPolicy,
|
|
||||||
"defaultFallbackMs": params.DefaultFallbackMs,
|
|
||||||
"ecsMode": params.ECSMode,
|
|
||||||
"ecsIPv4Prefix": params.ECSIPv4Prefix,
|
|
||||||
"ecsIPv6Prefix": params.ECSIPv6Prefix,
|
|
||||||
"pinningMode": params.PinningMode,
|
|
||||||
"sanMode": params.SANMode,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.Success()
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package apps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/iwind/TeaGo/maps"
|
|
||||||
)
|
|
||||||
|
|
||||||
var globalPoliciesStore = struct {
|
|
||||||
sync.RWMutex
|
|
||||||
data maps.Map
|
|
||||||
}{
|
|
||||||
data: maps.Map{
|
|
||||||
"defaultTTL": 30,
|
|
||||||
"defaultSniPolicy": "level2",
|
|
||||||
"defaultFallbackMs": 300,
|
|
||||||
"ecsMode": "auto",
|
|
||||||
"ecsIPv4Prefix": 24,
|
|
||||||
"ecsIPv6Prefix": 56,
|
|
||||||
"pinningMode": "report",
|
|
||||||
"sanMode": "strict",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadGlobalPolicies() maps.Map {
|
|
||||||
globalPoliciesStore.RLock()
|
|
||||||
defer globalPoliciesStore.RUnlock()
|
|
||||||
|
|
||||||
return maps.Map{
|
|
||||||
"defaultTTL": globalPoliciesStore.data.GetInt("defaultTTL"),
|
|
||||||
"defaultSniPolicy": globalPoliciesStore.data.GetString("defaultSniPolicy"),
|
|
||||||
"defaultFallbackMs": globalPoliciesStore.data.GetInt("defaultFallbackMs"),
|
|
||||||
"ecsMode": globalPoliciesStore.data.GetString("ecsMode"),
|
|
||||||
"ecsIPv4Prefix": globalPoliciesStore.data.GetInt("ecsIPv4Prefix"),
|
|
||||||
"ecsIPv6Prefix": globalPoliciesStore.data.GetInt("ecsIPv6Prefix"),
|
|
||||||
"pinningMode": globalPoliciesStore.data.GetString("pinningMode"),
|
|
||||||
"sanMode": globalPoliciesStore.data.GetString("sanMode"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveGlobalPolicies(policies maps.Map) {
|
|
||||||
globalPoliciesStore.Lock()
|
|
||||||
globalPoliciesStore.data = maps.Map{
|
|
||||||
"defaultTTL": policies.GetInt("defaultTTL"),
|
|
||||||
"defaultSniPolicy": policies.GetString("defaultSniPolicy"),
|
|
||||||
"defaultFallbackMs": policies.GetInt("defaultFallbackMs"),
|
|
||||||
"ecsMode": policies.GetString("ecsMode"),
|
|
||||||
"ecsIPv4Prefix": policies.GetInt("ecsIPv4Prefix"),
|
|
||||||
"ecsIPv6Prefix": policies.GetInt("ecsIPv6Prefix"),
|
|
||||||
"pinningMode": policies.GetString("pinningMode"),
|
|
||||||
"sanMode": policies.GetString("sanMode"),
|
|
||||||
}
|
|
||||||
globalPoliciesStore.Unlock()
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
package apps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func listAppMaps(parent *actionutils.ParentAction, keyword string) ([]maps.Map, error) {
|
||||||
|
resp, err := parent.RPC().HTTPDNSAppRPC().ListHTTPDNSApps(parent.AdminContext(), &pb.ListHTTPDNSAppsRequest{
|
||||||
|
Offset: 0,
|
||||||
|
Size: 10_000,
|
||||||
|
Keyword: strings.TrimSpace(keyword),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]maps.Map, 0, len(resp.GetApps()))
|
||||||
|
for _, app := range resp.GetApps() {
|
||||||
|
domainResp, err := parent.RPC().HTTPDNSDomainRPC().ListHTTPDNSDomainsWithAppId(parent.AdminContext(), &pb.ListHTTPDNSDomainsWithAppIdRequest{
|
||||||
|
AppDbId: app.GetId(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, appPBToMap(app, int64(len(domainResp.GetDomains()))))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findAppMap(parent *actionutils.ParentAction, appDbId int64) (maps.Map, error) {
|
||||||
|
if appDbId > 0 {
|
||||||
|
resp, err := parent.RPC().HTTPDNSAppRPC().FindHTTPDNSApp(parent.AdminContext(), &pb.FindHTTPDNSAppRequest{
|
||||||
|
AppDbId: appDbId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.GetApp() != nil {
|
||||||
|
domainResp, err := parent.RPC().HTTPDNSDomainRPC().ListHTTPDNSDomainsWithAppId(parent.AdminContext(), &pb.ListHTTPDNSDomainsWithAppIdRequest{
|
||||||
|
AppDbId: appDbId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return appPBToMap(resp.GetApp(), int64(len(domainResp.GetDomains()))), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apps, err := listAppMaps(parent, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(apps) == 0 {
|
||||||
|
return maps.Map{
|
||||||
|
"id": int64(0),
|
||||||
|
"name": "",
|
||||||
|
"appId": "",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return apps[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createApp(parent *actionutils.ParentAction, name string, primaryClusterId int64, backupClusterId int64) (int64, error) {
|
||||||
|
newAppId := "app" + strconv.FormatInt(time.Now().UnixNano()%1_000_000_000_000, 36)
|
||||||
|
resp, err := parent.RPC().HTTPDNSAppRPC().CreateHTTPDNSApp(parent.AdminContext(), &pb.CreateHTTPDNSAppRequest{
|
||||||
|
Name: strings.TrimSpace(name),
|
||||||
|
AppId: newAppId,
|
||||||
|
PrimaryClusterId: primaryClusterId,
|
||||||
|
BackupClusterId: backupClusterId,
|
||||||
|
IsOn: true,
|
||||||
|
SignEnabled: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return resp.GetAppDbId(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteAppByID(parent *actionutils.ParentAction, appDbId int64) error {
|
||||||
|
_, err := parent.RPC().HTTPDNSAppRPC().DeleteHTTPDNSApp(parent.AdminContext(), &pb.DeleteHTTPDNSAppRequest{
|
||||||
|
AppDbId: appDbId,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAppSettings(parent *actionutils.ParentAction, appDbId int64, name string, primaryClusterId int64, backupClusterId int64, isOn bool, userId int64) error {
|
||||||
|
_, err := parent.RPC().HTTPDNSAppRPC().UpdateHTTPDNSApp(parent.AdminContext(), &pb.UpdateHTTPDNSAppRequest{
|
||||||
|
AppDbId: appDbId,
|
||||||
|
Name: strings.TrimSpace(name),
|
||||||
|
PrimaryClusterId: primaryClusterId,
|
||||||
|
BackupClusterId: backupClusterId,
|
||||||
|
IsOn: isOn,
|
||||||
|
UserId: userId,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAppSignEnabled(parent *actionutils.ParentAction, appDbId int64, signEnabled bool) error {
|
||||||
|
_, err := parent.RPC().HTTPDNSAppRPC().UpdateHTTPDNSAppSignEnabled(parent.AdminContext(), &pb.UpdateHTTPDNSAppSignEnabledRequest{
|
||||||
|
AppDbId: appDbId,
|
||||||
|
SignEnabled: signEnabled,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetAppSignSecret(parent *actionutils.ParentAction, appDbId int64) (*pb.ResetHTTPDNSAppSignSecretResponse, error) {
|
||||||
|
return parent.RPC().HTTPDNSAppRPC().ResetHTTPDNSAppSignSecret(parent.AdminContext(), &pb.ResetHTTPDNSAppSignSecretRequest{
|
||||||
|
AppDbId: appDbId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func listDomainMaps(parent *actionutils.ParentAction, appDbId int64, keyword string) ([]maps.Map, error) {
|
||||||
|
resp, err := parent.RPC().HTTPDNSDomainRPC().ListHTTPDNSDomainsWithAppId(parent.AdminContext(), &pb.ListHTTPDNSDomainsWithAppIdRequest{
|
||||||
|
AppDbId: appDbId,
|
||||||
|
Keyword: strings.TrimSpace(keyword),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]maps.Map, 0, len(resp.GetDomains()))
|
||||||
|
for _, domain := range resp.GetDomains() {
|
||||||
|
result = append(result, maps.Map{
|
||||||
|
"id": domain.GetId(),
|
||||||
|
"name": domain.GetDomain(),
|
||||||
|
"isOn": domain.GetIsOn(),
|
||||||
|
"customRecordCount": domain.GetRuleCount(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDomain(parent *actionutils.ParentAction, appDbId int64, domain string) error {
|
||||||
|
_, err := parent.RPC().HTTPDNSDomainRPC().CreateHTTPDNSDomain(parent.AdminContext(), &pb.CreateHTTPDNSDomainRequest{
|
||||||
|
AppDbId: appDbId,
|
||||||
|
Domain: strings.TrimSpace(domain),
|
||||||
|
IsOn: true,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteDomain(parent *actionutils.ParentAction, domainId int64) error {
|
||||||
|
_, err := parent.RPC().HTTPDNSDomainRPC().DeleteHTTPDNSDomain(parent.AdminContext(), &pb.DeleteHTTPDNSDomainRequest{
|
||||||
|
DomainId: domainId,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func findDomainMap(domains []maps.Map, domainID int64) maps.Map {
|
||||||
|
if len(domains) == 0 {
|
||||||
|
return maps.Map{}
|
||||||
|
}
|
||||||
|
if domainID <= 0 {
|
||||||
|
return domains[0]
|
||||||
|
}
|
||||||
|
for _, domain := range domains {
|
||||||
|
if domain.GetInt64("id") == domainID {
|
||||||
|
return domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return domains[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func listCustomRuleMaps(parent *actionutils.ParentAction, domainId int64) ([]maps.Map, error) {
|
||||||
|
resp, err := parent.RPC().HTTPDNSRuleRPC().ListHTTPDNSCustomRulesWithDomainId(parent.AdminContext(), &pb.ListHTTPDNSCustomRulesWithDomainIdRequest{
|
||||||
|
DomainId: domainId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]maps.Map, 0, len(resp.GetRules()))
|
||||||
|
for _, rule := range resp.GetRules() {
|
||||||
|
recordValues := make([]maps.Map, 0, len(rule.GetRecords()))
|
||||||
|
recordType := "A"
|
||||||
|
weightEnabled := false
|
||||||
|
for _, record := range rule.GetRecords() {
|
||||||
|
if len(recordType) == 0 {
|
||||||
|
recordType = strings.ToUpper(strings.TrimSpace(record.GetRecordType()))
|
||||||
|
}
|
||||||
|
if record.GetWeight() > 0 && record.GetWeight() != 100 {
|
||||||
|
weightEnabled = true
|
||||||
|
}
|
||||||
|
recordValues = append(recordValues, maps.Map{
|
||||||
|
"type": strings.ToUpper(strings.TrimSpace(record.GetRecordType())),
|
||||||
|
"value": record.GetRecordValue(),
|
||||||
|
"weight": record.GetWeight(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(recordValues) == 0 {
|
||||||
|
recordValues = append(recordValues, maps.Map{
|
||||||
|
"type": "A",
|
||||||
|
"value": "",
|
||||||
|
"weight": 100,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
item := maps.Map{
|
||||||
|
"id": rule.GetId(),
|
||||||
|
"lineScope": rule.GetLineScope(),
|
||||||
|
"lineCarrier": defaultLineField(rule.GetLineCarrier()),
|
||||||
|
"lineRegion": defaultLineField(rule.GetLineRegion()),
|
||||||
|
"lineProvince": defaultLineField(rule.GetLineProvince()),
|
||||||
|
"lineContinent": defaultLineField(rule.GetLineContinent()),
|
||||||
|
"lineCountry": defaultLineField(rule.GetLineCountry()),
|
||||||
|
"ruleName": rule.GetRuleName(),
|
||||||
|
"recordType": recordType,
|
||||||
|
"recordValues": recordValues,
|
||||||
|
"weightEnabled": weightEnabled,
|
||||||
|
"ttl": rule.GetTtl(),
|
||||||
|
"isOn": rule.GetIsOn(),
|
||||||
|
"updatedAt": formatDateTime(rule.GetUpdatedAt()),
|
||||||
|
}
|
||||||
|
item["lineText"] = buildLineText(item)
|
||||||
|
item["recordValueText"] = buildRecordValueText(item)
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCustomRule(parent *actionutils.ParentAction, rule *pb.HTTPDNSCustomRule) (int64, error) {
|
||||||
|
resp, err := parent.RPC().HTTPDNSRuleRPC().CreateHTTPDNSCustomRule(parent.AdminContext(), &pb.CreateHTTPDNSCustomRuleRequest{
|
||||||
|
Rule: rule,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return resp.GetRuleId(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateCustomRule(parent *actionutils.ParentAction, rule *pb.HTTPDNSCustomRule) error {
|
||||||
|
_, err := parent.RPC().HTTPDNSRuleRPC().UpdateHTTPDNSCustomRule(parent.AdminContext(), &pb.UpdateHTTPDNSCustomRuleRequest{
|
||||||
|
Rule: rule,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteCustomRule(parent *actionutils.ParentAction, ruleId int64) error {
|
||||||
|
_, err := parent.RPC().HTTPDNSRuleRPC().DeleteHTTPDNSCustomRule(parent.AdminContext(), &pb.DeleteHTTPDNSCustomRuleRequest{
|
||||||
|
RuleId: ruleId,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func toggleCustomRule(parent *actionutils.ParentAction, ruleId int64, isOn bool) error {
|
||||||
|
_, err := parent.RPC().HTTPDNSRuleRPC().UpdateHTTPDNSCustomRuleStatus(parent.AdminContext(), &pb.UpdateHTTPDNSCustomRuleStatusRequest{
|
||||||
|
RuleId: ruleId,
|
||||||
|
IsOn: isOn,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func appPBToMap(app *pb.HTTPDNSApp, domainCount int64) maps.Map {
|
||||||
|
signSecret := app.GetSignSecret()
|
||||||
|
return maps.Map{
|
||||||
|
"id": app.GetId(),
|
||||||
|
"name": app.GetName(),
|
||||||
|
"appId": app.GetAppId(),
|
||||||
|
"clusterId": app.GetPrimaryClusterId(),
|
||||||
|
"primaryClusterId": app.GetPrimaryClusterId(),
|
||||||
|
"backupClusterId": app.GetBackupClusterId(),
|
||||||
|
"userId": app.GetUserId(),
|
||||||
|
"isOn": app.GetIsOn(),
|
||||||
|
"domainCount": domainCount,
|
||||||
|
"sniPolicyText": "隐匿 SNI",
|
||||||
|
"signEnabled": app.GetSignEnabled(),
|
||||||
|
"signSecretPlain": signSecret,
|
||||||
|
"signSecretMasked": maskSecret(signSecret),
|
||||||
|
"signSecretUpdated": formatDateTime(app.GetSignUpdatedAt()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultLineField(value string) string {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if len(value) == 0 {
|
||||||
|
return "默认"
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatDateTime(ts int64) string {
|
||||||
|
if ts <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return timeutil.FormatTime("Y-m-d H:i:s", ts)
|
||||||
|
}
|
||||||
@@ -5,23 +5,26 @@ import (
|
|||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SDKAction struct {
|
type SdkAction struct {
|
||||||
actionutils.ParentAction
|
actionutils.ParentAction
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *SDKAction) Init() {
|
func (this *SdkAction) Init() {
|
||||||
this.Nav("httpdns", "app", "sdk")
|
this.Nav("httpdns", "app", "sdk")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *SDKAction) RunGet(params struct {
|
func (this *SdkAction) RunGet(params struct {
|
||||||
AppId int64
|
AppId int64
|
||||||
}) {
|
}) {
|
||||||
httpdnsutils.AddLeftMenu(this.Parent())
|
httpdnsutils.AddLeftMenu(this.Parent())
|
||||||
app := pickApp(params.AppId)
|
|
||||||
|
|
||||||
// 构建顶部 tabbar
|
app, err := findAppMap(this.Parent(), params.AppId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "sdk")
|
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "sdk")
|
||||||
|
|
||||||
this.Data["app"] = app
|
this.Data["app"] = app
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package apps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SdkDocAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SdkDocAction) Init() {
|
||||||
|
this.Nav("", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SdkDocAction) RunGet(params struct {
|
||||||
|
Platform string
|
||||||
|
}) {
|
||||||
|
platform, _, readmeRelativePath, _, err := resolveSDKPlatform(params.Platform)
|
||||||
|
if err != nil {
|
||||||
|
this.Fail(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
uploadedDocPath := findUploadedSDKDocPath(platform)
|
||||||
|
if len(uploadedDocPath) > 0 {
|
||||||
|
data, err = os.ReadFile(uploadedDocPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
sdkRoot, sdkRootErr := findSDKRoot()
|
||||||
|
if len(data) == 0 && sdkRootErr == nil {
|
||||||
|
readmePath := filepath.Join(sdkRoot, readmeRelativePath)
|
||||||
|
data, err = os.ReadFile(readmePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) == 0 {
|
||||||
|
localDocPath := findLocalSDKDocPath(platform)
|
||||||
|
if len(localDocPath) > 0 {
|
||||||
|
data, err = os.ReadFile(localDocPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) == 0 || err != nil {
|
||||||
|
this.Fail("当前服务器未找到 SDK 集成文档,请先在“SDK 集成”页面上传对应平台文档")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.AddHeader("Content-Type", "text/markdown; charset=utf-8")
|
||||||
|
this.AddHeader("Content-Disposition", "attachment; filename=\"httpdns-sdk-"+strings.ToLower(platform)+"-README.md\";")
|
||||||
|
_, _ = this.ResponseWriter.Write(data)
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package apps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SdkDownloadAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SdkDownloadAction) Init() {
|
||||||
|
this.Nav("", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SdkDownloadAction) RunGet(params struct {
|
||||||
|
Platform string
|
||||||
|
}) {
|
||||||
|
_, _, _, filename, err := resolveSDKPlatform(params.Platform)
|
||||||
|
if err != nil {
|
||||||
|
this.Fail(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
archivePath := findSDKArchivePath(filename)
|
||||||
|
if len(archivePath) == 0 {
|
||||||
|
this.Fail("当前服务器未找到 SDK 包,请先在“SDK 集成”页面上传对应平台包: " + filename)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fp, err := os.Open(archivePath)
|
||||||
|
if err != nil {
|
||||||
|
this.Fail("打开 SDK 包失败: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = fp.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
this.AddHeader("Content-Type", "application/zip")
|
||||||
|
this.AddHeader("Content-Disposition", "attachment; filename=\""+filename+"\";")
|
||||||
|
this.AddHeader("X-Accel-Buffering", "no")
|
||||||
|
_, _ = io.Copy(this.ResponseWriter, fp)
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package apps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/iwind/TeaGo/Tea"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sdkUploadDir() string {
|
||||||
|
return filepath.Clean(Tea.Root + "/data/httpdns/sdk")
|
||||||
|
}
|
||||||
|
|
||||||
|
func findFirstExistingDir(paths []string) string {
|
||||||
|
for _, path := range paths {
|
||||||
|
stat, err := os.Stat(path)
|
||||||
|
if err == nil && stat.IsDir() {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func findFirstExistingFile(paths []string) string {
|
||||||
|
for _, path := range paths {
|
||||||
|
stat, err := os.Stat(path)
|
||||||
|
if err == nil && !stat.IsDir() {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func findNewestExistingFile(paths []string) string {
|
||||||
|
type fileInfo struct {
|
||||||
|
path string
|
||||||
|
modTime time.Time
|
||||||
|
}
|
||||||
|
result := fileInfo{}
|
||||||
|
for _, path := range paths {
|
||||||
|
stat, err := os.Stat(path)
|
||||||
|
if err != nil || stat.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(result.path) == 0 || stat.ModTime().After(result.modTime) || (stat.ModTime().Equal(result.modTime) && path > result.path) {
|
||||||
|
result.path = path
|
||||||
|
result.modTime = stat.ModTime()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.path
|
||||||
|
}
|
||||||
|
|
||||||
|
func findSDKRoot() (string, error) {
|
||||||
|
candidates := []string{
|
||||||
|
filepath.Clean(Tea.Root + "/EdgeHttpDNS/sdk"),
|
||||||
|
filepath.Clean(Tea.Root + "/edge-httpdns/sdk"),
|
||||||
|
filepath.Clean(Tea.Root + "/edge-httpdns/edge-httpdns/sdk"),
|
||||||
|
filepath.Clean(Tea.Root + "/../EdgeHttpDNS/sdk"),
|
||||||
|
filepath.Clean(Tea.Root + "/../../EdgeHttpDNS/sdk"),
|
||||||
|
filepath.Clean(Tea.Root + "/../edge-httpdns/sdk"),
|
||||||
|
filepath.Clean(Tea.Root + "/../../edge-httpdns/sdk"),
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := findFirstExistingDir(candidates)
|
||||||
|
if len(dir) > 0 {
|
||||||
|
return dir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New("SDK files are not found on current server")
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveSDKPlatform(platform string) (key string, relativeDir string, readmeRelativePath string, downloadFilename string, err error) {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(platform)) {
|
||||||
|
case "android":
|
||||||
|
return "android", "android", "android/README.md", "httpdns-sdk-android.zip", nil
|
||||||
|
case "ios":
|
||||||
|
return "ios", "ios", "ios/README.md", "httpdns-sdk-ios.zip", nil
|
||||||
|
case "flutter":
|
||||||
|
return "flutter", "flutter/aliyun_httpdns", "flutter/aliyun_httpdns/README.md", "httpdns-sdk-flutter.zip", nil
|
||||||
|
default:
|
||||||
|
return "", "", "", "", errors.New("invalid platform, expected one of: android, ios, flutter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findSDKArchivePath(downloadFilename string) string {
|
||||||
|
searchDirs := []string{sdkUploadDir()}
|
||||||
|
|
||||||
|
// 1) Exact filename first.
|
||||||
|
exactFiles := []string{}
|
||||||
|
for _, dir := range searchDirs {
|
||||||
|
exactFiles = append(exactFiles, filepath.Join(dir, downloadFilename))
|
||||||
|
}
|
||||||
|
path := findFirstExistingFile(exactFiles)
|
||||||
|
if len(path) > 0 {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Version-suffixed archives, e.g. httpdns-sdk-android-v1.4.8.zip
|
||||||
|
base := strings.TrimSuffix(downloadFilename, ".zip")
|
||||||
|
patternName := base + "-*.zip"
|
||||||
|
matches := []string{}
|
||||||
|
for _, dir := range searchDirs {
|
||||||
|
found, _ := filepath.Glob(filepath.Join(dir, patternName))
|
||||||
|
for _, file := range found {
|
||||||
|
stat, err := os.Stat(file)
|
||||||
|
if err == nil && !stat.IsDir() {
|
||||||
|
matches = append(matches, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(matches) > 0 {
|
||||||
|
return findNewestExistingFile(matches)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func findUploadedSDKDocPath(platform string) string {
|
||||||
|
platform = strings.ToLower(strings.TrimSpace(platform))
|
||||||
|
if len(platform) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
searchDir := sdkUploadDir()
|
||||||
|
exact := filepath.Join(searchDir, "httpdns-sdk-"+platform+".md")
|
||||||
|
if file := findFirstExistingFile([]string{exact}); len(file) > 0 {
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern := filepath.Join(searchDir, "httpdns-sdk-"+platform+"-*.md")
|
||||||
|
matches, _ := filepath.Glob(pattern)
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
sort.Strings(matches)
|
||||||
|
return findNewestExistingFile(matches)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findLocalSDKDocPath(platform string) string {
|
||||||
|
filename := strings.ToLower(strings.TrimSpace(platform)) + ".md"
|
||||||
|
candidates := []string{
|
||||||
|
filepath.Clean(Tea.Root + "/edge-admin/web/views/@default/httpdns/apps/docs/" + filename),
|
||||||
|
filepath.Clean(Tea.Root + "/EdgeAdmin/web/views/@default/httpdns/apps/docs/" + filename),
|
||||||
|
}
|
||||||
|
return findFirstExistingFile(candidates)
|
||||||
|
}
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
package apps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||||
|
"github.com/iwind/TeaGo/actions"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SdkUploadAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SdkUploadAction) Init() {
|
||||||
|
this.Nav("httpdns", "app", "sdk")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SdkUploadAction) RunGet(params struct {
|
||||||
|
AppId int64
|
||||||
|
}) {
|
||||||
|
httpdnsutils.AddLeftMenu(this.Parent())
|
||||||
|
|
||||||
|
app, err := findAppMap(this.Parent(), params.AppId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpdnsutils.AddAppTabbar(this.Parent(), app.GetString("name"), params.AppId, "sdk")
|
||||||
|
|
||||||
|
this.Data["app"] = app
|
||||||
|
this.Data["defaultVersion"] = "1.0.0"
|
||||||
|
this.Data["uploadedFiles"] = listUploadedSDKFiles()
|
||||||
|
this.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SdkUploadAction) RunPost(params struct {
|
||||||
|
AppId int64
|
||||||
|
Platform string
|
||||||
|
Version string
|
||||||
|
SDKFile *actions.File
|
||||||
|
DocFile *actions.File
|
||||||
|
|
||||||
|
Must *actions.Must
|
||||||
|
}) {
|
||||||
|
params.Must.Field("appId", params.AppId).Gt(0, "请选择应用")
|
||||||
|
|
||||||
|
platform, _, _, downloadFilename, err := resolveSDKPlatform(params.Platform)
|
||||||
|
if err != nil {
|
||||||
|
this.Fail(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
version, err := normalizeSDKVersion(params.Version)
|
||||||
|
if err != nil {
|
||||||
|
this.Fail(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.SDKFile == nil && params.DocFile == nil {
|
||||||
|
this.Fail("请至少上传一个文件")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadDir := sdkUploadDir()
|
||||||
|
err = os.MkdirAll(uploadDir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
this.Fail("创建上传目录失败: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.SDKFile != nil {
|
||||||
|
filename := strings.ToLower(strings.TrimSpace(params.SDKFile.Filename))
|
||||||
|
if !strings.HasSuffix(filename, ".zip") {
|
||||||
|
this.Fail("SDK 包仅支持 .zip 文件")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sdkData, readErr := params.SDKFile.Read()
|
||||||
|
if readErr != nil {
|
||||||
|
this.Fail("读取 SDK 包失败: " + readErr.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
baseName := strings.TrimSuffix(downloadFilename, ".zip")
|
||||||
|
err = saveSDKUploadFile(uploadDir, downloadFilename, sdkData)
|
||||||
|
if err == nil {
|
||||||
|
err = saveSDKUploadFile(uploadDir, baseName+"-v"+version+".zip", sdkData)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
this.Fail("保存 SDK 包失败: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.DocFile != nil {
|
||||||
|
docName := strings.ToLower(strings.TrimSpace(params.DocFile.Filename))
|
||||||
|
if !strings.HasSuffix(docName, ".md") {
|
||||||
|
this.Fail("集成文档仅支持 .md 文件")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
docData, readErr := params.DocFile.Read()
|
||||||
|
if readErr != nil {
|
||||||
|
this.Fail("读取集成文档失败: " + readErr.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := "httpdns-sdk-" + platform + ".md"
|
||||||
|
err = saveSDKUploadFile(uploadDir, filename, docData)
|
||||||
|
if err == nil {
|
||||||
|
err = saveSDKUploadFile(uploadDir, "httpdns-sdk-"+platform+"-v"+version+".md", docData)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
this.Fail("保存集成文档失败: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Success()
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeSDKVersion(version string) (string, error) {
|
||||||
|
version = strings.TrimSpace(version)
|
||||||
|
if len(version) == 0 {
|
||||||
|
version = "1.0.0"
|
||||||
|
}
|
||||||
|
for _, c := range version {
|
||||||
|
if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return "", errors.New("版本号格式不正确")
|
||||||
|
}
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveSDKUploadFile(baseDir string, filename string, data []byte) error {
|
||||||
|
targetPath := filepath.Join(baseDir, filename)
|
||||||
|
tmpPath := targetPath + ".tmp"
|
||||||
|
err := os.WriteFile(tmpPath, data, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Rename(tmpPath, targetPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listUploadedSDKFiles() []map[string]interface{} {
|
||||||
|
dir := sdkUploadDir()
|
||||||
|
entries, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
return []map[string]interface{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type item struct {
|
||||||
|
Name string
|
||||||
|
Platform string
|
||||||
|
FileType string
|
||||||
|
Version string
|
||||||
|
SizeBytes int64
|
||||||
|
UpdatedAt int64
|
||||||
|
}
|
||||||
|
|
||||||
|
items := make([]item, 0)
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := entry.Name()
|
||||||
|
platform, version, fileType, ok := parseSDKUploadFilename(name)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
info, statErr := entry.Info()
|
||||||
|
if statErr != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, item{
|
||||||
|
Name: name,
|
||||||
|
Platform: platform,
|
||||||
|
FileType: fileType,
|
||||||
|
Version: version,
|
||||||
|
SizeBytes: info.Size(),
|
||||||
|
UpdatedAt: info.ModTime().Unix(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(items, func(i, j int) bool {
|
||||||
|
if items[i].UpdatedAt == items[j].UpdatedAt {
|
||||||
|
return items[i].Name > items[j].Name
|
||||||
|
}
|
||||||
|
return items[i].UpdatedAt > items[j].UpdatedAt
|
||||||
|
})
|
||||||
|
|
||||||
|
result := make([]map[string]interface{}, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
result = append(result, map[string]interface{}{
|
||||||
|
"name": item.Name,
|
||||||
|
"platform": item.Platform,
|
||||||
|
"fileType": item.FileType,
|
||||||
|
"version": item.Version,
|
||||||
|
"sizeText": formatSDKFileSize(item.SizeBytes),
|
||||||
|
"updatedAt": time.Unix(item.UpdatedAt, 0).Format("2006-01-02 15:04:05"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSDKUploadFilename(filename string) (platform string, version string, fileType string, ok bool) {
|
||||||
|
if !strings.HasPrefix(filename, "httpdns-sdk-") {
|
||||||
|
return "", "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
ext := ""
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(filename, ".zip"):
|
||||||
|
ext = ".zip"
|
||||||
|
fileType = "SDK包"
|
||||||
|
case strings.HasSuffix(filename, ".md"):
|
||||||
|
ext = ".md"
|
||||||
|
fileType = "集成文档"
|
||||||
|
default:
|
||||||
|
return "", "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
main := strings.TrimSuffix(strings.TrimPrefix(filename, "httpdns-sdk-"), ext)
|
||||||
|
version = "latest"
|
||||||
|
if idx := strings.Index(main, "-v"); idx > 0 && idx+2 < len(main) {
|
||||||
|
version = main[idx+2:]
|
||||||
|
main = main[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
main = strings.ToLower(strings.TrimSpace(main))
|
||||||
|
switch main {
|
||||||
|
case "android", "ios", "flutter":
|
||||||
|
platform = main
|
||||||
|
return platform, version, fileType, true
|
||||||
|
default:
|
||||||
|
return "", "", "", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatSDKFileSize(size int64) string {
|
||||||
|
if size < 1024 {
|
||||||
|
return strconv.FormatInt(size, 10) + " B"
|
||||||
|
}
|
||||||
|
sizeKB := float64(size) / 1024
|
||||||
|
if sizeKB < 1024 {
|
||||||
|
return strconv.FormatFloat(sizeKB, 'f', 1, 64) + " KB"
|
||||||
|
}
|
||||||
|
sizeMB := sizeKB / 1024
|
||||||
|
if sizeMB < 1024 {
|
||||||
|
return strconv.FormatFloat(sizeMB, 'f', 1, 64) + " MB"
|
||||||
|
}
|
||||||
|
sizeGB := sizeMB / 1024
|
||||||
|
return strconv.FormatFloat(sizeGB, 'f', 1, 64) + " GB"
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package apps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SdkUploadDeleteAction struct {
|
||||||
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SdkUploadDeleteAction) Init() {
|
||||||
|
this.Nav("httpdns", "app", "sdk")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *SdkUploadDeleteAction) RunPost(params struct {
|
||||||
|
AppId int64
|
||||||
|
Filename string
|
||||||
|
}) {
|
||||||
|
if params.AppId <= 0 {
|
||||||
|
this.Fail("请选择应用")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := strings.TrimSpace(params.Filename)
|
||||||
|
if len(filename) == 0 {
|
||||||
|
this.Fail("文件名不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.Contains(filename, "/") || strings.Contains(filename, "\\") || strings.Contains(filename, "..") {
|
||||||
|
this.Fail("文件名不合法")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(filename, "httpdns-sdk-") {
|
||||||
|
this.Fail("不允许删除该文件")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !(strings.HasSuffix(filename, ".zip") || strings.HasSuffix(filename, ".md")) {
|
||||||
|
this.Fail("不允许删除该文件")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := filepath.Join(sdkUploadDir(), filename)
|
||||||
|
_, err := os.Stat(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
this.Success()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = os.Remove(fullPath); err != nil {
|
||||||
|
this.Fail("删除失败: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Success()
|
||||||
|
}
|
||||||
@@ -2,8 +2,6 @@ package clusters
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/policies"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CertsAction struct {
|
type CertsAction struct {
|
||||||
@@ -15,7 +13,5 @@ func (this *CertsAction) Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *CertsAction) RunGet(params struct{}) {
|
func (this *CertsAction) RunGet(params struct{}) {
|
||||||
httpdnsutils.AddLeftMenu(this.Parent())
|
this.RedirectURL("/httpdns/clusters")
|
||||||
this.Data["certs"] = policies.LoadPublicSNICertificates()
|
|
||||||
this.Show()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package clusters
|
package clusters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClusterAction struct {
|
type ClusterAction struct {
|
||||||
@@ -20,19 +23,75 @@ func (this *ClusterAction) RunGet(params struct {
|
|||||||
Keyword string
|
Keyword string
|
||||||
}) {
|
}) {
|
||||||
httpdnsutils.AddLeftMenu(this.Parent())
|
httpdnsutils.AddLeftMenu(this.Parent())
|
||||||
cluster := pickCluster(params.ClusterId)
|
|
||||||
|
|
||||||
// 构建顶部 tabbar
|
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "node")
|
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "node")
|
||||||
|
|
||||||
|
nodes, err := listNodeMaps(this.Parent(), params.ClusterId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nodes = filterClusterNodes(nodes, params.InstalledState, params.ActiveState, params.Keyword)
|
||||||
|
|
||||||
this.Data["clusterId"] = params.ClusterId
|
this.Data["clusterId"] = params.ClusterId
|
||||||
this.Data["cluster"] = cluster
|
this.Data["cluster"] = cluster
|
||||||
this.Data["installState"] = params.InstalledState
|
this.Data["installState"] = params.InstalledState
|
||||||
this.Data["activeState"] = params.ActiveState
|
this.Data["activeState"] = params.ActiveState
|
||||||
this.Data["keyword"] = params.Keyword
|
this.Data["keyword"] = params.Keyword
|
||||||
nodes := mockNodes(params.ClusterId, params.InstalledState, params.ActiveState, params.Keyword)
|
|
||||||
this.Data["nodes"] = nodes
|
this.Data["nodes"] = nodes
|
||||||
this.Data["hasNodes"] = len(nodes) > 0
|
this.Data["hasNodes"] = len(nodes) > 0
|
||||||
this.Data["page"] = ""
|
this.Data["page"] = ""
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterClusterNodes(nodes []maps.Map, installedState int, activeState int, keyword string) []maps.Map {
|
||||||
|
keyword = strings.ToLower(strings.TrimSpace(keyword))
|
||||||
|
|
||||||
|
result := make([]maps.Map, 0, len(nodes))
|
||||||
|
for _, node := range nodes {
|
||||||
|
isInstalled := node.GetBool("isInstalled")
|
||||||
|
if installedState == 1 && !isInstalled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if installedState == 2 && isInstalled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
status := node.GetMap("status")
|
||||||
|
isOnline := node.GetBool("isOn") && node.GetBool("isUp") && status.GetBool("isActive")
|
||||||
|
if activeState == 1 && !isOnline {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if activeState == 2 && isOnline {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keyword) > 0 {
|
||||||
|
hit := strings.Contains(strings.ToLower(node.GetString("name")), keyword)
|
||||||
|
if !hit {
|
||||||
|
ipAddresses, ok := node["ipAddresses"].([]maps.Map)
|
||||||
|
if ok {
|
||||||
|
for _, ipAddr := range ipAddresses {
|
||||||
|
if strings.Contains(strings.ToLower(ipAddr.GetString("ip")), keyword) {
|
||||||
|
hit = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hit {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/iwind/TeaGo/maps"
|
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IndexAction struct {
|
type IndexAction struct {
|
||||||
@@ -18,51 +20,37 @@ func (this *IndexAction) RunGet(params struct {
|
|||||||
ClusterId int64
|
ClusterId int64
|
||||||
NodeId int64
|
NodeId int64
|
||||||
}) {
|
}) {
|
||||||
this.Data["clusterId"] = params.ClusterId
|
node, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
|
||||||
this.Data["nodeId"] = params.NodeId
|
if err != nil {
|
||||||
this.Data["currentCluster"] = maps.Map{"id": params.ClusterId, "name": "Mock Cluster"}
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
this.Data["nodeDatetime"] = "2026-02-22 12:00:00"
|
}
|
||||||
this.Data["nodeTimeDiff"] = 0
|
cluster, err := findHTTPDNSClusterMap(this.Parent(), params.ClusterId)
|
||||||
this.Data["shouldUpgrade"] = false
|
if err != nil {
|
||||||
this.Data["newVersion"] = ""
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
this.Data["node"] = maps.Map{
|
|
||||||
"id": params.NodeId,
|
|
||||||
"name": "Mock HTTPDNS Node",
|
|
||||||
"ipAddresses": []maps.Map{{"ip": "100.200.100.200", "name": "Public IP", "canAccess": true, "isOn": true, "isUp": true}},
|
|
||||||
"cluster": maps.Map{"id": params.ClusterId, "name": "Mock Cluster", "installDir": "/opt/edge-httpdns"},
|
|
||||||
"installDir": "/opt/edge-httpdns",
|
|
||||||
"isInstalled": true,
|
|
||||||
"uniqueId": "m-1234567890",
|
|
||||||
"secret": "mock-secret-key",
|
|
||||||
"isOn": true,
|
|
||||||
"isUp": true,
|
|
||||||
"apiNodeAddrs": []string{"192.168.1.100:8001"},
|
|
||||||
"login": nil,
|
|
||||||
|
|
||||||
"status": maps.Map{
|
|
||||||
"isActive": true,
|
|
||||||
"updatedAt": 1670000000,
|
|
||||||
"hostname": "node-01.local",
|
|
||||||
"cpuUsage": 0.15,
|
|
||||||
"cpuUsageText": "15.00%",
|
|
||||||
"memUsage": 0.45,
|
|
||||||
"memUsageText": "45.00%",
|
|
||||||
"connectionCount": 100,
|
|
||||||
"buildVersion": "1.0.0",
|
|
||||||
"cpuPhysicalCount": 4,
|
|
||||||
"cpuLogicalCount": 8,
|
|
||||||
"load1m": "0.50",
|
|
||||||
"load5m": "0.60",
|
|
||||||
"load15m": "0.70",
|
|
||||||
"cacheTotalDiskSize": "10G",
|
|
||||||
"cacheTotalMemorySize": "2G",
|
|
||||||
"exePath": "/opt/edge-httpdns/bin/edge-httpdns",
|
|
||||||
"apiSuccessPercent": 100.0,
|
|
||||||
"apiAvgCostSeconds": 0.05,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.Data["clusterId"] = params.ClusterId
|
||||||
|
this.Data["nodeId"] = params.NodeId
|
||||||
|
this.Data["node"] = node
|
||||||
|
this.Data["currentCluster"] = cluster
|
||||||
|
|
||||||
|
status := node.GetMap("status")
|
||||||
|
updatedAt := status.GetInt64("updatedAt")
|
||||||
|
nodeDatetime := ""
|
||||||
|
nodeTimeDiff := int64(0)
|
||||||
|
if updatedAt > 0 {
|
||||||
|
nodeDatetime = timeutil.FormatTime("Y-m-d H:i:s", updatedAt)
|
||||||
|
nodeTimeDiff = time.Now().Unix() - updatedAt
|
||||||
|
if nodeTimeDiff < 0 {
|
||||||
|
nodeTimeDiff = -nodeTimeDiff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.Data["nodeDatetime"] = nodeDatetime
|
||||||
|
this.Data["nodeTimeDiff"] = nodeTimeDiff
|
||||||
|
|
||||||
|
this.Data["shouldUpgrade"] = false
|
||||||
|
this.Data["newVersion"] = ""
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InstallAction struct {
|
type InstallAction struct {
|
||||||
@@ -14,28 +18,87 @@ func (this *InstallAction) Init() {
|
|||||||
this.SecondMenu("nodes")
|
this.SecondMenu("nodes")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *InstallAction) RunGet(params struct{ ClusterId int64; NodeId int64 }) {
|
func (this *InstallAction) RunGet(params struct {
|
||||||
|
ClusterId int64
|
||||||
|
NodeId int64
|
||||||
|
}) {
|
||||||
|
node, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cluster, err := findHTTPDNSClusterMap(this.Parent(), params.ClusterId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.Data["clusterId"] = params.ClusterId
|
this.Data["clusterId"] = params.ClusterId
|
||||||
this.Data["nodeId"] = params.NodeId
|
this.Data["nodeId"] = params.NodeId
|
||||||
this.Data["currentCluster"] = maps.Map{"id": params.ClusterId, "name": "Mock Cluster"}
|
this.Data["currentCluster"] = cluster
|
||||||
|
this.Data["node"] = node
|
||||||
|
this.Data["installStatus"] = node.GetMap("installStatus")
|
||||||
|
|
||||||
this.Data["apiEndpoints"] = "\"http://127.0.0.1:7788\""
|
apiNodesResp, err := this.RPC().APINodeRPC().FindAllEnabledAPINodes(this.AdminContext(), &pb.FindAllEnabledAPINodesRequest{})
|
||||||
this.Data["sshAddr"] = "192.168.1.100:22"
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
this.Data["node"] = maps.Map{
|
return
|
||||||
"id": params.NodeId,
|
|
||||||
"name": "Mock Node",
|
|
||||||
"isInstalled": false,
|
|
||||||
"uniqueId": "m-1234567890",
|
|
||||||
"secret": "mock-secret-key",
|
|
||||||
"installDir": "/opt/edge-httpdns",
|
|
||||||
"cluster": maps.Map{"installDir": "/opt/edge-httpdns"},
|
|
||||||
}
|
}
|
||||||
this.Data["installStatus"] = nil
|
apiEndpoints := make([]string, 0, 8)
|
||||||
|
for _, apiNode := range apiNodesResp.GetApiNodes() {
|
||||||
|
if !apiNode.GetIsOn() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
apiEndpoints = append(apiEndpoints, apiNode.GetAccessAddrs()...)
|
||||||
|
}
|
||||||
|
if len(apiEndpoints) == 0 {
|
||||||
|
apiEndpoints = []string{"http://127.0.0.1:7788"}
|
||||||
|
}
|
||||||
|
this.Data["apiEndpoints"] = "\"" + strings.Join(apiEndpoints, "\", \"") + "\""
|
||||||
|
this.Data["sshAddr"] = ""
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *InstallAction) RunPost(params struct{ NodeId int64 }) {
|
func (this *InstallAction) RunPost(params struct {
|
||||||
|
NodeId int64
|
||||||
|
}) {
|
||||||
|
nodeResp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||||
|
NodeId: params.NodeId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node := nodeResp.GetNode()
|
||||||
|
if node == nil {
|
||||||
|
this.Fail("节点不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
existingStatus := map[string]interface{}{}
|
||||||
|
if len(node.GetInstallStatusJSON()) > 0 {
|
||||||
|
_ = json.Unmarshal(node.GetInstallStatusJSON(), &existingStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
existingStatus["isRunning"] = true
|
||||||
|
existingStatus["isFinished"] = false
|
||||||
|
existingStatus["isOk"] = false
|
||||||
|
existingStatus["error"] = ""
|
||||||
|
existingStatus["errorCode"] = ""
|
||||||
|
existingStatus["updatedAt"] = time.Now().Unix()
|
||||||
|
|
||||||
|
installStatusJSON, _ := json.Marshal(existingStatus)
|
||||||
|
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
|
||||||
|
NodeId: params.NodeId,
|
||||||
|
IsUp: node.GetIsUp(),
|
||||||
|
IsInstalled: false,
|
||||||
|
IsActive: node.GetIsActive(),
|
||||||
|
StatusJSON: node.GetStatusJSON(),
|
||||||
|
InstallStatusJSON: installStatusJSON,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,18 +18,65 @@ func (this *LogsAction) Init() {
|
|||||||
this.SecondMenu("nodes")
|
this.SecondMenu("nodes")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *LogsAction) RunGet(params struct{ ClusterId int64; NodeId int64 }) {
|
func (this *LogsAction) RunGet(params struct {
|
||||||
|
ClusterId int64
|
||||||
|
NodeId int64
|
||||||
|
DayFrom string
|
||||||
|
DayTo string
|
||||||
|
Level string
|
||||||
|
Keyword string
|
||||||
|
}) {
|
||||||
|
node, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cluster, err := findHTTPDNSClusterMap(this.Parent(), params.ClusterId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.Data["clusterId"] = params.ClusterId
|
this.Data["clusterId"] = params.ClusterId
|
||||||
this.Data["nodeId"] = params.NodeId
|
this.Data["nodeId"] = params.NodeId
|
||||||
this.Data["currentCluster"] = maps.Map{"id": params.ClusterId, "name": "Mock Cluster"}
|
this.Data["currentCluster"] = cluster
|
||||||
|
this.Data["node"] = node
|
||||||
|
this.Data["dayFrom"] = params.DayFrom
|
||||||
|
this.Data["dayTo"] = params.DayTo
|
||||||
|
this.Data["level"] = params.Level
|
||||||
|
this.Data["keyword"] = params.Keyword
|
||||||
|
|
||||||
this.Data["dayFrom"] = ""
|
day := strings.TrimSpace(params.DayFrom)
|
||||||
this.Data["dayTo"] = ""
|
if len(day) == 0 {
|
||||||
this.Data["keyword"] = ""
|
day = strings.TrimSpace(params.DayTo)
|
||||||
this.Data["level"] = ""
|
}
|
||||||
this.Data["logs"] = []maps.Map{}
|
|
||||||
|
resp, err := this.RPC().HTTPDNSRuntimeLogRPC().ListHTTPDNSRuntimeLogs(this.AdminContext(), &pb.ListHTTPDNSRuntimeLogsRequest{
|
||||||
|
Day: day,
|
||||||
|
ClusterId: params.ClusterId,
|
||||||
|
NodeId: params.NodeId,
|
||||||
|
Level: strings.TrimSpace(params.Level),
|
||||||
|
Keyword: strings.TrimSpace(params.Keyword),
|
||||||
|
Offset: 0,
|
||||||
|
Size: 100,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logs := make([]maps.Map, 0, len(resp.GetLogs()))
|
||||||
|
for _, item := range resp.GetLogs() {
|
||||||
|
logs = append(logs, maps.Map{
|
||||||
|
"level": item.GetLevel(),
|
||||||
|
"tag": item.GetType(),
|
||||||
|
"description": item.GetDescription(),
|
||||||
|
"createdAt": item.GetCreatedAt(),
|
||||||
|
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", item.GetCreatedAt()),
|
||||||
|
"count": item.GetCount(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.Data["logs"] = logs
|
||||||
this.Data["page"] = ""
|
this.Data["page"] = ""
|
||||||
this.Data["node"] = maps.Map{"id": params.NodeId, "name": "Mock Node"}
|
|
||||||
|
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findHTTPDNSClusterMap(parent *actionutils.ParentAction, clusterID int64) (maps.Map, error) {
|
||||||
|
resp, err := parent.RPC().HTTPDNSClusterRPC().FindHTTPDNSCluster(parent.AdminContext(), &pb.FindHTTPDNSClusterRequest{
|
||||||
|
ClusterId: clusterID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.GetCluster() == nil {
|
||||||
|
return maps.Map{
|
||||||
|
"id": clusterID,
|
||||||
|
"name": "",
|
||||||
|
"installDir": "/opt/edge-httpdns",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster := resp.GetCluster()
|
||||||
|
installDir := strings.TrimSpace(cluster.GetInstallDir())
|
||||||
|
if len(installDir) == 0 {
|
||||||
|
installDir = "/opt/edge-httpdns"
|
||||||
|
}
|
||||||
|
return maps.Map{
|
||||||
|
"id": cluster.GetId(),
|
||||||
|
"name": cluster.GetName(),
|
||||||
|
"installDir": installDir,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findHTTPDNSNodeMap(parent *actionutils.ParentAction, nodeID int64) (maps.Map, error) {
|
||||||
|
resp, err := parent.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(parent.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||||
|
NodeId: nodeID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.GetNode() == nil {
|
||||||
|
return maps.Map{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
node := resp.GetNode()
|
||||||
|
statusMap := decodeNodeStatus(node.GetStatusJSON())
|
||||||
|
installStatusMap := decodeInstallStatus(node.GetInstallStatusJSON())
|
||||||
|
|
||||||
|
var ipAddresses = []maps.Map{}
|
||||||
|
if installStatusMap.Has("ipAddresses") {
|
||||||
|
for _, addr := range installStatusMap.GetSlice("ipAddresses") {
|
||||||
|
if addrMap, ok := addr.(map[string]interface{}); ok {
|
||||||
|
ipAddresses = append(ipAddresses, maps.Map(addrMap))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ip := node.GetName()
|
||||||
|
if savedIP := strings.TrimSpace(installStatusMap.GetString("ipAddr")); len(savedIP) > 0 {
|
||||||
|
ip = savedIP
|
||||||
|
} else if hostIP := strings.TrimSpace(statusMap.GetString("hostIP")); len(hostIP) > 0 {
|
||||||
|
ip = hostIP
|
||||||
|
}
|
||||||
|
ipAddresses = append(ipAddresses, maps.Map{
|
||||||
|
"id": node.GetId(),
|
||||||
|
"name": "Public IP",
|
||||||
|
"ip": ip,
|
||||||
|
"canAccess": true,
|
||||||
|
"isOn": node.GetIsOn(),
|
||||||
|
"isUp": node.GetIsUp(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
installDir := strings.TrimSpace(node.GetInstallDir())
|
||||||
|
if len(installDir) == 0 {
|
||||||
|
installDir = "/opt/edge-httpdns"
|
||||||
|
}
|
||||||
|
|
||||||
|
clusterMap, err := findHTTPDNSClusterMap(parent, node.GetClusterId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构造 node.login 用于 index.html 展示 SSH 信息
|
||||||
|
var loginMap maps.Map = nil
|
||||||
|
sshInfo := installStatusMap.GetMap("ssh")
|
||||||
|
if sshInfo != nil {
|
||||||
|
grantId := sshInfo.GetInt64("grantId")
|
||||||
|
var grantMap maps.Map = nil
|
||||||
|
if grantId > 0 {
|
||||||
|
grantResp, grantErr := parent.RPC().NodeGrantRPC().FindEnabledNodeGrant(parent.AdminContext(), &pb.FindEnabledNodeGrantRequest{
|
||||||
|
NodeGrantId: grantId,
|
||||||
|
})
|
||||||
|
if grantErr == nil && grantResp.GetNodeGrant() != nil {
|
||||||
|
g := grantResp.GetNodeGrant()
|
||||||
|
grantMap = maps.Map{
|
||||||
|
"id": g.Id,
|
||||||
|
"name": g.Name,
|
||||||
|
"methodName": g.Method,
|
||||||
|
"username": g.Username,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loginMap = maps.Map{
|
||||||
|
"params": maps.Map{
|
||||||
|
"host": sshInfo.GetString("host"),
|
||||||
|
"port": sshInfo.GetInt("port"),
|
||||||
|
},
|
||||||
|
"grant": grantMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maps.Map{
|
||||||
|
"id": node.GetId(),
|
||||||
|
"clusterId": node.GetClusterId(),
|
||||||
|
"name": node.GetName(),
|
||||||
|
"isOn": node.GetIsOn(),
|
||||||
|
"isUp": node.GetIsUp(),
|
||||||
|
"isInstalled": node.GetIsInstalled(),
|
||||||
|
"isActive": node.GetIsActive(),
|
||||||
|
"uniqueId": node.GetUniqueId(),
|
||||||
|
"secret": node.GetSecret(),
|
||||||
|
"installDir": installDir,
|
||||||
|
"status": statusMap,
|
||||||
|
"installStatus": installStatusMap,
|
||||||
|
"cluster": clusterMap,
|
||||||
|
"login": loginMap,
|
||||||
|
"apiNodeAddrs": []string{},
|
||||||
|
"ipAddresses": ipAddresses,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeNodeStatus(raw []byte) maps.Map {
|
||||||
|
status := &nodeconfigs.NodeStatus{}
|
||||||
|
if len(raw) > 0 {
|
||||||
|
_ = json.Unmarshal(raw, status)
|
||||||
|
}
|
||||||
|
cpuText := fmt.Sprintf("%.2f%%", status.CPUUsage*100)
|
||||||
|
memText := fmt.Sprintf("%.2f%%", status.MemoryUsage*100)
|
||||||
|
|
||||||
|
return maps.Map{
|
||||||
|
"isActive": status.IsActive,
|
||||||
|
"updatedAt": status.UpdatedAt,
|
||||||
|
"hostname": status.Hostname,
|
||||||
|
"hostIP": status.HostIP,
|
||||||
|
"cpuUsage": status.CPUUsage,
|
||||||
|
"cpuUsageText": cpuText,
|
||||||
|
"memUsage": status.MemoryUsage,
|
||||||
|
"memUsageText": memText,
|
||||||
|
"load1m": status.Load1m,
|
||||||
|
"load5m": status.Load5m,
|
||||||
|
"load15m": status.Load15m,
|
||||||
|
"buildVersion": status.BuildVersion,
|
||||||
|
"cpuPhysicalCount": status.CPUPhysicalCount,
|
||||||
|
"cpuLogicalCount": status.CPULogicalCount,
|
||||||
|
"exePath": status.ExePath,
|
||||||
|
"apiSuccessPercent": status.APISuccessPercent,
|
||||||
|
"apiAvgCostSeconds": status.APIAvgCostSeconds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeInstallStatus(raw []byte) maps.Map {
|
||||||
|
result := maps.Map{
|
||||||
|
"isRunning": false,
|
||||||
|
"isFinished": true,
|
||||||
|
"isOk": true,
|
||||||
|
"error": "",
|
||||||
|
"errorCode": "",
|
||||||
|
}
|
||||||
|
if len(raw) == 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = json.Unmarshal(raw, &result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -1,13 +1,41 @@
|
|||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StartAction struct {
|
type StartAction struct {
|
||||||
actionutils.ParentAction
|
actionutils.ParentAction
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *StartAction) RunPost(params struct{ NodeId int64 }) {
|
func (this *StartAction) RunPost(params struct {
|
||||||
|
NodeId int64
|
||||||
|
}) {
|
||||||
|
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||||
|
NodeId: params.NodeId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node := resp.GetNode()
|
||||||
|
if node == nil {
|
||||||
|
this.Fail("节点不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
|
||||||
|
NodeId: params.NodeId,
|
||||||
|
IsUp: true,
|
||||||
|
IsInstalled: node.GetIsInstalled(),
|
||||||
|
IsActive: true,
|
||||||
|
StatusJSON: node.GetStatusJSON(),
|
||||||
|
InstallStatusJSON: node.GetInstallStatusJSON(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package node
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StatusAction struct {
|
type StatusAction struct {
|
||||||
@@ -14,14 +14,28 @@ func (this *StatusAction) Init() {
|
|||||||
this.SecondMenu("nodes")
|
this.SecondMenu("nodes")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *StatusAction) RunPost(params struct{ NodeId int64 }) {
|
func (this *StatusAction) RunPost(params struct {
|
||||||
this.Data["installStatus"] = maps.Map{
|
NodeId int64
|
||||||
"isRunning": false,
|
}) {
|
||||||
"isFinished": true,
|
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||||
"isOk": true,
|
NodeId: params.NodeId,
|
||||||
"error": "",
|
})
|
||||||
"errorCode": "",
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
this.Data["isInstalled"] = true
|
if resp.GetNode() == nil {
|
||||||
|
this.Fail("节点不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeMap, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Data["installStatus"] = nodeMap.GetMap("installStatus")
|
||||||
|
this.Data["isInstalled"] = nodeMap.GetBool("isInstalled")
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,41 @@
|
|||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StopAction struct {
|
type StopAction struct {
|
||||||
actionutils.ParentAction
|
actionutils.ParentAction
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *StopAction) RunPost(params struct{ NodeId int64 }) {
|
func (this *StopAction) RunPost(params struct {
|
||||||
|
NodeId int64
|
||||||
|
}) {
|
||||||
|
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||||
|
NodeId: params.NodeId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node := resp.GetNode()
|
||||||
|
if node == nil {
|
||||||
|
this.Fail("节点不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
|
||||||
|
NodeId: params.NodeId,
|
||||||
|
IsUp: node.GetIsUp(),
|
||||||
|
IsInstalled: node.GetIsInstalled(),
|
||||||
|
IsActive: false,
|
||||||
|
StatusJSON: node.GetStatusJSON(),
|
||||||
|
InstallStatusJSON: node.GetInstallStatusJSON(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/actions"
|
||||||
"github.com/iwind/TeaGo/maps"
|
"github.com/iwind/TeaGo/maps"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,28 +21,210 @@ func (this *UpdateAction) Init() {
|
|||||||
this.SecondMenu("nodes")
|
this.SecondMenu("nodes")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *UpdateAction) RunGet(params struct{ ClusterId int64; NodeId int64 }) {
|
func (this *UpdateAction) RunGet(params struct {
|
||||||
|
ClusterId int64
|
||||||
|
NodeId int64
|
||||||
|
}) {
|
||||||
|
node, err := findHTTPDNSNodeMap(this.Parent(), params.NodeId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cluster, err := findHTTPDNSClusterMap(this.Parent(), params.ClusterId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.Data["clusterId"] = params.ClusterId
|
this.Data["clusterId"] = params.ClusterId
|
||||||
this.Data["nodeId"] = params.NodeId
|
this.Data["nodeId"] = params.NodeId
|
||||||
this.Data["currentCluster"] = maps.Map{"id": params.ClusterId, "name": "Mock Cluster"}
|
this.Data["currentCluster"] = cluster
|
||||||
|
this.Data["clusters"] = []maps.Map{cluster}
|
||||||
this.Data["clusters"] = []maps.Map{{"id": params.ClusterId, "name": "Mock Cluster"}}
|
|
||||||
this.Data["loginId"] = 0
|
|
||||||
this.Data["sshHost"] = "192.168.1.100"
|
|
||||||
this.Data["sshPort"] = 22
|
|
||||||
this.Data["grant"] = nil
|
|
||||||
this.Data["apiNodeAddrs"] = []string{}
|
this.Data["apiNodeAddrs"] = []string{}
|
||||||
|
this.Data["node"] = node
|
||||||
|
|
||||||
this.Data["node"] = maps.Map{
|
sshHost := ""
|
||||||
"id": params.NodeId,
|
sshPort := 22
|
||||||
"name": "Mock Node",
|
ipAddresses := []maps.Map{}
|
||||||
|
var grantId int64
|
||||||
|
this.Data["grant"] = nil
|
||||||
|
|
||||||
|
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||||
|
NodeId: params.NodeId,
|
||||||
|
})
|
||||||
|
if err == nil && resp.GetNode() != nil {
|
||||||
|
if len(resp.GetNode().GetInstallStatusJSON()) > 0 {
|
||||||
|
installStatus := maps.Map{}
|
||||||
|
_ = json.Unmarshal(resp.GetNode().GetInstallStatusJSON(), &installStatus)
|
||||||
|
sshInfo := installStatus.GetMap("ssh")
|
||||||
|
if sshInfo != nil {
|
||||||
|
if h := strings.TrimSpace(sshInfo.GetString("host")); len(h) > 0 {
|
||||||
|
sshHost = h
|
||||||
|
}
|
||||||
|
if p := sshInfo.GetInt("port"); p > 0 {
|
||||||
|
sshPort = p
|
||||||
|
}
|
||||||
|
grantId = sshInfo.GetInt64("grantId")
|
||||||
|
}
|
||||||
|
|
||||||
|
if installStatus.Has("ipAddresses") {
|
||||||
|
for _, addr := range installStatus.GetSlice("ipAddresses") {
|
||||||
|
if addrMap, ok := addr.(map[string]interface{}); ok {
|
||||||
|
ipAddresses = append(ipAddresses, maps.Map(addrMap))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ip := strings.TrimSpace(installStatus.GetString("ipAddr")); len(ip) > 0 {
|
||||||
|
ipAddresses = append(ipAddresses, maps.Map{
|
||||||
|
"ip": ip,
|
||||||
|
"name": "",
|
||||||
|
"canAccess": true,
|
||||||
"isOn": true,
|
"isOn": true,
|
||||||
"ipAddresses": []maps.Map{},
|
"isUp": true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Data["sshHost"] = sshHost
|
||||||
|
this.Data["sshPort"] = sshPort
|
||||||
|
this.Data["ipAddresses"] = ipAddresses
|
||||||
|
|
||||||
|
if grantId > 0 {
|
||||||
|
grantResp, grantErr := this.RPC().NodeGrantRPC().FindEnabledNodeGrant(this.AdminContext(), &pb.FindEnabledNodeGrantRequest{
|
||||||
|
NodeGrantId: grantId,
|
||||||
|
})
|
||||||
|
if grantErr == nil && grantResp.GetNodeGrant() != nil {
|
||||||
|
g := grantResp.GetNodeGrant()
|
||||||
|
this.Data["grant"] = maps.Map{
|
||||||
|
"id": g.Id,
|
||||||
|
"name": g.Name,
|
||||||
|
"methodName": g.Method,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *UpdateAction) RunPost(params struct{ NodeId int64 }) {
|
func (this *UpdateAction) RunPost(params struct {
|
||||||
|
NodeId int64
|
||||||
|
Name string
|
||||||
|
ClusterId int64
|
||||||
|
IsOn bool
|
||||||
|
SshHost string
|
||||||
|
SshPort int
|
||||||
|
GrantId int64
|
||||||
|
IpAddressesJSON []byte
|
||||||
|
|
||||||
|
Must *actions.Must
|
||||||
|
}) {
|
||||||
|
params.Name = strings.TrimSpace(params.Name)
|
||||||
|
params.Must.Field("name", params.Name).Require("请输入节点名称")
|
||||||
|
|
||||||
|
params.SshHost = strings.TrimSpace(params.SshHost)
|
||||||
|
hasSSHUpdate := len(params.SshHost) > 0 || params.SshPort > 0 || params.GrantId > 0
|
||||||
|
if hasSSHUpdate {
|
||||||
|
if len(params.SshHost) == 0 {
|
||||||
|
this.Fail("请输入 SSH 主机地址")
|
||||||
|
}
|
||||||
|
if params.SshPort <= 0 || params.SshPort > 65535 {
|
||||||
|
this.Fail("SSH 端口范围必须在 1-65535 之间")
|
||||||
|
}
|
||||||
|
if params.GrantId <= 0 {
|
||||||
|
this.Fail("请选择 SSH 登录认证")
|
||||||
|
}
|
||||||
|
|
||||||
|
if regexp.MustCompile(`^\d+\.\d+\.\d+\.\d+$`).MatchString(params.SshHost) && net.ParseIP(params.SshHost) == nil {
|
||||||
|
this.Fail("SSH 主机地址格式错误")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ipAddresses := []maps.Map{}
|
||||||
|
if len(params.IpAddressesJSON) > 0 {
|
||||||
|
err := json.Unmarshal(params.IpAddressesJSON, &ipAddresses)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range ipAddresses {
|
||||||
|
ip := addr.GetString("ip")
|
||||||
|
if net.ParseIP(ip) == nil {
|
||||||
|
this.Fail("IP地址格式错误: " + ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
needUpdateInstallStatus := hasSSHUpdate || len(ipAddresses) > 0
|
||||||
|
|
||||||
|
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||||
|
NodeId: params.NodeId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if resp.GetNode() == nil {
|
||||||
|
this.Fail("节点不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node := resp.GetNode()
|
||||||
|
|
||||||
|
installDir := strings.TrimSpace(node.GetInstallDir())
|
||||||
|
if len(installDir) == 0 {
|
||||||
|
installDir = "/opt/edge-httpdns"
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNode(this.AdminContext(), &pb.UpdateHTTPDNSNodeRequest{
|
||||||
|
NodeId: params.NodeId,
|
||||||
|
Name: params.Name,
|
||||||
|
InstallDir: installDir,
|
||||||
|
IsOn: params.IsOn,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if needUpdateInstallStatus {
|
||||||
|
installStatus := maps.Map{
|
||||||
|
"isRunning": false,
|
||||||
|
"isFinished": true,
|
||||||
|
"isOk": node.GetIsInstalled(),
|
||||||
|
"error": "",
|
||||||
|
"errorCode": "",
|
||||||
|
}
|
||||||
|
if len(node.GetInstallStatusJSON()) > 0 {
|
||||||
|
_ = json.Unmarshal(node.GetInstallStatusJSON(), &installStatus)
|
||||||
|
}
|
||||||
|
if hasSSHUpdate {
|
||||||
|
installStatus["ssh"] = maps.Map{
|
||||||
|
"host": params.SshHost,
|
||||||
|
"port": params.SshPort,
|
||||||
|
"grantId": params.GrantId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ipAddresses) > 0 {
|
||||||
|
installStatus["ipAddresses"] = ipAddresses
|
||||||
|
} else {
|
||||||
|
delete(installStatus, "ipAddresses")
|
||||||
|
delete(installStatus, "ipAddr") // Cleanup legacy
|
||||||
|
}
|
||||||
|
|
||||||
|
installStatusJSON, _ := json.Marshal(installStatus)
|
||||||
|
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
|
||||||
|
NodeId: params.NodeId,
|
||||||
|
IsUp: node.GetIsUp(),
|
||||||
|
IsInstalled: node.GetIsInstalled(),
|
||||||
|
IsActive: node.GetIsActive(),
|
||||||
|
StatusJSON: node.GetStatusJSON(),
|
||||||
|
InstallStatusJSON: installStatusJSON,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,58 @@
|
|||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UpdateInstallStatusAction struct {
|
type UpdateInstallStatusAction struct {
|
||||||
actionutils.ParentAction
|
actionutils.ParentAction
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *UpdateInstallStatusAction) RunPost(params struct{ NodeId int64 }) {
|
func (this *UpdateInstallStatusAction) RunPost(params struct {
|
||||||
|
NodeId int64
|
||||||
|
IsInstalled bool
|
||||||
|
}) {
|
||||||
|
resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{
|
||||||
|
NodeId: params.NodeId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node := resp.GetNode()
|
||||||
|
if node == nil {
|
||||||
|
this.Fail("节点不存在")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
installStatus := map[string]interface{}{}
|
||||||
|
if len(node.GetInstallStatusJSON()) > 0 {
|
||||||
|
_ = json.Unmarshal(node.GetInstallStatusJSON(), &installStatus)
|
||||||
|
}
|
||||||
|
installStatus["isRunning"] = false
|
||||||
|
installStatus["isFinished"] = true
|
||||||
|
installStatus["isOk"] = params.IsInstalled
|
||||||
|
installStatus["error"] = ""
|
||||||
|
installStatus["errorCode"] = ""
|
||||||
|
installStatus["updatedAt"] = time.Now().Unix()
|
||||||
|
|
||||||
|
installStatusJSON, _ := json.Marshal(installStatus)
|
||||||
|
|
||||||
|
_, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNodeStatus(this.AdminContext(), &pb.UpdateHTTPDNSNodeStatusRequest{
|
||||||
|
NodeId: params.NodeId,
|
||||||
|
IsUp: node.GetIsUp(),
|
||||||
|
IsInstalled: params.IsInstalled,
|
||||||
|
IsActive: node.GetIsActive(),
|
||||||
|
StatusJSON: node.GetStatusJSON(),
|
||||||
|
InstallStatusJSON: installStatusJSON,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/policies"
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||||
"github.com/iwind/TeaGo/actions"
|
"github.com/iwind/TeaGo/actions"
|
||||||
@@ -27,80 +27,79 @@ func (this *ClusterSettingsAction) RunGet(params struct {
|
|||||||
Section string
|
Section string
|
||||||
}) {
|
}) {
|
||||||
httpdnsutils.AddLeftMenu(this.Parent())
|
httpdnsutils.AddLeftMenu(this.Parent())
|
||||||
cluster := pickCluster(params.ClusterId)
|
|
||||||
settings := loadClusterSettings(cluster)
|
|
||||||
cluster["name"] = settings.GetString("name")
|
|
||||||
|
|
||||||
// 构建顶部 tabbar
|
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "setting")
|
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "setting")
|
||||||
|
|
||||||
// 当前选中的 section
|
section := strings.TrimSpace(params.Section)
|
||||||
section := params.Section
|
|
||||||
if len(section) == 0 {
|
if len(section) == 0 {
|
||||||
section = "basic"
|
section = "basic"
|
||||||
}
|
}
|
||||||
this.Data["activeSection"] = section
|
|
||||||
|
|
||||||
// 左侧菜单
|
settings := maps.Map{
|
||||||
cid := strconv.FormatInt(params.ClusterId, 10)
|
"name": cluster.GetString("name"),
|
||||||
this.Data["leftMenuItems"] = []map[string]interface{}{
|
"gatewayDomain": cluster.GetString("gatewayDomain"),
|
||||||
{"name": "基础设置", "url": "/httpdns/clusters/cluster/settings?clusterId=" + cid + "§ion=basic", "isActive": section == "basic"},
|
"cacheTtl": cluster.GetInt("defaultTTL"),
|
||||||
{"name": "端口设置", "url": "/httpdns/clusters/cluster/settings?clusterId=" + cid + "§ion=tls", "isActive": section == "tls"},
|
"fallbackTimeout": cluster.GetInt("fallbackTimeout"),
|
||||||
|
"installDir": cluster.GetString("installDir"),
|
||||||
|
"isOn": cluster.GetBool("isOn"),
|
||||||
|
"isDefaultCluster": cluster.GetBool("isDefault"),
|
||||||
|
}
|
||||||
|
if settings.GetInt("cacheTtl") <= 0 {
|
||||||
|
settings["cacheTtl"] = 30
|
||||||
|
}
|
||||||
|
if settings.GetInt("fallbackTimeout") <= 0 {
|
||||||
|
settings["fallbackTimeout"] = 300
|
||||||
|
}
|
||||||
|
if len(settings.GetString("installDir")) == 0 {
|
||||||
|
settings["installDir"] = "/opt/edge-httpdns"
|
||||||
}
|
}
|
||||||
|
|
||||||
settings["isDefaultCluster"] = (policies.LoadDefaultClusterID() == cluster.GetInt64("id"))
|
listenAddresses := []*serverconfigs.NetworkAddressConfig{
|
||||||
|
|
||||||
this.Data["cluster"] = cluster
|
|
||||||
// 构造前端需要的 tlsConfig 格式
|
|
||||||
var listenAddresses []*serverconfigs.NetworkAddressConfig
|
|
||||||
listenAddrsRaw := settings.GetString("listenAddrsJSON")
|
|
||||||
if len(listenAddrsRaw) > 0 {
|
|
||||||
_ = json.Unmarshal([]byte(listenAddrsRaw), &listenAddresses)
|
|
||||||
} else {
|
|
||||||
// 默认 443 端口
|
|
||||||
listenAddresses = []*serverconfigs.NetworkAddressConfig{
|
|
||||||
{
|
{
|
||||||
Protocol: serverconfigs.ProtocolHTTPS,
|
Protocol: serverconfigs.ProtocolHTTPS,
|
||||||
Host: "",
|
Host: "",
|
||||||
PortRange: "443",
|
PortRange: "443",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
sslPolicy := &sslconfigs.SSLPolicy{
|
||||||
|
|
||||||
// 构造前端需要的 SSLPolicy
|
|
||||||
var sslPolicy *sslconfigs.SSLPolicy
|
|
||||||
originCertPem := settings.GetString("originCertPem")
|
|
||||||
originKeyPem := settings.GetString("originKeyPem")
|
|
||||||
if len(originCertPem) > 0 && len(originKeyPem) > 0 {
|
|
||||||
sslPolicy = &sslconfigs.SSLPolicy{
|
|
||||||
IsOn: true,
|
|
||||||
MinVersion: settings.GetString("tlsMinVersion"),
|
|
||||||
CipherSuitesIsOn: settings.GetBool("tlsCipherSuitesOn"),
|
|
||||||
OCSPIsOn: settings.GetBool("tlsOcspOn"),
|
|
||||||
ClientAuthType: int(settings.GetInt32("tlsClientAuthType")),
|
|
||||||
Certs: []*sslconfigs.SSLCertConfig{
|
|
||||||
{
|
|
||||||
IsOn: true,
|
|
||||||
CertData: []byte(originCertPem),
|
|
||||||
KeyData: []byte(originKeyPem),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sslPolicy = &sslconfigs.SSLPolicy{
|
|
||||||
IsOn: true,
|
IsOn: true,
|
||||||
MinVersion: "TLS 1.1",
|
MinVersion: "TLS 1.1",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rawTLS := strings.TrimSpace(cluster.GetString("tlsPolicyJSON")); len(rawTLS) > 0 {
|
||||||
|
tlsConfig := maps.Map{}
|
||||||
|
if err := json.Unmarshal([]byte(rawTLS), &tlsConfig); err == nil {
|
||||||
|
if listenRaw := tlsConfig.Get("listen"); listenRaw != nil {
|
||||||
|
if data, err := json.Marshal(listenRaw); err == nil {
|
||||||
|
_ = json.Unmarshal(data, &listenAddresses)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sslRaw := tlsConfig.Get("sslPolicy"); sslRaw != nil {
|
||||||
|
if data, err := json.Marshal(sslRaw); err == nil {
|
||||||
|
_ = json.Unmarshal(data, sslPolicy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.Data["activeSection"] = section
|
||||||
|
cid := strconv.FormatInt(params.ClusterId, 10)
|
||||||
|
this.Data["leftMenuItems"] = []map[string]interface{}{
|
||||||
|
{"name": "基础设置", "url": "/httpdns/clusters/cluster/settings?clusterId=" + cid + "§ion=basic", "isActive": section == "basic"},
|
||||||
|
{"name": "TLS", "url": "/httpdns/clusters/cluster/settings?clusterId=" + cid + "§ion=tls", "isActive": section == "tls"},
|
||||||
|
}
|
||||||
|
this.Data["cluster"] = cluster
|
||||||
|
this.Data["settings"] = settings
|
||||||
this.Data["tlsConfig"] = maps.Map{
|
this.Data["tlsConfig"] = maps.Map{
|
||||||
"isOn": true,
|
"isOn": true,
|
||||||
"listen": listenAddresses,
|
"listen": listenAddresses,
|
||||||
"sslPolicy": sslPolicy,
|
"sslPolicy": sslPolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Data["cluster"] = cluster
|
|
||||||
this.Data["settings"] = settings
|
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,88 +119,80 @@ func (this *ClusterSettingsAction) RunPost(params struct {
|
|||||||
Must *actions.Must
|
Must *actions.Must
|
||||||
CSRF *actionutils.CSRF
|
CSRF *actionutils.CSRF
|
||||||
}) {
|
}) {
|
||||||
params.Must.Field("clusterId", params.ClusterId).Gt(0, "please select cluster")
|
params.Name = strings.TrimSpace(params.Name)
|
||||||
params.Must.Field("name", params.Name).Require("please input cluster name")
|
params.GatewayDomain = strings.TrimSpace(params.GatewayDomain)
|
||||||
params.Must.Field("gatewayDomain", params.GatewayDomain).Require("please input service domain")
|
params.InstallDir = strings.TrimSpace(params.InstallDir)
|
||||||
params.Must.Field("cacheTtl", params.CacheTtl).Gt(0, "cache ttl should be greater than 0")
|
|
||||||
params.Must.Field("fallbackTimeout", params.FallbackTimeout).Gt(0, "fallback timeout should be greater than 0")
|
|
||||||
params.Must.Field("installDir", params.InstallDir).Require("please input install dir")
|
|
||||||
|
|
||||||
|
params.Must.Field("clusterId", params.ClusterId).Gt(0, "请选择集群")
|
||||||
|
params.Must.Field("name", params.Name).Require("请输入集群名称")
|
||||||
|
params.Must.Field("gatewayDomain", params.GatewayDomain).Require("请输入服务域名")
|
||||||
|
|
||||||
|
if params.CacheTtl <= 0 {
|
||||||
|
params.CacheTtl = 30
|
||||||
|
}
|
||||||
|
if params.FallbackTimeout <= 0 {
|
||||||
|
params.FallbackTimeout = 300
|
||||||
|
}
|
||||||
|
if len(params.InstallDir) == 0 {
|
||||||
|
params.InstallDir = "/opt/edge-httpdns"
|
||||||
|
}
|
||||||
if params.IsDefaultCluster && !params.IsOn {
|
if params.IsDefaultCluster && !params.IsOn {
|
||||||
this.Fail("默认集群必须保持启用状态")
|
this.Fail("默认集群必须保持启用状态")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cluster := pickCluster(params.ClusterId)
|
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
|
||||||
settings := loadClusterSettings(cluster)
|
|
||||||
settings["name"] = strings.TrimSpace(params.Name)
|
|
||||||
settings["gatewayDomain"] = strings.TrimSpace(params.GatewayDomain)
|
|
||||||
settings["cacheTtl"] = int(params.CacheTtl)
|
|
||||||
settings["fallbackTimeout"] = int(params.FallbackTimeout)
|
|
||||||
settings["installDir"] = strings.TrimSpace(params.InstallDir)
|
|
||||||
settings["isOn"] = params.IsOn
|
|
||||||
// 处理地址
|
|
||||||
var addresses = []*serverconfigs.NetworkAddressConfig{}
|
|
||||||
if len(params.Addresses) > 0 {
|
|
||||||
err := json.Unmarshal(params.Addresses, &addresses)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
this.Fail("端口地址解析失败:" + err.Error())
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
addressesJSON, _ := json.Marshal(addresses)
|
tlsConfig := maps.Map{}
|
||||||
settings["listenAddrsJSON"] = string(addressesJSON)
|
if rawTLS := strings.TrimSpace(cluster.GetString("tlsPolicyJSON")); len(rawTLS) > 0 {
|
||||||
|
_ = json.Unmarshal([]byte(rawTLS), &tlsConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理 SSL 配置
|
if len(params.Addresses) > 0 {
|
||||||
var originCertPem = ""
|
var addresses []*serverconfigs.NetworkAddressConfig
|
||||||
var originKeyPem = ""
|
if err := json.Unmarshal(params.Addresses, &addresses); err != nil {
|
||||||
var tlsMinVersion = "TLS 1.1"
|
this.Fail("监听端口配置格式不正确")
|
||||||
var tlsCipherSuitesOn = false
|
return
|
||||||
var tlsOcspOn = false
|
}
|
||||||
var tlsClientAuthType = sslconfigs.SSLClientAuthType(0)
|
tlsConfig["listen"] = addresses
|
||||||
|
}
|
||||||
|
|
||||||
if len(params.SslPolicyJSON) > 0 {
|
if len(params.SslPolicyJSON) > 0 {
|
||||||
sslPolicy := &sslconfigs.SSLPolicy{}
|
sslPolicy := &sslconfigs.SSLPolicy{}
|
||||||
err := json.Unmarshal(params.SslPolicyJSON, sslPolicy)
|
if err := json.Unmarshal(params.SslPolicyJSON, sslPolicy); err != nil {
|
||||||
if err == nil {
|
this.Fail("TLS 配置格式不正确")
|
||||||
tlsMinVersion = sslPolicy.MinVersion
|
return
|
||||||
tlsCipherSuitesOn = sslPolicy.CipherSuitesIsOn
|
|
||||||
tlsOcspOn = sslPolicy.OCSPIsOn
|
|
||||||
tlsClientAuthType = sslconfigs.SSLClientAuthType(sslPolicy.ClientAuthType)
|
|
||||||
|
|
||||||
if len(sslPolicy.Certs) > 0 {
|
|
||||||
cert := sslPolicy.Certs[0]
|
|
||||||
originCertPem = string(cert.CertData)
|
|
||||||
originKeyPem = string(cert.KeyData)
|
|
||||||
}
|
}
|
||||||
|
tlsConfig["sslPolicy"] = sslPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
var tlsPolicyJSON []byte
|
||||||
|
if len(tlsConfig) > 0 {
|
||||||
|
tlsPolicyJSON, err = json.Marshal(tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(originCertPem) == 0 || len(originKeyPem) == 0 {
|
_, err = this.RPC().HTTPDNSClusterRPC().UpdateHTTPDNSCluster(this.AdminContext(), &pb.UpdateHTTPDNSClusterRequest{
|
||||||
this.Fail("请上传或选择证书")
|
ClusterId: params.ClusterId,
|
||||||
}
|
Name: params.Name,
|
||||||
|
ServiceDomain: params.GatewayDomain,
|
||||||
settings["originHttps"] = true
|
DefaultTTL: params.CacheTtl,
|
||||||
settings["originCertPem"] = originCertPem
|
FallbackTimeoutMs: params.FallbackTimeout,
|
||||||
settings["originKeyPem"] = originKeyPem
|
InstallDir: params.InstallDir,
|
||||||
if len(tlsMinVersion) == 0 {
|
TlsPolicyJSON: tlsPolicyJSON,
|
||||||
tlsMinVersion = "TLS 1.1"
|
IsOn: params.IsOn,
|
||||||
}
|
IsDefault: params.IsDefaultCluster,
|
||||||
settings["tlsMinVersion"] = tlsMinVersion
|
})
|
||||||
settings["tlsCipherSuitesOn"] = tlsCipherSuitesOn
|
if err != nil {
|
||||||
settings["tlsOcspOn"] = tlsOcspOn
|
this.ErrorPage(err)
|
||||||
settings["tlsClientAuthType"] = int(tlsClientAuthType)
|
return
|
||||||
settings["lastModifiedAt"] = nowDateTime()
|
|
||||||
settings["certUpdatedAt"] = nowDateTime()
|
|
||||||
|
|
||||||
saveClusterSettings(params.ClusterId, settings)
|
|
||||||
|
|
||||||
currentDefaultClusterId := policies.LoadDefaultClusterID()
|
|
||||||
if params.IsDefaultCluster {
|
|
||||||
policies.SaveDefaultClusterID(params.ClusterId)
|
|
||||||
} else if currentDefaultClusterId == params.ClusterId {
|
|
||||||
policies.SaveDefaultClusterID(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Success()
|
this.Success()
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
package clusters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/iwind/TeaGo/maps"
|
|
||||||
)
|
|
||||||
|
|
||||||
var clusterSettingsStore = struct {
|
|
||||||
sync.RWMutex
|
|
||||||
data map[int64]maps.Map
|
|
||||||
}{
|
|
||||||
data: map[int64]maps.Map{},
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultClusterSettings(cluster maps.Map) maps.Map {
|
|
||||||
installDir := strings.TrimSpace(cluster.GetString("installDir"))
|
|
||||||
if len(installDir) == 0 {
|
|
||||||
installDir = "/opt/edge-httpdns"
|
|
||||||
}
|
|
||||||
|
|
||||||
return maps.Map{
|
|
||||||
"name": cluster.GetString("name"),
|
|
||||||
"gatewayDomain": strings.TrimSpace(cluster.GetString("gatewayDomain")),
|
|
||||||
"cacheTtl": cluster.GetInt("cacheTtl"),
|
|
||||||
"fallbackTimeout": cluster.GetInt("fallbackTimeout"),
|
|
||||||
"installDir": installDir,
|
|
||||||
"isOn": cluster.GetBool("isOn"),
|
|
||||||
"originHttps": true,
|
|
||||||
"originCertPem": "",
|
|
||||||
"originKeyPem": "",
|
|
||||||
"tlsMinVersion": "TLS 1.1",
|
|
||||||
"tlsCipherSuitesOn": false,
|
|
||||||
"tlsOcspOn": false,
|
|
||||||
"tlsClientAuthType": 0,
|
|
||||||
"certUpdatedAt": "",
|
|
||||||
"lastModifiedAt": "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloneClusterSettings(settings maps.Map) maps.Map {
|
|
||||||
return maps.Map{
|
|
||||||
"name": settings.GetString("name"),
|
|
||||||
"gatewayDomain": settings.GetString("gatewayDomain"),
|
|
||||||
"cacheTtl": settings.GetInt("cacheTtl"),
|
|
||||||
"fallbackTimeout": settings.GetInt("fallbackTimeout"),
|
|
||||||
"installDir": settings.GetString("installDir"),
|
|
||||||
"isOn": settings.GetBool("isOn"),
|
|
||||||
"originHttps": true,
|
|
||||||
"originCertPem": settings.GetString("originCertPem"),
|
|
||||||
"originKeyPem": settings.GetString("originKeyPem"),
|
|
||||||
"tlsMinVersion": settings.GetString("tlsMinVersion"),
|
|
||||||
"tlsCipherSuitesOn": settings.GetBool("tlsCipherSuitesOn"),
|
|
||||||
"tlsOcspOn": settings.GetBool("tlsOcspOn"),
|
|
||||||
"tlsClientAuthType": settings.GetInt("tlsClientAuthType"),
|
|
||||||
"certUpdatedAt": settings.GetString("certUpdatedAt"),
|
|
||||||
"lastModifiedAt": settings.GetString("lastModifiedAt"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadClusterSettings(cluster maps.Map) maps.Map {
|
|
||||||
clusterId := cluster.GetInt64("id")
|
|
||||||
|
|
||||||
clusterSettingsStore.RLock()
|
|
||||||
settings, ok := clusterSettingsStore.data[clusterId]
|
|
||||||
clusterSettingsStore.RUnlock()
|
|
||||||
if ok {
|
|
||||||
if len(settings.GetString("tlsMinVersion")) == 0 {
|
|
||||||
settings["tlsMinVersion"] = "TLS 1.1"
|
|
||||||
}
|
|
||||||
return cloneClusterSettings(settings)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings = defaultClusterSettings(cluster)
|
|
||||||
saveClusterSettings(clusterId, settings)
|
|
||||||
return cloneClusterSettings(settings)
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveClusterSettings(clusterId int64, settings maps.Map) {
|
|
||||||
clusterSettingsStore.Lock()
|
|
||||||
clusterSettingsStore.data[clusterId] = cloneClusterSettings(settings)
|
|
||||||
clusterSettingsStore.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyClusterSettingsOverrides(cluster maps.Map) {
|
|
||||||
clusterId := cluster.GetInt64("id")
|
|
||||||
if clusterId <= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clusterSettingsStore.RLock()
|
|
||||||
settings, ok := clusterSettingsStore.data[clusterId]
|
|
||||||
clusterSettingsStore.RUnlock()
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cluster["name"] = settings.GetString("name")
|
|
||||||
cluster["gatewayDomain"] = settings.GetString("gatewayDomain")
|
|
||||||
cluster["cacheTtl"] = settings.GetInt("cacheTtl")
|
|
||||||
cluster["fallbackTimeout"] = settings.GetInt("fallbackTimeout")
|
|
||||||
cluster["installDir"] = settings.GetString("installDir")
|
|
||||||
cluster["isOn"] = settings.GetBool("isOn")
|
|
||||||
}
|
|
||||||
|
|
||||||
func nowDateTime() string {
|
|
||||||
return time.Now().Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
package clusters
|
package clusters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
|
"github.com/iwind/TeaGo/actions"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateAction struct {
|
type CreateAction struct {
|
||||||
@@ -17,3 +21,48 @@ func (this *CreateAction) RunGet(params struct{}) {
|
|||||||
httpdnsutils.AddLeftMenu(this.Parent())
|
httpdnsutils.AddLeftMenu(this.Parent())
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (this *CreateAction) RunPost(params struct {
|
||||||
|
Name string
|
||||||
|
GatewayDomain string
|
||||||
|
CacheTtl int32
|
||||||
|
FallbackTimeout int32
|
||||||
|
InstallDir string
|
||||||
|
IsOn bool
|
||||||
|
IsDefault bool
|
||||||
|
|
||||||
|
Must *actions.Must
|
||||||
|
}) {
|
||||||
|
params.Name = strings.TrimSpace(params.Name)
|
||||||
|
params.GatewayDomain = strings.TrimSpace(params.GatewayDomain)
|
||||||
|
params.InstallDir = strings.TrimSpace(params.InstallDir)
|
||||||
|
if len(params.InstallDir) == 0 {
|
||||||
|
params.InstallDir = "/opt/edge-httpdns"
|
||||||
|
}
|
||||||
|
if params.CacheTtl <= 0 {
|
||||||
|
params.CacheTtl = 30
|
||||||
|
}
|
||||||
|
if params.FallbackTimeout <= 0 {
|
||||||
|
params.FallbackTimeout = 300
|
||||||
|
}
|
||||||
|
|
||||||
|
params.Must.Field("name", params.Name).Require("请输入集群名称")
|
||||||
|
params.Must.Field("gatewayDomain", params.GatewayDomain).Require("请输入服务域名")
|
||||||
|
|
||||||
|
resp, err := this.RPC().HTTPDNSClusterRPC().CreateHTTPDNSCluster(this.AdminContext(), &pb.CreateHTTPDNSClusterRequest{
|
||||||
|
Name: params.Name,
|
||||||
|
ServiceDomain: params.GatewayDomain,
|
||||||
|
DefaultTTL: params.CacheTtl,
|
||||||
|
FallbackTimeoutMs: params.FallbackTimeout,
|
||||||
|
InstallDir: params.InstallDir,
|
||||||
|
IsOn: params.IsOn,
|
||||||
|
IsDefault: params.IsDefault,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Data["clusterId"] = resp.GetClusterId()
|
||||||
|
this.Success()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package clusters
|
package clusters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||||
|
"github.com/iwind/TeaGo/actions"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateNodeAction struct {
|
type CreateNodeAction struct {
|
||||||
@@ -13,13 +16,18 @@ func (this *CreateNodeAction) Init() {
|
|||||||
this.Nav("httpdns", "cluster", "createNode")
|
this.Nav("httpdns", "cluster", "createNode")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *CreateNodeAction) RunGet(params struct{ ClusterId int64 }) {
|
func (this *CreateNodeAction) RunGet(params struct {
|
||||||
|
ClusterId int64
|
||||||
|
}) {
|
||||||
httpdnsutils.AddLeftMenu(this.Parent())
|
httpdnsutils.AddLeftMenu(this.Parent())
|
||||||
cluster := pickCluster(params.ClusterId)
|
|
||||||
|
|
||||||
// 构建顶部 tabbar
|
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "node")
|
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "node")
|
||||||
|
|
||||||
this.Data["clusterId"] = params.ClusterId
|
this.Data["clusterId"] = params.ClusterId
|
||||||
this.Data["cluster"] = cluster
|
this.Data["cluster"] = cluster
|
||||||
this.Show()
|
this.Show()
|
||||||
@@ -28,6 +36,28 @@ func (this *CreateNodeAction) RunGet(params struct{ ClusterId int64 }) {
|
|||||||
func (this *CreateNodeAction) RunPost(params struct {
|
func (this *CreateNodeAction) RunPost(params struct {
|
||||||
ClusterId int64
|
ClusterId int64
|
||||||
Name string
|
Name string
|
||||||
|
InstallDir string
|
||||||
|
|
||||||
|
Must *actions.Must
|
||||||
}) {
|
}) {
|
||||||
|
params.Name = strings.TrimSpace(params.Name)
|
||||||
|
params.InstallDir = strings.TrimSpace(params.InstallDir)
|
||||||
|
params.Must.Field("clusterId", params.ClusterId).Gt(0, "请选择集群")
|
||||||
|
params.Must.Field("name", params.Name).Require("请输入节点名称")
|
||||||
|
|
||||||
|
if len(params.InstallDir) == 0 {
|
||||||
|
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
|
||||||
|
if err == nil {
|
||||||
|
params.InstallDir = strings.TrimSpace(cluster.GetString("installDir"))
|
||||||
|
}
|
||||||
|
if len(params.InstallDir) == 0 {
|
||||||
|
params.InstallDir = "/opt/edge-httpdns"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := createNode(this.Parent(), params.ClusterId, params.Name, params.InstallDir); err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package clusters
|
|||||||
import (
|
import (
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils"
|
||||||
|
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeleteAction struct {
|
type DeleteAction struct {
|
||||||
@@ -17,11 +18,14 @@ func (this *DeleteAction) RunGet(params struct {
|
|||||||
ClusterId int64
|
ClusterId int64
|
||||||
}) {
|
}) {
|
||||||
httpdnsutils.AddLeftMenu(this.Parent())
|
httpdnsutils.AddLeftMenu(this.Parent())
|
||||||
cluster := pickCluster(params.ClusterId)
|
|
||||||
|
|
||||||
// 构建顶部 tabbar
|
cluster, err := findClusterMap(this.Parent(), params.ClusterId)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "delete")
|
httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "delete")
|
||||||
|
|
||||||
this.Data["cluster"] = cluster
|
this.Data["cluster"] = cluster
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
@@ -29,6 +33,12 @@ func (this *DeleteAction) RunGet(params struct {
|
|||||||
func (this *DeleteAction) RunPost(params struct {
|
func (this *DeleteAction) RunPost(params struct {
|
||||||
ClusterId int64
|
ClusterId int64
|
||||||
}) {
|
}) {
|
||||||
_ = params.ClusterId
|
_, err := this.RPC().HTTPDNSClusterRPC().DeleteHTTPDNSCluster(this.AdminContext(), &pb.DeleteHTTPDNSClusterRequest{
|
||||||
|
ClusterId: params.ClusterId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
this.Success()
|
this.Success()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
package clusters
|
package clusters
|
||||||
import (
|
|
||||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||||
)
|
|
||||||
type DeleteNodeAction struct { actionutils.ParentAction }
|
type DeleteNodeAction struct {
|
||||||
func (this *DeleteNodeAction) RunPost(params struct{ ClusterId int64; NodeId int64 }) { this.Success() }
|
actionutils.ParentAction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *DeleteNodeAction) RunPost(params struct {
|
||||||
|
ClusterId int64
|
||||||
|
NodeId int64
|
||||||
|
}) {
|
||||||
|
if err := deleteNode(this.Parent(), params.NodeId); err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.Success()
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,8 +13,27 @@ func (this *IndexAction) Init() {
|
|||||||
this.Nav("httpdns", "cluster", "")
|
this.Nav("httpdns", "cluster", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *IndexAction) RunGet(params struct{}) {
|
func (this *IndexAction) RunGet(params struct {
|
||||||
|
Keyword string
|
||||||
|
}) {
|
||||||
httpdnsutils.AddLeftMenu(this.Parent())
|
httpdnsutils.AddLeftMenu(this.Parent())
|
||||||
this.Data["clusters"] = mockClusters()
|
this.Data["keyword"] = params.Keyword
|
||||||
|
|
||||||
|
clusters, err := listClusterMaps(this.Parent(), params.Keyword)
|
||||||
|
if err != nil {
|
||||||
|
this.ErrorPage(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.Data["clusters"] = clusters
|
||||||
|
|
||||||
|
hasErrorLogs := false
|
||||||
|
for _, cluster := range clusters {
|
||||||
|
if cluster.GetInt("countAllNodes") > cluster.GetInt("countActiveNodes") {
|
||||||
|
hasErrorLogs = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.Data["hasErrorLogs"] = hasErrorLogs
|
||||||
|
this.Data["page"] = ""
|
||||||
this.Show()
|
this.Show()
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user