1.5.0
This commit is contained in:
@@ -128,13 +128,11 @@ function build() {
|
||||
|
||||
# copy files
|
||||
echo "copying ..."
|
||||
if [ ! -d "$DIST" ]; then
|
||||
mkdir "$DIST"
|
||||
mkdir "$DIST"/bin
|
||||
mkdir "$DIST"/configs
|
||||
mkdir "$DIST"/logs
|
||||
mkdir "$DIST"/data
|
||||
fi
|
||||
rm -rf "$DIST"
|
||||
mkdir -p "$DIST"/bin
|
||||
mkdir -p "$DIST"/configs
|
||||
mkdir -p "$DIST"/logs
|
||||
mkdir -p "$DIST"/data
|
||||
cp "$ROOT"/configs/api.template.yaml "$DIST"/configs/
|
||||
cp "$ROOT"/configs/db.template.yaml "$DIST"/configs/
|
||||
# 复制 EdgeCommon 的配置文件(如果存在)
|
||||
@@ -148,42 +146,6 @@ function build() {
|
||||
rm -f "$DIST"/deploy/.gitignore
|
||||
cp -R "$ROOT"/installers "$DIST"/
|
||||
|
||||
# copy fluent-bit templates and local packages from repo root
|
||||
FLUENT_ROOT="$ROOT/../../deploy/fluent-bit"
|
||||
FLUENT_DIST="$DIST/deploy/fluent-bit"
|
||||
if [ -d "$FLUENT_ROOT" ]; then
|
||||
rm -rf "$FLUENT_DIST"
|
||||
mkdir -p "$FLUENT_DIST"
|
||||
|
||||
FLUENT_FILES=(
|
||||
"fluent-bit.conf"
|
||||
"fluent-bit-dns.conf"
|
||||
"fluent-bit-https.conf"
|
||||
"fluent-bit-dns-https.conf"
|
||||
"fluent-bit-windows.conf"
|
||||
"fluent-bit-windows-https.conf"
|
||||
"parsers.conf"
|
||||
"clickhouse-upstream.conf"
|
||||
"clickhouse-upstream-windows.conf"
|
||||
"logrotate.conf"
|
||||
"README.md"
|
||||
)
|
||||
for file in "${FLUENT_FILES[@]}"; do
|
||||
if [ -f "$FLUENT_ROOT/$file" ]; then
|
||||
cp "$FLUENT_ROOT/$file" "$FLUENT_DIST/"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -d "$FLUENT_ROOT/packages" ]; then
|
||||
cp -R "$FLUENT_ROOT/packages" "$FLUENT_DIST/"
|
||||
fi
|
||||
|
||||
# remove local runtime artifacts if present
|
||||
rm -f "$FLUENT_DIST/.gitignore"
|
||||
rm -f "$FLUENT_DIST"/logs.db*
|
||||
rm -rf "$FLUENT_DIST/storage"
|
||||
fi
|
||||
|
||||
# building edge installer
|
||||
echo "building node installer ..."
|
||||
architects=("amd64")
|
||||
|
||||
@@ -47,6 +47,33 @@ type HTTPDNSAccessLogListFilter struct {
|
||||
Size int64
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogHourlyStat struct {
|
||||
Hour string
|
||||
CountRequests int64
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogDailyStat struct {
|
||||
Day string
|
||||
CountRequests int64
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogTopAppStat struct {
|
||||
AppId string
|
||||
AppName string
|
||||
CountRequests int64
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogTopDomainStat struct {
|
||||
Domain string
|
||||
CountRequests int64
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogTopNodeStat struct {
|
||||
ClusterId uint32
|
||||
NodeId uint32
|
||||
CountRequests int64
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogsStore struct {
|
||||
client *Client
|
||||
}
|
||||
@@ -176,6 +203,155 @@ func (s *HTTPDNSAccessLogsStore) List(ctx context.Context, f HTTPDNSAccessLogLis
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *HTTPDNSAccessLogsStore) FindHourlyStats(ctx context.Context, hourFrom string, hourTo string) ([]*HTTPDNSAccessLogHourlyStat, error) {
|
||||
if !s.client.IsConfigured() {
|
||||
return nil, fmt.Errorf("clickhouse: not configured")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(
|
||||
"SELECT formatDateTime(toDateTime(created_at), '%%Y%%m%%d%%H') AS hour, count() AS count_requests FROM %s WHERE formatDateTime(toDateTime(created_at), '%%Y%%m%%d%%H') BETWEEN '%s' AND '%s' GROUP BY hour ORDER BY hour ASC",
|
||||
s.tableName(),
|
||||
escapeString(hourFrom),
|
||||
escapeString(hourTo),
|
||||
)
|
||||
|
||||
rows := []map[string]interface{}{}
|
||||
if err := s.client.Query(ctx, query, &rows); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*HTTPDNSAccessLogHourlyStat, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogHourlyStat{
|
||||
Hour: toString(row["hour"]),
|
||||
CountRequests: toInt64(row["count_requests"]),
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *HTTPDNSAccessLogsStore) FindDailyStats(ctx context.Context, dayFrom string, dayTo string) ([]*HTTPDNSAccessLogDailyStat, error) {
|
||||
if !s.client.IsConfigured() {
|
||||
return nil, fmt.Errorf("clickhouse: not configured")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(
|
||||
"SELECT day, count() AS count_requests FROM %s WHERE day BETWEEN '%s' AND '%s' GROUP BY day ORDER BY day ASC",
|
||||
s.tableName(),
|
||||
escapeString(dayFrom),
|
||||
escapeString(dayTo),
|
||||
)
|
||||
|
||||
rows := []map[string]interface{}{}
|
||||
if err := s.client.Query(ctx, query, &rows); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*HTTPDNSAccessLogDailyStat, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogDailyStat{
|
||||
Day: toString(row["day"]),
|
||||
CountRequests: toInt64(row["count_requests"]),
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *HTTPDNSAccessLogsStore) ListTopApps(ctx context.Context, dayFrom string, dayTo string, limit int64) ([]*HTTPDNSAccessLogTopAppStat, error) {
|
||||
if !s.client.IsConfigured() {
|
||||
return nil, fmt.Errorf("clickhouse: not configured")
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(
|
||||
"SELECT app_id, max(app_name) AS app_name, count() AS count_requests FROM %s WHERE day BETWEEN '%s' AND '%s' GROUP BY app_id ORDER BY count_requests DESC LIMIT %d",
|
||||
s.tableName(),
|
||||
escapeString(dayFrom),
|
||||
escapeString(dayTo),
|
||||
limit,
|
||||
)
|
||||
|
||||
rows := []map[string]interface{}{}
|
||||
if err := s.client.Query(ctx, query, &rows); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*HTTPDNSAccessLogTopAppStat, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogTopAppStat{
|
||||
AppId: toString(row["app_id"]),
|
||||
AppName: toString(row["app_name"]),
|
||||
CountRequests: toInt64(row["count_requests"]),
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *HTTPDNSAccessLogsStore) ListTopDomains(ctx context.Context, dayFrom string, dayTo string, limit int64) ([]*HTTPDNSAccessLogTopDomainStat, error) {
|
||||
if !s.client.IsConfigured() {
|
||||
return nil, fmt.Errorf("clickhouse: not configured")
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(
|
||||
"SELECT domain, count() AS count_requests FROM %s WHERE day BETWEEN '%s' AND '%s' AND domain != '' GROUP BY domain ORDER BY count_requests DESC LIMIT %d",
|
||||
s.tableName(),
|
||||
escapeString(dayFrom),
|
||||
escapeString(dayTo),
|
||||
limit,
|
||||
)
|
||||
|
||||
rows := []map[string]interface{}{}
|
||||
if err := s.client.Query(ctx, query, &rows); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*HTTPDNSAccessLogTopDomainStat, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogTopDomainStat{
|
||||
Domain: toString(row["domain"]),
|
||||
CountRequests: toInt64(row["count_requests"]),
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *HTTPDNSAccessLogsStore) ListTopNodes(ctx context.Context, dayFrom string, dayTo string, limit int64) ([]*HTTPDNSAccessLogTopNodeStat, error) {
|
||||
if !s.client.IsConfigured() {
|
||||
return nil, fmt.Errorf("clickhouse: not configured")
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(
|
||||
"SELECT min(cluster_id) AS cluster_id, node_id, count() AS count_requests FROM %s WHERE day BETWEEN '%s' AND '%s' GROUP BY node_id ORDER BY count_requests DESC LIMIT %d",
|
||||
s.tableName(),
|
||||
escapeString(dayFrom),
|
||||
escapeString(dayTo),
|
||||
limit,
|
||||
)
|
||||
|
||||
rows := []map[string]interface{}{}
|
||||
if err := s.client.Query(ctx, query, &rows); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*HTTPDNSAccessLogTopNodeStat, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogTopNodeStat{
|
||||
ClusterId: uint32(toInt64(row["cluster_id"])),
|
||||
NodeId: uint32(toInt64(row["node_id"])),
|
||||
CountRequests: toInt64(row["count_requests"]),
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func HTTPDNSRowToPB(row *HTTPDNSAccessLogRow) *pb.HTTPDNSAccessLog {
|
||||
if row == nil {
|
||||
return nil
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.4.9" //1.3.9
|
||||
Version = "1.5.0" //1.3.9
|
||||
|
||||
ProductName = "Edge API"
|
||||
ProcessName = "edge-api"
|
||||
@@ -17,6 +17,6 @@ const (
|
||||
|
||||
// 其他节点版本号,用来检测是否有需要升级的节点
|
||||
|
||||
NodeVersion = "1.4.9" //1.3.8.2
|
||||
NodeVersion = "1.5.0" //1.3.8.2
|
||||
)
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
DNSNodeVersion = "1.4.9" //1.3.8.2
|
||||
UserNodeVersion = "1.4.9" //1.3.8.2
|
||||
ReportNodeVersion = "0.1.5"
|
||||
DNSNodeVersion = "1.5.0" //1.3.8.2
|
||||
UserNodeVersion = "1.5.0" //1.3.8.2
|
||||
ReportNodeVersion = "1.5.0"
|
||||
|
||||
DefaultMaxNodes int32 = 50
|
||||
)
|
||||
|
||||
138
EdgeAPI/internal/db/models/httpdns_access_log_dao_stat.go
Normal file
138
EdgeAPI/internal/db/models/httpdns_access_log_dao_stat.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
)
|
||||
|
||||
type HTTPDNSAccessLogHourlyStat struct {
|
||||
Hour string `field:"hour"`
|
||||
CountRequests int64 `field:"countRequests"`
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogDailyStat struct {
|
||||
Day string `field:"day"`
|
||||
CountRequests int64 `field:"countRequests"`
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogTopAppStat struct {
|
||||
AppId string `field:"appId"`
|
||||
AppName string `field:"appName"`
|
||||
CountRequests int64 `field:"countRequests"`
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogTopDomainStat struct {
|
||||
Domain string `field:"domain"`
|
||||
CountRequests int64 `field:"countRequests"`
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogTopNodeStat struct {
|
||||
ClusterId uint32 `field:"clusterId"`
|
||||
NodeId uint32 `field:"nodeId"`
|
||||
CountRequests int64 `field:"countRequests"`
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAccessLogDAO) FindHourlyStats(tx *dbs.Tx, hourFrom string, hourTo string) (result []*HTTPDNSAccessLogHourlyStat, err error) {
|
||||
rows, _, err := this.Query(tx).
|
||||
Result("CONCAT(day, LPAD(HOUR(FROM_UNIXTIME(createdAt)),2,'0')) AS hour", "COUNT(*) AS countRequests").
|
||||
Where("CONCAT(day, LPAD(HOUR(FROM_UNIXTIME(createdAt)),2,'0')) BETWEEN :hourFrom AND :hourTo").
|
||||
Param("hourFrom", hourFrom).
|
||||
Param("hourTo", hourTo).
|
||||
Group("hour").
|
||||
FindOnes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogHourlyStat{
|
||||
Hour: row.GetString("hour"),
|
||||
CountRequests: row.GetInt64("countRequests"),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAccessLogDAO) FindDailyStats(tx *dbs.Tx, dayFrom string, dayTo string) (result []*HTTPDNSAccessLogDailyStat, err error) {
|
||||
rows, _, err := this.Query(tx).
|
||||
Result("day", "COUNT(*) AS countRequests").
|
||||
Between("day", dayFrom, dayTo).
|
||||
Group("day").
|
||||
FindOnes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogDailyStat{
|
||||
Day: row.GetString("day"),
|
||||
CountRequests: row.GetInt64("countRequests"),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAccessLogDAO) ListTopApps(tx *dbs.Tx, dayFrom string, dayTo string, limit int64) (result []*HTTPDNSAccessLogTopAppStat, err error) {
|
||||
rows, _, err := this.Query(tx).
|
||||
Result("appId", "MAX(appName) AS appName", "COUNT(*) AS countRequests").
|
||||
Between("day", dayFrom, dayTo).
|
||||
Group("appId").
|
||||
Desc("countRequests").
|
||||
Limit(limit).
|
||||
FindOnes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogTopAppStat{
|
||||
AppId: row.GetString("appId"),
|
||||
AppName: row.GetString("appName"),
|
||||
CountRequests: row.GetInt64("countRequests"),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAccessLogDAO) ListTopDomains(tx *dbs.Tx, dayFrom string, dayTo string, limit int64) (result []*HTTPDNSAccessLogTopDomainStat, err error) {
|
||||
rows, _, err := this.Query(tx).
|
||||
Result("domain", "COUNT(*) AS countRequests").
|
||||
Between("day", dayFrom, dayTo).
|
||||
Where("domain != ''").
|
||||
Group("domain").
|
||||
Desc("countRequests").
|
||||
Limit(limit).
|
||||
FindOnes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogTopDomainStat{
|
||||
Domain: row.GetString("domain"),
|
||||
CountRequests: row.GetInt64("countRequests"),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAccessLogDAO) ListTopNodes(tx *dbs.Tx, dayFrom string, dayTo string, limit int64) (result []*HTTPDNSAccessLogTopNodeStat, err error) {
|
||||
rows, _, err := this.Query(tx).
|
||||
Result("MIN(clusterId) AS clusterId", "nodeId", "COUNT(*) AS countRequests").
|
||||
Between("day", dayFrom, dayTo).
|
||||
Group("nodeId").
|
||||
Desc("countRequests").
|
||||
Limit(limit).
|
||||
FindOnes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogTopNodeStat{
|
||||
ClusterId: row.GetUint32("clusterId"),
|
||||
NodeId: row.GetUint32("nodeId"),
|
||||
CountRequests: row.GetInt64("countRequests"),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -113,3 +113,11 @@ func (this *HTTPDNSDomainDAO) ListEnabledDomainsWithAppId(tx *dbs.Tx, appDbId in
|
||||
_, err = query.Slice(&result).FindAll()
|
||||
return
|
||||
}
|
||||
|
||||
func (this *HTTPDNSDomainDAO) CountEnabledDomains(tx *dbs.Tx, appDbId int64) (int64, error) {
|
||||
query := this.Query(tx).State(HTTPDNSDomainStateEnabled)
|
||||
if appDbId > 0 {
|
||||
query = query.Attr("appId", appDbId)
|
||||
}
|
||||
return query.Count()
|
||||
}
|
||||
|
||||
@@ -221,6 +221,41 @@ func (this *NodeValueDAO) ListValuesForNSNodes(tx *dbs.Tx, item string, key stri
|
||||
return
|
||||
}
|
||||
|
||||
// ListValuesForHTTPDNSNodes 列出HTTPDNS节点相关的平均数据
|
||||
func (this *NodeValueDAO) ListValuesForHTTPDNSNodes(tx *dbs.Tx, item string, key string, timeRange nodeconfigs.NodeValueRange) (result []*NodeValue, err error) {
|
||||
query := this.Query(tx).
|
||||
Attr("role", "httpdns").
|
||||
Attr("item", item).
|
||||
Result("AVG(JSON_EXTRACT(value, '$." + key + "')) AS value, MIN(createdAt) AS createdAt")
|
||||
|
||||
switch timeRange {
|
||||
// TODO 支持更多的时间范围
|
||||
case nodeconfigs.NodeValueRangeMinute:
|
||||
fromMinute := timeutil.FormatTime("YmdHi", time.Now().Unix()-3600) // 一个小时之前的
|
||||
query.Gte("minute", fromMinute)
|
||||
query.Result("minute")
|
||||
query.Group("minute")
|
||||
default:
|
||||
err = errors.New("invalid 'range' value: '" + timeRange + "'")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = query.Slice(&result).
|
||||
FindAll()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, nodeValue := range result {
|
||||
nodeValue.Value, _ = json.Marshal(maps.Map{
|
||||
key: types.Float32(string(nodeValue.Value)),
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SumAllNodeValues 计算所有节点的某项参数值
|
||||
func (this *NodeValueDAO) SumAllNodeValues(tx *dbs.Tx, role string, item nodeconfigs.NodeValueItem, param string, duration int32, durationUnit nodeconfigs.NodeValueDurationUnit) (total float64, avg float64, max float64, err error) {
|
||||
if duration <= 0 {
|
||||
|
||||
@@ -1,889 +0,0 @@
|
||||
package installers
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
slashpath "path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
)
|
||||
|
||||
const (
|
||||
fluentBitConfigDir = "/etc/fluent-bit"
|
||||
fluentBitStorageDir = "/var/lib/fluent-bit/storage"
|
||||
fluentBitMainConfigFile = "/etc/fluent-bit/fluent-bit.conf"
|
||||
fluentBitParsersFile = "/etc/fluent-bit/parsers.conf"
|
||||
fluentBitManagedMetaFile = "/etc/fluent-bit/.edge-managed.json"
|
||||
fluentBitManagedEnvFile = "/etc/fluent-bit/.edge-managed.env"
|
||||
fluentBitDropInDir = "/etc/systemd/system/fluent-bit.service.d"
|
||||
fluentBitDropInFile = "/etc/systemd/system/fluent-bit.service.d/edge-managed.conf"
|
||||
fluentBitServiceName = "fluent-bit"
|
||||
fluentBitDefaultBinPath = "/opt/fluent-bit/bin/fluent-bit"
|
||||
fluentBitLocalPackagesRoot = "packages"
|
||||
fluentBitHTTPPathPattern = "/var/log/edge/edge-node/*.log"
|
||||
fluentBitDNSPathPattern = "/var/log/edge/edge-dns/*.log"
|
||||
fluentBitHTTPDNSPathPattern = "/var/log/edge/edge-httpdns/*.log"
|
||||
fluentBitManagedMarker = "managed-by-edgeapi"
|
||||
fluentBitRoleNode = "node"
|
||||
fluentBitRoleDNS = "dns"
|
||||
fluentBitRoleHTTPDNS = "httpdns"
|
||||
)
|
||||
|
||||
var errFluentBitLocalPackageNotFound = errors.New("fluent-bit local package not found")
|
||||
|
||||
var fluentBitPackageFileMapping = map[string]string{
|
||||
"ubuntu22.04-amd64": "fluent-bit_4.2.2_amd64.deb",
|
||||
"ubuntu22.04-arm64": "fluent-bit_4.2.2_arm64.deb",
|
||||
"amzn2023-amd64": "fluent-bit-4.2.2-1.x86_64.rpm",
|
||||
"amzn2023-arm64": "fluent-bit-4.2.2-1.aarch64.rpm",
|
||||
}
|
||||
|
||||
type fluentBitManagedMeta struct {
|
||||
Roles []string `json:"roles"`
|
||||
Hash string `json:"hash"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
SourceVersion string `json:"sourceVersion"`
|
||||
}
|
||||
|
||||
type fluentBitDesiredConfig struct {
|
||||
Roles []string
|
||||
ClickHouse *systemconfigs.ClickHouseSetting
|
||||
HTTPPathPattern string
|
||||
DNSPathPattern string
|
||||
HTTPDNSPathPattern string
|
||||
}
|
||||
|
||||
// SetupFluentBit 安装并托管 Fluent Bit 配置(离线包 + 平台渲染配置)。
|
||||
func (this *BaseInstaller) SetupFluentBit(role nodeconfigs.NodeRole) error {
|
||||
if this.client == nil {
|
||||
return errors.New("ssh client is nil")
|
||||
}
|
||||
|
||||
uname := this.uname()
|
||||
if !strings.Contains(uname, "Linux") {
|
||||
return nil
|
||||
}
|
||||
|
||||
tempDir := strings.TrimRight(this.client.UserHome(), "/") + "/.edge-fluent-bit"
|
||||
_, _, _ = this.client.Exec("mkdir -p " + shQuote(tempDir))
|
||||
defer func() {
|
||||
_, _, _ = this.client.Exec("rm -rf " + shQuote(tempDir))
|
||||
}()
|
||||
|
||||
if err := this.ensureFluentBitInstalled(tempDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, stderr, err := this.client.Exec("mkdir -p " + shQuote(fluentBitConfigDir) + " " + shQuote(fluentBitStorageDir))
|
||||
if err != nil {
|
||||
return fmt.Errorf("prepare fluent-bit directories failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
|
||||
parserContent, err := this.readLocalParsersContent()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existingMeta, err := this.readManagedMeta()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mergedRoles, err := mergeManagedRoles(existingMeta, role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
desiredConfig, err := this.buildDesiredFluentBitConfig(mergedRoles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configChanged, err := this.applyManagedConfig(tempDir, desiredConfig, parserContent, existingMeta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
binPath, err := this.lookupFluentBitBinPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := this.ensureFluentBitService(tempDir, binPath, configChanged); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) ensureFluentBitInstalled(tempDir string) error {
|
||||
binPath, _ := this.lookupFluentBitBinPath()
|
||||
if binPath != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
platformKey, packageName, arch, err := this.detectRemotePlatformAndPackage()
|
||||
if err != nil {
|
||||
return fmt.Errorf("detect fluent-bit platform failed: %w", err)
|
||||
}
|
||||
|
||||
if err := this.installFluentBitFromLocalPackage(tempDir, arch, packageName); err != nil {
|
||||
if errors.Is(err, errFluentBitLocalPackageNotFound) {
|
||||
expectedPath := filepath.Join("deploy", "fluent-bit", fluentBitLocalPackagesRoot, "linux-"+arch, packageName)
|
||||
return fmt.Errorf("install fluent-bit failed: local package missing for platform '%s', expected '%s'", platformKey, expectedPath)
|
||||
}
|
||||
return fmt.Errorf("install fluent-bit from local package failed: %w", err)
|
||||
}
|
||||
|
||||
binPath, err = this.lookupFluentBitBinPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if binPath == "" {
|
||||
return errors.New("fluent-bit binary not found after local package install")
|
||||
}
|
||||
|
||||
_, stderr, err := this.client.Exec(binPath + " --version")
|
||||
if err != nil {
|
||||
return fmt.Errorf("verify fluent-bit version failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) installFluentBitFromLocalPackage(tempDir string, arch string, packageName string) error {
|
||||
packageDir := filepath.Join(Tea.Root, "deploy", "fluent-bit", fluentBitLocalPackagesRoot, "linux-"+arch)
|
||||
localPackagePath := filepath.Join(packageDir, packageName)
|
||||
if _, err := os.Stat(localPackagePath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errFluentBitLocalPackageNotFound
|
||||
}
|
||||
return fmt.Errorf("check local package failed: %w", err)
|
||||
}
|
||||
|
||||
remotePackagePath := tempDir + "/" + filepath.Base(localPackagePath)
|
||||
if err := this.client.Copy(localPackagePath, remotePackagePath, 0644); err != nil {
|
||||
return fmt.Errorf("upload local package failed: %w", err)
|
||||
}
|
||||
|
||||
var installCmd string
|
||||
lowerName := strings.ToLower(localPackagePath)
|
||||
switch {
|
||||
case strings.HasSuffix(lowerName, ".deb"):
|
||||
installCmd = "dpkg -i " + shQuote(remotePackagePath)
|
||||
case strings.HasSuffix(lowerName, ".rpm"):
|
||||
installCmd = "rpm -Uvh --force " + shQuote(remotePackagePath) + " || rpm -ivh --force " + shQuote(remotePackagePath)
|
||||
case strings.HasSuffix(lowerName, ".tar.gz") || strings.HasSuffix(lowerName, ".tgz"):
|
||||
extractDir := tempDir + "/extract"
|
||||
installCmd = "rm -rf " + shQuote(extractDir) + "; mkdir -p " + shQuote(extractDir) + "; tar -xzf " + shQuote(remotePackagePath) + " -C " + shQuote(extractDir) + "; " +
|
||||
"bin=$(find " + shQuote(extractDir) + " -type f -name fluent-bit | head -n 1); " +
|
||||
"if [ -z \"$bin\" ]; then exit 3; fi; " +
|
||||
"mkdir -p /opt/fluent-bit/bin /usr/local/bin; " +
|
||||
"install -m 0755 \"$bin\" /opt/fluent-bit/bin/fluent-bit; " +
|
||||
"ln -sf /opt/fluent-bit/bin/fluent-bit /usr/local/bin/fluent-bit"
|
||||
default:
|
||||
return fmt.Errorf("unsupported local package format: %s", packageName)
|
||||
}
|
||||
|
||||
_, stderr, err := this.client.Exec(installCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("install fluent-bit local package '%s' failed: %w, stderr: %s", filepath.Base(localPackagePath), err, stderr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) detectRemotePlatformAndPackage() (platformKey string, packageName string, arch string, err error) {
|
||||
arch, err = this.detectRemoteLinuxArch()
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
releaseData, stderr, err := this.client.Exec("cat /etc/os-release")
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("read /etc/os-release failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
if strings.TrimSpace(releaseData) == "" {
|
||||
return "", "", "", errors.New("/etc/os-release is empty")
|
||||
}
|
||||
|
||||
releaseMap := parseOSRelease(releaseData)
|
||||
id := strings.ToLower(strings.TrimSpace(releaseMap["ID"]))
|
||||
versionID := strings.TrimSpace(releaseMap["VERSION_ID"])
|
||||
|
||||
var distro string
|
||||
switch {
|
||||
case id == "ubuntu" && strings.HasPrefix(versionID, "22.04"):
|
||||
distro = "ubuntu22.04"
|
||||
case id == "amzn" && strings.HasPrefix(versionID, "2023"):
|
||||
distro = "amzn2023"
|
||||
default:
|
||||
return "", "", "", fmt.Errorf("unsupported linux platform id='%s' version='%s'", id, versionID)
|
||||
}
|
||||
|
||||
platformKey = distro + "-" + arch
|
||||
packageName, ok := fluentBitPackageFileMapping[platformKey]
|
||||
if !ok {
|
||||
return "", "", "", fmt.Errorf("no local package mapping for platform '%s'", platformKey)
|
||||
}
|
||||
return platformKey, packageName, arch, nil
|
||||
}
|
||||
|
||||
func parseOSRelease(content string) map[string]string {
|
||||
result := map[string]string{}
|
||||
lines := strings.Split(content, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") || !strings.Contains(line, "=") {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
value = strings.Trim(value, "\"")
|
||||
result[key] = value
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) detectRemoteLinuxArch() (string, error) {
|
||||
stdout, stderr, err := this.client.Exec("uname -m")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("detect remote arch failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
|
||||
arch := strings.ToLower(strings.TrimSpace(stdout))
|
||||
switch arch {
|
||||
case "x86_64", "amd64":
|
||||
return "amd64", nil
|
||||
case "aarch64", "arm64":
|
||||
return "arm64", nil
|
||||
default:
|
||||
return arch, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) lookupFluentBitBinPath() (string, error) {
|
||||
stdout, stderr, err := this.client.Exec("if command -v fluent-bit >/dev/null 2>&1; then command -v fluent-bit; elif [ -x " + fluentBitDefaultBinPath + " ]; then echo " + fluentBitDefaultBinPath + "; fi")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("lookup fluent-bit binary failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
return strings.TrimSpace(stdout), nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) readLocalParsersContent() (string, error) {
|
||||
parsersPath := filepath.Join(Tea.Root, "deploy", "fluent-bit", "parsers.conf")
|
||||
data, err := os.ReadFile(parsersPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read local parsers config failed: %w", err)
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) readManagedMeta() (*fluentBitManagedMeta, error) {
|
||||
exists, err := this.remoteFileExists(fluentBitManagedMetaFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
content, stderr, err := this.client.Exec("cat " + shQuote(fluentBitManagedMetaFile))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read fluent-bit managed metadata failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
if strings.TrimSpace(content) == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
meta := &fluentBitManagedMeta{}
|
||||
if err := json.Unmarshal([]byte(content), meta); err != nil {
|
||||
return nil, fmt.Errorf("decode fluent-bit managed metadata failed: %w", err)
|
||||
}
|
||||
meta.Roles = normalizeRoles(meta.Roles)
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
func mergeManagedRoles(meta *fluentBitManagedMeta, role nodeconfigs.NodeRole) ([]string, error) {
|
||||
roleName, err := mapNodeRole(role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roleSet := map[string]struct{}{}
|
||||
if meta != nil {
|
||||
for _, r := range normalizeRoles(meta.Roles) {
|
||||
roleSet[r] = struct{}{}
|
||||
}
|
||||
}
|
||||
roleSet[roleName] = struct{}{}
|
||||
|
||||
roles := make([]string, 0, len(roleSet))
|
||||
for r := range roleSet {
|
||||
roles = append(roles, r)
|
||||
}
|
||||
sort.Strings(roles)
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func mapNodeRole(role nodeconfigs.NodeRole) (string, error) {
|
||||
switch role {
|
||||
case nodeconfigs.NodeRoleNode:
|
||||
return fluentBitRoleNode, nil
|
||||
case nodeconfigs.NodeRoleDNS:
|
||||
return fluentBitRoleDNS, nil
|
||||
case nodeconfigs.NodeRoleHTTPDNS:
|
||||
return fluentBitRoleHTTPDNS, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported fluent-bit role '%s'", role)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeRoles(rawRoles []string) []string {
|
||||
roleSet := map[string]struct{}{}
|
||||
for _, role := range rawRoles {
|
||||
role = strings.ToLower(strings.TrimSpace(role))
|
||||
if role != fluentBitRoleNode && role != fluentBitRoleDNS && role != fluentBitRoleHTTPDNS {
|
||||
continue
|
||||
}
|
||||
roleSet[role] = struct{}{}
|
||||
}
|
||||
|
||||
roles := make([]string, 0, len(roleSet))
|
||||
for role := range roleSet {
|
||||
roles = append(roles, role)
|
||||
}
|
||||
sort.Strings(roles)
|
||||
return roles
|
||||
}
|
||||
|
||||
func hasRole(roles []string, role string) bool {
|
||||
for _, one := range roles {
|
||||
if one == role {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) buildDesiredFluentBitConfig(roles []string) (*fluentBitDesiredConfig, error) {
|
||||
if len(roles) == 0 {
|
||||
return nil, errors.New("fluent-bit roles should not be empty")
|
||||
}
|
||||
|
||||
ch, err := models.SharedSysSettingDAO.ReadClickHouseConfig(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read clickhouse setting failed: %w", err)
|
||||
}
|
||||
if ch == nil {
|
||||
ch = &systemconfigs.ClickHouseSetting{}
|
||||
}
|
||||
if strings.TrimSpace(ch.Host) == "" {
|
||||
ch.Host = "127.0.0.1"
|
||||
}
|
||||
|
||||
ch.Scheme = strings.ToLower(strings.TrimSpace(ch.Scheme))
|
||||
if ch.Scheme == "" {
|
||||
ch.Scheme = "https"
|
||||
}
|
||||
if ch.Scheme != "http" && ch.Scheme != "https" {
|
||||
return nil, fmt.Errorf("unsupported clickhouse scheme '%s'", ch.Scheme)
|
||||
}
|
||||
|
||||
if ch.Port <= 0 {
|
||||
if ch.Scheme == "https" {
|
||||
ch.Port = 8443
|
||||
} else {
|
||||
ch.Port = 8443
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(ch.Database) == "" {
|
||||
ch.Database = "default"
|
||||
}
|
||||
if strings.TrimSpace(ch.User) == "" {
|
||||
ch.User = "default"
|
||||
}
|
||||
// 当前平台策略:后台固定跳过 ClickHouse TLS 证书校验,不暴露 ServerName 配置。
|
||||
ch.TLSSkipVerify = true
|
||||
ch.TLSServerName = ""
|
||||
|
||||
httpPathPattern := fluentBitHTTPPathPattern
|
||||
dnsPathPattern := fluentBitDNSPathPattern
|
||||
httpdnsPathPattern := fluentBitHTTPDNSPathPattern
|
||||
publicPolicyPath, err := this.readPublicAccessLogPolicyPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policyDir := dirFromPolicyPath(publicPolicyPath)
|
||||
if policyDir != "" {
|
||||
pattern := strings.TrimRight(policyDir, "/") + "/*.log"
|
||||
httpPathPattern = pattern
|
||||
dnsPathPattern = pattern
|
||||
httpdnsPathPattern = pattern
|
||||
}
|
||||
|
||||
return &fluentBitDesiredConfig{
|
||||
Roles: normalizeRoles(roles),
|
||||
ClickHouse: ch,
|
||||
HTTPPathPattern: httpPathPattern,
|
||||
DNSPathPattern: dnsPathPattern,
|
||||
HTTPDNSPathPattern: httpdnsPathPattern,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) readPublicAccessLogPolicyPath() (string, error) {
|
||||
policyId, err := models.SharedHTTPAccessLogPolicyDAO.FindCurrentPublicPolicyId(nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("find current public access log policy failed: %w", err)
|
||||
}
|
||||
if policyId <= 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
policy, err := models.SharedHTTPAccessLogPolicyDAO.FindEnabledHTTPAccessLogPolicy(nil, policyId)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read public access log policy failed: %w", err)
|
||||
}
|
||||
if policy == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return strings.TrimSpace(models.ParseHTTPAccessLogPolicyFilePath(policy)), nil
|
||||
}
|
||||
|
||||
func dirFromPolicyPath(policyPath string) string {
|
||||
pathValue := strings.TrimSpace(policyPath)
|
||||
if pathValue == "" {
|
||||
return ""
|
||||
}
|
||||
pathValue = strings.ReplaceAll(pathValue, "\\", "/")
|
||||
dir := slashpath.Dir(pathValue)
|
||||
if dir == "." {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimRight(dir, "/")
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) applyManagedConfig(tempDir string, desired *fluentBitDesiredConfig, parserContent string, existingMeta *fluentBitManagedMeta) (bool, error) {
|
||||
mainExists, err := this.remoteFileExists(fluentBitMainConfigFile)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if mainExists && existingMeta == nil {
|
||||
containsMarker, err := this.remoteFileContains(fluentBitMainConfigFile, fluentBitManagedMarker)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !containsMarker {
|
||||
// Adopt unmanaged config by backing it up and replacing with managed config below.
|
||||
}
|
||||
}
|
||||
|
||||
configContent, err := renderManagedConfig(desired)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
envContent := renderManagedEnv(desired.ClickHouse)
|
||||
metaContent, newMeta, err := renderManagedMeta(desired, configContent, parserContent, envContent)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
requiredFiles := []string{fluentBitMainConfigFile, fluentBitParsersFile, fluentBitManagedEnvFile, fluentBitManagedMetaFile}
|
||||
if existingMeta != nil && existingMeta.Hash == newMeta.Hash {
|
||||
allExists := true
|
||||
for _, file := range requiredFiles {
|
||||
exists, err := this.remoteFileExists(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !exists {
|
||||
allExists = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allExists {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if mainExists {
|
||||
backup := fluentBitMainConfigFile + ".bak." + strconv.FormatInt(time.Now().Unix(), 10)
|
||||
_, stderr, err := this.client.Exec("cp -f " + shQuote(fluentBitMainConfigFile) + " " + shQuote(backup))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("backup existing fluent-bit config failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
if err := this.writeRemoteFileByTemp(tempDir, fluentBitMainConfigFile, configContent, 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := this.writeRemoteFileByTemp(tempDir, fluentBitParsersFile, parserContent, 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := this.writeRemoteFileByTemp(tempDir, fluentBitManagedEnvFile, envContent, 0600); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := this.writeRemoteFileByTemp(tempDir, fluentBitManagedMetaFile, metaContent, 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func renderManagedConfig(desired *fluentBitDesiredConfig) (string, error) {
|
||||
if desired == nil || desired.ClickHouse == nil {
|
||||
return "", errors.New("invalid fluent-bit desired config")
|
||||
}
|
||||
|
||||
scheme := strings.ToLower(strings.TrimSpace(desired.ClickHouse.Scheme))
|
||||
if scheme == "" {
|
||||
scheme = "http"
|
||||
}
|
||||
if scheme != "http" && scheme != "https" {
|
||||
return "", fmt.Errorf("unsupported clickhouse scheme '%s'", desired.ClickHouse.Scheme)
|
||||
}
|
||||
useTLS := scheme == "https"
|
||||
|
||||
insertHTTP := url.QueryEscape(fmt.Sprintf("INSERT INTO %s.logs_ingest FORMAT JSONEachRow", desired.ClickHouse.Database))
|
||||
insertDNS := url.QueryEscape(fmt.Sprintf("INSERT INTO %s.dns_logs_ingest FORMAT JSONEachRow", desired.ClickHouse.Database))
|
||||
insertHTTPDNS := url.QueryEscape(fmt.Sprintf("INSERT INTO %s.httpdns_access_logs_ingest FORMAT JSONEachRow", desired.ClickHouse.Database))
|
||||
|
||||
lines := []string{
|
||||
"# " + fluentBitManagedMarker,
|
||||
"[SERVICE]",
|
||||
" Flush 1",
|
||||
" Log_Level info",
|
||||
" Parsers_File " + fluentBitParsersFile,
|
||||
" storage.path " + fluentBitStorageDir,
|
||||
" storage.sync normal",
|
||||
" storage.checksum off",
|
||||
" storage.backlog.mem_limit 512MB",
|
||||
"",
|
||||
}
|
||||
|
||||
if hasRole(desired.Roles, fluentBitRoleNode) {
|
||||
lines = append(lines,
|
||||
"[INPUT]",
|
||||
" Name tail",
|
||||
" Path "+desired.HTTPPathPattern,
|
||||
" Tag app.http.logs",
|
||||
" Parser json",
|
||||
" Refresh_Interval 2",
|
||||
" Read_from_Head false",
|
||||
" DB /var/lib/fluent-bit/http-logs.db",
|
||||
" storage.type filesystem",
|
||||
" Mem_Buf_Limit 256MB",
|
||||
" Skip_Long_Lines On",
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
if hasRole(desired.Roles, fluentBitRoleDNS) {
|
||||
lines = append(lines,
|
||||
"[INPUT]",
|
||||
" Name tail",
|
||||
" Path "+desired.DNSPathPattern,
|
||||
" Tag app.dns.logs",
|
||||
" Parser json",
|
||||
" Refresh_Interval 2",
|
||||
" Read_from_Head false",
|
||||
" DB /var/lib/fluent-bit/dns-logs.db",
|
||||
" storage.type filesystem",
|
||||
" Mem_Buf_Limit 256MB",
|
||||
" Skip_Long_Lines On",
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
if hasRole(desired.Roles, fluentBitRoleHTTPDNS) {
|
||||
lines = append(lines,
|
||||
"[INPUT]",
|
||||
" Name tail",
|
||||
" Path "+desired.HTTPDNSPathPattern,
|
||||
" Tag app.httpdns.logs",
|
||||
" Parser json",
|
||||
" Refresh_Interval 2",
|
||||
" Read_from_Head false",
|
||||
" DB /var/lib/fluent-bit/httpdns-logs.db",
|
||||
" storage.type filesystem",
|
||||
" Mem_Buf_Limit 256MB",
|
||||
" Skip_Long_Lines On",
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
if hasRole(desired.Roles, fluentBitRoleNode) {
|
||||
lines = append(lines,
|
||||
"[OUTPUT]",
|
||||
" Name http",
|
||||
" Match app.http.logs",
|
||||
" Host "+desired.ClickHouse.Host,
|
||||
" Port "+strconv.Itoa(desired.ClickHouse.Port),
|
||||
" URI /?query="+insertHTTP,
|
||||
" Format json_lines",
|
||||
" http_user ${CH_USER}",
|
||||
" http_passwd ${CH_PASSWORD}",
|
||||
" json_date_key timestamp",
|
||||
" json_date_format epoch",
|
||||
" workers 2",
|
||||
" net.keepalive On",
|
||||
" Retry_Limit False",
|
||||
)
|
||||
if useTLS {
|
||||
lines = append(lines, " tls On")
|
||||
if desired.ClickHouse.TLSSkipVerify {
|
||||
lines = append(lines, " tls.verify Off")
|
||||
} else {
|
||||
lines = append(lines, " tls.verify On")
|
||||
}
|
||||
if strings.TrimSpace(desired.ClickHouse.TLSServerName) != "" {
|
||||
lines = append(lines, " tls.vhost "+strings.TrimSpace(desired.ClickHouse.TLSServerName))
|
||||
}
|
||||
}
|
||||
lines = append(lines, "")
|
||||
}
|
||||
|
||||
if hasRole(desired.Roles, fluentBitRoleDNS) {
|
||||
lines = append(lines,
|
||||
"[OUTPUT]",
|
||||
" Name http",
|
||||
" Match app.dns.logs",
|
||||
" Host "+desired.ClickHouse.Host,
|
||||
" Port "+strconv.Itoa(desired.ClickHouse.Port),
|
||||
" URI /?query="+insertDNS,
|
||||
" Format json_lines",
|
||||
" http_user ${CH_USER}",
|
||||
" http_passwd ${CH_PASSWORD}",
|
||||
" json_date_key timestamp",
|
||||
" json_date_format epoch",
|
||||
" workers 2",
|
||||
" net.keepalive On",
|
||||
" Retry_Limit False",
|
||||
)
|
||||
if useTLS {
|
||||
lines = append(lines, " tls On")
|
||||
if desired.ClickHouse.TLSSkipVerify {
|
||||
lines = append(lines, " tls.verify Off")
|
||||
} else {
|
||||
lines = append(lines, " tls.verify On")
|
||||
}
|
||||
if strings.TrimSpace(desired.ClickHouse.TLSServerName) != "" {
|
||||
lines = append(lines, " tls.vhost "+strings.TrimSpace(desired.ClickHouse.TLSServerName))
|
||||
}
|
||||
}
|
||||
lines = append(lines, "")
|
||||
}
|
||||
|
||||
if hasRole(desired.Roles, fluentBitRoleHTTPDNS) {
|
||||
lines = append(lines,
|
||||
"[OUTPUT]",
|
||||
" Name http",
|
||||
" Match app.httpdns.logs",
|
||||
" Host "+desired.ClickHouse.Host,
|
||||
" Port "+strconv.Itoa(desired.ClickHouse.Port),
|
||||
" URI /?query="+insertHTTPDNS,
|
||||
" Format json_lines",
|
||||
" http_user ${CH_USER}",
|
||||
" http_passwd ${CH_PASSWORD}",
|
||||
" json_date_key timestamp",
|
||||
" json_date_format epoch",
|
||||
" workers 2",
|
||||
" net.keepalive On",
|
||||
" Retry_Limit False",
|
||||
)
|
||||
if useTLS {
|
||||
lines = append(lines, " tls On")
|
||||
if desired.ClickHouse.TLSSkipVerify {
|
||||
lines = append(lines, " tls.verify Off")
|
||||
} else {
|
||||
lines = append(lines, " tls.verify On")
|
||||
}
|
||||
if strings.TrimSpace(desired.ClickHouse.TLSServerName) != "" {
|
||||
lines = append(lines, " tls.vhost "+strings.TrimSpace(desired.ClickHouse.TLSServerName))
|
||||
}
|
||||
}
|
||||
lines = append(lines, "")
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n"), nil
|
||||
}
|
||||
|
||||
func renderManagedEnv(ch *systemconfigs.ClickHouseSetting) string {
|
||||
user := "default"
|
||||
password := ""
|
||||
if ch != nil {
|
||||
if strings.TrimSpace(ch.User) != "" {
|
||||
user = strings.TrimSpace(ch.User)
|
||||
}
|
||||
password = ch.Password
|
||||
}
|
||||
return "CH_USER=" + strconv.Quote(user) + "\n" +
|
||||
"CH_PASSWORD=" + strconv.Quote(password) + "\n"
|
||||
}
|
||||
|
||||
func renderManagedMeta(desired *fluentBitDesiredConfig, configContent string, parserContent string, envContent string) (string, *fluentBitManagedMeta, error) {
|
||||
hashInput := configContent + "\n---\n" + parserContent + "\n---\n" + envContent + "\n---\n" + strings.Join(desired.Roles, ",")
|
||||
hashBytes := sha256.Sum256([]byte(hashInput))
|
||||
hash := fmt.Sprintf("%x", hashBytes[:])
|
||||
|
||||
meta := &fluentBitManagedMeta{
|
||||
Roles: desired.Roles,
|
||||
Hash: hash,
|
||||
UpdatedAt: time.Now().Unix(),
|
||||
SourceVersion: teaconst.Version,
|
||||
}
|
||||
data, err := json.MarshalIndent(meta, "", " ")
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("encode fluent-bit managed metadata failed: %w", err)
|
||||
}
|
||||
return string(data) + "\n", meta, nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) copyLocalFileToRemote(tempDir string, localPath string, remotePath string, mode os.FileMode) error {
|
||||
tempFile := tempDir + "/" + filepath.Base(remotePath)
|
||||
if err := this.client.Copy(localPath, tempFile, mode); err != nil {
|
||||
return fmt.Errorf("upload fluent-bit file '%s' failed: %w", localPath, err)
|
||||
}
|
||||
_, stderr, err := this.client.Exec("cp -f " + shQuote(tempFile) + " " + shQuote(remotePath) + " && chmod " + fmt.Sprintf("%04o", mode) + " " + shQuote(remotePath))
|
||||
if err != nil {
|
||||
return fmt.Errorf("install remote fluent-bit file '%s' failed: %w, stderr: %s", remotePath, err, stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) writeRemoteFileByTemp(tempDir string, remotePath string, content string, mode os.FileMode) error {
|
||||
tempFile := tempDir + "/" + filepath.Base(remotePath) + ".tmp"
|
||||
if _, err := this.client.WriteFile(tempFile, []byte(content)); err != nil {
|
||||
return fmt.Errorf("write temp fluent-bit file '%s' failed: %w", tempFile, err)
|
||||
}
|
||||
|
||||
_, stderr, err := this.client.Exec("cp -f " + shQuote(tempFile) + " " + shQuote(remotePath) + " && chmod " + fmt.Sprintf("%04o", mode) + " " + shQuote(remotePath))
|
||||
if err != nil {
|
||||
return fmt.Errorf("write managed fluent-bit file '%s' failed: %w, stderr: %s", remotePath, err, stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) ensureFluentBitService(tempDir string, binPath string, configChanged bool) error {
|
||||
_, _, _ = this.client.Exec("if command -v systemctl >/dev/null 2>&1 && [ ! -f /etc/systemd/system/" + fluentBitServiceName + ".service ] && [ ! -f /lib/systemd/system/" + fluentBitServiceName + ".service ]; then " +
|
||||
"cat > /etc/systemd/system/" + fluentBitServiceName + ".service <<'EOF'\n" +
|
||||
"[Unit]\n" +
|
||||
"Description=Fluent Bit\n" +
|
||||
"After=network.target\n" +
|
||||
"\n" +
|
||||
"[Service]\n" +
|
||||
"ExecStart=" + binPath + " -c " + fluentBitMainConfigFile + "\n" +
|
||||
"Restart=always\n" +
|
||||
"RestartSec=5\n" +
|
||||
"\n" +
|
||||
"[Install]\n" +
|
||||
"WantedBy=multi-user.target\n" +
|
||||
"EOF\n" +
|
||||
"fi")
|
||||
|
||||
stdout, _, err := this.client.Exec("if command -v systemctl >/dev/null 2>&1; then echo 1; else echo 0; fi")
|
||||
if err != nil {
|
||||
return fmt.Errorf("check systemctl failed: %w", err)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(stdout) == "1" {
|
||||
dropInChanged, err := this.ensureServiceDropIn(tempDir, binPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
restartRequired := configChanged || dropInChanged
|
||||
_, stderr, err := this.client.Exec("systemctl daemon-reload; systemctl enable " + fluentBitServiceName + " >/dev/null 2>&1 || true; " +
|
||||
"if systemctl is-active " + fluentBitServiceName + " >/dev/null 2>&1; then " +
|
||||
"if [ \"" + boolToString(restartRequired) + "\" = \"1\" ]; then systemctl restart " + fluentBitServiceName + "; fi; " +
|
||||
"else systemctl start " + fluentBitServiceName + "; fi")
|
||||
if err != nil {
|
||||
return fmt.Errorf("ensure fluent-bit service failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if configChanged {
|
||||
_, _, _ = this.client.Exec("pkill -f \"fluent-bit.*fluent-bit.conf\" >/dev/null 2>&1 || true")
|
||||
}
|
||||
|
||||
_, _, runningErr := this.client.Exec("pgrep -f \"fluent-bit.*fluent-bit.conf\" >/dev/null 2>&1")
|
||||
if runningErr != nil {
|
||||
startCmd := "set -a; [ -f " + shQuote(fluentBitManagedEnvFile) + " ] && . " + shQuote(fluentBitManagedEnvFile) + "; set +a; " +
|
||||
shQuote(binPath) + " -c " + shQuote(fluentBitMainConfigFile) + " >/dev/null 2>&1 &"
|
||||
_, stderr, err := this.client.Exec(startCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("start fluent-bit without systemd failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) ensureServiceDropIn(tempDir string, binPath string) (bool, error) {
|
||||
_, stderr, err := this.client.Exec("mkdir -p " + shQuote(fluentBitDropInDir))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("prepare fluent-bit drop-in dir failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
|
||||
content := "[Service]\n" +
|
||||
"EnvironmentFile=-" + fluentBitManagedEnvFile + "\n" +
|
||||
"ExecStart=\n" +
|
||||
"ExecStart=" + binPath + " -c " + fluentBitMainConfigFile + "\n"
|
||||
|
||||
existing, _, _ := this.client.Exec("if [ -f " + shQuote(fluentBitDropInFile) + " ]; then cat " + shQuote(fluentBitDropInFile) + "; fi")
|
||||
if existing == content {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := this.writeRemoteFileByTemp(tempDir, fluentBitDropInFile, content, 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) remoteFileExists(path string) (bool, error) {
|
||||
stdout, stderr, err := this.client.Exec("if [ -f " + shQuote(path) + " ]; then echo 1; else echo 0; fi")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("check remote file '%s' failed: %w, stderr: %s", path, err, stderr)
|
||||
}
|
||||
return strings.TrimSpace(stdout) == "1", nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) remoteFileContains(path string, pattern string) (bool, error) {
|
||||
stdout, stderr, err := this.client.Exec("if grep -F " + shQuote(pattern) + " " + shQuote(path) + " >/dev/null 2>&1; then echo 1; else echo 0; fi")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("check remote file content '%s' failed: %w, stderr: %s", path, err, stderr)
|
||||
}
|
||||
return strings.TrimSpace(stdout) == "1", nil
|
||||
}
|
||||
|
||||
func shQuote(value string) string {
|
||||
if value == "" {
|
||||
return "''"
|
||||
}
|
||||
return "'" + strings.ReplaceAll(value, "'", "'\"'\"'") + "'"
|
||||
}
|
||||
|
||||
func boolToString(v bool) string {
|
||||
if v {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
@@ -279,3 +279,27 @@ func (this *BaseInstaller) uname() (uname string) {
|
||||
|
||||
return "x86_64 GNU/Linux"
|
||||
}
|
||||
|
||||
func parseOSRelease(content string) map[string]string {
|
||||
result := map[string]string{}
|
||||
lines := strings.Split(content, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") || !strings.Contains(line, "=") {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
value = strings.Trim(value, "\"")
|
||||
result[key] = value
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func shQuote(s string) string {
|
||||
if len(s) == 0 {
|
||||
return "''"
|
||||
}
|
||||
return "'" + strings.ReplaceAll(s, "'", "'\"'\"'") + "'"
|
||||
}
|
||||
|
||||
@@ -131,11 +131,6 @@ https.key: "${keyFile}"`)
|
||||
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 "
|
||||
|
||||
|
||||
@@ -137,12 +137,6 @@ secret: "${nodeSecret}"`)
|
||||
}
|
||||
}
|
||||
|
||||
// 在线安装/更新 Fluent Bit(与边缘节点安装流程联动)
|
||||
err = this.SetupFluentBit(nodeconfigs.NodeRoleNode)
|
||||
if err != nil {
|
||||
installStatus.ErrorCode = "SETUP_FLUENT_BIT_FAILED"
|
||||
return fmt.Errorf("setup fluent-bit failed: %w", err)
|
||||
}
|
||||
|
||||
// 测试
|
||||
_, stderr, err = this.client.Exec(dir + "/edge-node/bin/edge-node test")
|
||||
|
||||
@@ -139,12 +139,6 @@ secret: "${nodeSecret}"`)
|
||||
}
|
||||
}
|
||||
|
||||
// 在线安装/更新 Fluent Bit(与 DNS 节点安装流程联动)
|
||||
err = this.SetupFluentBit(nodeconfigs.NodeRoleDNS)
|
||||
if err != nil {
|
||||
installStatus.ErrorCode = "SETUP_FLUENT_BIT_FAILED"
|
||||
return fmt.Errorf("setup fluent-bit failed: %w", err)
|
||||
}
|
||||
|
||||
// 测试
|
||||
_, stderr, err = this.client.Exec(dir + "/edge-dns/bin/edge-dns test")
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/accounts"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/anti-ddos"
|
||||
httpdnsServices "github.com/TeaOSLab/EdgeAPI/internal/rpc/services/httpdns"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/nameservers"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/posts"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/reporters"
|
||||
@@ -313,4 +314,9 @@ func APINodeServicesRegister(node *APINode, server *grpc.Server) {
|
||||
pb.RegisterPostCategoryServiceServer(server, instance)
|
||||
node.rest(instance)
|
||||
}
|
||||
{
|
||||
var instance = node.serviceInstance(&httpdnsServices.HTTPDNSBoardService{}).(*httpdnsServices.HTTPDNSBoardService)
|
||||
pb.RegisterHTTPDNSBoardServiceServer(server, instance)
|
||||
node.rest(instance)
|
||||
}
|
||||
}
|
||||
|
||||
324
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_board.go
Normal file
324
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_board.go
Normal file
@@ -0,0 +1,324 @@
|
||||
package httpdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/clickhouse"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
// HTTPDNSBoardService HTTPDNS 仪表盘服务
|
||||
type HTTPDNSBoardService struct {
|
||||
services.BaseService
|
||||
}
|
||||
|
||||
// ComposeHTTPDNSBoard 组合看板数据
|
||||
func (this *HTTPDNSBoardService) ComposeHTTPDNSBoard(ctx context.Context, req *pb.ComposeHTTPDNSBoardRequest) (*pb.ComposeHTTPDNSBoardResponse, error) {
|
||||
_, err := this.ValidateAdmin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx := this.NullTx()
|
||||
result := &pb.ComposeHTTPDNSBoardResponse{}
|
||||
|
||||
countApps, err := models.SharedHTTPDNSAppDAO.CountEnabledApps(tx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.CountApps = countApps
|
||||
|
||||
countDomains, err := models.SharedHTTPDNSDomainDAO.CountEnabledDomains(tx, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.CountDomains = countDomains
|
||||
|
||||
countClusters, err := models.SharedHTTPDNSClusterDAO.CountEnabledClusters(tx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.CountClusters = countClusters
|
||||
|
||||
allNodes, err := models.SharedHTTPDNSNodeDAO.ListEnabledNodes(tx, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.CountNodes = int64(len(allNodes))
|
||||
|
||||
var countOffline int64
|
||||
for _, node := range allNodes {
|
||||
if !node.IsActive {
|
||||
countOffline++
|
||||
}
|
||||
}
|
||||
result.CountOfflineNodes = countOffline
|
||||
|
||||
hourFrom := timeutil.Format("YmdH", time.Now().Add(-23*time.Hour))
|
||||
hourTo := timeutil.Format("YmdH")
|
||||
dayFrom := timeutil.Format("Ymd", time.Now().AddDate(0, 0, -14))
|
||||
dayTo := timeutil.Format("Ymd")
|
||||
todayFrom := timeutil.Format("Ymd", time.Now().Add(-24*time.Hour))
|
||||
|
||||
store := clickhouse.NewHTTPDNSAccessLogsStore()
|
||||
if store.Client().IsConfigured() {
|
||||
err = this.composeTrafficAndRanksFromClickHouse(ctx, tx, store, result, hourFrom, hourTo, dayFrom, dayTo, todayFrom)
|
||||
}
|
||||
if err != nil || !store.Client().IsConfigured() {
|
||||
err = this.composeTrafficAndRanksFromMySQL(tx, result, hourFrom, hourTo, dayFrom, dayTo, todayFrom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = this.fillNodeValues(tx, result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (this *HTTPDNSBoardService) composeTrafficAndRanksFromClickHouse(ctx context.Context, tx *dbs.Tx, store *clickhouse.HTTPDNSAccessLogsStore, result *pb.ComposeHTTPDNSBoardResponse, hourFrom string, hourTo string, dayFrom string, dayTo string, todayFrom string) error {
|
||||
hourlyStats, err := store.FindHourlyStats(ctx, hourFrom, hourTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hourlyMap := map[string]*clickhouse.HTTPDNSAccessLogHourlyStat{}
|
||||
for _, stat := range hourlyStats {
|
||||
hourlyMap[stat.Hour] = stat
|
||||
}
|
||||
hours, err := utils.RangeHours(hourFrom, hourTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, hour := range hours {
|
||||
stat, ok := hourlyMap[hour]
|
||||
if ok {
|
||||
result.HourlyTrafficStats = append(result.HourlyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_HourlyTrafficStat{
|
||||
Hour: hour,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
} else {
|
||||
result.HourlyTrafficStats = append(result.HourlyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_HourlyTrafficStat{Hour: hour})
|
||||
}
|
||||
}
|
||||
|
||||
dailyStats, err := store.FindDailyStats(ctx, dayFrom, dayTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dailyMap := map[string]*clickhouse.HTTPDNSAccessLogDailyStat{}
|
||||
for _, stat := range dailyStats {
|
||||
dailyMap[stat.Day] = stat
|
||||
}
|
||||
days, err := utils.RangeDays(dayFrom, dayTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, day := range days {
|
||||
stat, ok := dailyMap[day]
|
||||
if ok {
|
||||
result.DailyTrafficStats = append(result.DailyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_DailyTrafficStat{
|
||||
Day: day,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
} else {
|
||||
result.DailyTrafficStats = append(result.DailyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_DailyTrafficStat{Day: day})
|
||||
}
|
||||
}
|
||||
|
||||
topAppStats, err := store.ListTopApps(ctx, todayFrom, dayTo, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stat := range topAppStats {
|
||||
appName := stat.AppName
|
||||
if len(appName) == 0 {
|
||||
appName = stat.AppId
|
||||
}
|
||||
result.TopAppStats = append(result.TopAppStats, &pb.ComposeHTTPDNSBoardResponse_TopAppStat{
|
||||
AppId: 0,
|
||||
AppName: appName,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
}
|
||||
|
||||
topDomainStats, err := store.ListTopDomains(ctx, todayFrom, dayTo, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stat := range topDomainStats {
|
||||
result.TopDomainStats = append(result.TopDomainStats, &pb.ComposeHTTPDNSBoardResponse_TopDomainStat{
|
||||
DomainName: stat.Domain,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
}
|
||||
|
||||
topNodeStats, err := store.ListTopNodes(ctx, todayFrom, dayTo, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stat := range topNodeStats {
|
||||
nodeName := ""
|
||||
node, nodeErr := models.SharedHTTPDNSNodeDAO.FindEnabledNode(tx, int64(stat.NodeId))
|
||||
if nodeErr == nil && node != nil {
|
||||
nodeName = node.Name
|
||||
}
|
||||
if len(nodeName) == 0 {
|
||||
continue
|
||||
}
|
||||
result.TopNodeStats = append(result.TopNodeStats, &pb.ComposeHTTPDNSBoardResponse_TopNodeStat{
|
||||
ClusterId: int64(stat.ClusterId),
|
||||
NodeId: int64(stat.NodeId),
|
||||
NodeName: nodeName,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *HTTPDNSBoardService) composeTrafficAndRanksFromMySQL(tx *dbs.Tx, result *pb.ComposeHTTPDNSBoardResponse, hourFrom string, hourTo string, dayFrom string, dayTo string, todayFrom string) error {
|
||||
hourlyStats, err := models.SharedHTTPDNSAccessLogDAO.FindHourlyStats(tx, hourFrom, hourTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hourlyMap := map[string]*models.HTTPDNSAccessLogHourlyStat{}
|
||||
for _, stat := range hourlyStats {
|
||||
hourlyMap[stat.Hour] = stat
|
||||
}
|
||||
hours, err := utils.RangeHours(hourFrom, hourTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, hour := range hours {
|
||||
stat, ok := hourlyMap[hour]
|
||||
if ok {
|
||||
result.HourlyTrafficStats = append(result.HourlyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_HourlyTrafficStat{
|
||||
Hour: hour,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
} else {
|
||||
result.HourlyTrafficStats = append(result.HourlyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_HourlyTrafficStat{Hour: hour})
|
||||
}
|
||||
}
|
||||
|
||||
dailyStats, err := models.SharedHTTPDNSAccessLogDAO.FindDailyStats(tx, dayFrom, dayTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dailyMap := map[string]*models.HTTPDNSAccessLogDailyStat{}
|
||||
for _, stat := range dailyStats {
|
||||
dailyMap[stat.Day] = stat
|
||||
}
|
||||
days, err := utils.RangeDays(dayFrom, dayTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, day := range days {
|
||||
stat, ok := dailyMap[day]
|
||||
if ok {
|
||||
result.DailyTrafficStats = append(result.DailyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_DailyTrafficStat{
|
||||
Day: day,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
} else {
|
||||
result.DailyTrafficStats = append(result.DailyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_DailyTrafficStat{Day: day})
|
||||
}
|
||||
}
|
||||
|
||||
topAppStats, err := models.SharedHTTPDNSAccessLogDAO.ListTopApps(tx, todayFrom, dayTo, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stat := range topAppStats {
|
||||
appName := stat.AppName
|
||||
if len(appName) == 0 {
|
||||
appName = stat.AppId
|
||||
}
|
||||
result.TopAppStats = append(result.TopAppStats, &pb.ComposeHTTPDNSBoardResponse_TopAppStat{
|
||||
AppId: 0,
|
||||
AppName: appName,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
}
|
||||
|
||||
topDomainStats, err := models.SharedHTTPDNSAccessLogDAO.ListTopDomains(tx, todayFrom, dayTo, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stat := range topDomainStats {
|
||||
result.TopDomainStats = append(result.TopDomainStats, &pb.ComposeHTTPDNSBoardResponse_TopDomainStat{
|
||||
DomainName: stat.Domain,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
}
|
||||
|
||||
topNodeStats, err := models.SharedHTTPDNSAccessLogDAO.ListTopNodes(tx, todayFrom, dayTo, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stat := range topNodeStats {
|
||||
nodeName := ""
|
||||
node, nodeErr := models.SharedHTTPDNSNodeDAO.FindEnabledNode(tx, int64(stat.NodeId))
|
||||
if nodeErr == nil && node != nil {
|
||||
nodeName = node.Name
|
||||
}
|
||||
if len(nodeName) == 0 {
|
||||
continue
|
||||
}
|
||||
result.TopNodeStats = append(result.TopNodeStats, &pb.ComposeHTTPDNSBoardResponse_TopNodeStat{
|
||||
ClusterId: int64(stat.ClusterId),
|
||||
NodeId: int64(stat.NodeId),
|
||||
NodeName: nodeName,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *HTTPDNSBoardService) fillNodeValues(tx *dbs.Tx, result *pb.ComposeHTTPDNSBoardResponse) error {
|
||||
cpuValues, err := models.SharedNodeValueDAO.ListValuesForHTTPDNSNodes(tx, nodeconfigs.NodeValueItemCPU, "usage", nodeconfigs.NodeValueRangeMinute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range cpuValues {
|
||||
result.CpuNodeValues = append(result.CpuNodeValues, &pb.NodeValue{
|
||||
ValueJSON: v.Value,
|
||||
CreatedAt: int64(v.CreatedAt),
|
||||
})
|
||||
}
|
||||
|
||||
memoryValues, err := models.SharedNodeValueDAO.ListValuesForHTTPDNSNodes(tx, nodeconfigs.NodeValueItemMemory, "usage", nodeconfigs.NodeValueRangeMinute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range memoryValues {
|
||||
result.MemoryNodeValues = append(result.MemoryNodeValues, &pb.NodeValue{
|
||||
ValueJSON: v.Value,
|
||||
CreatedAt: int64(v.CreatedAt),
|
||||
})
|
||||
}
|
||||
|
||||
loadValues, err := models.SharedNodeValueDAO.ListValuesForHTTPDNSNodes(tx, nodeconfigs.NodeValueItemLoad, "load1m", nodeconfigs.NodeValueRangeMinute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range loadValues {
|
||||
result.LoadNodeValues = append(result.LoadNodeValues, &pb.NodeValue{
|
||||
ValueJSON: v.Value,
|
||||
CreatedAt: int64(v.CreatedAt),
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -17,7 +17,7 @@ type NodeValueService struct {
|
||||
|
||||
// CreateNodeValue 记录数据
|
||||
func (this *NodeValueService) CreateNodeValue(ctx context.Context, req *pb.CreateNodeValueRequest) (*pb.RPCSuccess, error) {
|
||||
role, nodeId, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeNode, rpcutils.UserTypeDNS, rpcutils.UserTypeUser)
|
||||
role, nodeId, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeNode, rpcutils.UserTypeDNS, rpcutils.UserTypeUser, rpcutils.UserTypeHTTPDNS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -30,6 +30,8 @@ func (this *NodeValueService) CreateNodeValue(ctx context.Context, req *pb.Creat
|
||||
clusterId, err = models.SharedNodeDAO.FindNodeClusterId(tx, nodeId)
|
||||
case rpcutils.UserTypeDNS:
|
||||
clusterId, err = models.SharedNSNodeDAO.FindNodeClusterId(tx, nodeId)
|
||||
case rpcutils.UserTypeHTTPDNS:
|
||||
clusterId, err = models.SharedHTTPDNSNodeDAO.FindNodeClusterId(tx, nodeId)
|
||||
case rpcutils.UserTypeUser:
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -97,12 +97,10 @@ function build() {
|
||||
|
||||
# create dir & copy files
|
||||
echo "copying ..."
|
||||
if [ ! -d "$DIST" ]; then
|
||||
mkdir "$DIST"
|
||||
mkdir "$DIST"/bin
|
||||
mkdir "$DIST"/configs
|
||||
mkdir "$DIST"/logs
|
||||
fi
|
||||
rm -rf "$DIST"
|
||||
mkdir -p "$DIST"/bin
|
||||
mkdir -p "$DIST"/configs
|
||||
mkdir -p "$DIST"/logs
|
||||
|
||||
cp -R "$ROOT"/../web "$DIST"/
|
||||
rm -f "$DIST"/web/tmp/*
|
||||
@@ -135,30 +133,6 @@ function build() {
|
||||
rm -f "$(basename "$EDGE_API_ZIP_FILE")"
|
||||
|
||||
# ensure edge-api package always contains fluent-bit runtime assets/packages
|
||||
FLUENT_ROOT="$ROOT/../../deploy/fluent-bit"
|
||||
FLUENT_DIST="$DIST/edge-api/deploy/fluent-bit"
|
||||
if [ -d "$FLUENT_ROOT" ]; then
|
||||
verify_fluent_bit_package_matrix "$FLUENT_ROOT" "$ARCH" || exit 1
|
||||
rm -rf "$FLUENT_DIST"
|
||||
mkdir -p "$FLUENT_DIST"
|
||||
|
||||
FLUENT_FILES=(
|
||||
"parsers.conf"
|
||||
)
|
||||
for file in "${FLUENT_FILES[@]}"; do
|
||||
if [ -f "$FLUENT_ROOT/$file" ]; then
|
||||
cp "$FLUENT_ROOT/$file" "$FLUENT_DIST/"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -d "$FLUENT_ROOT/packages" ]; then
|
||||
cp -R "$FLUENT_ROOT/packages" "$FLUENT_DIST/"
|
||||
fi
|
||||
|
||||
rm -f "$FLUENT_DIST/.gitignore"
|
||||
rm -f "$FLUENT_DIST"/logs.db*
|
||||
rm -rf "$FLUENT_DIST/storage"
|
||||
fi
|
||||
|
||||
# 鍒犻櫎 MaxMind 鏁版嵁搴撴枃浠讹紙浣跨敤宓屽叆鐨勬暟鎹簱锛屼笉闇€瑕佸閮ㄦ枃浠讹級
|
||||
find . -name "*.mmdb" -type f -delete
|
||||
@@ -220,39 +194,6 @@ function build() {
|
||||
echo "[done]"
|
||||
}
|
||||
|
||||
function verify_fluent_bit_package_matrix() {
|
||||
FLUENT_ROOT=$1
|
||||
ARCH=$2
|
||||
REQUIRED_FILES=()
|
||||
if [ "$ARCH" = "amd64" ]; then
|
||||
REQUIRED_FILES=(
|
||||
"packages/linux-amd64/fluent-bit_4.2.2_amd64.deb"
|
||||
"packages/linux-amd64/fluent-bit-4.2.2-1.x86_64.rpm"
|
||||
)
|
||||
elif [ "$ARCH" = "arm64" ]; then
|
||||
REQUIRED_FILES=(
|
||||
"packages/linux-arm64/fluent-bit_4.2.2_arm64.deb"
|
||||
"packages/linux-arm64/fluent-bit-4.2.2-1.aarch64.rpm"
|
||||
)
|
||||
else
|
||||
echo "[error] unsupported arch for fluent-bit package validation: $ARCH"
|
||||
return 1
|
||||
fi
|
||||
|
||||
MISSING=0
|
||||
for FILE in "${REQUIRED_FILES[@]}"; do
|
||||
if [ ! -f "$FLUENT_ROOT/$FILE" ]; then
|
||||
echo "[error] fluent-bit matrix package missing: $FLUENT_ROOT/$FILE"
|
||||
MISSING=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$MISSING" -ne 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function lookup-version() {
|
||||
FILE=$1
|
||||
VERSION_DATA=$(cat "$FILE")
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.4.9" //1.3.9
|
||||
Version = "1.5.0" //1.3.9
|
||||
|
||||
APINodeVersion = "1.4.9" //1.3.9
|
||||
APINodeVersion = "1.5.0" //1.3.9
|
||||
|
||||
ProductName = "Edge Admin"
|
||||
ProcessName = "edge-admin"
|
||||
|
||||
@@ -248,3 +248,7 @@ func (this *RPCClient) PostCategoryRPC() pb.PostCategoryServiceClient {
|
||||
func (this *RPCClient) PostRPC() pb.PostServiceClient {
|
||||
return pb.NewPostServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) HTTPDNSBoardRPC() pb.HTTPDNSBoardServiceClient {
|
||||
return pb.NewHTTPDNSBoardServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
nodethresholds "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/settings/thresholds"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/cc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/http3"
|
||||
networksecurity "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/network-security"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/pages"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/thresholds"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/settings/uam"
|
||||
@@ -53,7 +52,6 @@ func init() {
|
||||
GetPost("/thresholds", new(thresholds.IndexAction)).
|
||||
|
||||
//
|
||||
GetPost("/network-security", new(networksecurity.IndexAction)).
|
||||
|
||||
// 节点设置相关
|
||||
Prefix("/clusters/cluster/node/settings").
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus
|
||||
|
||||
package networksecurity
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "setting", "index")
|
||||
this.SecondMenu("networkSecurity")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
}) {
|
||||
policyResp, err := this.RPC().NodeClusterRPC().FindNodeClusterNetworkSecurityPolicy(this.AdminContext(), &pb.FindNodeClusterNetworkSecurityPolicyRequest{
|
||||
NodeClusterId: params.ClusterId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var policy = nodeconfigs.NewNetworkSecurityPolicy()
|
||||
if len(policyResp.NetworkSecurityPolicyJSON) > 0 {
|
||||
err = json.Unmarshal(policyResp.NetworkSecurityPolicyJSON, policy)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
this.Data["policy"] = policy
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunPost(params struct {
|
||||
ClusterId int64
|
||||
Status string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
policyResp, err := this.RPC().NodeClusterRPC().FindNodeClusterNetworkSecurityPolicy(this.AdminContext(), &pb.FindNodeClusterNetworkSecurityPolicyRequest{
|
||||
NodeClusterId: params.ClusterId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var policy = nodeconfigs.NewNetworkSecurityPolicy()
|
||||
if len(policyResp.NetworkSecurityPolicyJSON) > 0 {
|
||||
err = json.Unmarshal(policyResp.NetworkSecurityPolicyJSON, policy)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
policy.Status = params.Status
|
||||
|
||||
err = policy.Init()
|
||||
if err != nil {
|
||||
this.Fail("配置校验失败:" + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
policyJSON, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = this.RPC().NodeClusterRPC().UpdateNodeClusterNetworkSecurityPolicy(this.AdminContext(), &pb.UpdateNodeClusterNetworkSecurityPolicyRequest{
|
||||
NodeClusterId: params.ClusterId,
|
||||
NetworkSecurityPolicyJSON: policyJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -44,17 +44,10 @@ func (this *ClusterHelper) filterMenuItems1(items []maps.Map, info *pb.FindEnabl
|
||||
|
||||
func (this *ClusterHelper) filterMenuItems2(items []maps.Map, info *pb.FindEnabledNodeClusterConfigInfoResponse, clusterIdString string, selectedItem string, actionPtr actions.ActionWrapper) []maps.Map {
|
||||
if teaconst.IsPlus {
|
||||
items = append(items, maps.Map{
|
||||
"name": this.Lang(actionPtr, codes.NodeClusterMenu_SettingSecurityPolicy),
|
||||
"url": "/clusters/cluster/settings/network-security?clusterId=" + clusterIdString,
|
||||
"isActive": selectedItem == "networkSecurity",
|
||||
"isOn": info != nil && info.HasNetworkSecurityPolicy, // TODO 将来 加入 info.HasDDoSProtection
|
||||
})
|
||||
|
||||
items = append(items, maps.Map{
|
||||
"name": "-",
|
||||
})
|
||||
if plusutils.CheckComponent(plusutils.ComponentCodeScheduling) {
|
||||
items = append(items, maps.Map{
|
||||
"name": "-",
|
||||
})
|
||||
items = append(items, maps.Map{
|
||||
"name": this.Lang(actionPtr, codes.NodeClusterMenu_SettingSchedule),
|
||||
"url": "/clusters/cluster/settings/schedule?clusterId=" + clusterIdString,
|
||||
@@ -89,14 +82,12 @@ func (this *ClusterHelper) filterMenuItems2(items []maps.Map, info *pb.FindEnabl
|
||||
"isOn": info != nil && info.HasSystemServices,
|
||||
})
|
||||
|
||||
{
|
||||
items = append(items, maps.Map{
|
||||
"name": this.Lang(actionPtr, codes.NodeClusterMenu_SettingTOA),
|
||||
"url": "/clusters/cluster/settings/toa?clusterId=" + clusterIdString,
|
||||
"isActive": selectedItem == "toa",
|
||||
"isOn": info != nil && info.IsTOAEnabled,
|
||||
})
|
||||
}
|
||||
items = append(items, maps.Map{
|
||||
"name": this.Lang(actionPtr, codes.NodeClusterMenu_SettingTOA),
|
||||
"url": "/clusters/cluster/settings/toa?clusterId=" + clusterIdString,
|
||||
"isActive": selectedItem == "toa",
|
||||
"isOn": info != nil && info.IsTOAEnabled,
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build plus
|
||||
|
||||
package boards
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dashboard/boards/boardutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type HTTPDNSAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAction) Init() {
|
||||
this.Nav("", "", "httpdns")
|
||||
this.ViewDir("@default")
|
||||
this.View("dashboard/boards/httpdns")
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAction) RunGet(params struct{}) {
|
||||
if !teaconst.IsPlus {
|
||||
this.RedirectURL("/dashboard")
|
||||
return
|
||||
}
|
||||
|
||||
err := boardutils.InitBoard(this.Parent())
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["board"] = maps.Map{
|
||||
"countApps": 0,
|
||||
"countDomains": 0,
|
||||
"countClusters": 0,
|
||||
"countNodes": 0,
|
||||
"countOfflineNodes": 0,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAction) RunPost(params struct{}) {
|
||||
resp, err := this.RPC().HTTPDNSBoardRPC().ComposeHTTPDNSBoard(this.AdminContext(), &pb.ComposeHTTPDNSBoardRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["board"] = maps.Map{
|
||||
"countApps": resp.CountApps,
|
||||
"countDomains": resp.CountDomains,
|
||||
"countClusters": resp.CountClusters,
|
||||
"countNodes": resp.CountNodes,
|
||||
"countOfflineNodes": resp.CountOfflineNodes,
|
||||
}
|
||||
|
||||
{
|
||||
var statMaps = []maps.Map{}
|
||||
for _, stat := range resp.HourlyTrafficStats {
|
||||
statMaps = append(statMaps, maps.Map{
|
||||
"day": stat.Hour[4:6] + "月" + stat.Hour[6:8] + "日",
|
||||
"hour": stat.Hour[8:],
|
||||
"countRequests": stat.CountRequests,
|
||||
"bytes": stat.Bytes,
|
||||
})
|
||||
}
|
||||
this.Data["hourlyStats"] = statMaps
|
||||
}
|
||||
|
||||
{
|
||||
var statMaps = []maps.Map{}
|
||||
for _, stat := range resp.DailyTrafficStats {
|
||||
statMaps = append(statMaps, maps.Map{
|
||||
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
|
||||
"countRequests": stat.CountRequests,
|
||||
"bytes": stat.Bytes,
|
||||
})
|
||||
}
|
||||
this.Data["dailyStats"] = statMaps
|
||||
}
|
||||
|
||||
{
|
||||
var statMaps = []maps.Map{}
|
||||
for _, stat := range resp.TopAppStats {
|
||||
statMaps = append(statMaps, maps.Map{
|
||||
"appId": stat.AppId,
|
||||
"appName": stat.AppName,
|
||||
"countRequests": stat.CountRequests,
|
||||
"bytes": stat.Bytes,
|
||||
})
|
||||
}
|
||||
this.Data["topAppStats"] = statMaps
|
||||
}
|
||||
|
||||
{
|
||||
var statMaps = []maps.Map{}
|
||||
for _, stat := range resp.TopDomainStats {
|
||||
statMaps = append(statMaps, maps.Map{
|
||||
"domainId": stat.DomainId,
|
||||
"domainName": stat.DomainName,
|
||||
"countRequests": stat.CountRequests,
|
||||
"bytes": stat.Bytes,
|
||||
})
|
||||
}
|
||||
this.Data["topDomainStats"] = statMaps
|
||||
}
|
||||
|
||||
{
|
||||
var statMaps = []maps.Map{}
|
||||
for _, stat := range resp.TopNodeStats {
|
||||
statMaps = append(statMaps, maps.Map{
|
||||
"clusterId": stat.ClusterId,
|
||||
"nodeId": stat.NodeId,
|
||||
"nodeName": stat.NodeName,
|
||||
"countRequests": stat.CountRequests,
|
||||
"bytes": stat.Bytes,
|
||||
})
|
||||
}
|
||||
this.Data["topNodeStats"] = statMaps
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"regexp"
|
||||
"time"
|
||||
@@ -409,5 +410,55 @@ func (this *IndexAction) RunPost(params struct {
|
||||
}
|
||||
this.Data["countWeakAdmins"] = countWeakAdminsResp.Count
|
||||
|
||||
upgradeConfig, err := configloaders.LoadUpgradeConfig()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["autoUpgrade"] = upgradeConfig.AutoUpgrade
|
||||
|
||||
httpdnsNodeUpgradeInfo, err := this.composeHTTPDNSNodeUpgradeInfo()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["httpdnsNodeUpgradeInfo"] = httpdnsNodeUpgradeInfo
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
func (this *IndexAction) composeHTTPDNSNodeUpgradeInfo() (maps.Map, error) {
|
||||
clustersResp, err := this.RPC().HTTPDNSClusterRPC().ListHTTPDNSClusters(this.AdminContext(), &pb.ListHTTPDNSClustersRequest{
|
||||
Offset: 0,
|
||||
Size: 10000,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
count := 0
|
||||
version := ""
|
||||
for _, cluster := range clustersResp.Clusters {
|
||||
resp, err := this.RPC().HTTPDNSNodeRPC().FindAllUpgradeHTTPDNSNodesWithClusterId(this.AdminContext(), &pb.FindAllUpgradeHTTPDNSNodesWithClusterIdRequest{
|
||||
ClusterId: cluster.Id,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
count += len(resp.Nodes)
|
||||
for _, nodeUpgrade := range resp.Nodes {
|
||||
if len(nodeUpgrade.NewVersion) == 0 {
|
||||
continue
|
||||
}
|
||||
if len(version) == 0 || stringutil.VersionCompare(version, nodeUpgrade.NewVersion) < 0 {
|
||||
version = nodeUpgrade.NewVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return maps.Map{
|
||||
"count": count,
|
||||
"version": version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -245,5 +245,12 @@ func (this *IndexAction) RunPost(params struct{}) {
|
||||
}
|
||||
this.Data["countWeakAdmins"] = countWeakAdminsResp.Count
|
||||
|
||||
upgradeConfig, err := configloaders.LoadUpgradeConfig()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["autoUpgrade"] = upgradeConfig.AutoUpgrade
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ func init() {
|
||||
Get("/waf", new(boards.WafAction)).
|
||||
Post("/wafLogs", new(boards.WafLogsAction)).
|
||||
GetPost("/dns", new(boards.DnsAction)).
|
||||
GetPost("/httpdns", new(boards.HTTPDNSAction)).
|
||||
Get("/user", new(boards.UserAction)).
|
||||
Get("/events", new(boards.EventsAction)).
|
||||
Post("/readLogs", new(boards.ReadLogsAction)).
|
||||
|
||||
@@ -1,11 +1,110 @@
|
||||
package httpdns
|
||||
|
||||
import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
this.RedirectURL("/httpdns/clusters")
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "httpdns")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
this.Data["board"] = maps.Map{
|
||||
"countApps": 0,
|
||||
"countDomains": 0,
|
||||
"countClusters": 0,
|
||||
"countNodes": 0,
|
||||
"countOfflineNodes": 0,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunPost(params struct{}) {
|
||||
resp, err := this.RPC().HTTPDNSBoardRPC().ComposeHTTPDNSBoard(this.AdminContext(), &pb.ComposeHTTPDNSBoardRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["board"] = maps.Map{
|
||||
"countApps": resp.CountApps,
|
||||
"countDomains": resp.CountDomains,
|
||||
"countClusters": resp.CountClusters,
|
||||
"countNodes": resp.CountNodes,
|
||||
"countOfflineNodes": resp.CountOfflineNodes,
|
||||
}
|
||||
|
||||
{
|
||||
statMaps := []maps.Map{}
|
||||
for _, stat := range resp.HourlyTrafficStats {
|
||||
statMaps = append(statMaps, maps.Map{
|
||||
"day": stat.Hour[4:6] + "月" + stat.Hour[6:8] + "日",
|
||||
"hour": stat.Hour[8:],
|
||||
"countRequests": stat.CountRequests,
|
||||
"bytes": stat.Bytes,
|
||||
})
|
||||
}
|
||||
this.Data["hourlyStats"] = statMaps
|
||||
}
|
||||
|
||||
{
|
||||
statMaps := []maps.Map{}
|
||||
for _, stat := range resp.DailyTrafficStats {
|
||||
statMaps = append(statMaps, maps.Map{
|
||||
"day": stat.Day[4:6] + "月" + stat.Day[6:] + "日",
|
||||
"countRequests": stat.CountRequests,
|
||||
"bytes": stat.Bytes,
|
||||
})
|
||||
}
|
||||
this.Data["dailyStats"] = statMaps
|
||||
}
|
||||
|
||||
{
|
||||
statMaps := []maps.Map{}
|
||||
for _, stat := range resp.TopAppStats {
|
||||
statMaps = append(statMaps, maps.Map{
|
||||
"appId": stat.AppId,
|
||||
"appName": stat.AppName,
|
||||
"countRequests": stat.CountRequests,
|
||||
"bytes": stat.Bytes,
|
||||
})
|
||||
}
|
||||
this.Data["topAppStats"] = statMaps
|
||||
}
|
||||
|
||||
{
|
||||
statMaps := []maps.Map{}
|
||||
for _, stat := range resp.TopDomainStats {
|
||||
statMaps = append(statMaps, maps.Map{
|
||||
"domainId": stat.DomainId,
|
||||
"domainName": stat.DomainName,
|
||||
"countRequests": stat.CountRequests,
|
||||
"bytes": stat.Bytes,
|
||||
})
|
||||
}
|
||||
this.Data["topDomainStats"] = statMaps
|
||||
}
|
||||
|
||||
{
|
||||
statMaps := []maps.Map{}
|
||||
for _, stat := range resp.TopNodeStats {
|
||||
statMaps = append(statMaps, maps.Map{
|
||||
"clusterId": stat.ClusterId,
|
||||
"nodeId": stat.NodeId,
|
||||
"nodeName": stat.NodeName,
|
||||
"countRequests": stat.CountRequests,
|
||||
"bytes": stat.Bytes,
|
||||
})
|
||||
}
|
||||
this.Data["topNodeStats"] = statMaps
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ func init() {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeHttpDNS)).
|
||||
Data("teaMenu", "httpdns").
|
||||
Data("teaSubMenu", "cluster").
|
||||
Data("teaSubMenu", "board").
|
||||
Prefix("/httpdns").
|
||||
Get("", new(IndexAction)).
|
||||
GetPost("", new(IndexAction)).
|
||||
GetPost("/addPortPopup", new(AddPortPopupAction)).
|
||||
EndAll()
|
||||
})
|
||||
|
||||
@@ -25,7 +25,7 @@ func (this *CreatePopupAction) Init() {
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunGet(params struct{}) {
|
||||
var authMethods = []*serverconfigs.HTTPAuthTypeDefinition{}
|
||||
authMethods := []*serverconfigs.HTTPAuthTypeDefinition{}
|
||||
for _, method := range serverconfigs.FindAllHTTPAuthTypes(teaconst.Role) {
|
||||
if !method.IsPlus || (method.IsPlus && teaconst.IsPlus) {
|
||||
authMethods = append(authMethods, method)
|
||||
@@ -59,6 +59,10 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
TypeDTimestampParamName string
|
||||
TypeDLife int
|
||||
|
||||
// TypeE
|
||||
TypeESecret string
|
||||
TypeELife int
|
||||
|
||||
// BasicAuth
|
||||
HttpAuthBasicAuthUsersJSON []byte
|
||||
BasicAuthRealm string
|
||||
@@ -81,29 +85,25 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
Field("type", params.Type).
|
||||
Require("请输入鉴权类型")
|
||||
|
||||
var ref = &serverconfigs.HTTPAuthPolicyRef{IsOn: true}
|
||||
ref := &serverconfigs.HTTPAuthPolicyRef{IsOn: true}
|
||||
var method serverconfigs.HTTPAuthMethodInterface
|
||||
|
||||
// 扩展名
|
||||
var exts = utils.NewStringsStream(params.Exts).
|
||||
exts := utils.NewStringsStream(params.Exts).
|
||||
Map(strings.TrimSpace, strings.ToLower).
|
||||
Filter(utils.FilterNotEmpty).
|
||||
Map(utils.MapAddPrefixFunc(".")).
|
||||
Unique().
|
||||
Result()
|
||||
|
||||
// 域名
|
||||
var domains = []string{}
|
||||
domains := []string{}
|
||||
if len(params.DomainsJSON) > 0 {
|
||||
var rawDomains = []string{}
|
||||
rawDomains := []string{}
|
||||
err := json.Unmarshal(params.DomainsJSON, &rawDomains)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO 如果用户填写了一个网址,应该分析域名并填入
|
||||
|
||||
domains = utils.NewStringsStream(rawDomains).
|
||||
Map(strings.TrimSpace, strings.ToLower).
|
||||
Filter(utils.FilterNotEmpty).
|
||||
@@ -116,11 +116,11 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
params.Must.
|
||||
Field("typeASecret", params.TypeASecret).
|
||||
Require("请输入鉴权密钥").
|
||||
MaxLength(40, "鉴权密钥不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母、数字").
|
||||
MaxLength(40, "鉴权密钥长度不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母和数字").
|
||||
Field("typeASignParamName", params.TypeASignParamName).
|
||||
Require("请输入签名参数").
|
||||
Match(`^[a-zA-Z0-9_]{1,40}$`, "签名参数中只能包含字母、数字、下划线")
|
||||
Match(`^[a-zA-Z0-9_]{1,40}$`, "签名参数中只能包含字母、数字和下划线")
|
||||
|
||||
if params.TypeALife < 0 {
|
||||
params.TypeALife = 0
|
||||
@@ -135,8 +135,8 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
params.Must.
|
||||
Field("typeBSecret", params.TypeBSecret).
|
||||
Require("请输入鉴权密钥").
|
||||
MaxLength(40, "鉴权密钥不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母、数字")
|
||||
MaxLength(40, "鉴权密钥长度不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母和数字")
|
||||
|
||||
method = &serverconfigs.HTTPAuthTypeBMethod{
|
||||
Secret: params.TypeBSecret,
|
||||
@@ -146,8 +146,8 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
params.Must.
|
||||
Field("typeCSecret", params.TypeCSecret).
|
||||
Require("请输入鉴权密钥").
|
||||
MaxLength(40, "鉴权密钥不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母、数字")
|
||||
MaxLength(40, "鉴权密钥长度不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母和数字")
|
||||
|
||||
method = &serverconfigs.HTTPAuthTypeCMethod{
|
||||
Secret: params.TypeCSecret,
|
||||
@@ -157,14 +157,14 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
params.Must.
|
||||
Field("typeDSecret", params.TypeDSecret).
|
||||
Require("请输入鉴权密钥").
|
||||
MaxLength(40, "鉴权密钥不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母、数字").
|
||||
MaxLength(40, "鉴权密钥长度不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母和数字").
|
||||
Field("typeDSignParamName", params.TypeDSignParamName).
|
||||
Require("请输入签名参数").
|
||||
Match(`^[a-zA-Z0-9_]{1,40}$`, "签名参数中只能包含字母、数字、下划线").
|
||||
Match(`^[a-zA-Z0-9_]{1,40}$`, "签名参数中只能包含字母、数字和下划线").
|
||||
Field("typeDTimestampParamName", params.TypeDTimestampParamName).
|
||||
Require("请输入时间戳参数").
|
||||
Match(`^[a-zA-Z0-9_]{1,40}$`, "时间戳参数中只能包含字母、数字、下划线")
|
||||
Require("请输入时间参数").
|
||||
Match(`^[a-zA-Z0-9_]{1,40}$`, "时间参数中只能包含字母、数字和下划线")
|
||||
|
||||
method = &serverconfigs.HTTPAuthTypeDMethod{
|
||||
Secret: params.TypeDSecret,
|
||||
@@ -172,15 +172,26 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
TimestampParamName: params.TypeDTimestampParamName,
|
||||
Life: params.TypeDLife,
|
||||
}
|
||||
case serverconfigs.HTTPAuthTypeTypeE:
|
||||
params.Must.
|
||||
Field("typeESecret", params.TypeESecret).
|
||||
Require("请输入鉴权密钥").
|
||||
MaxLength(40, "鉴权密钥长度不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母和数字")
|
||||
|
||||
method = &serverconfigs.HTTPAuthTypeEMethod{
|
||||
Secret: params.TypeESecret,
|
||||
Life: params.TypeELife,
|
||||
}
|
||||
case serverconfigs.HTTPAuthTypeBasicAuth:
|
||||
var users = []*serverconfigs.HTTPAuthBasicMethodUser{}
|
||||
users := []*serverconfigs.HTTPAuthBasicMethodUser{}
|
||||
err := json.Unmarshal(params.HttpAuthBasicAuthUsersJSON, &users)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if len(users) == 0 {
|
||||
this.Fail("请添加至少一个用户")
|
||||
this.Fail("请至少添加一个用户")
|
||||
}
|
||||
method = &serverconfigs.HTTPAuthBasicMethod{
|
||||
Users: users,
|
||||
@@ -188,8 +199,9 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
Charset: params.BasicAuthCharset,
|
||||
}
|
||||
case serverconfigs.HTTPAuthTypeSubRequest:
|
||||
params.Must.Field("subRequestURL", params.SubRequestURL).
|
||||
Require("请输入子请求URL")
|
||||
params.Must.
|
||||
Field("subRequestURL", params.SubRequestURL).
|
||||
Require("请输入子请求 URL")
|
||||
if params.SubRequestFollowRequest {
|
||||
params.SubRequestMethod = ""
|
||||
}
|
||||
@@ -198,7 +210,7 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
Method: params.SubRequestMethod,
|
||||
}
|
||||
default:
|
||||
this.Fail("不支持的鉴权类型'" + params.Type + "'")
|
||||
this.Fail("不支持的鉴权类型 '" + params.Type + "'")
|
||||
}
|
||||
|
||||
if method == nil {
|
||||
@@ -214,7 +226,7 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
var paramsMap = maps.Map{}
|
||||
paramsMap := maps.Map{}
|
||||
err = json.Unmarshal(methodJSON, ¶msMap)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -231,6 +243,7 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
defer this.CreateLogInfo(codes.HTTPAuthPolicy_LogCreateHTTPAuthPolicy, createResp.HttpAuthPolicyId)
|
||||
|
||||
ref.AuthPolicyId = createResp.HttpAuthPolicyId
|
||||
ref.AuthPolicy = &serverconfigs.HTTPAuthPolicy{
|
||||
Id: createResp.HttpAuthPolicyId,
|
||||
|
||||
@@ -27,7 +27,7 @@ func (this *UpdatePopupAction) Init() {
|
||||
func (this *UpdatePopupAction) RunGet(params struct {
|
||||
PolicyId int64
|
||||
}) {
|
||||
var authMethods = []*serverconfigs.HTTPAuthTypeDefinition{}
|
||||
authMethods := []*serverconfigs.HTTPAuthTypeDefinition{}
|
||||
for _, method := range serverconfigs.FindAllHTTPAuthTypes(teaconst.Role) {
|
||||
if !method.IsPlus || (method.IsPlus && teaconst.IsPlus) {
|
||||
authMethods = append(authMethods, method)
|
||||
@@ -47,7 +47,7 @@ func (this *UpdatePopupAction) RunGet(params struct {
|
||||
return
|
||||
}
|
||||
|
||||
var authParams = map[string]interface{}{}
|
||||
authParams := map[string]interface{}{}
|
||||
if len(policy.ParamsJSON) > 0 {
|
||||
err = json.Unmarshal(policy.ParamsJSON, &authParams)
|
||||
if err != nil {
|
||||
@@ -91,6 +91,10 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
TypeDTimestampParamName string
|
||||
TypeDLife int
|
||||
|
||||
// TypeE
|
||||
TypeESecret string
|
||||
TypeELife int
|
||||
|
||||
// BasicAuth
|
||||
HttpAuthBasicAuthUsersJSON []byte
|
||||
BasicAuthRealm string
|
||||
@@ -125,29 +129,25 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
Field("name", params.Name).
|
||||
Require("请输入名称")
|
||||
|
||||
var ref = &serverconfigs.HTTPAuthPolicyRef{IsOn: true}
|
||||
ref := &serverconfigs.HTTPAuthPolicyRef{IsOn: true}
|
||||
var method serverconfigs.HTTPAuthMethodInterface
|
||||
|
||||
// 扩展名
|
||||
var exts = utils.NewStringsStream(params.Exts).
|
||||
exts := utils.NewStringsStream(params.Exts).
|
||||
Map(strings.TrimSpace, strings.ToLower).
|
||||
Filter(utils.FilterNotEmpty).
|
||||
Map(utils.MapAddPrefixFunc(".")).
|
||||
Unique().
|
||||
Result()
|
||||
|
||||
// 域名
|
||||
var domains = []string{}
|
||||
domains := []string{}
|
||||
if len(params.DomainsJSON) > 0 {
|
||||
var rawDomains = []string{}
|
||||
rawDomains := []string{}
|
||||
err := json.Unmarshal(params.DomainsJSON, &rawDomains)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO 如果用户填写了一个网址,应该分析域名并填入
|
||||
|
||||
domains = utils.NewStringsStream(rawDomains).
|
||||
Map(strings.TrimSpace, strings.ToLower).
|
||||
Filter(utils.FilterNotEmpty).
|
||||
@@ -160,11 +160,11 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
params.Must.
|
||||
Field("typeASecret", params.TypeASecret).
|
||||
Require("请输入鉴权密钥").
|
||||
MaxLength(40, "鉴权密钥不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母、数字").
|
||||
MaxLength(40, "鉴权密钥长度不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母和数字").
|
||||
Field("typeASignParamName", params.TypeASignParamName).
|
||||
Require("请输入签名参数").
|
||||
Match(`^[a-zA-Z0-9_]{1,40}$`, "签名参数中只能包含字母、数字、下划线")
|
||||
Match(`^[a-zA-Z0-9_]{1,40}$`, "签名参数中只能包含字母、数字和下划线")
|
||||
|
||||
if params.TypeALife < 0 {
|
||||
params.TypeALife = 0
|
||||
@@ -179,8 +179,8 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
params.Must.
|
||||
Field("typeBSecret", params.TypeBSecret).
|
||||
Require("请输入鉴权密钥").
|
||||
MaxLength(40, "鉴权密钥不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母、数字")
|
||||
MaxLength(40, "鉴权密钥长度不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母和数字")
|
||||
|
||||
method = &serverconfigs.HTTPAuthTypeBMethod{
|
||||
Secret: params.TypeBSecret,
|
||||
@@ -190,8 +190,8 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
params.Must.
|
||||
Field("typeCSecret", params.TypeCSecret).
|
||||
Require("请输入鉴权密钥").
|
||||
MaxLength(40, "鉴权密钥不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母、数字")
|
||||
MaxLength(40, "鉴权密钥长度不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母和数字")
|
||||
|
||||
method = &serverconfigs.HTTPAuthTypeCMethod{
|
||||
Secret: params.TypeCSecret,
|
||||
@@ -201,14 +201,14 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
params.Must.
|
||||
Field("typeDSecret", params.TypeDSecret).
|
||||
Require("请输入鉴权密钥").
|
||||
MaxLength(40, "鉴权密钥不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母、数字").
|
||||
MaxLength(40, "鉴权密钥长度不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母和数字").
|
||||
Field("typeDSignParamName", params.TypeDSignParamName).
|
||||
Require("请输入签名参数").
|
||||
Match(`^[a-zA-Z0-9_]{1,40}$`, "签名参数中只能包含字母、数字、下划线").
|
||||
Match(`^[a-zA-Z0-9_]{1,40}$`, "签名参数中只能包含字母、数字和下划线").
|
||||
Field("typeDTimestampParamName", params.TypeDTimestampParamName).
|
||||
Require("请输入时间戳参数").
|
||||
Match(`^[a-zA-Z0-9_]{1,40}$`, "时间戳参数中只能包含字母、数字、下划线")
|
||||
Require("请输入时间参数").
|
||||
Match(`^[a-zA-Z0-9_]{1,40}$`, "时间参数中只能包含字母、数字和下划线")
|
||||
|
||||
method = &serverconfigs.HTTPAuthTypeDMethod{
|
||||
Secret: params.TypeDSecret,
|
||||
@@ -216,6 +216,17 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
TimestampParamName: params.TypeDTimestampParamName,
|
||||
Life: params.TypeDLife,
|
||||
}
|
||||
case serverconfigs.HTTPAuthTypeTypeE:
|
||||
params.Must.
|
||||
Field("typeESecret", params.TypeESecret).
|
||||
Require("请输入鉴权密钥").
|
||||
MaxLength(40, "鉴权密钥长度不能超过40个字符").
|
||||
Match(`^[a-zA-Z0-9]{1,40}$`, "鉴权密钥中只能包含字母和数字")
|
||||
|
||||
method = &serverconfigs.HTTPAuthTypeEMethod{
|
||||
Secret: params.TypeESecret,
|
||||
Life: params.TypeELife,
|
||||
}
|
||||
case serverconfigs.HTTPAuthTypeBasicAuth:
|
||||
users := []*serverconfigs.HTTPAuthBasicMethodUser{}
|
||||
err := json.Unmarshal(params.HttpAuthBasicAuthUsersJSON, &users)
|
||||
@@ -224,7 +235,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
return
|
||||
}
|
||||
if len(users) == 0 {
|
||||
this.Fail("请添加至少一个用户")
|
||||
this.Fail("请至少添加一个用户")
|
||||
}
|
||||
method = &serverconfigs.HTTPAuthBasicMethod{
|
||||
Users: users,
|
||||
@@ -232,8 +243,9 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
Charset: params.BasicAuthCharset,
|
||||
}
|
||||
case serverconfigs.HTTPAuthTypeSubRequest:
|
||||
params.Must.Field("subRequestURL", params.SubRequestURL).
|
||||
Require("请输入子请求URL")
|
||||
params.Must.
|
||||
Field("subRequestURL", params.SubRequestURL).
|
||||
Require("请输入子请求 URL")
|
||||
if params.SubRequestFollowRequest {
|
||||
params.SubRequestMethod = ""
|
||||
}
|
||||
@@ -242,7 +254,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
Method: params.SubRequestMethod,
|
||||
}
|
||||
default:
|
||||
this.Fail("不支持的鉴权类型'" + policyType + "'")
|
||||
this.Fail("不支持的鉴权类型 '" + policyType + "'")
|
||||
}
|
||||
|
||||
if method == nil {
|
||||
@@ -275,6 +287,7 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
ref.AuthPolicy = &serverconfigs.HTTPAuthPolicy{
|
||||
Id: params.PolicyId,
|
||||
Name: params.Name,
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type ComponentsAction actions.Action
|
||||
@@ -41,12 +43,7 @@ func (this *ComponentsAction) RunGet(params struct{}) {
|
||||
|
||||
var buffer = bytes.NewBuffer([]byte{})
|
||||
|
||||
var webRoot string
|
||||
if Tea.IsTesting() {
|
||||
webRoot = Tea.Root + "/../web/public/js/components/"
|
||||
} else {
|
||||
webRoot = Tea.Root + "/web/public/js/components/"
|
||||
}
|
||||
webRoot := findComponentsRoot()
|
||||
f := files.NewFile(webRoot)
|
||||
|
||||
f.Range(func(file *files.File) {
|
||||
@@ -173,3 +170,27 @@ func (this *ComponentsAction) RunGet(params struct{}) {
|
||||
|
||||
_, _ = this.Write(componentsData)
|
||||
}
|
||||
|
||||
func findComponentsRoot() string {
|
||||
candidates := []string{
|
||||
filepath.Join(Tea.Root, "web", "public", "js", "components"),
|
||||
filepath.Join(Tea.Root, "..", "web", "public", "js", "components"),
|
||||
filepath.Join(Tea.Root, "..", "..", "web", "public", "js", "components"),
|
||||
}
|
||||
|
||||
if cwd, err := os.Getwd(); err == nil && len(cwd) > 0 {
|
||||
candidates = append(candidates,
|
||||
filepath.Join(cwd, "web", "public", "js", "components"),
|
||||
filepath.Join(cwd, "EdgeAdmin", "web", "public", "js", "components"),
|
||||
)
|
||||
}
|
||||
|
||||
for _, candidate := range candidates {
|
||||
info, err := os.Stat(candidate)
|
||||
if err == nil && info.IsDir() {
|
||||
return candidate + string(filepath.Separator)
|
||||
}
|
||||
}
|
||||
|
||||
return filepath.Join(Tea.Root, "web", "public", "js", "components") + string(filepath.Separator)
|
||||
}
|
||||
|
||||
@@ -141,7 +141,6 @@ import (
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/apps"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/clusters"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/clusters"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/resolveLogs"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/runtimeLogs"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/sandbox"
|
||||
|
||||
@@ -2272,6 +2272,57 @@ Vue.component("health-check-config-box", {
|
||||
</div>`
|
||||
})
|
||||
|
||||
Vue.component("httpdns-clusters-selector", {
|
||||
props: ["vClusters", "vName"],
|
||||
data: function () {
|
||||
let inputClusters = this.vClusters
|
||||
let clusters = []
|
||||
|
||||
if (inputClusters != null && inputClusters.length > 0) {
|
||||
if (inputClusters[0].isChecked !== undefined) {
|
||||
// 带 isChecked 标志的完整集群列表
|
||||
clusters = inputClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: c.isChecked}
|
||||
})
|
||||
} else {
|
||||
// 仅包含已选集群,全部标记为选中
|
||||
clusters = inputClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: true}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 无 prop 时从根实例读取所有集群(如创建应用页面)
|
||||
if (clusters.length === 0) {
|
||||
let rootClusters = this.$root.clusters
|
||||
if (rootClusters != null && rootClusters.length > 0) {
|
||||
clusters = rootClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: false}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
clusters: clusters,
|
||||
fieldName: this.vName || "clusterIds"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeCluster: function (cluster) {
|
||||
cluster.isChecked = !cluster.isChecked
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<div v-if="clusters.length > 0">
|
||||
<checkbox v-for="cluster in clusters" :key="cluster.id" :v-value="cluster.id" :value="cluster.isChecked ? cluster.id : 0" style="margin-right: 1em" @input="changeCluster(cluster)" :name="fieldName">
|
||||
{{cluster.name}}
|
||||
</checkbox>
|
||||
</div>
|
||||
<span class="grey" v-else>暂无可用集群</span>
|
||||
</div>`
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* 菜单项
|
||||
*/
|
||||
@@ -11294,10 +11345,18 @@ Vue.component("http-auth-config-box", {
|
||||
if (authConfig.policyRefs == null) {
|
||||
authConfig.policyRefs = []
|
||||
}
|
||||
return {
|
||||
return {
|
||||
authConfig: authConfig
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
"authConfig.isOn": function () {
|
||||
this.change()
|
||||
},
|
||||
"authConfig.isPrior": function () {
|
||||
this.change()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isOn: function () {
|
||||
return (!this.vIsLocation || this.authConfig.isPrior) && this.authConfig.isOn
|
||||
@@ -11309,18 +11368,17 @@ Vue.component("http-auth-config-box", {
|
||||
that.authConfig.policyRefs.push(resp.data.policyRef)
|
||||
that.change()
|
||||
},
|
||||
height: "28em"
|
||||
height: "32em"
|
||||
})
|
||||
},
|
||||
update: function (index, policyId) {
|
||||
let that = this
|
||||
teaweb.popup("/servers/server/settings/access/updatePopup?policyId=" + policyId, {
|
||||
callback: function (resp) {
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
},
|
||||
height: "28em"
|
||||
height: "32em"
|
||||
})
|
||||
},
|
||||
remove: function (index) {
|
||||
@@ -11341,14 +11399,15 @@ Vue.component("http-auth-config-box", {
|
||||
return "URL鉴权C"
|
||||
case "typeD":
|
||||
return "URL鉴权D"
|
||||
case "typeE":
|
||||
return "URL鉴权E"
|
||||
}
|
||||
return ""
|
||||
},
|
||||
change: function () {
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
// 延时通知,是为了让表单有机会变更数据
|
||||
that.$emit("change", this.authConfig)
|
||||
that.$emit("change", that.authConfig)
|
||||
}, 100)
|
||||
}
|
||||
},
|
||||
@@ -11369,7 +11428,6 @@ Vue.component("http-auth-config-box", {
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
<!-- 鉴权方式 -->
|
||||
<div v-show="isOn()">
|
||||
<h4>鉴权方式</h4>
|
||||
<table class="ui table selectable celled" v-show="authConfig.policyRefs.length > 0">
|
||||
@@ -11385,9 +11443,7 @@ Vue.component("http-auth-config-box", {
|
||||
<tbody v-for="(ref, index) in authConfig.policyRefs" :key="ref.authPolicyId">
|
||||
<tr>
|
||||
<td>{{ref.authPolicy.name}}</td>
|
||||
<td>
|
||||
{{methodName(ref.authPolicy.type)}}
|
||||
</td>
|
||||
<td>{{methodName(ref.authPolicy.type)}}</td>
|
||||
<td>
|
||||
<span v-if="ref.authPolicy.type == 'basicAuth'">{{ref.authPolicy.params.users.length}}个用户</span>
|
||||
<span v-if="ref.authPolicy.type == 'subRequest'">
|
||||
@@ -11398,7 +11454,7 @@ Vue.component("http-auth-config-box", {
|
||||
<span v-if="ref.authPolicy.type == 'typeB'">有效期{{ref.authPolicy.params.life}}秒</span>
|
||||
<span v-if="ref.authPolicy.type == 'typeC'">有效期{{ref.authPolicy.params.life}}秒</span>
|
||||
<span v-if="ref.authPolicy.type == 'typeD'">{{ref.authPolicy.params.signParamName}}/{{ref.authPolicy.params.timestampParamName}}/有效期{{ref.authPolicy.params.life}}秒</span>
|
||||
|
||||
<span v-if="ref.authPolicy.type == 'typeE'">路径模式/有效期{{ref.authPolicy.params.life}}秒</span>
|
||||
<div v-if="(ref.authPolicy.params.exts != null && ref.authPolicy.params.exts.length > 0) || (ref.authPolicy.params.domains != null && ref.authPolicy.params.domains.length > 0)">
|
||||
<grey-label v-if="ref.authPolicy.params.exts != null" v-for="ext in ref.authPolicy.params.exts">扩展名:{{ext}}</grey-label>
|
||||
<grey-label v-if="ref.authPolicy.params.domains != null" v-for="domain in ref.authPolicy.params.domains">域名:{{domain}}</grey-label>
|
||||
@@ -11420,6 +11476,7 @@ Vue.component("http-auth-config-box", {
|
||||
</div>`
|
||||
})
|
||||
|
||||
|
||||
Vue.component("http-cache-config-box", {
|
||||
props: ["v-cache-config", "v-is-location", "v-is-group", "v-cache-policy", "v-web-id"],
|
||||
data: function () {
|
||||
|
||||
@@ -2272,6 +2272,57 @@ Vue.component("health-check-config-box", {
|
||||
</div>`
|
||||
})
|
||||
|
||||
Vue.component("httpdns-clusters-selector", {
|
||||
props: ["vClusters", "vName"],
|
||||
data: function () {
|
||||
let inputClusters = this.vClusters
|
||||
let clusters = []
|
||||
|
||||
if (inputClusters != null && inputClusters.length > 0) {
|
||||
if (inputClusters[0].isChecked !== undefined) {
|
||||
// 带 isChecked 标志的完整集群列表
|
||||
clusters = inputClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: c.isChecked}
|
||||
})
|
||||
} else {
|
||||
// 仅包含已选集群,全部标记为选中
|
||||
clusters = inputClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: true}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 无 prop 时从根实例读取所有集群(如创建应用页面)
|
||||
if (clusters.length === 0) {
|
||||
let rootClusters = this.$root.clusters
|
||||
if (rootClusters != null && rootClusters.length > 0) {
|
||||
clusters = rootClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: false}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
clusters: clusters,
|
||||
fieldName: this.vName || "clusterIds"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeCluster: function (cluster) {
|
||||
cluster.isChecked = !cluster.isChecked
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<div v-if="clusters.length > 0">
|
||||
<checkbox v-for="cluster in clusters" :key="cluster.id" :v-value="cluster.id" :value="cluster.isChecked ? cluster.id : 0" style="margin-right: 1em" @input="changeCluster(cluster)" :name="fieldName">
|
||||
{{cluster.name}}
|
||||
</checkbox>
|
||||
</div>
|
||||
<span class="grey" v-else>暂无可用集群</span>
|
||||
</div>`
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* 菜单项
|
||||
*/
|
||||
@@ -11294,10 +11345,18 @@ Vue.component("http-auth-config-box", {
|
||||
if (authConfig.policyRefs == null) {
|
||||
authConfig.policyRefs = []
|
||||
}
|
||||
return {
|
||||
return {
|
||||
authConfig: authConfig
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
"authConfig.isOn": function () {
|
||||
this.change()
|
||||
},
|
||||
"authConfig.isPrior": function () {
|
||||
this.change()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isOn: function () {
|
||||
return (!this.vIsLocation || this.authConfig.isPrior) && this.authConfig.isOn
|
||||
@@ -11309,18 +11368,17 @@ Vue.component("http-auth-config-box", {
|
||||
that.authConfig.policyRefs.push(resp.data.policyRef)
|
||||
that.change()
|
||||
},
|
||||
height: "28em"
|
||||
height: "32em"
|
||||
})
|
||||
},
|
||||
update: function (index, policyId) {
|
||||
let that = this
|
||||
teaweb.popup("/servers/server/settings/access/updatePopup?policyId=" + policyId, {
|
||||
callback: function (resp) {
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
},
|
||||
height: "28em"
|
||||
height: "32em"
|
||||
})
|
||||
},
|
||||
remove: function (index) {
|
||||
@@ -11341,14 +11399,15 @@ Vue.component("http-auth-config-box", {
|
||||
return "URL鉴权C"
|
||||
case "typeD":
|
||||
return "URL鉴权D"
|
||||
case "typeE":
|
||||
return "URL鉴权E"
|
||||
}
|
||||
return ""
|
||||
},
|
||||
change: function () {
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
// 延时通知,是为了让表单有机会变更数据
|
||||
that.$emit("change", this.authConfig)
|
||||
that.$emit("change", that.authConfig)
|
||||
}, 100)
|
||||
}
|
||||
},
|
||||
@@ -11369,7 +11428,6 @@ Vue.component("http-auth-config-box", {
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
<!-- 鉴权方式 -->
|
||||
<div v-show="isOn()">
|
||||
<h4>鉴权方式</h4>
|
||||
<table class="ui table selectable celled" v-show="authConfig.policyRefs.length > 0">
|
||||
@@ -11385,9 +11443,7 @@ Vue.component("http-auth-config-box", {
|
||||
<tbody v-for="(ref, index) in authConfig.policyRefs" :key="ref.authPolicyId">
|
||||
<tr>
|
||||
<td>{{ref.authPolicy.name}}</td>
|
||||
<td>
|
||||
{{methodName(ref.authPolicy.type)}}
|
||||
</td>
|
||||
<td>{{methodName(ref.authPolicy.type)}}</td>
|
||||
<td>
|
||||
<span v-if="ref.authPolicy.type == 'basicAuth'">{{ref.authPolicy.params.users.length}}个用户</span>
|
||||
<span v-if="ref.authPolicy.type == 'subRequest'">
|
||||
@@ -11398,7 +11454,7 @@ Vue.component("http-auth-config-box", {
|
||||
<span v-if="ref.authPolicy.type == 'typeB'">有效期{{ref.authPolicy.params.life}}秒</span>
|
||||
<span v-if="ref.authPolicy.type == 'typeC'">有效期{{ref.authPolicy.params.life}}秒</span>
|
||||
<span v-if="ref.authPolicy.type == 'typeD'">{{ref.authPolicy.params.signParamName}}/{{ref.authPolicy.params.timestampParamName}}/有效期{{ref.authPolicy.params.life}}秒</span>
|
||||
|
||||
<span v-if="ref.authPolicy.type == 'typeE'">路径模式/有效期{{ref.authPolicy.params.life}}秒</span>
|
||||
<div v-if="(ref.authPolicy.params.exts != null && ref.authPolicy.params.exts.length > 0) || (ref.authPolicy.params.domains != null && ref.authPolicy.params.domains.length > 0)">
|
||||
<grey-label v-if="ref.authPolicy.params.exts != null" v-for="ext in ref.authPolicy.params.exts">扩展名:{{ext}}</grey-label>
|
||||
<grey-label v-if="ref.authPolicy.params.domains != null" v-for="domain in ref.authPolicy.params.domains">域名:{{domain}}</grey-label>
|
||||
@@ -11420,6 +11476,7 @@ Vue.component("http-auth-config-box", {
|
||||
</div>`
|
||||
})
|
||||
|
||||
|
||||
Vue.component("http-cache-config-box", {
|
||||
props: ["v-cache-config", "v-is-location", "v-is-group", "v-cache-policy", "v-web-id"],
|
||||
data: function () {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
Vue.component("httpdns-clusters-selector", {
|
||||
props: ["vClusters", "vName"],
|
||||
data: function () {
|
||||
let inputClusters = this.vClusters
|
||||
let clusters = []
|
||||
|
||||
if (inputClusters != null && inputClusters.length > 0) {
|
||||
if (inputClusters[0].isChecked !== undefined) {
|
||||
// 带 isChecked 标志的完整集群列表
|
||||
clusters = inputClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: c.isChecked}
|
||||
})
|
||||
} else {
|
||||
// 仅包含已选集群,全部标记为选中
|
||||
clusters = inputClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: true}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 无 prop 时从根实例读取所有集群(如创建应用页面)
|
||||
if (clusters.length === 0) {
|
||||
let rootClusters = this.$root.clusters
|
||||
if (rootClusters != null && rootClusters.length > 0) {
|
||||
clusters = rootClusters.map(function (c) {
|
||||
return {id: c.id, name: c.name, isChecked: false}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
clusters: clusters,
|
||||
fieldName: this.vName || "clusterIds"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeCluster: function (cluster) {
|
||||
cluster.isChecked = !cluster.isChecked
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<div v-if="clusters.length > 0">
|
||||
<checkbox v-for="cluster in clusters" :key="cluster.id" :v-value="cluster.id" :value="cluster.isChecked ? cluster.id : 0" style="margin-right: 1em" @input="changeCluster(cluster)" :name="fieldName">
|
||||
{{cluster.name}}
|
||||
</checkbox>
|
||||
</div>
|
||||
<span class="grey" v-else>暂无可用集群</span>
|
||||
</div>`
|
||||
})
|
||||
@@ -12,10 +12,18 @@ Vue.component("http-auth-config-box", {
|
||||
if (authConfig.policyRefs == null) {
|
||||
authConfig.policyRefs = []
|
||||
}
|
||||
return {
|
||||
return {
|
||||
authConfig: authConfig
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
"authConfig.isOn": function () {
|
||||
this.change()
|
||||
},
|
||||
"authConfig.isPrior": function () {
|
||||
this.change()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isOn: function () {
|
||||
return (!this.vIsLocation || this.authConfig.isPrior) && this.authConfig.isOn
|
||||
@@ -27,18 +35,17 @@ Vue.component("http-auth-config-box", {
|
||||
that.authConfig.policyRefs.push(resp.data.policyRef)
|
||||
that.change()
|
||||
},
|
||||
height: "28em"
|
||||
height: "32em"
|
||||
})
|
||||
},
|
||||
update: function (index, policyId) {
|
||||
let that = this
|
||||
teaweb.popup("/servers/server/settings/access/updatePopup?policyId=" + policyId, {
|
||||
callback: function (resp) {
|
||||
callback: function () {
|
||||
teaweb.success("保存成功", function () {
|
||||
teaweb.reload()
|
||||
})
|
||||
},
|
||||
height: "28em"
|
||||
height: "32em"
|
||||
})
|
||||
},
|
||||
remove: function (index) {
|
||||
@@ -59,14 +66,15 @@ Vue.component("http-auth-config-box", {
|
||||
return "URL鉴权C"
|
||||
case "typeD":
|
||||
return "URL鉴权D"
|
||||
case "typeE":
|
||||
return "URL鉴权E"
|
||||
}
|
||||
return ""
|
||||
},
|
||||
change: function () {
|
||||
let that = this
|
||||
setTimeout(function () {
|
||||
// 延时通知,是为了让表单有机会变更数据
|
||||
that.$emit("change", this.authConfig)
|
||||
that.$emit("change", that.authConfig)
|
||||
}, 100)
|
||||
}
|
||||
},
|
||||
@@ -87,7 +95,6 @@ Vue.component("http-auth-config-box", {
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="margin"></div>
|
||||
<!-- 鉴权方式 -->
|
||||
<div v-show="isOn()">
|
||||
<h4>鉴权方式</h4>
|
||||
<table class="ui table selectable celled" v-show="authConfig.policyRefs.length > 0">
|
||||
@@ -103,9 +110,7 @@ Vue.component("http-auth-config-box", {
|
||||
<tbody v-for="(ref, index) in authConfig.policyRefs" :key="ref.authPolicyId">
|
||||
<tr>
|
||||
<td>{{ref.authPolicy.name}}</td>
|
||||
<td>
|
||||
{{methodName(ref.authPolicy.type)}}
|
||||
</td>
|
||||
<td>{{methodName(ref.authPolicy.type)}}</td>
|
||||
<td>
|
||||
<span v-if="ref.authPolicy.type == 'basicAuth'">{{ref.authPolicy.params.users.length}}个用户</span>
|
||||
<span v-if="ref.authPolicy.type == 'subRequest'">
|
||||
@@ -116,7 +121,7 @@ Vue.component("http-auth-config-box", {
|
||||
<span v-if="ref.authPolicy.type == 'typeB'">有效期{{ref.authPolicy.params.life}}秒</span>
|
||||
<span v-if="ref.authPolicy.type == 'typeC'">有效期{{ref.authPolicy.params.life}}秒</span>
|
||||
<span v-if="ref.authPolicy.type == 'typeD'">{{ref.authPolicy.params.signParamName}}/{{ref.authPolicy.params.timestampParamName}}/有效期{{ref.authPolicy.params.life}}秒</span>
|
||||
|
||||
<span v-if="ref.authPolicy.type == 'typeE'">路径模式/有效期{{ref.authPolicy.params.life}}秒</span>
|
||||
<div v-if="(ref.authPolicy.params.exts != null && ref.authPolicy.params.exts.length > 0) || (ref.authPolicy.params.domains != null && ref.authPolicy.params.domains.length > 0)">
|
||||
<grey-label v-if="ref.authPolicy.params.exts != null" v-for="ext in ref.authPolicy.params.exts">扩展名:{{ext}}</grey-label>
|
||||
<grey-label v-if="ref.authPolicy.params.domains != null" v-for="domain in ref.authPolicy.params.domains">域名:{{domain}}</grey-label>
|
||||
@@ -136,4 +141,4 @@ Vue.component("http-auth-config-box", {
|
||||
</div>
|
||||
<div class="margin"></div>
|
||||
</div>`
|
||||
})
|
||||
})
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</script>
|
||||
<script type="text/javascript" src="/js/config/brand.js"></script>
|
||||
<script type="text/javascript" src="/_/@default/@layout.js"></script>
|
||||
<script type="text/javascript" src="/js/components.js"></script>
|
||||
<script type="text/javascript" src="/js/components.js?v={$ .teaVersion}"></script>
|
||||
<script type="text/javascript" src="/js/utils.min.js"></script>
|
||||
<script type="text/javascript" src="/js/sweetalert2/dist/sweetalert2.all.min.js" async></script>
|
||||
<script type="text/javascript" src="/js/date.tea.js"></script>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
{$layout}
|
||||
|
||||
{$template "../menu"}
|
||||
{$template "/left_menu_with_menu"}
|
||||
|
||||
<div class="right-box with-menu">
|
||||
<div class="margin"></div>
|
||||
|
||||
<warning-message>此功能为试验功能,目前只能做一些简单的网络数据包统计。</warning-message>
|
||||
|
||||
<form class="ui form" data-tea-action="$" data-tea-success="success">
|
||||
<csrf-token></csrf-token>
|
||||
|
||||
<input type="hidden" name="clusterId" :value="clusterId"/>
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">启用数据包统计</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="status" v-model="policy.status">
|
||||
<option value="auto">自动</option>
|
||||
<option value="on">启用</option>
|
||||
<option value="off">停用</option>
|
||||
</select>
|
||||
<p class="comment" v-if="policy.status == 'auto'">自动根据服务器硬件配置决定是否启用,避免影响性能。</p>
|
||||
<p class="comment" v-if="policy.status == 'on'">强制启用;如果节点服务器配置较低,可能会严重影响性能,建议仅在8线程以上CPU节点服务器上选择强制启用。</p>
|
||||
<p class="comment" v-if="policy.status == 'off'">完全关闭此功能。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
Tea.context(function () {
|
||||
this.success = NotifyReloadSuccess("保存成功")
|
||||
})
|
||||
@@ -6,6 +6,7 @@
|
||||
<span v-if="countTodayAttacks > 0" :class="{red: countTodayAttacks != countTodayAttacksRead}">({{countTodayAttacksFormat}})</span>
|
||||
</menu-item>
|
||||
<menu-item href="/dashboard/boards/dns" code="dns">{{LANG('admin_dashboard@ui_dns')}}</menu-item>
|
||||
<menu-item href="/dashboard/boards/httpdns" code="httpdns">HTTPDNS</menu-item>
|
||||
<menu-item href="/dashboard/boards/user" code="user">{{LANG('admin_dashboard@ui_user')}}</menu-item>
|
||||
<menu-item href="/dashboard/boards/events" code="event">{{LANG('admin_dashboard@ui_events')}}<span :class="{red: countEvents > 0}">({{countEvents}})</span></menu-item>
|
||||
</first-menu>
|
||||
</first-menu>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.ui.message .icon {
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 1.8em;
|
||||
}
|
||||
.chart-box {
|
||||
height: 14em;
|
||||
}
|
||||
67
EdgeAdmin/web/views/@default/dashboard/boards/httpdns.html
Normal file
67
EdgeAdmin/web/views/@default/dashboard/boards/httpdns.html
Normal file
@@ -0,0 +1,67 @@
|
||||
{$layout}
|
||||
{$template "menu"}
|
||||
{$template "/echarts"}
|
||||
|
||||
<div style="margin-top: 0.8em" v-if="isLoading">
|
||||
<div class="ui message loading">
|
||||
<div class="ui active inline loader small"></div> 数据加载中...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="!isLoading">
|
||||
<columns-grid>
|
||||
<div class="ui column">
|
||||
<h4>应用<link-icon href="/httpdns/apps"></link-icon></h4>
|
||||
<div class="value"><span>{{board.countApps}}</span>个</div>
|
||||
</div>
|
||||
<div class="ui column">
|
||||
<h4>域名<link-icon href="/httpdns/apps"></link-icon></h4>
|
||||
<div class="value"><span>{{board.countDomains}}</span>个</div>
|
||||
</div>
|
||||
<div class="ui column">
|
||||
<h4>集群<link-icon href="/httpdns/clusters"></link-icon></h4>
|
||||
<div class="value"><span>{{board.countClusters}}</span>个</div>
|
||||
</div>
|
||||
<div class="ui column">
|
||||
<h4>节点<link-icon href="/httpdns/clusters"></link-icon></h4>
|
||||
<div class="value"><span>{{board.countNodes}}</span>
|
||||
<span v-if="board.countOfflineNodes > 0" style="font-size: 1em">
|
||||
/ <a href="/httpdns/clusters"><span class="red" style="font-size: 1em">{{board.countOfflineNodes}}离线</span></a>
|
||||
</span>
|
||||
<span v-else style="font-size: 1em">个</span>
|
||||
</div>
|
||||
</div>
|
||||
</columns-grid>
|
||||
|
||||
<chart-columns-grid>
|
||||
<div class="ui column">
|
||||
<div class="ui menu text blue">
|
||||
<a href="" class="item" :class="{active: trafficTab == 'hourly'}" @click.prevent="selectTrafficTab('hourly')">24小时请求趋势</a>
|
||||
<a href="" class="item" :class="{active: trafficTab == 'daily'}" @click.prevent="selectTrafficTab('daily')">15天请求趋势</a>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
<div class="chart-box" id="hourly-traffic-chart" v-show="trafficTab == 'hourly'"></div>
|
||||
<div class="chart-box" id="daily-traffic-chart" v-show="trafficTab == 'daily'"></div>
|
||||
</div>
|
||||
|
||||
<div class="ui column">
|
||||
<h4>应用请求排行 <span>(24小时)</span></h4>
|
||||
<div class="ui divider"></div>
|
||||
<div class="chart-box" id="top-apps-chart"></div>
|
||||
</div>
|
||||
|
||||
<div class="ui column">
|
||||
<h4>域名请求排行 <span>(24小时)</span></h4>
|
||||
<div class="ui divider"></div>
|
||||
<div class="chart-box" id="top-domains-chart"></div>
|
||||
</div>
|
||||
|
||||
<div class="ui column">
|
||||
<h4>节点访问排行 <span>(24小时)</span></h4>
|
||||
<div class="ui divider"></div>
|
||||
<div class="chart-box" id="top-nodes-chart"></div>
|
||||
</div>
|
||||
|
||||
</chart-columns-grid>
|
||||
</div>
|
||||
192
EdgeAdmin/web/views/@default/dashboard/boards/httpdns.js
Normal file
192
EdgeAdmin/web/views/@default/dashboard/boards/httpdns.js
Normal file
@@ -0,0 +1,192 @@
|
||||
Tea.context(function () {
|
||||
this.isLoading = true
|
||||
|
||||
this.$delay(function () {
|
||||
this.$post("$")
|
||||
.success(function (resp) {
|
||||
for (let k in resp.data) {
|
||||
this[k] = resp.data[k]
|
||||
}
|
||||
|
||||
this.$delay(function () {
|
||||
this.reloadHourlyTrafficChart()
|
||||
this.reloadTopAppsChart()
|
||||
this.reloadTopDomainsChart()
|
||||
this.reloadTopNodesChart()
|
||||
})
|
||||
|
||||
this.isLoading = false
|
||||
})
|
||||
})
|
||||
|
||||
this.trafficTab = "hourly"
|
||||
|
||||
this.selectTrafficTab = function (tab) {
|
||||
this.trafficTab = tab
|
||||
if (tab == "hourly") {
|
||||
this.$delay(function () {
|
||||
this.reloadHourlyTrafficChart()
|
||||
})
|
||||
} else if (tab == "daily") {
|
||||
this.$delay(function () {
|
||||
this.reloadDailyTrafficChart()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.reloadHourlyTrafficChart = function () {
|
||||
let stats = this.hourlyStats
|
||||
if (stats == null || stats.length == 0) {
|
||||
return
|
||||
}
|
||||
this.reloadTrafficChart("hourly-traffic-chart", stats, function (args) {
|
||||
return stats[args.dataIndex].day + " " + stats[args.dataIndex].hour + "时<br/>请求数:" + teaweb.formatNumber(stats[args.dataIndex].countRequests)
|
||||
})
|
||||
}
|
||||
|
||||
this.reloadDailyTrafficChart = function () {
|
||||
let stats = this.dailyStats
|
||||
if (stats == null || stats.length == 0) {
|
||||
return
|
||||
}
|
||||
this.reloadTrafficChart("daily-traffic-chart", stats, function (args) {
|
||||
return stats[args.dataIndex].day + "<br/>请求数:" + teaweb.formatNumber(stats[args.dataIndex].countRequests)
|
||||
})
|
||||
}
|
||||
|
||||
this.reloadTrafficChart = function (chartId, stats, tooltipFunc) {
|
||||
let chartBox = document.getElementById(chartId)
|
||||
if (chartBox == null) {
|
||||
return
|
||||
}
|
||||
|
||||
let axis = teaweb.countAxis(stats, function (v) {
|
||||
return v.countRequests
|
||||
})
|
||||
|
||||
let chart = teaweb.initChart(chartBox)
|
||||
let option = {
|
||||
xAxis: {
|
||||
data: stats.map(function (v) {
|
||||
if (v.hour != null) {
|
||||
return v.hour
|
||||
}
|
||||
return v.day
|
||||
})
|
||||
},
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
formatter: function (value) {
|
||||
return value + axis.unit
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: "item",
|
||||
formatter: tooltipFunc
|
||||
},
|
||||
grid: {
|
||||
left: 50,
|
||||
top: 10,
|
||||
right: 20,
|
||||
bottom: 20
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "请求数",
|
||||
type: "line",
|
||||
data: stats.map(function (v) {
|
||||
return v.countRequests / axis.divider
|
||||
}),
|
||||
itemStyle: {
|
||||
color: teaweb.DefaultChartColor
|
||||
},
|
||||
areaStyle: {
|
||||
color: teaweb.DefaultChartColor
|
||||
},
|
||||
smooth: true
|
||||
}
|
||||
],
|
||||
animation: true
|
||||
}
|
||||
chart.setOption(option)
|
||||
chart.resize()
|
||||
}
|
||||
|
||||
this.reloadTopAppsChart = function () {
|
||||
if (this.topAppStats == null || this.topAppStats.length == 0) {
|
||||
return
|
||||
}
|
||||
let axis = teaweb.countAxis(this.topAppStats, function (v) {
|
||||
return v.countRequests
|
||||
})
|
||||
teaweb.renderBarChart({
|
||||
id: "top-apps-chart",
|
||||
name: "应用",
|
||||
values: this.topAppStats,
|
||||
x: function (v) {
|
||||
return v.appName
|
||||
},
|
||||
tooltip: function (args, stats) {
|
||||
return stats[args.dataIndex].appName + "<br/>请求数:" + teaweb.formatNumber(stats[args.dataIndex].countRequests)
|
||||
},
|
||||
value: function (v) {
|
||||
return v.countRequests / axis.divider
|
||||
},
|
||||
axis: axis
|
||||
})
|
||||
}
|
||||
|
||||
this.reloadTopDomainsChart = function () {
|
||||
if (this.topDomainStats == null || this.topDomainStats.length == 0) {
|
||||
return
|
||||
}
|
||||
let axis = teaweb.countAxis(this.topDomainStats, function (v) {
|
||||
return v.countRequests
|
||||
})
|
||||
teaweb.renderBarChart({
|
||||
id: "top-domains-chart",
|
||||
name: "域名",
|
||||
values: this.topDomainStats,
|
||||
x: function (v) {
|
||||
return v.domainName
|
||||
},
|
||||
tooltip: function (args, stats) {
|
||||
return stats[args.dataIndex].domainName + "<br/>请求数:" + teaweb.formatNumber(stats[args.dataIndex].countRequests)
|
||||
},
|
||||
value: function (v) {
|
||||
return v.countRequests / axis.divider
|
||||
},
|
||||
axis: axis
|
||||
})
|
||||
}
|
||||
|
||||
this.reloadTopNodesChart = function () {
|
||||
if (this.topNodeStats == null || this.topNodeStats.length == 0) {
|
||||
return
|
||||
}
|
||||
let axis = teaweb.countAxis(this.topNodeStats, function (v) {
|
||||
return v.countRequests
|
||||
})
|
||||
teaweb.renderBarChart({
|
||||
id: "top-nodes-chart",
|
||||
name: "节点",
|
||||
values: this.topNodeStats,
|
||||
x: function (v) {
|
||||
return v.nodeName
|
||||
},
|
||||
tooltip: function (args, stats) {
|
||||
return stats[args.dataIndex].nodeName + "<br/>请求数:" + teaweb.formatNumber(stats[args.dataIndex].countRequests)
|
||||
},
|
||||
value: function (v) {
|
||||
return v.countRequests / axis.divider
|
||||
},
|
||||
axis: axis,
|
||||
click: function (args, stats) {
|
||||
window.location = "/httpdns/clusters/cluster/node?nodeId=" + stats[args.dataIndex].nodeId + "&clusterId=" + stats[args.dataIndex].clusterId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
@@ -53,8 +53,9 @@
|
||||
<!-- 升级提醒 -->
|
||||
<div class="ui icon message error" v-if="!isLoading && nodeUpgradeInfo.count > 0">
|
||||
<i class="icon warning circle"></i>
|
||||
<a href="/clusters">
|
||||
升级提醒:有 {{nodeUpgradeInfo.count}} 个边缘节点需要升级到 v{{nodeUpgradeInfo.version}} 版本,系统正在尝试自动升级,请耐心等待...</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||
<a href="/clusters" v-if="autoUpgrade">升级提醒:有 {{nodeUpgradeInfo.count}} 个边缘节点需要升级到 v{{nodeUpgradeInfo.version}} 版本,系统正在尝试自动升级,请耐心等待...</a>
|
||||
<a href="/settings/upgrade" v-else>升级提醒:有 {{nodeUpgradeInfo.count}} 个边缘节点需要升级到 v{{nodeUpgradeInfo.version}} 版本,请到系统设置-升级设置页面自行升级...</a>
|
||||
<a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||
</div>
|
||||
<div class="ui icon message error" v-if="!isLoading && userNodeUpgradeInfo.count > 0 && teaIsPlus">
|
||||
<i class="icon warning circle"></i>
|
||||
@@ -73,7 +74,15 @@
|
||||
|
||||
<div class="ui icon message error" v-if="!isLoading && nsNodeUpgradeInfo.count > 0 && teaIsPlus">
|
||||
<i class="icon warning circle"></i>
|
||||
<a href="/ns/clusters">升级提醒:有 {{nsNodeUpgradeInfo.count}} 个DNS节点需要升级到 v{{nsNodeUpgradeInfo.version}} 版本,系统正在尝试自动升级,请耐心等待...</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||
<a href="/ns/clusters" v-if="autoUpgrade">升级提醒:有 {{nsNodeUpgradeInfo.count}} 个DNS节点需要升级到 v{{nsNodeUpgradeInfo.version}} 版本,系统正在尝试自动升级,请耐心等待...</a>
|
||||
<a href="/settings/upgrade" v-else>升级提醒:有 {{nsNodeUpgradeInfo.count}} 个DNS节点需要升级到 v{{nsNodeUpgradeInfo.version}} 版本,请到系统设置-升级设置页面自行升级...</a>
|
||||
<a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||
</div>
|
||||
<div class="ui icon message error" v-if="!isLoading && httpdnsNodeUpgradeInfo.count > 0 && teaIsPlus">
|
||||
<i class="icon warning circle"></i>
|
||||
<a href="/httpdns/clusters" v-if="autoUpgrade">升级提醒:有 {{httpdnsNodeUpgradeInfo.count}} 个HTTPDNS节点需要升级到 v{{httpdnsNodeUpgradeInfo.version}} 版本,系统正在尝试自动升级,请耐心等待...</a>
|
||||
<a href="/settings/upgrade" v-else>升级提醒:有 {{httpdnsNodeUpgradeInfo.count}} 个HTTPDNS节点需要升级到 v{{httpdnsNodeUpgradeInfo.version}} 版本,请到系统设置-升级设置页面自行升级...</a>
|
||||
<a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||
</div>
|
||||
<div class="ui icon message error" v-if="!isLoading && reportNodeUpgradeInfo.count > 0 && teaIsPlus">
|
||||
<i class="icon warning circle"></i>
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
<!-- 边缘节点升级提醒 -->
|
||||
<div class="ui icon message error" v-if="!isLoading && nodeUpgradeInfo.count > 0">
|
||||
<i class="icon warning circle"></i>
|
||||
<a href="/clusters">升级提醒:有 {{nodeUpgradeInfo.count}} 个边缘节点需要升级到 v{{nodeUpgradeInfo.version}} 版本,系统正在尝试自动升级,请耐心等待...</a><a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||
<a href="/clusters" v-if="autoUpgrade">升级提醒:有 {{nodeUpgradeInfo.count}} 个边缘节点需要升级到 v{{nodeUpgradeInfo.version}} 版本,系统正在尝试自动升级,请耐心等待...</a>
|
||||
<a href="/settings/upgrade" v-else>升级提醒:有 {{nodeUpgradeInfo.count}} 个边缘节点需要升级到 v{{nodeUpgradeInfo.version}} 版本,请到系统设置-升级设置页面自行升级...</a>
|
||||
<a href="" title="关闭" @click.prevent="closeMessage"><i class="ui icon remove small"></i></a>
|
||||
</div>
|
||||
|
||||
<!-- API节点升级提醒 -->
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
Tea.context(function () {
|
||||
this.isOn = true
|
||||
|
||||
this.syncDefaultClusterEnabled = function () {
|
||||
}
|
||||
|
||||
this.success = function (resp) {
|
||||
let clusterId = 0
|
||||
if (resp != null && resp.data != null && typeof resp.data.clusterId != "undefined") {
|
||||
|
||||
8
EdgeAdmin/web/views/@default/httpdns/index.css
Normal file
8
EdgeAdmin/web/views/@default/httpdns/index.css
Normal file
@@ -0,0 +1,8 @@
|
||||
.ui.message .icon {
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
top: 1.8em;
|
||||
}
|
||||
.chart-box {
|
||||
height: 14em;
|
||||
}
|
||||
62
EdgeAdmin/web/views/@default/httpdns/index.html
Normal file
62
EdgeAdmin/web/views/@default/httpdns/index.html
Normal file
@@ -0,0 +1,62 @@
|
||||
{$layout}
|
||||
{$template "/echarts"}
|
||||
|
||||
<div style="margin-top: 0.8em" v-if="isLoading">
|
||||
<div class="ui message loading">
|
||||
<div class="ui active inline loader small"></div> 数据加载中...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="!isLoading">
|
||||
<div class="ui four columns grid counter-chart">
|
||||
<div class="ui column">
|
||||
<h4>应用<link-icon href="/httpdns/apps"></link-icon></h4>
|
||||
<div class="value"><span>{{board.countApps}}</span>个</div>
|
||||
</div>
|
||||
<div class="ui column">
|
||||
<h4>域名<link-icon href="/httpdns/apps"></link-icon></h4>
|
||||
<div class="value"><span>{{board.countDomains}}</span>个</div>
|
||||
</div>
|
||||
<div class="ui column">
|
||||
<h4>集群<link-icon href="/httpdns/clusters"></link-icon></h4>
|
||||
<div class="value"><span>{{board.countClusters}}</span>个</div>
|
||||
</div>
|
||||
<div class="ui column with-border">
|
||||
<h4>节点<link-icon href="/httpdns/clusters"></link-icon></h4>
|
||||
<div class="value"><span>{{board.countNodes}}</span>
|
||||
<span v-if="board.countOfflineNodes > 0" class="red" style="font-size: 1em">{{board.countOfflineNodes}}离线</span>
|
||||
<span v-else style="font-size: 1em">个</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui four columns grid chart-grid" style="margin-top: 1em">
|
||||
<div class="ui column">
|
||||
<div class="ui menu text blue small">
|
||||
<a href="" class="item" :class="{active: trafficTab == 'hourly'}" @click.prevent="selectTrafficTab('hourly')">24小时请求趋势</a>
|
||||
<a href="" class="item" :class="{active: trafficTab == 'daily'}" @click.prevent="selectTrafficTab('daily')">15天请求趋势</a>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="chart-box" id="hourly-traffic-chart" v-show="trafficTab == 'hourly'"></div>
|
||||
<div class="chart-box" id="daily-traffic-chart" v-show="trafficTab == 'daily'"></div>
|
||||
</div>
|
||||
|
||||
<div class="ui column">
|
||||
<h4>应用请求排行 <span>(24小时)</span></h4>
|
||||
<div class="ui divider"></div>
|
||||
<div class="chart-box" id="top-apps-chart"></div>
|
||||
</div>
|
||||
|
||||
<div class="ui column">
|
||||
<h4>域名请求排行 <span>(24小时)</span></h4>
|
||||
<div class="ui divider"></div>
|
||||
<div class="chart-box" id="top-domains-chart"></div>
|
||||
</div>
|
||||
|
||||
<div class="ui column">
|
||||
<h4>节点访问排行 <span>(24小时)</span></h4>
|
||||
<div class="ui divider"></div>
|
||||
<div class="chart-box" id="top-nodes-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
191
EdgeAdmin/web/views/@default/httpdns/index.js
Normal file
191
EdgeAdmin/web/views/@default/httpdns/index.js
Normal file
@@ -0,0 +1,191 @@
|
||||
Tea.context(function () {
|
||||
this.isLoading = true
|
||||
|
||||
this.$delay(function () {
|
||||
this.$post("$")
|
||||
.success(function (resp) {
|
||||
for (let k in resp.data) {
|
||||
this[k] = resp.data[k]
|
||||
}
|
||||
|
||||
this.$delay(function () {
|
||||
this.reloadHourlyTrafficChart()
|
||||
this.reloadTopAppsChart()
|
||||
this.reloadTopDomainsChart()
|
||||
this.reloadTopNodesChart()
|
||||
})
|
||||
|
||||
this.isLoading = false
|
||||
})
|
||||
})
|
||||
|
||||
this.trafficTab = "hourly"
|
||||
|
||||
this.selectTrafficTab = function (tab) {
|
||||
this.trafficTab = tab
|
||||
if (tab == "hourly") {
|
||||
this.$delay(function () {
|
||||
this.reloadHourlyTrafficChart()
|
||||
})
|
||||
} else if (tab == "daily") {
|
||||
this.$delay(function () {
|
||||
this.reloadDailyTrafficChart()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.reloadHourlyTrafficChart = function () {
|
||||
let stats = this.hourlyStats
|
||||
if (stats == null || stats.length == 0) {
|
||||
return
|
||||
}
|
||||
this.reloadTrafficChart("hourly-traffic-chart", stats, function (args) {
|
||||
return stats[args.dataIndex].day + " " + stats[args.dataIndex].hour + "时<br/>请求数:" + teaweb.formatNumber(stats[args.dataIndex].countRequests)
|
||||
})
|
||||
}
|
||||
|
||||
this.reloadDailyTrafficChart = function () {
|
||||
let stats = this.dailyStats
|
||||
if (stats == null || stats.length == 0) {
|
||||
return
|
||||
}
|
||||
this.reloadTrafficChart("daily-traffic-chart", stats, function (args) {
|
||||
return stats[args.dataIndex].day + "<br/>请求数:" + teaweb.formatNumber(stats[args.dataIndex].countRequests)
|
||||
})
|
||||
}
|
||||
|
||||
this.reloadTrafficChart = function (chartId, stats, tooltipFunc) {
|
||||
let chartBox = document.getElementById(chartId)
|
||||
if (chartBox == null) {
|
||||
return
|
||||
}
|
||||
|
||||
let axis = teaweb.countAxis(stats, function (v) {
|
||||
return v.countRequests
|
||||
})
|
||||
|
||||
let chart = teaweb.initChart(chartBox)
|
||||
let option = {
|
||||
xAxis: {
|
||||
data: stats.map(function (v) {
|
||||
if (v.hour != null) {
|
||||
return v.hour
|
||||
}
|
||||
return v.day
|
||||
})
|
||||
},
|
||||
yAxis: {
|
||||
axisLabel: {
|
||||
formatter: function (value) {
|
||||
return value + axis.unit
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: "item",
|
||||
formatter: tooltipFunc
|
||||
},
|
||||
grid: {
|
||||
left: 50,
|
||||
top: 10,
|
||||
right: 20,
|
||||
bottom: 20
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "请求数",
|
||||
type: "line",
|
||||
data: stats.map(function (v) {
|
||||
return v.countRequests / axis.divider
|
||||
}),
|
||||
itemStyle: {
|
||||
color: teaweb.DefaultChartColor
|
||||
},
|
||||
areaStyle: {
|
||||
color: teaweb.DefaultChartColor
|
||||
},
|
||||
smooth: true
|
||||
}
|
||||
],
|
||||
animation: true
|
||||
}
|
||||
chart.setOption(option)
|
||||
chart.resize()
|
||||
}
|
||||
|
||||
this.reloadTopAppsChart = function () {
|
||||
if (this.topAppStats == null || this.topAppStats.length == 0) {
|
||||
return
|
||||
}
|
||||
let axis = teaweb.countAxis(this.topAppStats, function (v) {
|
||||
return v.countRequests
|
||||
})
|
||||
teaweb.renderBarChart({
|
||||
id: "top-apps-chart",
|
||||
name: "应用",
|
||||
values: this.topAppStats,
|
||||
x: function (v) {
|
||||
return v.appName
|
||||
},
|
||||
tooltip: function (args, stats) {
|
||||
return stats[args.dataIndex].appName + "<br/>请求数:" + teaweb.formatNumber(stats[args.dataIndex].countRequests)
|
||||
},
|
||||
value: function (v) {
|
||||
return v.countRequests / axis.divider
|
||||
},
|
||||
axis: axis
|
||||
})
|
||||
}
|
||||
|
||||
this.reloadTopDomainsChart = function () {
|
||||
if (this.topDomainStats == null || this.topDomainStats.length == 0) {
|
||||
return
|
||||
}
|
||||
let axis = teaweb.countAxis(this.topDomainStats, function (v) {
|
||||
return v.countRequests
|
||||
})
|
||||
teaweb.renderBarChart({
|
||||
id: "top-domains-chart",
|
||||
name: "域名",
|
||||
values: this.topDomainStats,
|
||||
x: function (v) {
|
||||
return v.domainName
|
||||
},
|
||||
tooltip: function (args, stats) {
|
||||
return stats[args.dataIndex].domainName + "<br/>请求数:" + teaweb.formatNumber(stats[args.dataIndex].countRequests)
|
||||
},
|
||||
value: function (v) {
|
||||
return v.countRequests / axis.divider
|
||||
},
|
||||
axis: axis
|
||||
})
|
||||
}
|
||||
|
||||
this.reloadTopNodesChart = function () {
|
||||
if (this.topNodeStats == null || this.topNodeStats.length == 0) {
|
||||
return
|
||||
}
|
||||
let axis = teaweb.countAxis(this.topNodeStats, function (v) {
|
||||
return v.countRequests
|
||||
})
|
||||
teaweb.renderBarChart({
|
||||
id: "top-nodes-chart",
|
||||
name: "节点",
|
||||
values: this.topNodeStats,
|
||||
x: function (v) {
|
||||
return v.nodeName
|
||||
},
|
||||
tooltip: function (args, stats) {
|
||||
return stats[args.dataIndex].nodeName + "<br/>请求数:" + teaweb.formatNumber(stats[args.dataIndex].countRequests)
|
||||
},
|
||||
value: function (v) {
|
||||
return v.countRequests / axis.divider
|
||||
},
|
||||
axis: axis,
|
||||
click: function (args, stats) {
|
||||
window.location = "/httpdns/clusters/cluster/node?nodeId=" + stats[args.dataIndex].nodeId + "&clusterId=" + stats[args.dataIndex].clusterId
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -124,6 +124,27 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- TypeE -->
|
||||
<tbody v-show="type == 'typeE'">
|
||||
<tr>
|
||||
<td>鉴权密钥 *</td>
|
||||
<td>
|
||||
<input type="text" maxlength="40" name="typeESecret" v-model="typeESecret" autocomplete="off"/>
|
||||
<p class="comment">只能包含字母、数字,长度不超过40。<a href="" @click.prevent="generateTypeESecret()">[随机生成]</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>有效时间</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" maxlength="8" name="typeELife" value="30" style="width: 7em"/>
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
<p class="comment">URL 格式:<code-label>/hash/timestamp/path</code-label>;签名算法:<code-label>md5(secret + uri + timestamp)</code-label>;timestamp 为十六进制 Unix 秒数。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- BasicAuth -->
|
||||
<tbody v-show="type == 'basicAuth'">
|
||||
<tr>
|
||||
@@ -138,7 +159,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="moreBasicAuthOptionsVisible">
|
||||
<td>认证领域名<em>(Realm)</em></td>
|
||||
<td>认证领域名 <em>(Realm)</em></td>
|
||||
<td>
|
||||
<input type="text" name="basicAuthRealm" value="" maxlength="100"/>
|
||||
</td>
|
||||
@@ -147,7 +168,7 @@
|
||||
<td>字符集</td>
|
||||
<td>
|
||||
<input type="text" name="basicAuthCharset" style="width: 6em" maxlength="50"/>
|
||||
<p class="comment">类似于<code-label>UTF-8</code-label>。</p>
|
||||
<p class="comment">类似于 <code-label>UTF-8</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -186,7 +207,7 @@
|
||||
<td>限定文件扩展名</td>
|
||||
<td>
|
||||
<values-box name="exts"></values-box>
|
||||
<p class="comment">如果不为空,则表示只有这些扩展名的文件才需要鉴权;扩展名需要包含点符号(.),比如<code-label>.png</code-label></p>
|
||||
<p class="comment">如果不为空,则表示只有这些扩展名的文件才需要鉴权;扩展名需要包含点符号,比如 <code-label>.png</code-label></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -200,4 +221,4 @@
|
||||
</table>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
@@ -84,6 +84,18 @@ Tea.context(function () {
|
||||
this.authDescription = this.authDescription.replace("t=", this.typeDTimestampParamName + "=")
|
||||
}
|
||||
|
||||
/**
|
||||
* TypeE
|
||||
*/
|
||||
this.typeESecret = ""
|
||||
|
||||
this.generateTypeESecret = function () {
|
||||
this.$post(".random")
|
||||
.success(function (resp) {
|
||||
this.typeESecret = resp.data.random
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 基本认证
|
||||
*/
|
||||
@@ -97,4 +109,4 @@ Tea.context(function () {
|
||||
* 子请求
|
||||
*/
|
||||
this.subRequestFollowRequest = 1
|
||||
})
|
||||
})
|
||||
|
||||
@@ -122,6 +122,27 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- TypeE -->
|
||||
<tbody v-show="type == 'typeE'">
|
||||
<tr>
|
||||
<td>鉴权密钥 *</td>
|
||||
<td>
|
||||
<input type="text" maxlength="40" name="typeESecret" v-model="typeESecret" autocomplete="off"/>
|
||||
<p class="comment">只能包含字母、数字,长度不超过40。<a href="" @click.prevent="generateTypeESecret()">[随机生成]</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>有效时间</td>
|
||||
<td>
|
||||
<div class="ui input right labeled">
|
||||
<input type="text" maxlength="8" name="typeELife" value="30" style="width: 7em" v-model="policy.params.life"/>
|
||||
<span class="ui label">秒</span>
|
||||
</div>
|
||||
<p class="comment">URL 格式:<code-label>/hash/timestamp/path</code-label>;签名算法:<code-label>md5(secret + uri + timestamp)</code-label>;timestamp 为十六进制 Unix 秒数。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<!-- BasicAuth -->
|
||||
<tbody v-show="type == 'basicAuth'">
|
||||
<tr>
|
||||
@@ -136,7 +157,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="moreBasicAuthOptionsVisible">
|
||||
<td>认证领域名<em>(Realm)</em></td>
|
||||
<td>认证领域名 <em>(Realm)</em></td>
|
||||
<td>
|
||||
<input type="text" name="basicAuthRealm" value="" maxlength="100" v-model="policy.params.realm"/>
|
||||
</td>
|
||||
@@ -145,7 +166,7 @@
|
||||
<td>字符集</td>
|
||||
<td>
|
||||
<input type="text" name="basicAuthCharset" style="width: 6em" v-model="policy.params.charset" maxlength="50"/>
|
||||
<p class="comment">类似于<code-label>utf-8</code-label>。</p>
|
||||
<p class="comment">类似于 <code-label>utf-8</code-label>。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -185,7 +206,7 @@
|
||||
<td>限定文件扩展名</td>
|
||||
<td>
|
||||
<values-box name="exts" :v-values="policy.params.exts"></values-box>
|
||||
<p class="comment">如果不为空,则表示只有这些扩展名的文件才需要鉴权;扩展名需要包含点符号(.),比如<code-label>.png</code-label></p>
|
||||
<p class="comment">如果不为空,则表示只有这些扩展名的文件才需要鉴权;扩展名需要包含点符号,比如 <code-label>.png</code-label></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -203,4 +224,4 @@
|
||||
</table>
|
||||
|
||||
<submit-btn></submit-btn>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
@@ -115,6 +115,22 @@ Tea.context(function () {
|
||||
this.authDescription = this.authDescription.replace("t=", this.typeDTimestampParamName + "=")
|
||||
}
|
||||
|
||||
/**
|
||||
* TypeE
|
||||
*/
|
||||
this.typeESecret = ""
|
||||
|
||||
if (this.policy.type == "typeE") {
|
||||
this.typeESecret = this.policy.params.secret
|
||||
}
|
||||
|
||||
this.generateTypeESecret = function () {
|
||||
this.$post(".random")
|
||||
.success(function (resp) {
|
||||
this.typeESecret = resp.data.random
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 基本鉴权
|
||||
*/
|
||||
@@ -128,4 +144,4 @@ Tea.context(function () {
|
||||
* 子请求
|
||||
*/
|
||||
this.subRequestFollowRequest = (this.policy.params.method != null && this.policy.params.method.length > 0) ? 0 : 1
|
||||
})
|
||||
})
|
||||
|
||||
1
EdgeCommon/.gitignore
vendored
1
EdgeCommon/.gitignore
vendored
@@ -0,0 +1 @@
|
||||
build/temp_build.sh
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
650
EdgeCommon/pkg/rpc/pb/service_httpdns_board.pb.go
Normal file
650
EdgeCommon/pkg/rpc/pb/service_httpdns_board.pb.go
Normal file
@@ -0,0 +1,650 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v3.12.4
|
||||
// source: service_httpdns_board.proto
|
||||
|
||||
package pb
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// 组合看板数据请求
|
||||
type ComposeHTTPDNSBoardRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardRequest) Reset() {
|
||||
*x = ComposeHTTPDNSBoardRequest{}
|
||||
mi := &file_service_httpdns_board_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ComposeHTTPDNSBoardRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_service_httpdns_board_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ComposeHTTPDNSBoardRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ComposeHTTPDNSBoardRequest) Descriptor() ([]byte, []int) {
|
||||
return file_service_httpdns_board_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
// 组合看板数据响应
|
||||
type ComposeHTTPDNSBoardResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
CountApps int64 `protobuf:"varint,1,opt,name=countApps,proto3" json:"countApps,omitempty"`
|
||||
CountDomains int64 `protobuf:"varint,2,opt,name=countDomains,proto3" json:"countDomains,omitempty"`
|
||||
CountClusters int64 `protobuf:"varint,3,opt,name=countClusters,proto3" json:"countClusters,omitempty"`
|
||||
CountNodes int64 `protobuf:"varint,4,opt,name=countNodes,proto3" json:"countNodes,omitempty"`
|
||||
CountOfflineNodes int64 `protobuf:"varint,5,opt,name=countOfflineNodes,proto3" json:"countOfflineNodes,omitempty"`
|
||||
DailyTrafficStats []*ComposeHTTPDNSBoardResponse_DailyTrafficStat `protobuf:"bytes,30,rep,name=dailyTrafficStats,proto3" json:"dailyTrafficStats,omitempty"`
|
||||
HourlyTrafficStats []*ComposeHTTPDNSBoardResponse_HourlyTrafficStat `protobuf:"bytes,31,rep,name=hourlyTrafficStats,proto3" json:"hourlyTrafficStats,omitempty"`
|
||||
TopAppStats []*ComposeHTTPDNSBoardResponse_TopAppStat `protobuf:"bytes,32,rep,name=topAppStats,proto3" json:"topAppStats,omitempty"`
|
||||
TopDomainStats []*ComposeHTTPDNSBoardResponse_TopDomainStat `protobuf:"bytes,33,rep,name=topDomainStats,proto3" json:"topDomainStats,omitempty"`
|
||||
TopNodeStats []*ComposeHTTPDNSBoardResponse_TopNodeStat `protobuf:"bytes,34,rep,name=topNodeStats,proto3" json:"topNodeStats,omitempty"`
|
||||
CpuNodeValues []*NodeValue `protobuf:"bytes,35,rep,name=cpuNodeValues,proto3" json:"cpuNodeValues,omitempty"`
|
||||
MemoryNodeValues []*NodeValue `protobuf:"bytes,36,rep,name=memoryNodeValues,proto3" json:"memoryNodeValues,omitempty"`
|
||||
LoadNodeValues []*NodeValue `protobuf:"bytes,37,rep,name=loadNodeValues,proto3" json:"loadNodeValues,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) Reset() {
|
||||
*x = ComposeHTTPDNSBoardResponse{}
|
||||
mi := &file_service_httpdns_board_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ComposeHTTPDNSBoardResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_service_httpdns_board_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ComposeHTTPDNSBoardResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ComposeHTTPDNSBoardResponse) Descriptor() ([]byte, []int) {
|
||||
return file_service_httpdns_board_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) GetCountApps() int64 {
|
||||
if x != nil {
|
||||
return x.CountApps
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) GetCountDomains() int64 {
|
||||
if x != nil {
|
||||
return x.CountDomains
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) GetCountClusters() int64 {
|
||||
if x != nil {
|
||||
return x.CountClusters
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) GetCountNodes() int64 {
|
||||
if x != nil {
|
||||
return x.CountNodes
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) GetCountOfflineNodes() int64 {
|
||||
if x != nil {
|
||||
return x.CountOfflineNodes
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) GetDailyTrafficStats() []*ComposeHTTPDNSBoardResponse_DailyTrafficStat {
|
||||
if x != nil {
|
||||
return x.DailyTrafficStats
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) GetHourlyTrafficStats() []*ComposeHTTPDNSBoardResponse_HourlyTrafficStat {
|
||||
if x != nil {
|
||||
return x.HourlyTrafficStats
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) GetTopAppStats() []*ComposeHTTPDNSBoardResponse_TopAppStat {
|
||||
if x != nil {
|
||||
return x.TopAppStats
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) GetTopDomainStats() []*ComposeHTTPDNSBoardResponse_TopDomainStat {
|
||||
if x != nil {
|
||||
return x.TopDomainStats
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) GetTopNodeStats() []*ComposeHTTPDNSBoardResponse_TopNodeStat {
|
||||
if x != nil {
|
||||
return x.TopNodeStats
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) GetCpuNodeValues() []*NodeValue {
|
||||
if x != nil {
|
||||
return x.CpuNodeValues
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) GetMemoryNodeValues() []*NodeValue {
|
||||
if x != nil {
|
||||
return x.MemoryNodeValues
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse) GetLoadNodeValues() []*NodeValue {
|
||||
if x != nil {
|
||||
return x.LoadNodeValues
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ComposeHTTPDNSBoardResponse_DailyTrafficStat struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Day string `protobuf:"bytes,1,opt,name=day,proto3" json:"day,omitempty"`
|
||||
Bytes int64 `protobuf:"varint,2,opt,name=bytes,proto3" json:"bytes,omitempty"`
|
||||
CountRequests int64 `protobuf:"varint,3,opt,name=countRequests,proto3" json:"countRequests,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_DailyTrafficStat) Reset() {
|
||||
*x = ComposeHTTPDNSBoardResponse_DailyTrafficStat{}
|
||||
mi := &file_service_httpdns_board_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_DailyTrafficStat) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ComposeHTTPDNSBoardResponse_DailyTrafficStat) ProtoMessage() {}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_DailyTrafficStat) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_service_httpdns_board_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ComposeHTTPDNSBoardResponse_DailyTrafficStat.ProtoReflect.Descriptor instead.
|
||||
func (*ComposeHTTPDNSBoardResponse_DailyTrafficStat) Descriptor() ([]byte, []int) {
|
||||
return file_service_httpdns_board_proto_rawDescGZIP(), []int{1, 0}
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_DailyTrafficStat) GetDay() string {
|
||||
if x != nil {
|
||||
return x.Day
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_DailyTrafficStat) GetBytes() int64 {
|
||||
if x != nil {
|
||||
return x.Bytes
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_DailyTrafficStat) GetCountRequests() int64 {
|
||||
if x != nil {
|
||||
return x.CountRequests
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ComposeHTTPDNSBoardResponse_HourlyTrafficStat struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Hour string `protobuf:"bytes,1,opt,name=hour,proto3" json:"hour,omitempty"`
|
||||
Bytes int64 `protobuf:"varint,2,opt,name=bytes,proto3" json:"bytes,omitempty"`
|
||||
CountRequests int64 `protobuf:"varint,3,opt,name=countRequests,proto3" json:"countRequests,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_HourlyTrafficStat) Reset() {
|
||||
*x = ComposeHTTPDNSBoardResponse_HourlyTrafficStat{}
|
||||
mi := &file_service_httpdns_board_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_HourlyTrafficStat) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ComposeHTTPDNSBoardResponse_HourlyTrafficStat) ProtoMessage() {}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_HourlyTrafficStat) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_service_httpdns_board_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ComposeHTTPDNSBoardResponse_HourlyTrafficStat.ProtoReflect.Descriptor instead.
|
||||
func (*ComposeHTTPDNSBoardResponse_HourlyTrafficStat) Descriptor() ([]byte, []int) {
|
||||
return file_service_httpdns_board_proto_rawDescGZIP(), []int{1, 1}
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_HourlyTrafficStat) GetHour() string {
|
||||
if x != nil {
|
||||
return x.Hour
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_HourlyTrafficStat) GetBytes() int64 {
|
||||
if x != nil {
|
||||
return x.Bytes
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_HourlyTrafficStat) GetCountRequests() int64 {
|
||||
if x != nil {
|
||||
return x.CountRequests
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ComposeHTTPDNSBoardResponse_TopAppStat struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
AppId int64 `protobuf:"varint,1,opt,name=appId,proto3" json:"appId,omitempty"`
|
||||
AppName string `protobuf:"bytes,2,opt,name=appName,proto3" json:"appName,omitempty"`
|
||||
CountRequests int64 `protobuf:"varint,3,opt,name=countRequests,proto3" json:"countRequests,omitempty"`
|
||||
Bytes int64 `protobuf:"varint,4,opt,name=bytes,proto3" json:"bytes,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopAppStat) Reset() {
|
||||
*x = ComposeHTTPDNSBoardResponse_TopAppStat{}
|
||||
mi := &file_service_httpdns_board_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopAppStat) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ComposeHTTPDNSBoardResponse_TopAppStat) ProtoMessage() {}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopAppStat) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_service_httpdns_board_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ComposeHTTPDNSBoardResponse_TopAppStat.ProtoReflect.Descriptor instead.
|
||||
func (*ComposeHTTPDNSBoardResponse_TopAppStat) Descriptor() ([]byte, []int) {
|
||||
return file_service_httpdns_board_proto_rawDescGZIP(), []int{1, 2}
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopAppStat) GetAppId() int64 {
|
||||
if x != nil {
|
||||
return x.AppId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopAppStat) GetAppName() string {
|
||||
if x != nil {
|
||||
return x.AppName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopAppStat) GetCountRequests() int64 {
|
||||
if x != nil {
|
||||
return x.CountRequests
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopAppStat) GetBytes() int64 {
|
||||
if x != nil {
|
||||
return x.Bytes
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ComposeHTTPDNSBoardResponse_TopDomainStat struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
DomainId int64 `protobuf:"varint,1,opt,name=domainId,proto3" json:"domainId,omitempty"`
|
||||
DomainName string `protobuf:"bytes,2,opt,name=domainName,proto3" json:"domainName,omitempty"`
|
||||
CountRequests int64 `protobuf:"varint,3,opt,name=countRequests,proto3" json:"countRequests,omitempty"`
|
||||
Bytes int64 `protobuf:"varint,4,opt,name=bytes,proto3" json:"bytes,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopDomainStat) Reset() {
|
||||
*x = ComposeHTTPDNSBoardResponse_TopDomainStat{}
|
||||
mi := &file_service_httpdns_board_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopDomainStat) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ComposeHTTPDNSBoardResponse_TopDomainStat) ProtoMessage() {}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopDomainStat) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_service_httpdns_board_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ComposeHTTPDNSBoardResponse_TopDomainStat.ProtoReflect.Descriptor instead.
|
||||
func (*ComposeHTTPDNSBoardResponse_TopDomainStat) Descriptor() ([]byte, []int) {
|
||||
return file_service_httpdns_board_proto_rawDescGZIP(), []int{1, 3}
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopDomainStat) GetDomainId() int64 {
|
||||
if x != nil {
|
||||
return x.DomainId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopDomainStat) GetDomainName() string {
|
||||
if x != nil {
|
||||
return x.DomainName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopDomainStat) GetCountRequests() int64 {
|
||||
if x != nil {
|
||||
return x.CountRequests
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopDomainStat) GetBytes() int64 {
|
||||
if x != nil {
|
||||
return x.Bytes
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ComposeHTTPDNSBoardResponse_TopNodeStat struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ClusterId int64 `protobuf:"varint,1,opt,name=clusterId,proto3" json:"clusterId,omitempty"`
|
||||
NodeId int64 `protobuf:"varint,2,opt,name=nodeId,proto3" json:"nodeId,omitempty"`
|
||||
NodeName string `protobuf:"bytes,3,opt,name=nodeName,proto3" json:"nodeName,omitempty"`
|
||||
CountRequests int64 `protobuf:"varint,4,opt,name=countRequests,proto3" json:"countRequests,omitempty"`
|
||||
Bytes int64 `protobuf:"varint,5,opt,name=bytes,proto3" json:"bytes,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopNodeStat) Reset() {
|
||||
*x = ComposeHTTPDNSBoardResponse_TopNodeStat{}
|
||||
mi := &file_service_httpdns_board_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopNodeStat) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ComposeHTTPDNSBoardResponse_TopNodeStat) ProtoMessage() {}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopNodeStat) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_service_httpdns_board_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ComposeHTTPDNSBoardResponse_TopNodeStat.ProtoReflect.Descriptor instead.
|
||||
func (*ComposeHTTPDNSBoardResponse_TopNodeStat) Descriptor() ([]byte, []int) {
|
||||
return file_service_httpdns_board_proto_rawDescGZIP(), []int{1, 4}
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopNodeStat) GetClusterId() int64 {
|
||||
if x != nil {
|
||||
return x.ClusterId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopNodeStat) GetNodeId() int64 {
|
||||
if x != nil {
|
||||
return x.NodeId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopNodeStat) GetNodeName() string {
|
||||
if x != nil {
|
||||
return x.NodeName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopNodeStat) GetCountRequests() int64 {
|
||||
if x != nil {
|
||||
return x.CountRequests
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ComposeHTTPDNSBoardResponse_TopNodeStat) GetBytes() int64 {
|
||||
if x != nil {
|
||||
return x.Bytes
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_service_httpdns_board_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_service_httpdns_board_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x1bservice_httpdns_board.proto\x12\x02pb\x1a\x1dmodels/model_node_value.proto\"\x1c\n" +
|
||||
"\x1aComposeHTTPDNSBoardRequest\"\x9c\v\n" +
|
||||
"\x1bComposeHTTPDNSBoardResponse\x12\x1c\n" +
|
||||
"\tcountApps\x18\x01 \x01(\x03R\tcountApps\x12\"\n" +
|
||||
"\fcountDomains\x18\x02 \x01(\x03R\fcountDomains\x12$\n" +
|
||||
"\rcountClusters\x18\x03 \x01(\x03R\rcountClusters\x12\x1e\n" +
|
||||
"\n" +
|
||||
"countNodes\x18\x04 \x01(\x03R\n" +
|
||||
"countNodes\x12,\n" +
|
||||
"\x11countOfflineNodes\x18\x05 \x01(\x03R\x11countOfflineNodes\x12^\n" +
|
||||
"\x11dailyTrafficStats\x18\x1e \x03(\v20.pb.ComposeHTTPDNSBoardResponse.DailyTrafficStatR\x11dailyTrafficStats\x12a\n" +
|
||||
"\x12hourlyTrafficStats\x18\x1f \x03(\v21.pb.ComposeHTTPDNSBoardResponse.HourlyTrafficStatR\x12hourlyTrafficStats\x12L\n" +
|
||||
"\vtopAppStats\x18 \x03(\v2*.pb.ComposeHTTPDNSBoardResponse.TopAppStatR\vtopAppStats\x12U\n" +
|
||||
"\x0etopDomainStats\x18! \x03(\v2-.pb.ComposeHTTPDNSBoardResponse.TopDomainStatR\x0etopDomainStats\x12O\n" +
|
||||
"\ftopNodeStats\x18\" \x03(\v2+.pb.ComposeHTTPDNSBoardResponse.TopNodeStatR\ftopNodeStats\x123\n" +
|
||||
"\rcpuNodeValues\x18# \x03(\v2\r.pb.NodeValueR\rcpuNodeValues\x129\n" +
|
||||
"\x10memoryNodeValues\x18$ \x03(\v2\r.pb.NodeValueR\x10memoryNodeValues\x125\n" +
|
||||
"\x0eloadNodeValues\x18% \x03(\v2\r.pb.NodeValueR\x0eloadNodeValues\x1a`\n" +
|
||||
"\x10DailyTrafficStat\x12\x10\n" +
|
||||
"\x03day\x18\x01 \x01(\tR\x03day\x12\x14\n" +
|
||||
"\x05bytes\x18\x02 \x01(\x03R\x05bytes\x12$\n" +
|
||||
"\rcountRequests\x18\x03 \x01(\x03R\rcountRequests\x1ac\n" +
|
||||
"\x11HourlyTrafficStat\x12\x12\n" +
|
||||
"\x04hour\x18\x01 \x01(\tR\x04hour\x12\x14\n" +
|
||||
"\x05bytes\x18\x02 \x01(\x03R\x05bytes\x12$\n" +
|
||||
"\rcountRequests\x18\x03 \x01(\x03R\rcountRequests\x1ax\n" +
|
||||
"\n" +
|
||||
"TopAppStat\x12\x14\n" +
|
||||
"\x05appId\x18\x01 \x01(\x03R\x05appId\x12\x18\n" +
|
||||
"\aappName\x18\x02 \x01(\tR\aappName\x12$\n" +
|
||||
"\rcountRequests\x18\x03 \x01(\x03R\rcountRequests\x12\x14\n" +
|
||||
"\x05bytes\x18\x04 \x01(\x03R\x05bytes\x1a\x87\x01\n" +
|
||||
"\rTopDomainStat\x12\x1a\n" +
|
||||
"\bdomainId\x18\x01 \x01(\x03R\bdomainId\x12\x1e\n" +
|
||||
"\n" +
|
||||
"domainName\x18\x02 \x01(\tR\n" +
|
||||
"domainName\x12$\n" +
|
||||
"\rcountRequests\x18\x03 \x01(\x03R\rcountRequests\x12\x14\n" +
|
||||
"\x05bytes\x18\x04 \x01(\x03R\x05bytes\x1a\x9b\x01\n" +
|
||||
"\vTopNodeStat\x12\x1c\n" +
|
||||
"\tclusterId\x18\x01 \x01(\x03R\tclusterId\x12\x16\n" +
|
||||
"\x06nodeId\x18\x02 \x01(\x03R\x06nodeId\x12\x1a\n" +
|
||||
"\bnodeName\x18\x03 \x01(\tR\bnodeName\x12$\n" +
|
||||
"\rcountRequests\x18\x04 \x01(\x03R\rcountRequests\x12\x14\n" +
|
||||
"\x05bytes\x18\x05 \x01(\x03R\x05bytes2m\n" +
|
||||
"\x13HTTPDNSBoardService\x12V\n" +
|
||||
"\x13composeHTTPDNSBoard\x12\x1e.pb.ComposeHTTPDNSBoardRequest\x1a\x1f.pb.ComposeHTTPDNSBoardResponseB\x06Z\x04./pbb\x06proto3"
|
||||
|
||||
var (
|
||||
file_service_httpdns_board_proto_rawDescOnce sync.Once
|
||||
file_service_httpdns_board_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_service_httpdns_board_proto_rawDescGZIP() []byte {
|
||||
file_service_httpdns_board_proto_rawDescOnce.Do(func() {
|
||||
file_service_httpdns_board_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_service_httpdns_board_proto_rawDesc), len(file_service_httpdns_board_proto_rawDesc)))
|
||||
})
|
||||
return file_service_httpdns_board_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_service_httpdns_board_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||
var file_service_httpdns_board_proto_goTypes = []any{
|
||||
(*ComposeHTTPDNSBoardRequest)(nil), // 0: pb.ComposeHTTPDNSBoardRequest
|
||||
(*ComposeHTTPDNSBoardResponse)(nil), // 1: pb.ComposeHTTPDNSBoardResponse
|
||||
(*ComposeHTTPDNSBoardResponse_DailyTrafficStat)(nil), // 2: pb.ComposeHTTPDNSBoardResponse.DailyTrafficStat
|
||||
(*ComposeHTTPDNSBoardResponse_HourlyTrafficStat)(nil), // 3: pb.ComposeHTTPDNSBoardResponse.HourlyTrafficStat
|
||||
(*ComposeHTTPDNSBoardResponse_TopAppStat)(nil), // 4: pb.ComposeHTTPDNSBoardResponse.TopAppStat
|
||||
(*ComposeHTTPDNSBoardResponse_TopDomainStat)(nil), // 5: pb.ComposeHTTPDNSBoardResponse.TopDomainStat
|
||||
(*ComposeHTTPDNSBoardResponse_TopNodeStat)(nil), // 6: pb.ComposeHTTPDNSBoardResponse.TopNodeStat
|
||||
(*NodeValue)(nil), // 7: pb.NodeValue
|
||||
}
|
||||
var file_service_httpdns_board_proto_depIdxs = []int32{
|
||||
2, // 0: pb.ComposeHTTPDNSBoardResponse.dailyTrafficStats:type_name -> pb.ComposeHTTPDNSBoardResponse.DailyTrafficStat
|
||||
3, // 1: pb.ComposeHTTPDNSBoardResponse.hourlyTrafficStats:type_name -> pb.ComposeHTTPDNSBoardResponse.HourlyTrafficStat
|
||||
4, // 2: pb.ComposeHTTPDNSBoardResponse.topAppStats:type_name -> pb.ComposeHTTPDNSBoardResponse.TopAppStat
|
||||
5, // 3: pb.ComposeHTTPDNSBoardResponse.topDomainStats:type_name -> pb.ComposeHTTPDNSBoardResponse.TopDomainStat
|
||||
6, // 4: pb.ComposeHTTPDNSBoardResponse.topNodeStats:type_name -> pb.ComposeHTTPDNSBoardResponse.TopNodeStat
|
||||
7, // 5: pb.ComposeHTTPDNSBoardResponse.cpuNodeValues:type_name -> pb.NodeValue
|
||||
7, // 6: pb.ComposeHTTPDNSBoardResponse.memoryNodeValues:type_name -> pb.NodeValue
|
||||
7, // 7: pb.ComposeHTTPDNSBoardResponse.loadNodeValues:type_name -> pb.NodeValue
|
||||
0, // 8: pb.HTTPDNSBoardService.composeHTTPDNSBoard:input_type -> pb.ComposeHTTPDNSBoardRequest
|
||||
1, // 9: pb.HTTPDNSBoardService.composeHTTPDNSBoard:output_type -> pb.ComposeHTTPDNSBoardResponse
|
||||
9, // [9:10] is the sub-list for method output_type
|
||||
8, // [8:9] is the sub-list for method input_type
|
||||
8, // [8:8] is the sub-list for extension type_name
|
||||
8, // [8:8] is the sub-list for extension extendee
|
||||
0, // [0:8] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_service_httpdns_board_proto_init() }
|
||||
func file_service_httpdns_board_proto_init() {
|
||||
if File_service_httpdns_board_proto != nil {
|
||||
return
|
||||
}
|
||||
file_models_model_node_value_proto_init()
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_service_httpdns_board_proto_rawDesc), len(file_service_httpdns_board_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 7,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_service_httpdns_board_proto_goTypes,
|
||||
DependencyIndexes: file_service_httpdns_board_proto_depIdxs,
|
||||
MessageInfos: file_service_httpdns_board_proto_msgTypes,
|
||||
}.Build()
|
||||
File_service_httpdns_board_proto = out.File
|
||||
file_service_httpdns_board_proto_goTypes = nil
|
||||
file_service_httpdns_board_proto_depIdxs = nil
|
||||
}
|
||||
125
EdgeCommon/pkg/rpc/pb/service_httpdns_board_grpc.pb.go
Normal file
125
EdgeCommon/pkg/rpc/pb/service_httpdns_board_grpc.pb.go
Normal file
@@ -0,0 +1,125 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.1
|
||||
// - protoc v3.12.4
|
||||
// source: service_httpdns_board.proto
|
||||
|
||||
package pb
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
HTTPDNSBoardService_ComposeHTTPDNSBoard_FullMethodName = "/pb.HTTPDNSBoardService/composeHTTPDNSBoard"
|
||||
)
|
||||
|
||||
// HTTPDNSBoardServiceClient is the client API for HTTPDNSBoardService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
//
|
||||
// HTTPDNS 看板服务
|
||||
type HTTPDNSBoardServiceClient interface {
|
||||
// 组合看板数据
|
||||
ComposeHTTPDNSBoard(ctx context.Context, in *ComposeHTTPDNSBoardRequest, opts ...grpc.CallOption) (*ComposeHTTPDNSBoardResponse, error)
|
||||
}
|
||||
|
||||
type hTTPDNSBoardServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewHTTPDNSBoardServiceClient(cc grpc.ClientConnInterface) HTTPDNSBoardServiceClient {
|
||||
return &hTTPDNSBoardServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *hTTPDNSBoardServiceClient) ComposeHTTPDNSBoard(ctx context.Context, in *ComposeHTTPDNSBoardRequest, opts ...grpc.CallOption) (*ComposeHTTPDNSBoardResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ComposeHTTPDNSBoardResponse)
|
||||
err := c.cc.Invoke(ctx, HTTPDNSBoardService_ComposeHTTPDNSBoard_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// HTTPDNSBoardServiceServer is the server API for HTTPDNSBoardService service.
|
||||
// All implementations should embed UnimplementedHTTPDNSBoardServiceServer
|
||||
// for forward compatibility.
|
||||
//
|
||||
// HTTPDNS 看板服务
|
||||
type HTTPDNSBoardServiceServer interface {
|
||||
// 组合看板数据
|
||||
ComposeHTTPDNSBoard(context.Context, *ComposeHTTPDNSBoardRequest) (*ComposeHTTPDNSBoardResponse, error)
|
||||
}
|
||||
|
||||
// UnimplementedHTTPDNSBoardServiceServer should be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedHTTPDNSBoardServiceServer struct{}
|
||||
|
||||
func (UnimplementedHTTPDNSBoardServiceServer) ComposeHTTPDNSBoard(context.Context, *ComposeHTTPDNSBoardRequest) (*ComposeHTTPDNSBoardResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method ComposeHTTPDNSBoard not implemented")
|
||||
}
|
||||
func (UnimplementedHTTPDNSBoardServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeHTTPDNSBoardServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to HTTPDNSBoardServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeHTTPDNSBoardServiceServer interface {
|
||||
mustEmbedUnimplementedHTTPDNSBoardServiceServer()
|
||||
}
|
||||
|
||||
func RegisterHTTPDNSBoardServiceServer(s grpc.ServiceRegistrar, srv HTTPDNSBoardServiceServer) {
|
||||
// If the following call panics, it indicates UnimplementedHTTPDNSBoardServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&HTTPDNSBoardService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _HTTPDNSBoardService_ComposeHTTPDNSBoard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ComposeHTTPDNSBoardRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(HTTPDNSBoardServiceServer).ComposeHTTPDNSBoard(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: HTTPDNSBoardService_ComposeHTTPDNSBoard_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(HTTPDNSBoardServiceServer).ComposeHTTPDNSBoard(ctx, req.(*ComposeHTTPDNSBoardRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// HTTPDNSBoardService_ServiceDesc is the grpc.ServiceDesc for HTTPDNSBoardService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var HTTPDNSBoardService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "pb.HTTPDNSBoardService",
|
||||
HandlerType: (*HTTPDNSBoardServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "composeHTTPDNSBoard",
|
||||
Handler: _HTTPDNSBoardService_ComposeHTTPDNSBoard_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "service_httpdns_board.proto",
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
)
|
||||
|
||||
var httpAuthTimestampRegexp = regexp.MustCompile(`^\d{10}$`)
|
||||
var httpAuthHexTimestampRegexp = regexp.MustCompile(`^[0-9a-fA-F]{1,16}$`)
|
||||
|
||||
type HTTPAuthBaseMethod struct {
|
||||
Exts []string `json:"exts"`
|
||||
|
||||
@@ -7,12 +7,12 @@ type HTTPAuthType = string
|
||||
|
||||
const (
|
||||
HTTPAuthTypeBasicAuth HTTPAuthType = "basicAuth" // BasicAuth
|
||||
HTTPAuthTypeSubRequest HTTPAuthType = "subRequest" // 子请求
|
||||
|
||||
HTTPAuthTypeTypeA HTTPAuthType = "typeA"
|
||||
HTTPAuthTypeTypeB HTTPAuthType = "typeB"
|
||||
HTTPAuthTypeTypeC HTTPAuthType = "typeC"
|
||||
HTTPAuthTypeTypeD HTTPAuthType = "typeD"
|
||||
HTTPAuthTypeSubRequest HTTPAuthType = "subRequest" // SubRequest
|
||||
HTTPAuthTypeTypeA HTTPAuthType = "typeA"
|
||||
HTTPAuthTypeTypeB HTTPAuthType = "typeB"
|
||||
HTTPAuthTypeTypeC HTTPAuthType = "typeC"
|
||||
HTTPAuthTypeTypeD HTTPAuthType = "typeD"
|
||||
HTTPAuthTypeTypeE HTTPAuthType = "typeE"
|
||||
)
|
||||
|
||||
type HTTPAuthTypeDefinition struct {
|
||||
@@ -23,10 +23,11 @@ type HTTPAuthTypeDefinition struct {
|
||||
}
|
||||
|
||||
func FindAllHTTPAuthTypes(role string) []*HTTPAuthTypeDefinition {
|
||||
var urlDocA = ""
|
||||
var urlDocB = ""
|
||||
var urlDocC = ""
|
||||
var urlDocD = ""
|
||||
urlDocA := ""
|
||||
urlDocB := ""
|
||||
urlDocC := ""
|
||||
urlDocD := ""
|
||||
urlDocE := "https://help.aliyun.com/zh/cdn/user-guide/type-c-signing"
|
||||
|
||||
switch role {
|
||||
case "admin":
|
||||
@@ -66,15 +67,21 @@ func FindAllHTTPAuthTypes(role string) []*HTTPAuthTypeDefinition {
|
||||
Description: "示例URL:https://example.com/images/test.jpg?sign=f66af42f87cf63a64f4b86ec11c7797a&t=1661753717<br/><a href=\"" + urlDocD + "\" target=\"_blank\">[算法详解]</a>",
|
||||
IsPlus: true,
|
||||
},
|
||||
{
|
||||
Name: "URL鉴权E",
|
||||
Code: HTTPAuthTypeTypeE,
|
||||
Description: "严格兼容阿里云 Type-C 路径鉴权,示例URL:https://example.com/3a2c79f2d2f0df2f8f9e05ec9f482e5d/67cfdb9e/images/test.jpg<br/><a href=\"" + urlDocE + "\" target=\"_blank\">[阿里云文档]</a>",
|
||||
IsPlus: true,
|
||||
},
|
||||
{
|
||||
Name: "基本认证",
|
||||
Code: HTTPAuthTypeBasicAuth,
|
||||
Description: "BasicAuth,最简单的HTTP请求认证方式,通过传递<span class=\"ui label tiny basic text\">Authorization: Basic xxx</span> Header认证。",
|
||||
Description: "BasicAuth,最简单的 HTTP 请求认证方式,通过传递 <span class=\"ui label tiny basic text\">Authorization: Basic xxx</span> Header 认证。",
|
||||
},
|
||||
{
|
||||
Name: "子请求",
|
||||
Code: HTTPAuthTypeSubRequest,
|
||||
Description: "通过自定义的URL子请求来认证请求。",
|
||||
Description: "通过自定义的 URL 子请求来认证请求。",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ func (this *HTTPAuthPolicy) Init() error {
|
||||
this.method = NewHTTPAuthTypeCMethod()
|
||||
case HTTPAuthTypeTypeD:
|
||||
this.method = NewHTTPAuthTypeDMethod()
|
||||
case HTTPAuthTypeTypeE:
|
||||
this.method = NewHTTPAuthTypeEMethod()
|
||||
}
|
||||
|
||||
if this.method == nil {
|
||||
|
||||
@@ -1,5 +1,138 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function find-binary() {
|
||||
local candidate
|
||||
for candidate in "$@"; do
|
||||
if [ -z "$candidate" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ -x "$candidate" ]; then
|
||||
echo "$candidate"
|
||||
return 0
|
||||
fi
|
||||
if command -v "$candidate" >/dev/null 2>&1; then
|
||||
command -v "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
function host-goarch() {
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64)
|
||||
echo "amd64"
|
||||
;;
|
||||
aarch64|arm64)
|
||||
echo "arm64"
|
||||
;;
|
||||
i386|i486|i586|i686)
|
||||
echo "386"
|
||||
;;
|
||||
mips64)
|
||||
echo "mips64"
|
||||
;;
|
||||
mips64el)
|
||||
echo "mips64le"
|
||||
;;
|
||||
*)
|
||||
echo ""
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function find-linux-static-toolchain() {
|
||||
local arch=$1
|
||||
local cc_bin=""
|
||||
local cxx_bin=""
|
||||
local host_arch
|
||||
|
||||
host_arch=$(host-goarch)
|
||||
|
||||
case "$arch" in
|
||||
amd64)
|
||||
cc_bin=$(find-binary \
|
||||
"/usr/local/gcc/x86_64-unknown-linux-gnu/bin/x86_64-unknown-linux-gnu-gcc" \
|
||||
"/usr/local/opt/musl-cross/bin/x86_64-linux-musl-gcc" \
|
||||
"x86_64-unknown-linux-gnu-gcc" \
|
||||
"x86_64-linux-musl-gcc" \
|
||||
"musl-gcc")
|
||||
cxx_bin=$(find-binary \
|
||||
"/usr/local/gcc/x86_64-unknown-linux-gnu/bin/x86_64-unknown-linux-gnu-g++" \
|
||||
"/usr/local/opt/musl-cross/bin/x86_64-linux-musl-g++" \
|
||||
"x86_64-unknown-linux-gnu-g++" \
|
||||
"x86_64-linux-musl-g++")
|
||||
if [ -z "$cc_bin" ] && [ "$host_arch" = "amd64" ]; then
|
||||
cc_bin=$(find-binary "gcc" "cc" "clang")
|
||||
fi
|
||||
if [ -z "$cxx_bin" ] && [ "$host_arch" = "amd64" ]; then
|
||||
cxx_bin=$(find-binary "g++" "c++" "clang++")
|
||||
fi
|
||||
;;
|
||||
386)
|
||||
cc_bin=$(find-binary "/usr/local/opt/musl-cross/bin/i486-linux-musl-gcc" "i486-linux-musl-gcc")
|
||||
cxx_bin=$(find-binary "/usr/local/opt/musl-cross/bin/i486-linux-musl-g++" "i486-linux-musl-g++")
|
||||
if [ -z "$cc_bin" ] && [ "$host_arch" = "386" ]; then
|
||||
cc_bin=$(find-binary "gcc" "cc" "clang")
|
||||
fi
|
||||
if [ -z "$cxx_bin" ] && [ "$host_arch" = "386" ]; then
|
||||
cxx_bin=$(find-binary "g++" "c++" "clang++")
|
||||
fi
|
||||
;;
|
||||
arm64)
|
||||
cc_bin=$(find-binary \
|
||||
"/usr/local/gcc/aarch64-unknown-linux-gnu/bin/aarch64-unknown-linux-gnu-gcc" \
|
||||
"/usr/local/opt/musl-cross/bin/aarch64-linux-musl-gcc" \
|
||||
"aarch64-unknown-linux-gnu-gcc" \
|
||||
"aarch64-linux-musl-gcc")
|
||||
cxx_bin=$(find-binary \
|
||||
"/usr/local/gcc/aarch64-unknown-linux-gnu/bin/aarch64-unknown-linux-gnu-g++" \
|
||||
"/usr/local/opt/musl-cross/bin/aarch64-linux-musl-g++" \
|
||||
"aarch64-unknown-linux-gnu-g++" \
|
||||
"aarch64-linux-musl-g++")
|
||||
if [ -z "$cc_bin" ] && [ "$host_arch" = "arm64" ]; then
|
||||
cc_bin=$(find-binary "gcc" "cc" "clang")
|
||||
fi
|
||||
if [ -z "$cxx_bin" ] && [ "$host_arch" = "arm64" ]; then
|
||||
cxx_bin=$(find-binary "g++" "c++" "clang++")
|
||||
fi
|
||||
;;
|
||||
mips64)
|
||||
cc_bin=$(find-binary "/usr/local/opt/musl-cross/bin/mips64-linux-musl-gcc" "mips64-linux-musl-gcc")
|
||||
cxx_bin=$(find-binary "/usr/local/opt/musl-cross/bin/mips64-linux-musl-g++" "mips64-linux-musl-g++")
|
||||
if [ -z "$cc_bin" ] && [ "$host_arch" = "mips64" ]; then
|
||||
cc_bin=$(find-binary "gcc" "cc" "clang")
|
||||
fi
|
||||
if [ -z "$cxx_bin" ] && [ "$host_arch" = "mips64" ]; then
|
||||
cxx_bin=$(find-binary "g++" "c++" "clang++")
|
||||
fi
|
||||
;;
|
||||
mips64le)
|
||||
cc_bin=$(find-binary "/usr/local/opt/musl-cross/bin/mips64el-linux-musl-gcc" "mips64el-linux-musl-gcc")
|
||||
cxx_bin=$(find-binary "/usr/local/opt/musl-cross/bin/mips64el-linux-musl-g++" "mips64el-linux-musl-g++")
|
||||
if [ -z "$cc_bin" ] && [ "$host_arch" = "mips64le" ]; then
|
||||
cc_bin=$(find-binary "gcc" "cc" "clang")
|
||||
fi
|
||||
if [ -z "$cxx_bin" ] && [ "$host_arch" = "mips64le" ]; then
|
||||
cxx_bin=$(find-binary "g++" "c++" "clang++")
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$cc_bin" ]; then
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$cxx_bin" ]; then
|
||||
cxx_bin="$cc_bin"
|
||||
fi
|
||||
|
||||
echo "$cc_bin|$cxx_bin"
|
||||
return 0
|
||||
}
|
||||
|
||||
function build() {
|
||||
ROOT=$(dirname "$0")
|
||||
NAME="edge-dns"
|
||||
@@ -18,10 +151,9 @@ function build() {
|
||||
fi
|
||||
|
||||
echo "checking ..."
|
||||
ZIP_PATH=$(which zip)
|
||||
if [ -z "$ZIP_PATH" ]; then
|
||||
if ! command -v zip >/dev/null 2>&1; then
|
||||
echo "we need 'zip' command to compress files"
|
||||
exit
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "building v${VERSION}/${OS}/${ARCH} ..."
|
||||
@@ -37,38 +169,20 @@ function build() {
|
||||
fi
|
||||
|
||||
cp "$ROOT"/configs/api_dns.template.yaml "$DIST"/configs
|
||||
copy_fluent_bit_assets "$ROOT" "$DIST" "$OS" "$ARCH" || exit 1
|
||||
|
||||
echo "building ..."
|
||||
|
||||
MUSL_DIR="/usr/local/opt/musl-cross/bin"
|
||||
CC_PATH=""
|
||||
CXX_PATH=""
|
||||
if [[ $(uname -a) == *"Darwin"* && "${OS}" == "linux" ]]; then
|
||||
# /usr/local/opt/musl-cross/bin/
|
||||
if [ "${ARCH}" == "amd64" ]; then
|
||||
CC_PATH="x86_64-linux-musl-gcc"
|
||||
CXX_PATH="x86_64-linux-musl-g++"
|
||||
if [ "$OS" == "linux" ]; then
|
||||
TOOLCHAIN=$(find-linux-static-toolchain "$ARCH")
|
||||
if [ -z "$TOOLCHAIN" ]; then
|
||||
echo "could not find a static Linux toolchain for ${ARCH}"
|
||||
echo "install a musl toolchain before building edge-dns"
|
||||
exit 1
|
||||
fi
|
||||
if [ "${ARCH}" == "386" ]; then
|
||||
CC_PATH="i486-linux-musl-gcc"
|
||||
CXX_PATH="i486-linux-musl-g++"
|
||||
fi
|
||||
if [ "${ARCH}" == "arm64" ]; then
|
||||
CC_PATH="aarch64-linux-musl-gcc"
|
||||
CXX_PATH="aarch64-linux-musl-g++"
|
||||
fi
|
||||
if [ "${ARCH}" == "mips64" ]; then
|
||||
CC_PATH="mips64-linux-musl-gcc"
|
||||
CXX_PATH="mips64-linux-musl-g++"
|
||||
fi
|
||||
if [ "${ARCH}" == "mips64le" ]; then
|
||||
CC_PATH="mips64el-linux-musl-gcc"
|
||||
CXX_PATH="mips64el-linux-musl-g++"
|
||||
fi
|
||||
fi
|
||||
if [ ! -z $CC_PATH ]; then
|
||||
env CC=$MUSL_DIR/$CC_PATH CXX=$MUSL_DIR/$CXX_PATH GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags="plus" -o "$DIST"/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" "$ROOT"/../cmd/edge-dns/main.go
|
||||
|
||||
CC_BIN=${TOOLCHAIN%|*}
|
||||
CXX_BIN=${TOOLCHAIN#*|}
|
||||
env CC="$CC_BIN" CXX="$CXX_BIN" GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags="plus" -o "$DIST"/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" "$ROOT"/../cmd/edge-dns/main.go
|
||||
else
|
||||
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags="plus" -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-dns/main.go
|
||||
fi
|
||||
@@ -76,7 +190,7 @@ function build() {
|
||||
# check build result
|
||||
RESULT=$?
|
||||
if [ "${RESULT}" != "0" ]; then
|
||||
exit
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# delete hidden files
|
||||
@@ -95,81 +209,6 @@ function build() {
|
||||
echo "OK"
|
||||
}
|
||||
|
||||
function copy_fluent_bit_assets() {
|
||||
ROOT=$1
|
||||
DIST=$2
|
||||
OS=$3
|
||||
ARCH=$4
|
||||
FLUENT_ROOT="$ROOT/../../deploy/fluent-bit"
|
||||
FLUENT_DIST="$DIST/deploy/fluent-bit"
|
||||
|
||||
if [ ! -d "$FLUENT_ROOT" ]; then
|
||||
echo "[error] fluent-bit source directory not found: $FLUENT_ROOT"
|
||||
return 1
|
||||
fi
|
||||
verify_fluent_bit_package_matrix "$FLUENT_ROOT" "$ARCH" || return 1
|
||||
|
||||
rm -rf "$FLUENT_DIST"
|
||||
mkdir -p "$FLUENT_DIST"
|
||||
|
||||
for file in fluent-bit.conf fluent-bit-dns.conf fluent-bit-https.conf fluent-bit-dns-https.conf fluent-bit-windows.conf fluent-bit-windows-https.conf parsers.conf clickhouse-upstream.conf clickhouse-upstream-windows.conf README.md; do
|
||||
if [ -f "$FLUENT_ROOT/$file" ]; then
|
||||
cp "$FLUENT_ROOT/$file" "$FLUENT_DIST/"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$OS" = "linux" ]; then
|
||||
PACKAGE_SRC="$FLUENT_ROOT/packages/linux-$ARCH"
|
||||
PACKAGE_DST="$FLUENT_DIST/packages/linux-$ARCH"
|
||||
if [ -d "$PACKAGE_SRC" ]; then
|
||||
mkdir -p "$PACKAGE_DST"
|
||||
cp -R "$PACKAGE_SRC/." "$PACKAGE_DST/"
|
||||
else
|
||||
echo "[error] fluent-bit package directory not found: $PACKAGE_SRC"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$FLUENT_DIST/.gitignore"
|
||||
rm -f "$FLUENT_DIST"/logs.db*
|
||||
rm -rf "$FLUENT_DIST/storage"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function verify_fluent_bit_package_matrix() {
|
||||
FLUENT_ROOT=$1
|
||||
ARCH=$2
|
||||
REQUIRED_FILES=()
|
||||
if [ "$ARCH" = "amd64" ]; then
|
||||
REQUIRED_FILES=(
|
||||
"packages/linux-amd64/fluent-bit_4.2.2_amd64.deb"
|
||||
"packages/linux-amd64/fluent-bit-4.2.2-1.x86_64.rpm"
|
||||
)
|
||||
elif [ "$ARCH" = "arm64" ]; then
|
||||
REQUIRED_FILES=(
|
||||
"packages/linux-arm64/fluent-bit_4.2.2_arm64.deb"
|
||||
"packages/linux-arm64/fluent-bit-4.2.2-1.aarch64.rpm"
|
||||
)
|
||||
else
|
||||
echo "[error] unsupported arch for fluent-bit package validation: $ARCH"
|
||||
return 1
|
||||
fi
|
||||
|
||||
MISSING=0
|
||||
for FILE in "${REQUIRED_FILES[@]}"; do
|
||||
if [ ! -f "$FLUENT_ROOT/$FILE" ]; then
|
||||
echo "[error] fluent-bit matrix package missing: $FLUENT_ROOT/$FILE"
|
||||
MISSING=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$MISSING" -ne 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function lookup-version() {
|
||||
FILE=$1
|
||||
VERSION_DATA=$(cat "$FILE")
|
||||
@@ -179,7 +218,7 @@ function lookup-version() {
|
||||
echo "$VERSION"
|
||||
else
|
||||
echo "could not match version"
|
||||
exit
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.4.9" //1.3.8.2
|
||||
Version = "1.5.0" //1.3.8.2
|
||||
|
||||
ProductName = "Edge DNS"
|
||||
ProcessName = "edge-dns"
|
||||
|
||||
@@ -34,10 +34,14 @@ function build() {
|
||||
mkdir -p "$DIST"/data
|
||||
|
||||
cp "$ROOT"/configs/api_httpdns.template.yaml "$DIST"/configs
|
||||
copy_fluent_bit_assets "$ROOT" "$DIST" "$OS" "$ARCH" || exit 1
|
||||
|
||||
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 \
|
||||
go build -trimpath -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-httpdns/main.go
|
||||
if [ "${OS}" = "linux" ]; then
|
||||
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=0 \
|
||||
go build -trimpath -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-httpdns/main.go
|
||||
else
|
||||
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 \
|
||||
go build -trimpath -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-httpdns/main.go
|
||||
fi
|
||||
|
||||
if [ ! -f "$DIST"/bin/${NAME} ]; then
|
||||
echo "build failed!"
|
||||
@@ -60,81 +64,6 @@ function build() {
|
||||
echo "OK"
|
||||
}
|
||||
|
||||
function copy_fluent_bit_assets() {
|
||||
ROOT=$1
|
||||
DIST=$2
|
||||
OS=$3
|
||||
ARCH=$4
|
||||
FLUENT_ROOT="$ROOT/../../deploy/fluent-bit"
|
||||
FLUENT_DIST="$DIST/deploy/fluent-bit"
|
||||
|
||||
if [ ! -d "$FLUENT_ROOT" ]; then
|
||||
echo "[error] fluent-bit source directory not found: $FLUENT_ROOT"
|
||||
return 1
|
||||
fi
|
||||
verify_fluent_bit_package_matrix "$FLUENT_ROOT" "$ARCH" || return 1
|
||||
|
||||
rm -rf "$FLUENT_DIST"
|
||||
mkdir -p "$FLUENT_DIST"
|
||||
|
||||
for file in fluent-bit.conf fluent-bit-dns.conf fluent-bit-https.conf fluent-bit-dns-https.conf fluent-bit-windows.conf fluent-bit-windows-https.conf parsers.conf clickhouse-upstream.conf clickhouse-upstream-windows.conf README.md; do
|
||||
if [ -f "$FLUENT_ROOT/$file" ]; then
|
||||
cp "$FLUENT_ROOT/$file" "$FLUENT_DIST/"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$OS" = "linux" ]; then
|
||||
PACKAGE_SRC="$FLUENT_ROOT/packages/linux-$ARCH"
|
||||
PACKAGE_DST="$FLUENT_DIST/packages/linux-$ARCH"
|
||||
if [ -d "$PACKAGE_SRC" ]; then
|
||||
mkdir -p "$PACKAGE_DST"
|
||||
cp -R "$PACKAGE_SRC/." "$PACKAGE_DST/"
|
||||
else
|
||||
echo "[error] fluent-bit package directory not found: $PACKAGE_SRC"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$FLUENT_DIST/.gitignore"
|
||||
rm -f "$FLUENT_DIST"/logs.db*
|
||||
rm -rf "$FLUENT_DIST/storage"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function verify_fluent_bit_package_matrix() {
|
||||
FLUENT_ROOT=$1
|
||||
ARCH=$2
|
||||
REQUIRED_FILES=()
|
||||
if [ "$ARCH" = "amd64" ]; then
|
||||
REQUIRED_FILES=(
|
||||
"packages/linux-amd64/fluent-bit_4.2.2_amd64.deb"
|
||||
"packages/linux-amd64/fluent-bit-4.2.2-1.x86_64.rpm"
|
||||
)
|
||||
elif [ "$ARCH" = "arm64" ]; then
|
||||
REQUIRED_FILES=(
|
||||
"packages/linux-arm64/fluent-bit_4.2.2_arm64.deb"
|
||||
"packages/linux-arm64/fluent-bit-4.2.2-1.aarch64.rpm"
|
||||
)
|
||||
else
|
||||
echo "[error] unsupported arch for fluent-bit package validation: $ARCH"
|
||||
return 1
|
||||
fi
|
||||
|
||||
MISSING=0
|
||||
for FILE in "${REQUIRED_FILES[@]}"; do
|
||||
if [ ! -f "$FLUENT_ROOT/$FILE" ]; then
|
||||
echo "[error] fluent-bit matrix package missing: $FLUENT_ROOT/$FILE"
|
||||
MISSING=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$MISSING" -ne 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function lookup-version() {
|
||||
FILE=$1
|
||||
VERSION_DATA=$(cat "$FILE")
|
||||
|
||||
@@ -8,21 +8,29 @@ require (
|
||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||
github.com/iwind/TeaGo v0.0.0-20240411075713-6c1fc9aca7b6
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
|
||||
github.com/miekg/dns v1.1.72
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
golang.org/x/sys v0.39.0
|
||||
google.golang.org/grpc v1.78.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/miekg/dns v1.1.72 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/oschwald/geoip2-golang v1.13.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.13 // indirect
|
||||
github.com/tklauser/numcpus v0.7.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -16,12 +21,32 @@ github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI=
|
||||
github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
|
||||
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
|
||||
github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
|
||||
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
|
||||
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
|
||||
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
@@ -36,22 +61,19 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.4.9"
|
||||
Version = "1.5.0"
|
||||
|
||||
ProductName = "Edge HTTPDNS"
|
||||
ProcessName = "edge-httpdns"
|
||||
|
||||
@@ -15,18 +15,28 @@ import (
|
||||
teaconst "github.com/TeaOSLab/EdgeHttpDNS/internal/const"
|
||||
"github.com/TeaOSLab/EdgeHttpDNS/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeHttpDNS/internal/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
)
|
||||
|
||||
|
||||
type StatusManager struct {
|
||||
quitCh <-chan struct{}
|
||||
ticker *time.Ticker
|
||||
|
||||
isFirstTime bool
|
||||
cpuUpdatedTime time.Time
|
||||
cpuLogicalCount int
|
||||
cpuPhysicalCount int
|
||||
}
|
||||
|
||||
func NewStatusManager(quitCh <-chan struct{}) *StatusManager {
|
||||
return &StatusManager{
|
||||
quitCh: quitCh,
|
||||
ticker: time.NewTicker(30 * time.Second),
|
||||
quitCh: quitCh,
|
||||
ticker: time.NewTicker(30 * time.Second),
|
||||
isFirstTime: true,
|
||||
cpuUpdatedTime: time.Now(),
|
||||
cpuLogicalCount: runtime.NumCPU(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,10 +82,12 @@ func (m *StatusManager) update() {
|
||||
IsActive: true,
|
||||
StatusJSON: statusJSON,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Println("[HTTPDNS_NODE][status]update status failed:", err.Error())
|
||||
}
|
||||
|
||||
m.reportNodeValues(rpcClient, status)
|
||||
m.isFirstTime = false
|
||||
}
|
||||
|
||||
func (m *StatusManager) collectStatus() *nodeconfigs.NodeStatus {
|
||||
@@ -87,8 +99,8 @@ func (m *StatusManager) collectStatus() *nodeconfigs.NodeStatus {
|
||||
ConfigVersion: 0,
|
||||
OS: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
CPULogicalCount: runtime.NumCPU(),
|
||||
CPUPhysicalCount: runtime.NumCPU(),
|
||||
CPULogicalCount: m.cpuLogicalCount,
|
||||
CPUPhysicalCount: m.cpuPhysicalCount,
|
||||
IsActive: true,
|
||||
ConnectionCount: 0,
|
||||
UpdatedAt: now,
|
||||
@@ -104,26 +116,104 @@ func (m *StatusManager) collectStatus() *nodeconfigs.NodeStatus {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
hostname, _ := os.Hostname()
|
||||
status.Hostname = hostname
|
||||
|
||||
exePath, _ := os.Executable()
|
||||
status.ExePath = exePath
|
||||
|
||||
var memStats runtime.MemStats
|
||||
runtime.ReadMemStats(&memStats)
|
||||
status.MemoryTotal = memStats.Sys
|
||||
if status.MemoryTotal > 0 {
|
||||
status.MemoryUsage = float64(memStats.Alloc) / float64(status.MemoryTotal)
|
||||
m.updateCPU(status)
|
||||
m.updateMemory(status)
|
||||
m.updateLoad(status)
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func (m *StatusManager) updateCPU(status *nodeconfigs.NodeStatus) {
|
||||
duration := time.Duration(0)
|
||||
if m.isFirstTime {
|
||||
duration = 100 * time.Millisecond
|
||||
}
|
||||
|
||||
percents, err := cpu.Percent(duration, false)
|
||||
if err == nil && len(percents) > 0 {
|
||||
status.CPUUsage = percents[0] / 100
|
||||
}
|
||||
|
||||
if time.Since(m.cpuUpdatedTime) >= 5*time.Minute || m.cpuLogicalCount <= 0 {
|
||||
m.cpuUpdatedTime = time.Now()
|
||||
|
||||
if logicalCount, countErr := cpu.Counts(true); countErr == nil && logicalCount > 0 {
|
||||
m.cpuLogicalCount = logicalCount
|
||||
}
|
||||
if physicalCount, countErr := cpu.Counts(false); countErr == nil && physicalCount > 0 {
|
||||
m.cpuPhysicalCount = physicalCount
|
||||
}
|
||||
}
|
||||
|
||||
status.CPULogicalCount = m.cpuLogicalCount
|
||||
if m.cpuPhysicalCount > 0 {
|
||||
status.CPUPhysicalCount = m.cpuPhysicalCount
|
||||
}
|
||||
}
|
||||
|
||||
func (m *StatusManager) updateMemory(status *nodeconfigs.NodeStatus) {
|
||||
stat, err := mem.VirtualMemory()
|
||||
if err != nil || stat == nil || stat.Total == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
usedBytes := stat.Used
|
||||
if stat.Total > stat.Free+stat.Buffers+stat.Cached {
|
||||
usedBytes = stat.Total - stat.Free - stat.Buffers - stat.Cached
|
||||
}
|
||||
|
||||
status.MemoryTotal = stat.Total
|
||||
status.MemoryUsage = float64(usedBytes) / float64(stat.Total)
|
||||
}
|
||||
|
||||
func (m *StatusManager) updateLoad(status *nodeconfigs.NodeStatus) {
|
||||
load1m, load5m, load15m := readLoadAvg()
|
||||
status.Load1m = load1m
|
||||
status.Load5m = load5m
|
||||
status.Load15m = load15m
|
||||
}
|
||||
|
||||
return status
|
||||
func (m *StatusManager) reportNodeValues(rpcClient *rpc.RPCClient, status *nodeconfigs.NodeStatus) {
|
||||
if rpcClient == nil || status == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.createNodeValue(rpcClient, nodeconfigs.NodeValueItemCPU, maps.Map{
|
||||
"usage": status.CPUUsage,
|
||||
"cores": status.CPULogicalCount,
|
||||
})
|
||||
m.createNodeValue(rpcClient, nodeconfigs.NodeValueItemMemory, maps.Map{
|
||||
"usage": status.MemoryUsage,
|
||||
"total": status.MemoryTotal,
|
||||
})
|
||||
m.createNodeValue(rpcClient, nodeconfigs.NodeValueItemLoad, maps.Map{
|
||||
"load1m": status.Load1m,
|
||||
"load5m": status.Load5m,
|
||||
"load15m": status.Load15m,
|
||||
})
|
||||
}
|
||||
|
||||
func (m *StatusManager) createNodeValue(rpcClient *rpc.RPCClient, item string, value maps.Map) {
|
||||
valueJSON, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
log.Println("[HTTPDNS_NODE][status]marshal node value failed:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
_, err = rpcClient.NodeValueRPC.CreateNodeValue(rpcClient.Context(), &pb.CreateNodeValueRequest{
|
||||
Item: item,
|
||||
ValueJSON: valueJSON,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("[HTTPDNS_NODE][status]create node value failed:", item, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func readLoadAvg() (float64, float64, float64) {
|
||||
|
||||
@@ -26,13 +26,13 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
|
||||
type RPCClient struct {
|
||||
apiConfig *configs.APIConfig
|
||||
conns []*grpc.ClientConn
|
||||
locker sync.RWMutex
|
||||
|
||||
NodeTaskRPC pb.NodeTaskServiceClient
|
||||
NodeValueRPC pb.NodeValueServiceClient
|
||||
HTTPDNSNodeRPC pb.HTTPDNSNodeServiceClient
|
||||
HTTPDNSClusterRPC pb.HTTPDNSClusterServiceClient
|
||||
HTTPDNSAppRPC pb.HTTPDNSAppServiceClient
|
||||
@@ -47,7 +47,6 @@ type RPCClient struct {
|
||||
totalCostMs int64
|
||||
}
|
||||
|
||||
|
||||
func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
|
||||
if apiConfig == nil {
|
||||
return nil, errors.New("api config should not be nil")
|
||||
@@ -55,6 +54,7 @@ func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
|
||||
|
||||
client := &RPCClient{apiConfig: apiConfig}
|
||||
client.NodeTaskRPC = pb.NewNodeTaskServiceClient(client)
|
||||
client.NodeValueRPC = pb.NewNodeValueServiceClient(client)
|
||||
client.HTTPDNSNodeRPC = pb.NewHTTPDNSNodeServiceClient(client)
|
||||
client.HTTPDNSClusterRPC = pb.NewHTTPDNSClusterServiceClient(client)
|
||||
client.HTTPDNSAppRPC = pb.NewHTTPDNSAppServiceClient(client)
|
||||
@@ -212,7 +212,6 @@ func (c *RPCClient) GetAndResetMetrics() (total int64, failed int64, avgCostSeco
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func (c *RPCClient) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
conn := c.pickConn()
|
||||
if conn == nil {
|
||||
|
||||
2
EdgeNode/.gitignore
vendored
2
EdgeNode/.gitignore
vendored
@@ -8,6 +8,8 @@ configs/api_node.yaml
|
||||
|
||||
# Build binaries
|
||||
bin/
|
||||
libs/
|
||||
.third_party_build/
|
||||
|
||||
# Runtime Data
|
||||
data/
|
||||
|
||||
178
EdgeNode/build/build-libs-ubuntu2204.sh
Executable file
178
EdgeNode/build/build-libs-ubuntu2204.sh
Executable file
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
function host-goarch() {
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64)
|
||||
echo "amd64"
|
||||
;;
|
||||
aarch64|arm64)
|
||||
echo "arm64"
|
||||
;;
|
||||
*)
|
||||
echo ""
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function require-command() {
|
||||
local command_name=$1
|
||||
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||
echo "missing required command: $command_name"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function cpu_count() {
|
||||
if command -v nproc >/dev/null 2>&1; then
|
||||
nproc
|
||||
else
|
||||
getconf _NPROCESSORS_ONLN
|
||||
fi
|
||||
}
|
||||
|
||||
function download() {
|
||||
local url=$1
|
||||
local output=$2
|
||||
if [ ! -f "$output" ]; then
|
||||
curl -fsSL "$url" -o "$output"
|
||||
fi
|
||||
}
|
||||
|
||||
function prepare-dir() {
|
||||
local dir=$1
|
||||
rm -rf "$dir"
|
||||
mkdir -p "$dir"
|
||||
}
|
||||
|
||||
function copy-tree() {
|
||||
local from_dir=$1
|
||||
local to_dir=$2
|
||||
rm -rf "$to_dir"
|
||||
mkdir -p "$to_dir"
|
||||
cp -a "$from_dir"/. "$to_dir"/
|
||||
}
|
||||
|
||||
function copy-static-lib() {
|
||||
local pattern=$1
|
||||
local destination=$2
|
||||
local source_path=""
|
||||
|
||||
source_path=$(find "$(dirname "$pattern")" -type f -name "$(basename "$pattern")" | head -n 1)
|
||||
if [ -z "$source_path" ]; then
|
||||
echo "missing static library: $pattern"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cp "$source_path" "$destination"
|
||||
}
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
||||
ARCH=${1:-$(host-goarch)}
|
||||
HOST_ARCH=$(host-goarch)
|
||||
LIBPCAP_VERSION=${LIBPCAP_VERSION:-1.10.5}
|
||||
BROTLI_VERSION=${BROTLI_VERSION:-1.1.0}
|
||||
WORKDIR=${WORKDIR:-"$ROOT/.third_party_build"}
|
||||
DOWNLOAD_DIR="$WORKDIR/downloads"
|
||||
SOURCE_DIR="$WORKDIR/sources"
|
||||
BUILD_DIR="$WORKDIR/build"
|
||||
LIBPCAP_URL="https://www.tcpdump.org/release/libpcap-${LIBPCAP_VERSION}.tar.gz"
|
||||
BROTLI_URL="https://github.com/google/brotli/archive/refs/tags/v${BROTLI_VERSION}.tar.gz"
|
||||
|
||||
if [ -z "$ARCH" ]; then
|
||||
echo "unsupported host architecture: $(uname -m)"
|
||||
echo "usage: $0 [amd64|arm64]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$ARCH" != "amd64" ] && [ "$ARCH" != "arm64" ]; then
|
||||
echo "unsupported architecture: $ARCH"
|
||||
echo "supported architectures: amd64 arm64"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$HOST_ARCH" ] && [ "$ARCH" != "$HOST_ARCH" ]; then
|
||||
echo "this helper only builds native Ubuntu 22.04 static libraries"
|
||||
echo "host arch: ${HOST_ARCH}, requested arch: ${ARCH}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for tool in curl tar make cmake gcc g++ ar ranlib; do
|
||||
require-command "$tool"
|
||||
done
|
||||
|
||||
mkdir -p "$DOWNLOAD_DIR" "$SOURCE_DIR" "$BUILD_DIR"
|
||||
|
||||
echo "building third-party static libraries for ${ARCH} ..."
|
||||
echo "workspace: $WORKDIR"
|
||||
|
||||
LIBPCAP_ARCHIVE="$DOWNLOAD_DIR/libpcap-${LIBPCAP_VERSION}.tar.gz"
|
||||
BROTLI_ARCHIVE="$DOWNLOAD_DIR/brotli-${BROTLI_VERSION}.tar.gz"
|
||||
|
||||
download "$LIBPCAP_URL" "$LIBPCAP_ARCHIVE"
|
||||
download "$BROTLI_URL" "$BROTLI_ARCHIVE"
|
||||
|
||||
LIBPCAP_SOURCE="$SOURCE_DIR/libpcap-${LIBPCAP_VERSION}"
|
||||
BROTLI_SOURCE="$SOURCE_DIR/brotli-${BROTLI_VERSION}"
|
||||
prepare-dir "$LIBPCAP_SOURCE"
|
||||
prepare-dir "$BROTLI_SOURCE"
|
||||
|
||||
tar -xzf "$LIBPCAP_ARCHIVE" -C "$LIBPCAP_SOURCE" --strip-components=1
|
||||
tar -xzf "$BROTLI_ARCHIVE" -C "$BROTLI_SOURCE" --strip-components=1
|
||||
|
||||
LIBPCAP_BUILD="$BUILD_DIR/libpcap-${ARCH}"
|
||||
BROTLI_BUILD_SOURCE="$BUILD_DIR/brotli-source-${ARCH}"
|
||||
BROTLI_BUILD_DIR="$BUILD_DIR/brotli-build-${ARCH}"
|
||||
prepare-dir "$LIBPCAP_BUILD"
|
||||
prepare-dir "$BROTLI_BUILD_SOURCE"
|
||||
prepare-dir "$BROTLI_BUILD_DIR"
|
||||
|
||||
cp -a "$LIBPCAP_SOURCE"/. "$LIBPCAP_BUILD"/
|
||||
cp -a "$BROTLI_SOURCE"/. "$BROTLI_BUILD_SOURCE"/
|
||||
|
||||
pushd "$LIBPCAP_BUILD" >/dev/null
|
||||
./configure \
|
||||
--disable-shared \
|
||||
--enable-static \
|
||||
--without-dbus \
|
||||
--without-libnl \
|
||||
--without-bluetooth \
|
||||
--without-dag \
|
||||
--without-septel \
|
||||
--without-snf \
|
||||
--without-turbocap \
|
||||
--without-rdma \
|
||||
--without-netmap \
|
||||
CFLAGS="-O2 -fPIC"
|
||||
make -j"$(cpu_count)"
|
||||
popd >/dev/null
|
||||
|
||||
cmake -S "$BROTLI_BUILD_SOURCE" -B "$BROTLI_BUILD_DIR" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DBROTLI_DISABLE_TESTS=ON \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
||||
cmake --build "$BROTLI_BUILD_DIR" --parallel "$(cpu_count)"
|
||||
|
||||
mkdir -p "$ROOT/libs/libpcap/${ARCH}"
|
||||
mkdir -p "$ROOT/libs/libbrotli/${ARCH}"
|
||||
mkdir -p "$ROOT/libs/libpcap/src"
|
||||
mkdir -p "$ROOT/libs/libbrotli/src"
|
||||
|
||||
cp "$LIBPCAP_BUILD/libpcap.a" "$ROOT/libs/libpcap/${ARCH}/libpcap.a"
|
||||
copy-tree "$LIBPCAP_BUILD" "$ROOT/libs/libpcap/src/libpcap"
|
||||
|
||||
copy-static-lib "$BROTLI_BUILD_DIR/libbrotlicommon*.a" "$ROOT/libs/libbrotli/${ARCH}/libbrotlicommon.a"
|
||||
copy-static-lib "$BROTLI_BUILD_DIR/libbrotlidec*.a" "$ROOT/libs/libbrotli/${ARCH}/libbrotlidec.a"
|
||||
copy-static-lib "$BROTLI_BUILD_DIR/libbrotlienc*.a" "$ROOT/libs/libbrotli/${ARCH}/libbrotlienc.a"
|
||||
copy-tree "$BROTLI_SOURCE" "$ROOT/libs/libbrotli/src/brotli"
|
||||
|
||||
echo "done"
|
||||
echo "generated:"
|
||||
echo " $ROOT/libs/libpcap/${ARCH}/libpcap.a"
|
||||
echo " $ROOT/libs/libbrotli/${ARCH}/libbrotlicommon.a"
|
||||
echo " $ROOT/libs/libbrotli/${ARCH}/libbrotlidec.a"
|
||||
echo " $ROOT/libs/libbrotli/${ARCH}/libbrotlienc.a"
|
||||
echo ""
|
||||
echo "next:"
|
||||
echo " cd \"$ROOT/build\" && ./build.sh linux ${ARCH} plus"
|
||||
@@ -1,42 +1,217 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function find-binary() {
|
||||
local candidate
|
||||
for candidate in "$@"; do
|
||||
if [ -z "$candidate" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ -x "$candidate" ]; then
|
||||
echo "$candidate"
|
||||
return 0
|
||||
fi
|
||||
if command -v "$candidate" >/dev/null 2>&1; then
|
||||
command -v "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
function host-goarch() {
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64)
|
||||
echo "amd64"
|
||||
;;
|
||||
aarch64|arm64)
|
||||
echo "arm64"
|
||||
;;
|
||||
i386|i486|i586|i686)
|
||||
echo "386"
|
||||
;;
|
||||
mips64)
|
||||
echo "mips64"
|
||||
;;
|
||||
mips64el)
|
||||
echo "mips64le"
|
||||
;;
|
||||
*)
|
||||
echo ""
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
function find-linux-static-toolchain() {
|
||||
local arch=$1
|
||||
local cc_bin=""
|
||||
local cxx_bin=""
|
||||
local host_arch
|
||||
|
||||
host_arch=$(host-goarch)
|
||||
|
||||
case "$arch" in
|
||||
amd64)
|
||||
cc_bin=$(find-binary \
|
||||
"/usr/local/gcc/x86_64-unknown-linux-gnu/bin/x86_64-unknown-linux-gnu-gcc" \
|
||||
"/usr/local/opt/musl-cross/bin/x86_64-linux-musl-gcc" \
|
||||
"x86_64-unknown-linux-gnu-gcc" \
|
||||
"x86_64-linux-musl-gcc" \
|
||||
"musl-gcc")
|
||||
cxx_bin=$(find-binary \
|
||||
"/usr/local/gcc/x86_64-unknown-linux-gnu/bin/x86_64-unknown-linux-gnu-g++" \
|
||||
"/usr/local/opt/musl-cross/bin/x86_64-linux-musl-g++" \
|
||||
"x86_64-unknown-linux-gnu-g++" \
|
||||
"x86_64-linux-musl-g++")
|
||||
if [ -z "$cc_bin" ] && [ "$host_arch" = "amd64" ]; then
|
||||
cc_bin=$(find-binary "gcc" "cc" "clang")
|
||||
fi
|
||||
if [ -z "$cxx_bin" ] && [ "$host_arch" = "amd64" ]; then
|
||||
cxx_bin=$(find-binary "g++" "c++" "clang++")
|
||||
fi
|
||||
;;
|
||||
386)
|
||||
cc_bin=$(find-binary \
|
||||
"/usr/local/opt/musl-cross/bin/i486-linux-musl-gcc" \
|
||||
"i486-linux-musl-gcc")
|
||||
cxx_bin=$(find-binary \
|
||||
"/usr/local/opt/musl-cross/bin/i486-linux-musl-g++" \
|
||||
"i486-linux-musl-g++")
|
||||
if [ -z "$cc_bin" ] && [ "$host_arch" = "386" ]; then
|
||||
cc_bin=$(find-binary "gcc" "cc" "clang")
|
||||
fi
|
||||
if [ -z "$cxx_bin" ] && [ "$host_arch" = "386" ]; then
|
||||
cxx_bin=$(find-binary "g++" "c++" "clang++")
|
||||
fi
|
||||
;;
|
||||
arm64)
|
||||
cc_bin=$(find-binary \
|
||||
"/usr/local/gcc/aarch64-unknown-linux-gnu/bin/aarch64-unknown-linux-gnu-gcc" \
|
||||
"/usr/local/opt/musl-cross/bin/aarch64-linux-musl-gcc" \
|
||||
"aarch64-unknown-linux-gnu-gcc" \
|
||||
"aarch64-linux-musl-gcc")
|
||||
cxx_bin=$(find-binary \
|
||||
"/usr/local/gcc/aarch64-unknown-linux-gnu/bin/aarch64-unknown-linux-gnu-g++" \
|
||||
"/usr/local/opt/musl-cross/bin/aarch64-linux-musl-g++" \
|
||||
"aarch64-unknown-linux-gnu-g++" \
|
||||
"aarch64-linux-musl-g++")
|
||||
if [ -z "$cc_bin" ] && [ "$host_arch" = "arm64" ]; then
|
||||
cc_bin=$(find-binary "gcc" "cc" "clang")
|
||||
fi
|
||||
if [ -z "$cxx_bin" ] && [ "$host_arch" = "arm64" ]; then
|
||||
cxx_bin=$(find-binary "g++" "c++" "clang++")
|
||||
fi
|
||||
;;
|
||||
arm)
|
||||
cc_bin=$(find-binary \
|
||||
"/usr/local/opt/musl-cross/bin/arm-linux-musleabi-gcc" \
|
||||
"arm-linux-musleabi-gcc")
|
||||
cxx_bin=$(find-binary \
|
||||
"/usr/local/opt/musl-cross/bin/arm-linux-musleabi-g++" \
|
||||
"arm-linux-musleabi-g++")
|
||||
if [ -z "$cc_bin" ] && [ "$host_arch" = "arm" ]; then
|
||||
cc_bin=$(find-binary "gcc" "cc" "clang")
|
||||
fi
|
||||
if [ -z "$cxx_bin" ] && [ "$host_arch" = "arm" ]; then
|
||||
cxx_bin=$(find-binary "g++" "c++" "clang++")
|
||||
fi
|
||||
;;
|
||||
mips64)
|
||||
cc_bin=$(find-binary \
|
||||
"/usr/local/opt/musl-cross/bin/mips64-linux-musl-gcc" \
|
||||
"mips64-linux-musl-gcc")
|
||||
cxx_bin=$(find-binary \
|
||||
"/usr/local/opt/musl-cross/bin/mips64-linux-musl-g++" \
|
||||
"mips64-linux-musl-g++")
|
||||
if [ -z "$cc_bin" ] && [ "$host_arch" = "mips64" ]; then
|
||||
cc_bin=$(find-binary "gcc" "cc" "clang")
|
||||
fi
|
||||
if [ -z "$cxx_bin" ] && [ "$host_arch" = "mips64" ]; then
|
||||
cxx_bin=$(find-binary "g++" "c++" "clang++")
|
||||
fi
|
||||
;;
|
||||
mips64le)
|
||||
cc_bin=$(find-binary \
|
||||
"/usr/local/opt/musl-cross/bin/mips64el-linux-musl-gcc" \
|
||||
"mips64el-linux-musl-gcc")
|
||||
cxx_bin=$(find-binary \
|
||||
"/usr/local/opt/musl-cross/bin/mips64el-linux-musl-g++" \
|
||||
"mips64el-linux-musl-g++")
|
||||
if [ -z "$cc_bin" ] && [ "$host_arch" = "mips64le" ]; then
|
||||
cc_bin=$(find-binary "gcc" "cc" "clang")
|
||||
fi
|
||||
if [ -z "$cxx_bin" ] && [ "$host_arch" = "mips64le" ]; then
|
||||
cxx_bin=$(find-binary "g++" "c++" "clang++")
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$cc_bin" ]; then
|
||||
return 1
|
||||
fi
|
||||
if [ -z "$cxx_bin" ]; then
|
||||
cxx_bin="$cc_bin"
|
||||
fi
|
||||
|
||||
echo "$cc_bin|$cxx_bin"
|
||||
return 0
|
||||
}
|
||||
|
||||
function require-packet-static-libs() {
|
||||
local srcdir=$1
|
||||
local arch=$2
|
||||
local missing=0
|
||||
local file
|
||||
|
||||
for file in \
|
||||
"${srcdir}/libs/libpcap/${arch}/libpcap.a" \
|
||||
"${srcdir}/libs/libbrotli/${arch}/libbrotlienc.a" \
|
||||
"${srcdir}/libs/libbrotli/${arch}/libbrotlidec.a" \
|
||||
"${srcdir}/libs/libbrotli/${arch}/libbrotlicommon.a"; do
|
||||
if [ ! -f "$file" ]; then
|
||||
echo "missing required plus packet library: $file"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$missing" -ne 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function build() {
|
||||
ROOT=$(dirname $0)
|
||||
ROOT=$(dirname "$0")
|
||||
NAME="edge-node"
|
||||
VERSION=$(lookup-version "$ROOT"/../internal/const/const.go)
|
||||
DIST=$ROOT/"../dist/${NAME}"
|
||||
MUSL_DIR="/usr/local/opt/musl-cross/bin"
|
||||
SRCDIR=$(realpath "$ROOT/..")
|
||||
|
||||
# for macOS users: precompiled gcc can be downloaded from https://github.com/messense/homebrew-macos-cross-toolchains
|
||||
GCC_X86_64_DIR="/usr/local/gcc/x86_64-unknown-linux-gnu/bin"
|
||||
GCC_ARM64_DIR="/usr/local/gcc/aarch64-unknown-linux-gnu/bin"
|
||||
|
||||
OS=${1}
|
||||
ARCH=${2}
|
||||
TAG=${3}
|
||||
|
||||
if [ -z "$OS" ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$ARCH" ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="community"
|
||||
fi
|
||||
|
||||
echo "checking ..."
|
||||
ZIP_PATH=$(which zip)
|
||||
if [ -z "$ZIP_PATH" ]; then
|
||||
if ! command -v zip >/dev/null 2>&1; then
|
||||
echo "we need 'zip' command to compress files"
|
||||
exit
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "building v${VERSION}/${OS}/${ARCH}/${TAG} ..."
|
||||
# 生成 zip 文件名时不包含 plus 标记
|
||||
if [ "${TAG}" = "plus" ]; then
|
||||
ZIP="${NAME}-${OS}-${ARCH}-v${VERSION}.zip"
|
||||
else
|
||||
@@ -63,11 +238,8 @@ function build() {
|
||||
cp -R "$ROOT"/pages "$DIST"/
|
||||
copy_fluent_bit_assets "$ROOT" "$DIST" "$OS" "$ARCH" || exit 1
|
||||
|
||||
# we support TOA on linux only
|
||||
if [ "$OS" == "linux" ] && [ -f "${ROOT}/edge-toa/edge-toa-${ARCH}" ]
|
||||
then
|
||||
if [ ! -d "$DIST/edge-toa" ]
|
||||
then
|
||||
if [ "$OS" == "linux" ] && [ -f "${ROOT}/edge-toa/edge-toa-${ARCH}" ]; then
|
||||
if [ ! -d "$DIST/edge-toa" ]; then
|
||||
mkdir "$DIST/edge-toa"
|
||||
fi
|
||||
cp "${ROOT}/edge-toa/edge-toa-${ARCH}" "$DIST/edge-toa/edge-toa"
|
||||
@@ -75,96 +247,59 @@ function build() {
|
||||
|
||||
echo "building ..."
|
||||
|
||||
CC_PATH=""
|
||||
CXX_PATH=""
|
||||
CC_BIN=""
|
||||
CXX_BIN=""
|
||||
CGO_LDFLAGS=""
|
||||
CGO_CFLAGS=""
|
||||
BUILD_TAG=$TAG
|
||||
if [[ `uname -a` == *"Darwin"* && "${OS}" == "linux" ]]; then
|
||||
if [ "${ARCH}" == "amd64" ]; then
|
||||
# build with script support
|
||||
if [ -d $GCC_X86_64_DIR ]; then
|
||||
MUSL_DIR=$GCC_X86_64_DIR
|
||||
CC_PATH="x86_64-unknown-linux-gnu-gcc"
|
||||
CXX_PATH="x86_64-unknown-linux-gnu-g++"
|
||||
if [ "$TAG" = "plus" ]; then
|
||||
BUILD_TAG="plus,script,packet"
|
||||
fi
|
||||
else
|
||||
CC_PATH="x86_64-linux-musl-gcc"
|
||||
CXX_PATH="x86_64-linux-musl-g++"
|
||||
fi
|
||||
fi
|
||||
if [ "${ARCH}" == "386" ]; then
|
||||
CC_PATH="i486-linux-musl-gcc"
|
||||
CXX_PATH="i486-linux-musl-g++"
|
||||
fi
|
||||
if [ "${ARCH}" == "arm64" ]; then
|
||||
# build with script support
|
||||
if [ -d $GCC_ARM64_DIR ]; then
|
||||
MUSL_DIR=$GCC_ARM64_DIR
|
||||
CC_PATH="aarch64-unknown-linux-gnu-gcc"
|
||||
CXX_PATH="aarch64-unknown-linux-gnu-g++"
|
||||
if [ "$TAG" = "plus" ]; then
|
||||
BUILD_TAG="plus,script,packet"
|
||||
fi
|
||||
else
|
||||
CC_PATH="aarch64-linux-musl-gcc"
|
||||
CXX_PATH="aarch64-linux-musl-g++"
|
||||
fi
|
||||
fi
|
||||
if [ "${ARCH}" == "arm" ]; then
|
||||
CC_PATH="arm-linux-musleabi-gcc"
|
||||
CXX_PATH="arm-linux-musleabi-g++"
|
||||
fi
|
||||
if [ "${ARCH}" == "mips64" ]; then
|
||||
CC_PATH="mips64-linux-musl-gcc"
|
||||
CXX_PATH="mips64-linux-musl-g++"
|
||||
fi
|
||||
if [ "${ARCH}" == "mips64le" ]; then
|
||||
CC_PATH="mips64el-linux-musl-gcc"
|
||||
CXX_PATH="mips64el-linux-musl-g++"
|
||||
fi
|
||||
fi
|
||||
|
||||
# libpcap
|
||||
if [ "$OS" == "linux" ] && [[ "$ARCH" == "amd64" || "$ARCH" == "arm64" ]] && [ "$TAG" == "plus" ]; then
|
||||
if [ "$OS" == "linux" ] && [[ "$ARCH" == "amd64" || "$ARCH" == "arm64" ]] && [ "$TAG" == "plus" ]; then
|
||||
require-packet-static-libs "$SRCDIR" "$ARCH" || exit 1
|
||||
BUILD_TAG="plus,script,packet"
|
||||
CGO_LDFLAGS="-L${SRCDIR}/libs/libpcap/${ARCH} -lpcap -L${SRCDIR}/libs/libbrotli/${ARCH} -lbrotlienc -lbrotlidec -lbrotlicommon"
|
||||
CGO_CFLAGS="-I${SRCDIR}/libs/libpcap/src/libpcap -I${SRCDIR}/libs/libpcap/src/libpcap/pcap -I${SRCDIR}/libs/libbrotli/src/brotli/c/include -I${SRCDIR}/libs/libbrotli/src/brotli/c/include/brotli"
|
||||
fi
|
||||
|
||||
if [ ! -z $CC_PATH ]; then
|
||||
env CC=$MUSL_DIR/$CC_PATH \
|
||||
CXX=$MUSL_DIR/$CXX_PATH GOOS="${OS}" \
|
||||
GOARCH="${ARCH}" CGO_ENABLED=1 \
|
||||
CGO_LDFLAGS="${CGO_LDFLAGS}" \
|
||||
CGO_CFLAGS="${CGO_CFLAGS}" \
|
||||
go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
else
|
||||
if [[ `uname` == *"Linux"* ]] && [ "$OS" == "linux" ] && [[ "$ARCH" == "amd64" || "$ARCH" == "arm64" ]] && [ "$TAG" == "plus" ]; then
|
||||
BUILD_TAG="plus,script,packet"
|
||||
if [ "$OS" == "linux" ]; then
|
||||
TOOLCHAIN=$(find-linux-static-toolchain "$ARCH")
|
||||
if [ -z "$TOOLCHAIN" ]; then
|
||||
echo "could not find a static Linux toolchain for ${ARCH}"
|
||||
echo "install a musl cross compiler or provide /usr/local/gcc toolchains before building"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 CGO_LDFLAGS="${CGO_LDFLAGS}" CGO_CFLAGS="${CGO_CFLAGS}" go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
CC_BIN=${TOOLCHAIN%|*}
|
||||
CXX_BIN=${TOOLCHAIN#*|}
|
||||
|
||||
env CC="$CC_BIN" \
|
||||
CXX="$CXX_BIN" \
|
||||
GOOS="${OS}" \
|
||||
GOARCH="${ARCH}" \
|
||||
CGO_ENABLED=1 \
|
||||
CGO_LDFLAGS="${CGO_LDFLAGS}" \
|
||||
CGO_CFLAGS="${CGO_CFLAGS}" \
|
||||
go build -trimpath -tags "$BUILD_TAG" -o "$DIST"/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
else
|
||||
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 CGO_LDFLAGS="${CGO_LDFLAGS}" CGO_CFLAGS="${CGO_CFLAGS}" \
|
||||
go build -trimpath -tags "$BUILD_TAG" -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
fi
|
||||
|
||||
if [ ! -f "${DIST}/bin/${NAME}" ]; then
|
||||
echo "build failed!"
|
||||
exit
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# delete hidden files
|
||||
find "$DIST" -name ".DS_Store" -delete
|
||||
find "$DIST" -name ".gitignore" -delete
|
||||
|
||||
echo "zip files"
|
||||
cd "${DIST}/../" || exit
|
||||
cd "${DIST}/../" || exit 1
|
||||
if [ -f "${ZIP}" ]; then
|
||||
rm -f "${ZIP}"
|
||||
fi
|
||||
zip -r -X -q "${ZIP}" ${NAME}/
|
||||
rm -rf ${NAME}
|
||||
cd - || exit
|
||||
cd - || exit 1
|
||||
|
||||
echo "OK"
|
||||
}
|
||||
@@ -253,7 +388,7 @@ function lookup-version() {
|
||||
echo "$VERSION"
|
||||
else
|
||||
echo "could not match version"
|
||||
exit
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"speed":1,"speedMB":1400,"countTests":3}
|
||||
{"speed":1,"speedMB":1510,"countTests":10}
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sudo go run -tags="plus script packet" ../cmd/edge-node/main.go
|
||||
sudo go run -tags="plus script" ../cmd/edge-node/main.go
|
||||
|
||||
@@ -20,7 +20,6 @@ require (
|
||||
github.com/dchest/captcha v0.0.0-00010101000000-000000000000
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/google/nftables v0.2.0
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible
|
||||
github.com/iwind/TeaGo v0.0.0-20240411075713-6c1fc9aca7b6
|
||||
|
||||
@@ -67,8 +67,6 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/nftables v0.2.0 h1:PbJwaBmbVLzpeldoeUKGkE2RjstrjPKMl6oLrfEJ6/8=
|
||||
github.com/google/nftables v0.2.0/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.4.9" //1.3.8.2
|
||||
Version = "1.5.0" //1.3.8.2
|
||||
|
||||
ProductName = "Edge Node"
|
||||
ProcessName = "edge-node"
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus && packet
|
||||
|
||||
package networksecurity
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/monitor"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/netpackets"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var SharedManager = NewManager()
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
events.On(events.EventLoaded, func() {
|
||||
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
|
||||
if nodeConfig != nil {
|
||||
go SharedManager.Apply(nodeConfig.NetworkSecurityPolicy)
|
||||
}
|
||||
})
|
||||
|
||||
events.On(events.EventQuit, func() {
|
||||
go SharedManager.Apply(nil)
|
||||
})
|
||||
|
||||
goman.New(func() {
|
||||
var ticker = time.NewTicker(1 * time.Minute)
|
||||
for range ticker.C {
|
||||
SharedManager.Upload()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
listener *netpackets.Listener
|
||||
isRunning bool
|
||||
|
||||
policy *nodeconfigs.NetworkSecurityPolicy
|
||||
|
||||
totalTCPPacketsMinutely uint64
|
||||
totalUDPPacketsMinutely uint64
|
||||
totalICMPPacketsMinutely uint64
|
||||
}
|
||||
|
||||
func NewManager() *Manager {
|
||||
return &Manager{}
|
||||
}
|
||||
|
||||
// Apply 应用配置
|
||||
// 非线程安全
|
||||
func (this *Manager) Apply(policy *nodeconfigs.NetworkSecurityPolicy) {
|
||||
if this.policy != nil && this.policy.IsSame(policy) {
|
||||
return
|
||||
}
|
||||
|
||||
this.policy = policy
|
||||
|
||||
if policy == nil ||
|
||||
policy.Status == nodeconfigs.NetworkSecurityStatusOff ||
|
||||
(policy.Status == nodeconfigs.NetworkSecurityStatusAuto && runtime.NumCPU() < 8) {
|
||||
if this.listener != nil {
|
||||
remotelogs.Println("NETWORK_SECURITY_MANAGER", "stop")
|
||||
this.listener.Stop()
|
||||
}
|
||||
this.isRunning = false
|
||||
return
|
||||
}
|
||||
|
||||
if this.listener == nil {
|
||||
this.listener = netpackets.NewListener()
|
||||
|
||||
// References:
|
||||
// - https://biot.com/capstats/bpf.html
|
||||
// - https://www.ibm.com/docs/en/qsip/7.4?topic=queries-berkeley-packet-filters
|
||||
// - https://www.tcpdump.org/manpages/tcpdump.1.html
|
||||
|
||||
if Tea.IsTesting() || utils.IsDebugEnv() { // dev environment
|
||||
this.listener.SetBPF("(tcp or udp or icmp) and not net 127 and not net ::1")
|
||||
} else {
|
||||
this.listener.SetBPF("(tcp or udp or icmp) and not src net 127 and not src net 192.168 and not src net 172.16 and not src net ::1 and not src net 10")
|
||||
}
|
||||
this.listener.AddFilter(this)
|
||||
}
|
||||
|
||||
if !this.isRunning {
|
||||
this.isRunning = true
|
||||
remotelogs.Println("NETWORK_SECURITY_MANAGER", "start")
|
||||
err := this.listener.Start() // long run function
|
||||
if err != nil {
|
||||
remotelogs.Error("NETWORK_SECURITY_MANAGER", "start listener failed: "+err.Error())
|
||||
}
|
||||
this.isRunning = false
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Manager) FilterMeta(meta *netpackets.PacketMeta) {
|
||||
switch meta.LayerType {
|
||||
case netpackets.LayerTypeTCP:
|
||||
// 这里不需要试用atomic,因为数据不需要那么精确
|
||||
this.totalTCPPacketsMinutely++
|
||||
case netpackets.LayerTypeUDP:
|
||||
this.totalUDPPacketsMinutely++
|
||||
case netpackets.LayerTypeICMPv4, netpackets.LayerTypeICMPv6:
|
||||
this.totalICMPPacketsMinutely++
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Manager) Upload() {
|
||||
if !this.isRunning {
|
||||
return
|
||||
}
|
||||
|
||||
monitor.SharedValueQueue.Add(nodeconfigs.NodeValueItemNetworkPackets, maps.Map{
|
||||
"tcpInPPS": this.totalTCPPacketsMinutely / 60,
|
||||
"udpInPPS": this.totalUDPPacketsMinutely / 60,
|
||||
"icmpInPPS": this.totalICMPPacketsMinutely / 60,
|
||||
})
|
||||
|
||||
this.totalTCPPacketsMinutely = 0
|
||||
this.totalUDPPacketsMinutely = 0
|
||||
this.totalICMPPacketsMinutely = 0
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus && packet
|
||||
|
||||
package networksecurity_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
networksecurity "github.com/TeaOSLab/EdgeNode/internal/network-security"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestManager_Apply(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
if os.Getgid() > 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var manager = networksecurity.NewManager()
|
||||
var policy = nodeconfigs.NewNetworkSecurityPolicy()
|
||||
manager.Apply(policy)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sudo go test -v -tags="plus packet" -run '^TestManager_Apply'
|
||||
@@ -9,14 +9,20 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// 执行认证
|
||||
// 鎵ц璁よ瘉
|
||||
func (this *HTTPRequest) doAuth() (shouldStop bool) {
|
||||
if this.web.Auth == nil || !this.web.Auth.IsOn {
|
||||
return
|
||||
}
|
||||
|
||||
for _, ref := range this.web.Auth.PolicyRefs {
|
||||
if !ref.IsOn || ref.AuthPolicy == nil || !ref.AuthPolicy.IsOn {
|
||||
if !ref.IsOn {
|
||||
continue
|
||||
}
|
||||
if ref.AuthPolicy == nil {
|
||||
continue
|
||||
}
|
||||
if !ref.AuthPolicy.IsOn {
|
||||
continue
|
||||
}
|
||||
if !ref.AuthPolicy.MatchRequest(this.RawReq) {
|
||||
@@ -36,7 +42,7 @@ func (this *HTTPRequest) doAuth() (shouldStop bool) {
|
||||
return writer.StatusCode(), nil
|
||||
}, this.Format)
|
||||
if err != nil {
|
||||
this.write50x(err, http.StatusInternalServerError, "Failed to execute the AuthPolicy", "认证策略执行失败", false)
|
||||
this.write50x(err, http.StatusInternalServerError, "Failed to execute the AuthPolicy", "璁よ瘉绛栫暐鎵ц澶辫触", false)
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
@@ -45,28 +51,28 @@ func (this *HTTPRequest) doAuth() (shouldStop bool) {
|
||||
}
|
||||
this.tags = append(this.tags, "auth:"+ref.AuthPolicy.Type)
|
||||
return
|
||||
} else {
|
||||
// Basic Auth比较特殊
|
||||
if ref.AuthPolicy.Type == serverconfigs.HTTPAuthTypeBasicAuth {
|
||||
method, ok := ref.AuthPolicy.Method().(*serverconfigs.HTTPAuthBasicMethod)
|
||||
if ok {
|
||||
var headerValue = "Basic realm=\""
|
||||
if len(method.Realm) > 0 {
|
||||
headerValue += method.Realm
|
||||
} else {
|
||||
headerValue += this.ReqHost
|
||||
}
|
||||
headerValue += "\""
|
||||
if len(method.Charset) > 0 {
|
||||
headerValue += ", charset=\"" + method.Charset + "\""
|
||||
}
|
||||
this.writer.Header()["WWW-Authenticate"] = []string{headerValue}
|
||||
}
|
||||
}
|
||||
this.writer.WriteHeader(http.StatusUnauthorized)
|
||||
this.tags = append(this.tags, "auth:"+ref.AuthPolicy.Type)
|
||||
return true
|
||||
}
|
||||
|
||||
// Basic Auth 姣旇緝鐗规畩
|
||||
if ref.AuthPolicy.Type == serverconfigs.HTTPAuthTypeBasicAuth {
|
||||
method, ok := ref.AuthPolicy.Method().(*serverconfigs.HTTPAuthBasicMethod)
|
||||
if ok {
|
||||
var headerValue = "Basic realm=\""
|
||||
if len(method.Realm) > 0 {
|
||||
headerValue += method.Realm
|
||||
} else {
|
||||
headerValue += this.ReqHost
|
||||
}
|
||||
headerValue += "\""
|
||||
if len(method.Charset) > 0 {
|
||||
headerValue += ", charset=\"" + method.Charset + "\""
|
||||
}
|
||||
this.writer.Header()["WWW-Authenticate"] = []string{headerValue}
|
||||
}
|
||||
}
|
||||
this.writer.WriteHeader(http.StatusUnauthorized)
|
||||
this.tags = append(this.tags, "auth:"+ref.AuthPolicy.Type)
|
||||
return true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus && packet
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
networksecurity "github.com/TeaOSLab/EdgeNode/internal/network-security"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
)
|
||||
|
||||
func (this *Node) execNetworkSecurityPolicyChangedTask(rpcClient *rpc.RPCClient) error {
|
||||
remotelogs.Println("NODE", "updating network security policy ...")
|
||||
resp, err := rpcClient.NodeRPC.FindNodeNetworkSecurityPolicy(rpcClient.Context(), &pb.FindNodeNetworkSecurityPolicyRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var policy = nodeconfigs.NewNetworkSecurityPolicy()
|
||||
if len(resp.NetworkSecurityPolicyJSON) > 0 {
|
||||
err = json.Unmarshal(resp.NetworkSecurityPolicyJSON, policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
sharedNodeConfig.NetworkSecurityPolicy = policy
|
||||
|
||||
go networksecurity.SharedManager.Apply(policy)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus && !packet
|
||||
//go:build plus
|
||||
|
||||
package nodes
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus && packet
|
||||
|
||||
package netpackets
|
||||
|
||||
type FilterInterface interface {
|
||||
FilterMeta(meta *PacketMeta)
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus
|
||||
|
||||
package netpackets
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type IgnoredIPList struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
itemMap map[string]*linkedlist.Item[string] // linked => item
|
||||
list *linkedlist.List[string]
|
||||
|
||||
capacity int
|
||||
lastIP string
|
||||
}
|
||||
|
||||
func NewIgnoredIPList(capacity int) *IgnoredIPList {
|
||||
return &IgnoredIPList{
|
||||
itemMap: map[string]*linkedlist.Item[string]{},
|
||||
list: linkedlist.NewList[string](),
|
||||
capacity: capacity,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *IgnoredIPList) Add(ip string) {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
|
||||
if this.lastIP == ip {
|
||||
return
|
||||
}
|
||||
this.lastIP = ip
|
||||
|
||||
item, ok := this.itemMap[ip]
|
||||
if !ok {
|
||||
if this.capacity > 0 && len(this.itemMap) == this.capacity {
|
||||
var firstItem = this.list.Shift()
|
||||
if firstItem != nil {
|
||||
delete(this.itemMap, firstItem.Value)
|
||||
}
|
||||
}
|
||||
|
||||
item = linkedlist.NewItem[string](ip)
|
||||
this.itemMap[ip] = item
|
||||
}
|
||||
this.list.Push(item)
|
||||
}
|
||||
|
||||
func (this *IgnoredIPList) Remove(ip string) {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
|
||||
item, ok := this.itemMap[ip]
|
||||
if ok {
|
||||
delete(this.itemMap, ip)
|
||||
this.list.Remove(item)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *IgnoredIPList) Contains(ip string) bool {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
_, ok := this.itemMap[ip]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (this *IgnoredIPList) List(size int) (ipList []string) {
|
||||
if size <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
|
||||
this.list.RangeReverse(func(item *linkedlist.Item[string]) (goNext bool) {
|
||||
ipList = append(ipList, item.Value)
|
||||
size--
|
||||
return size > 0
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *IgnoredIPList) Len() int {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
return len(this.itemMap)
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus
|
||||
|
||||
package netpackets_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/netpackets"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIgnoredIPList_Add(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var list = netpackets.NewIgnoredIPList(10)
|
||||
list.Add("192.168.2.1")
|
||||
list.Add("192.168.2.2")
|
||||
list.Add("192.168.2.3")
|
||||
|
||||
a.IsTrue(list.Contains("192.168.2.1"))
|
||||
a.IsFalse(list.Contains("192.168.2.0"))
|
||||
|
||||
t.Log(list.List(0))
|
||||
t.Log(list.List(2))
|
||||
t.Log(list.List(4))
|
||||
}
|
||||
|
||||
func TestIgnoredIPList_Add_Capacity(t *testing.T) {
|
||||
var list = netpackets.NewIgnoredIPList(4)
|
||||
list.Add("192.168.2.1")
|
||||
list.Add("192.168.2.2")
|
||||
list.Add("192.168.2.3")
|
||||
list.Add("192.168.2.4")
|
||||
list.Add("192.168.2.5")
|
||||
list.Add("192.168.2.6")
|
||||
list.Add("192.168.2.7")
|
||||
t.Log(list.List(10))
|
||||
t.Log(list.Len(), "items")
|
||||
}
|
||||
|
||||
func TestIgnoredIPList_Remove(t *testing.T) {
|
||||
var list = netpackets.NewIgnoredIPList(10)
|
||||
list.Add("192.168.2.1")
|
||||
list.Add("192.168.2.2")
|
||||
list.Add("192.168.2.3")
|
||||
list.Remove("192.168.2.2")
|
||||
t.Log(list.List(4))
|
||||
}
|
||||
|
||||
func BenchmarkIgnoredIPList_Add(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
var genIPFunc = func() string {
|
||||
return fmt.Sprintf("%d.%d.%d.%d", rands.Int(0, 255), rands.Int(0, 255), rands.Int(0, 255), rands.Int(0, 255))
|
||||
}
|
||||
|
||||
var list = netpackets.NewIgnoredIPList(65535)
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
list.Add(genIPFunc())
|
||||
list.Remove(genIPFunc())
|
||||
list.Contains(genIPFunc())
|
||||
if rands.Int(0, 100) == 0 {
|
||||
list.List(1000)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus && packet
|
||||
|
||||
package netpackets
|
||||
|
||||
import (
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
)
|
||||
|
||||
type LayerType = gopacket.LayerType
|
||||
|
||||
var (
|
||||
LayerTypeTCP = layers.LayerTypeTCP
|
||||
LayerTypeUDP = layers.LayerTypeUDP
|
||||
LayerTypeICMPv4 = layers.LayerTypeICMPv4
|
||||
LayerTypeICMPv6 = layers.LayerTypeICMPv6
|
||||
)
|
||||
@@ -1,293 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus && packet
|
||||
|
||||
package netpackets
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/google/gopacket/pcap"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultBPFFilter = "(tcp or udp or icmp) and not net 127.0.0.1"
|
||||
|
||||
type Listener struct {
|
||||
filters []FilterInterface
|
||||
incomingHandle *pcap.Handle
|
||||
bpfFilter string
|
||||
|
||||
decodeDstIP bool
|
||||
|
||||
isClosed bool
|
||||
|
||||
outgoingIPList *IgnoredIPList
|
||||
outgoingHandle *pcap.Handle
|
||||
|
||||
filterTicker *time.Ticker
|
||||
}
|
||||
|
||||
func NewListener() *Listener {
|
||||
return &Listener{
|
||||
isClosed: true,
|
||||
outgoingIPList: NewIgnoredIPList(65535),
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Listener) Start() error {
|
||||
if !this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.isClosed = false
|
||||
|
||||
go func() {
|
||||
startErr := this.loopOutgoing()
|
||||
if startErr != nil {
|
||||
remotelogs.Error("NET_PACKET", "start outgoing packet listener failed: "+startErr.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
this.loopUpdateFilter()
|
||||
}()
|
||||
|
||||
for { // 无限 for 是为了防止意外退出
|
||||
err := this.loopIncoming()
|
||||
if err != nil {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("start packet listener failed: %w", err)
|
||||
}
|
||||
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Listener) AddFilter(filter FilterInterface) {
|
||||
this.filters = append(this.filters, filter)
|
||||
}
|
||||
|
||||
func (this *Listener) SetBPF(bpfFilter string) {
|
||||
this.bpfFilter = bpfFilter
|
||||
}
|
||||
|
||||
func (this *Listener) DecodeDstIP() {
|
||||
this.decodeDstIP = true
|
||||
}
|
||||
|
||||
func (this *Listener) Stop() {
|
||||
this.isClosed = true
|
||||
this.incomingHandle.Close()
|
||||
this.outgoingHandle.Close()
|
||||
}
|
||||
|
||||
func (this *Listener) IsRunning() bool {
|
||||
return this.incomingHandle != nil && !this.isClosed
|
||||
}
|
||||
|
||||
func (this *Listener) loopIncoming() error {
|
||||
const device = "any"
|
||||
var err error
|
||||
this.incomingHandle, err = pcap.OpenLive(device, 128, false /** ignore collision domain **/, pcap.BlockForever)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
this.incomingHandle.Close()
|
||||
}()
|
||||
|
||||
err = this.incomingHandle.SetDirection(pcap.DirectionIn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(this.bpfFilter) > 0 {
|
||||
err = this.incomingHandle.SetBPFFilter(this.bpfFilter)
|
||||
} else {
|
||||
this.bpfFilter = defaultBPFFilter
|
||||
err = this.incomingHandle.SetBPFFilter(defaultBPFFilter)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var meta = &PacketMeta{}
|
||||
|
||||
var packetSource = gopacket.NewPacketSource(this.incomingHandle, this.incomingHandle.LinkType())
|
||||
packetSource.NoCopy = true
|
||||
packetSource.Lazy = true
|
||||
|
||||
var filters = this.filters
|
||||
var countFilters = len(filters)
|
||||
|
||||
for packet := range packetSource.Packets() {
|
||||
var networkLayer = packet.NetworkLayer()
|
||||
if networkLayer == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var networkFlow = networkLayer.NetworkFlow()
|
||||
|
||||
var src = networkFlow.Src()
|
||||
|
||||
meta.SrcIP = src.String()
|
||||
|
||||
// ignore outgoing ip
|
||||
if this.outgoingIPList.Contains(meta.SrcIP) {
|
||||
continue
|
||||
}
|
||||
|
||||
if this.decodeDstIP {
|
||||
meta.DstIP = networkFlow.Dst().String()
|
||||
}
|
||||
meta.Length = packet.Metadata().Length
|
||||
|
||||
var transportLayer = packet.TransportLayer()
|
||||
if transportLayer == nil {
|
||||
meta.SrcPort = 0
|
||||
meta.DstPort = 0
|
||||
|
||||
switch x := networkLayer.(type) {
|
||||
case *layers.IPv4:
|
||||
meta.LayerType = x.NextLayerType()
|
||||
case *layers.IPv6:
|
||||
meta.LayerType = x.NextLayerType()
|
||||
}
|
||||
|
||||
// call filters
|
||||
if countFilters == 1 {
|
||||
filters[0].FilterMeta(meta)
|
||||
} else {
|
||||
for _, filter := range filters {
|
||||
filter.FilterMeta(meta)
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var transportFlow = transportLayer.TransportFlow()
|
||||
meta.SrcPort = int(binary.BigEndian.Uint16(transportFlow.Src().Raw()))
|
||||
meta.LayerType = transportLayer.LayerType()
|
||||
meta.DstPort = int(binary.BigEndian.Uint16(transportFlow.Dst().Raw()))
|
||||
|
||||
// call filters
|
||||
if countFilters == 1 {
|
||||
filters[0].FilterMeta(meta)
|
||||
} else {
|
||||
for _, filter := range filters {
|
||||
filter.FilterMeta(meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Listener) loopOutgoing() error {
|
||||
const device = "any"
|
||||
var err error
|
||||
this.outgoingHandle, err = pcap.OpenLive(device, 128, true /** ignore collision domain **/, pcap.BlockForever)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
this.outgoingHandle.Close()
|
||||
}()
|
||||
|
||||
err = this.outgoingHandle.SetDirection(pcap.DirectionOut)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = this.outgoingHandle.SetBPFFilter("tcp and tcp[tcpflags] & (tcp-syn) != 0 and tcp[tcpflags] & (tcp-ack) = 0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var packetSource = gopacket.NewPacketSource(this.outgoingHandle, this.outgoingHandle.LinkType())
|
||||
packetSource.NoCopy = true
|
||||
packetSource.Lazy = true
|
||||
|
||||
for packet := range packetSource.Packets() {
|
||||
var networkLayer = packet.NetworkLayer()
|
||||
if networkLayer == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var networkFlow = networkLayer.NetworkFlow()
|
||||
var dstIP = networkFlow.Dst().String()
|
||||
this.outgoingIPList.Add(dstIP)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Listener) loopUpdateFilter() {
|
||||
if this.filterTicker != nil {
|
||||
return
|
||||
}
|
||||
|
||||
this.filterTicker = time.NewTicker(1 * time.Second)
|
||||
var lastIPList []string
|
||||
for range this.filterTicker.C {
|
||||
if this.isClosed {
|
||||
continue
|
||||
}
|
||||
|
||||
var ipList = this.outgoingIPList.List(512) // 基于bfp长度的限制,这里数量不能太多
|
||||
sort.Strings(ipList)
|
||||
if this.equalStrings(lastIPList, ipList) {
|
||||
continue
|
||||
}
|
||||
|
||||
lastIPList = ipList
|
||||
|
||||
// apply
|
||||
var incomingHandle = this.incomingHandle
|
||||
if incomingHandle != nil {
|
||||
var rules = []string{}
|
||||
for _, ip := range ipList {
|
||||
rules = append(rules, "not src host "+ip)
|
||||
}
|
||||
var newBPFFilter = this.bpfFilter + " and " + strings.Join(rules, " and ")
|
||||
if utils.IsDebugEnv() {
|
||||
remotelogs.Debug("NET_PACKET", "set new BPF filter: "+newBPFFilter)
|
||||
}
|
||||
err := incomingHandle.SetBPFFilter(newBPFFilter)
|
||||
if err != nil {
|
||||
remotelogs.Error("NET_PACKET", "set new BPF filter failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Listener) equalStrings(s1 []string, s2 []string) bool {
|
||||
var l = len(s1)
|
||||
if len(s2) != l {
|
||||
return false
|
||||
}
|
||||
if l == 0 {
|
||||
return true
|
||||
}
|
||||
for i := 0; i < l; i++ {
|
||||
if s1[i] != s2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus && packet
|
||||
|
||||
package netpackets_test
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/netpackets"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type testFilter struct {
|
||||
}
|
||||
|
||||
func (this *testFilter) FilterMeta(meta *netpackets.PacketMeta) {
|
||||
log.Println(meta.LayerType.String() + " " + meta.SrcIP + ":" + types.String(meta.SrcPort) + " => " + meta.DstIP + ":" + types.String(meta.DstPort) + " " + types.String(meta.Length) + "bytes")
|
||||
}
|
||||
|
||||
func TestListener_Start(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
if os.Getgid() > 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var listener = netpackets.NewListener()
|
||||
listener.AddFilter(&testFilter{})
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Second)
|
||||
t.Log("stopping ...")
|
||||
listener.Stop()
|
||||
}()
|
||||
|
||||
t.Log("starting ...")
|
||||
err := listener.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListener_DecodePacket_UDP(t *testing.T) {
|
||||
var packetData = []byte{69, 0, 0, 134, 140, 133, 0, 0, 118, 17, 16, 79, 223, 5, 5, 5, 192, 168, 2, 224, 0, 53, 232, 163, 0, 114, 0, 0, 69, 42, 129, 128, 0, 1, 0, 3, 0, 0, 0, 0, 6, 115, 116, 97, 116, 105, 99, 7, 111, 115, 99, 104, 105, 110, 97, 3, 110, 101, 116, 0, 0, 1, 0, 1, 192, 12, 0, 5, 0, 1, 0, 0, 0, 1, 0, 25, 10, 115, 116, 97, 116, 105, 99, 45, 111, 115, 99, 2, 98, 48, 5, 97, 105, 99, 100, 110, 3, 99, 111, 109, 0, 192, 48, 0, 5, 0, 1, 0, 0, 0, 1, 0, 5, 2, 118, 109, 192, 62, 192, 85, 0, 1, 0, 1, 0, 0, 0, 1, 0, 4, 218, 28, 104, 157}
|
||||
|
||||
var packet = gopacket.NewPacket(packetData, layers.LayerTypeIPv4, gopacket.DecodeOptions{})
|
||||
var networkFlow = packet.NetworkLayer().NetworkFlow()
|
||||
|
||||
t.Log(networkFlow)
|
||||
|
||||
t.Log(packet.Metadata().Length)
|
||||
t.Log(packet.TransportLayer().TransportFlow())
|
||||
}
|
||||
|
||||
func TestListener_DecodePacket_TCP(t *testing.T) {
|
||||
var packetData = []byte{69, 8, 0, 64, 6, 51, 64, 0, 52, 6, 188, 222, 74, 91, 117, 187, 192, 168, 2, 224, 1, 187, 225, 226, 137, 198, 251, 25, 221, 137, 133, 93, 176, 16, 1, 245, 224, 6, 0, 0, 1, 1, 8, 10, 30, 187, 162, 175, 35, 215, 100, 174, 1, 1, 5, 10, 221, 137, 133, 68, 221, 137, 133, 93}
|
||||
|
||||
var packet = gopacket.NewPacket(packetData, layers.LayerTypeIPv4, gopacket.DecodeOptions{})
|
||||
var networkFlow = packet.NetworkLayer().NetworkFlow()
|
||||
t.Log(networkFlow.Src().Raw(), len(networkFlow.Src().Raw()))
|
||||
|
||||
t.Log(networkFlow)
|
||||
|
||||
t.Log(packet.Metadata().Length)
|
||||
t.Log(packet.TransportLayer().TransportFlow())
|
||||
}
|
||||
|
||||
func BenchmarkListener_DecodePacket(b *testing.B) {
|
||||
var packetData = []byte{69, 0, 0, 134, 140, 133, 0, 0, 118, 17, 16, 79, 223, 5, 5, 5, 192, 168, 2, 224, 0, 53, 232, 163, 0, 114, 0, 0, 69, 42, 129, 128, 0, 1, 0, 3, 0, 0, 0, 0, 6, 115, 116, 97, 116, 105, 99, 7, 111, 115, 99, 104, 105, 110, 97, 3, 110, 101, 116, 0, 0, 1, 0, 1, 192, 12, 0, 5, 0, 1, 0, 0, 0, 1, 0, 25, 10, 115, 116, 97, 116, 105, 99, 45, 111, 115, 99, 2, 98, 48, 5, 97, 105, 99, 100, 110, 3, 99, 111, 109, 0, 192, 48, 0, 5, 0, 1, 0, 0, 0, 1, 0, 5, 2, 118, 109, 192, 62, 192, 85, 0, 1, 0, 1, 0, 0, 0, 1, 0, 4, 218, 28, 104, 157}
|
||||
|
||||
var decodeOptions = gopacket.DecodeOptions{
|
||||
Lazy: true,
|
||||
NoCopy: true,
|
||||
//SkipDecodeRecovery: true,
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
var packet = gopacket.NewPacket(packetData, layers.LayerTypeIPv4, decodeOptions)
|
||||
|
||||
var networkFlow = packet.NetworkLayer().NetworkFlow()
|
||||
var src = networkFlow.Src()
|
||||
var dest = networkFlow.Dst()
|
||||
|
||||
_ = netpackets.IsLocalRawIPv4(src.Raw())
|
||||
_ = netpackets.IsLocalRawIPv4(dest.Raw())
|
||||
|
||||
_ = src.String()
|
||||
_ = dest.String()
|
||||
|
||||
_ = packet.Metadata().Length
|
||||
|
||||
var transportFlow = packet.TransportLayer().TransportFlow()
|
||||
//_ = transportFlow.Src().String()
|
||||
//_ = transportFlow.Dst().String()
|
||||
|
||||
_ = int(binary.BigEndian.Uint16(transportFlow.Src().Raw()))
|
||||
_ = int(binary.BigEndian.Uint16(transportFlow.Dst().Raw()))
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build plus && packet
|
||||
|
||||
package netpackets
|
||||
|
||||
type PacketMeta struct {
|
||||
LayerType LayerType
|
||||
SrcIP string
|
||||
SrcPort int
|
||||
DstIP string
|
||||
DstPort int
|
||||
Length int
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package netpackets
|
||||
|
||||
// IsLocalRawIPv4 使用原始IP数据判断是否为本地IPv4
|
||||
func IsLocalRawIPv4(ip []byte) bool {
|
||||
if len(ip) != 4 {
|
||||
return false
|
||||
}
|
||||
if ip[0] == 127 ||
|
||||
ip[0] == 10 ||
|
||||
(ip[0] == 172 && ip[1]&0xf0 == 16) ||
|
||||
(ip[0] == 192 && ip[1] == 168) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package netpackets_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/netpackets"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsLocalRawIPv4(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
a.IsTrue(netpackets.IsLocalRawIPv4(net.ParseIP("192.168.2.100").To4()))
|
||||
a.IsTrue(netpackets.IsLocalRawIPv4(net.ParseIP("127.0.0.1").To4()))
|
||||
a.IsTrue(netpackets.IsLocalRawIPv4(net.ParseIP("172.16.0.1").To4()))
|
||||
a.IsTrue(netpackets.IsLocalRawIPv4(net.ParseIP("10.0.0.1").To4()))
|
||||
|
||||
a.IsFalse(netpackets.IsLocalRawIPv4(net.ParseIP("1.2.3.4").To4()))
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "0.1.5"
|
||||
Version = "1.5.0"
|
||||
|
||||
ProductName = "Edge Reporter"
|
||||
ProcessName = "edge-reporter"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.4.9" //1.3.8.2
|
||||
Version = "1.5.0" //1.3.8.2
|
||||
|
||||
ProductName = "Edge User"
|
||||
ProcessName = "edge-user"
|
||||
|
||||
@@ -174,6 +174,8 @@ replace_files() {
|
||||
# 替换 EdgeAdmin bin
|
||||
if [ -d "$TEMP_DIR/edge-admin/bin" ]; then
|
||||
log_info "替换 EdgeAdmin 可执行文件..."
|
||||
rm -rf "$TARGET_DIR/bin"
|
||||
mkdir -p "$TARGET_DIR/bin"
|
||||
cp -r "$TEMP_DIR/edge-admin/bin"/* "$TARGET_DIR/bin/"
|
||||
log_info "✅ EdgeAdmin bin 已更新"
|
||||
fi
|
||||
@@ -181,15 +183,9 @@ replace_files() {
|
||||
# 替换 EdgeAdmin web(排除 tmp)
|
||||
if [ -d "$TEMP_DIR/edge-admin/web" ]; then
|
||||
log_info "替换 EdgeAdmin 前端文件..."
|
||||
if command -v rsync > /dev/null; then
|
||||
rsync -av --exclude='tmp' \
|
||||
"$TEMP_DIR/edge-admin/web/" "$TARGET_DIR/web/"
|
||||
else
|
||||
# 如果没有 rsync,使用 cp
|
||||
cp -r "$TEMP_DIR/edge-admin/web"/* "$TARGET_DIR/web/" 2>/dev/null || true
|
||||
rm -rf "$TARGET_DIR/web/tmp"/* 2>/dev/null || true
|
||||
fi
|
||||
# 清空 tmp 目录
|
||||
rm -rf "$TARGET_DIR/web"
|
||||
cp -r "$TEMP_DIR/edge-admin/web" "$TARGET_DIR/"
|
||||
mkdir -p "$TARGET_DIR/web/tmp"
|
||||
rm -rf "$TARGET_DIR/web/tmp"/* 2>/dev/null || true
|
||||
log_info "✅ EdgeAdmin web 已更新"
|
||||
fi
|
||||
@@ -203,6 +199,7 @@ replace_files() {
|
||||
|
||||
# 替换 bin
|
||||
if [ -d "$TEMP_DIR/edge-admin/edge-api/bin" ]; then
|
||||
rm -rf "$TARGET_DIR/edge-api/bin"
|
||||
mkdir -p "$TARGET_DIR/edge-api/bin"
|
||||
cp -r "$TEMP_DIR/edge-admin/edge-api/bin"/* \
|
||||
"$TARGET_DIR/edge-api/bin/" 2>/dev/null || true
|
||||
@@ -211,6 +208,7 @@ replace_files() {
|
||||
|
||||
# 替换 deploy(节点安装包)
|
||||
if [ -d "$TEMP_DIR/edge-admin/edge-api/deploy" ]; then
|
||||
rm -rf "$TARGET_DIR/edge-api/deploy"
|
||||
mkdir -p "$TARGET_DIR/edge-api/deploy"
|
||||
cp -r "$TEMP_DIR/edge-admin/edge-api/deploy"/* \
|
||||
"$TARGET_DIR/edge-api/deploy/" 2>/dev/null || true
|
||||
@@ -219,6 +217,7 @@ replace_files() {
|
||||
|
||||
# 替换 installers(安装工具)
|
||||
if [ -d "$TEMP_DIR/edge-admin/edge-api/installers" ]; then
|
||||
rm -rf "$TARGET_DIR/edge-api/installers"
|
||||
mkdir -p "$TARGET_DIR/edge-api/installers"
|
||||
cp -r "$TEMP_DIR/edge-admin/edge-api/installers"/* \
|
||||
"$TARGET_DIR/edge-api/installers/" 2>/dev/null || true
|
||||
@@ -345,4 +344,3 @@ main() {
|
||||
|
||||
# 执行主函数
|
||||
main
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user