This commit is contained in:
ryan
2026-07-13 15:08:40 +08:00
parent 08e8eea932
commit 1eff7878a1
441 changed files with 26491 additions and 18405 deletions
+1 -1
View File
@@ -26,4 +26,4 @@ func EnsureWorkerReadableTree(rootDir string) error {
// shared runtime user.
func (m *Manager) EnsureWorkerReadAccess() error {
return m.ensureOpenRestyWorkerReadAccess()
}
}
@@ -72,4 +72,4 @@ func TestManagerEnsureWorkerReadAccessIncludesPagesDir(t *testing.T) {
if indexInfo.Mode().Perm() != nginxConfigFilePerm {
t.Fatalf("expected index.html mode %o, got %o", nginxConfigFilePerm, indexInfo.Mode().Perm())
}
}
}
@@ -189,4 +189,4 @@ func ensureModesOnly(root string, dirPerm os.FileMode, filePerm os.FileMode) err
}
return nil
})
}
}
@@ -36,4 +36,4 @@ func TestEnsurePathOwnershipNormalizesModes(t *testing.T) {
if indexInfo.Mode().Perm() != 0o644 {
t.Fatalf("expected mode 0644, got %o", indexInfo.Mode().Perm())
}
}
}
@@ -17,4 +17,4 @@ func init() {
}
return nil
}
}
}
+1 -1
View File
@@ -50,4 +50,4 @@ func TestMillisecondDurationMarshalJSON(t *testing.T) {
if string(data) != "7000" {
t.Fatalf("unexpected marshaled value: %s", string(data))
}
}
}
+1 -1
View File
@@ -20,4 +20,4 @@ func RunLoop(ctx context.Context, interval time.Duration, fn func(context.Contex
fn(ctx)
}
}
}
}
+1 -1
View File
@@ -38,4 +38,4 @@ func TestRunLoopImmediateAndTicker(t *testing.T) {
case <-time.After(time.Second):
t.Fatal("RunLoop did not exit after context cancellation")
}
}
}
@@ -75,4 +75,4 @@ func TestStatFilesystem(t *testing.T) {
if used < 0 || used > total {
t.Fatalf("StatFilesystem() used = %d, total = %d", used, total)
}
}
}
@@ -12,4 +12,4 @@ func TestRemoveBackupBinaryIgnoresMissingFile(t *testing.T) {
if err := removeBackupBinary(backupPath); err != nil {
t.Fatalf("expected missing backup cleanup to be ignored: %v", err)
}
}
}
@@ -50,4 +50,4 @@ func buildWindowsCommandLine(execPath string, args []string) string {
func quoteWindowsArg(value string) string {
return `"` + strings.ReplaceAll(value, `"`, `""`) + `"`
}
}
+1 -1
View File
@@ -63,4 +63,4 @@ func TestClientURL(t *testing.T) {
}
})
}
}
}
+1 -1
View File
@@ -40,4 +40,4 @@ type HeartbeatResponse struct {
AgentSettings *Settings `json:"agent_settings"`
ActiveConfig *ActiveConfigMeta `json:"active_config"`
WAFIPGroups []WAFIPGroup `json:"waf_ip_groups,omitempty"`
}
}
+1 -1
View File
@@ -41,4 +41,4 @@ func NamedIDParam(c *gin.Context, name string) (uint, bool) {
return 0, false
}
return uint(id64), true
}
}
+1 -1
View File
@@ -31,4 +31,4 @@ func AbortBadRequestOnError(c *gin.Context, err error) bool {
}
response.AbortBadRequest(c, err.Error())
return true
}
}
@@ -14,4 +14,4 @@ import (
// the same gate: user.IsAdmin for session users, token_admin for Access Token callers.
func AdminMiddlewares() []gin.HandlerFunc {
return []gin.HandlerFunc{oauth.LoginRequired(), admin.LoginAdminRequired()}
}
}
@@ -155,4 +155,4 @@ func TestAdminRequiredAdminWithTokenAdmin(t *testing.T) {
assert.Equal(t, http.StatusOK, rec.Code)
resp := decodeResponse(t, rec)
assert.Empty(t, resp.ErrorMsg)
}
}
+1 -1
View File
@@ -16,4 +16,4 @@ func RegisterCollection(route *gin.RouterGroup, method string, handlers ...gin.H
if !strings.HasSuffix(route.BasePath(), "/") {
route.Handle(method, "", handlers...)
}
}
}
+1 -2
View File
@@ -12,7 +12,6 @@ import (
"github.com/gin-gonic/gin"
)
// GetApplyLogs lists apply logs with pagination and optional node_id filter.
// @Summary 获取配置下发日志
// @Description 分页返回节点配置下发记录,支持按节点 ID 筛选,需要管理员权限
@@ -74,4 +73,4 @@ func readIntQuery(c *gin.Context, primary, secondary string) int {
}
parsed, _ := strconv.Atoi(value)
return parsed
}
}
+1 -1
View File
@@ -30,4 +30,4 @@ func setCachedOverview(payload *OverviewPayload) {
defer overviewCache.mu.Unlock()
overviewCache.payload = payload
overviewCache.expiresAt = time.Now().Add(overviewCacheTTL)
}
}
@@ -60,19 +60,19 @@ func TestGetOverviewStructure(t *testing.T) {
// Seed older + newer snapshots per node; health must use latest-per-node, not a global raw limit.
require.NoError(t, model.InsertOpenFlareMetricSnapshot(ctx, &model.OpenFlareMetricSnapshot{
NodeID: "node-dashboard-1",
CapturedAt: now.Add(-2 * time.Hour),
CPUUsagePercent: 10,
MemoryUsedBytes: 1,
NodeID: "node-dashboard-1",
CapturedAt: now.Add(-2 * time.Hour),
CPUUsagePercent: 10,
MemoryUsedBytes: 1,
MemoryTotalBytes: 10,
}))
require.NoError(t, model.InsertOpenFlareMetricSnapshot(ctx, &model.OpenFlareMetricSnapshot{
NodeID: "node-dashboard-1",
CapturedAt: now.Add(-time.Minute),
CPUUsagePercent: 55,
MemoryUsedBytes: 5,
MemoryTotalBytes: 10,
StorageUsedBytes: 2,
NodeID: "node-dashboard-1",
CapturedAt: now.Add(-time.Minute),
CPUUsagePercent: 55,
MemoryUsedBytes: 5,
MemoryTotalBytes: 10,
StorageUsedBytes: 2,
StorageTotalBytes: 10,
}))
require.NoError(t, model.InsertOpenFlareRequestReport(ctx, &model.OpenFlareRequestReport{
+1 -1
View File
@@ -29,4 +29,4 @@ func GetOverviewHandler(c *gin.Context) {
return
}
c.JSON(http.StatusOK, response.OK(overview))
}
}
+1 -1
View File
@@ -46,4 +46,4 @@ func authenticateAccessToken(ctx context.Context, token string) (*model.OpenFlar
return nil, err
}
return node, nil
}
}
+1 -1
View File
@@ -117,4 +117,4 @@ func GetWebSocket(c *gin.Context) {
}
node := authNode.(*model.OpenFlareNode)
ofws.ServeFlared(c, node.NodeID)
}
}
@@ -100,4 +100,4 @@ func adminAuthHeaders(token string) map[string]string {
return map[string]string{
"X-Access-Token": token,
}
}
}
+1 -2
View File
@@ -14,7 +14,6 @@ import (
"github.com/gin-gonic/gin"
)
func handleLogicError(c *gin.Context, err error) bool {
if err == nil {
return false
@@ -321,4 +320,4 @@ func bindOptionalJSON(body io.Reader, target any) error {
return err
}
return nil
}
}
@@ -200,10 +200,10 @@ func TestBuildDiskIOTrendPointsFromHourlyFillsBuckets(t *testing.T) {
now := time.Date(2026, 7, 10, 9, 30, 0, 0, time.UTC)
hourly := []*model.OpenFlareMetricHourly{
{
Hour: now.Add(-1 * time.Hour).Truncate(time.Hour),
DiskReadBytes: 1024,
DiskWriteBytes: 2048,
ReportedNodes: 1,
Hour: now.Add(-1 * time.Hour).Truncate(time.Hour),
DiskReadBytes: 1024,
DiskWriteBytes: 2048,
ReportedNodes: 1,
},
}
+1 -2
View File
@@ -11,7 +11,6 @@ import (
"github.com/gin-gonic/gin"
)
func handleLogicError(c *gin.Context, err error) bool {
if err == nil {
return false
@@ -138,4 +137,4 @@ func DeleteOriginHandler(c *gin.Context) {
return
}
c.JSON(http.StatusOK, response.OKNil())
}
}
+1 -2
View File
@@ -12,7 +12,6 @@ import (
"github.com/gin-gonic/gin"
)
func handleLogicError(c *gin.Context, err error) bool {
if err == nil {
return false
@@ -307,4 +306,4 @@ func ListDeploymentFilesHandler(c *gin.Context) {
return
}
c.JSON(http.StatusOK, response.OK(files))
}
}
@@ -151,7 +151,7 @@ func updateProxyRouteRecord(tx *gorm.DB, route *model.ProxyRoute) error {
"cache_rules": route.CacheRules, "custom_headers": route.CustomHeaders,
"basic_auth_enabled": route.BasicAuthEnabled, "basic_auth_username": route.BasicAuthUsername,
"basic_auth_password": route.BasicAuthPassword,
"upstream_type": route.UpstreamType, "tunnel_node_id": route.TunnelNodeID,
"upstream_type": route.UpstreamType, "tunnel_node_id": route.TunnelNodeID,
"tunnel_target_addr": route.TunnelTargetAddr, "tunnel_target_protocol": route.TunnelTargetProtocol,
"pages_project_id": route.PagesProjectID,
}).Error
+1 -1
View File
@@ -63,4 +63,4 @@ func GetWebSocket(c *gin.Context) {
}
node := authNode.(*model.OpenFlareNode)
ofws.ServeRelay(c, node.NodeID)
}
}
@@ -62,15 +62,15 @@ type DatabaseCleanupInput struct {
// - retention path / cleanup_mode=ttl_materialize: DeletedCount is always 0;
// EligibleCount estimates rows past the table DDL TTL (not an arbitrary younger cutoff).
type DatabaseCleanupResult struct {
Target string `json:"target"`
TargetLabel string `json:"target_label"`
DeletedCount int64 `json:"deleted_count"`
EligibleCount int64 `json:"eligible_count,omitempty"`
CleanupMode string `json:"cleanup_mode,omitempty"`
TableTTLDays int `json:"table_ttl_days,omitempty"`
DeleteAll bool `json:"delete_all"`
RetentionDays *int `json:"retention_days,omitempty"`
Cutoff *time.Time `json:"cutoff,omitempty"`
Target string `json:"target"`
TargetLabel string `json:"target_label"`
DeletedCount int64 `json:"deleted_count"`
EligibleCount int64 `json:"eligible_count,omitempty"`
CleanupMode string `json:"cleanup_mode,omitempty"`
TableTTLDays int `json:"table_ttl_days,omitempty"`
DeleteAll bool `json:"delete_all"`
RetentionDays *int `json:"retention_days,omitempty"`
Cutoff *time.Time `json:"cutoff,omitempty"`
}
// DatabaseAutoCleanupSummary summarizes a scheduled auto-cleanup run.
+1 -1
View File
@@ -6,4 +6,4 @@
// Scheduled execution is handled by the Wavelet Asynq task framework; handlers
// live in internal/apps/openflare/async_tasks.go and are registered via
// bootstrap.RegisterTasks().
package tasks
package tasks
+1 -1
View File
@@ -41,4 +41,4 @@ func RunSSLRenewJob(ctx context.Context) error {
logger.InfoF(ctx, "[OpenFlareTasks] SSL renew job completed: triggered=%d eligible=%d", triggered, len(due))
return nil
}
}
-1
View File
@@ -17,6 +17,5 @@ const (
errCertificateFilesRequired = "certificate file and key file cannot be empty"
errCertificatePEMInvalid = "证书 PEM 内容不合法"
errDNSAccountInUse = "该 DNS 账号已被证书使用,无法删除"
)
+4 -5
View File
@@ -4,14 +4,13 @@
package tls
import (
"net/http"
"net/http"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/apiutil"
"github.com/Rain-kl/Wavelet/internal/common/response"
"github.com/gin-gonic/gin"
"github.com/Rain-kl/Wavelet/internal/apps/openflare/apiutil"
"github.com/Rain-kl/Wavelet/internal/common/response"
"github.com/gin-gonic/gin"
)
func handleLogicError(c *gin.Context, err error) bool {
if err == nil {
return false
+3 -3
View File
@@ -87,9 +87,9 @@ func TestSSLSingleRenewHandler_Execute(t *testing.T) {
t.Run("certificate provider is not ACME", func(t *testing.T) {
cert := &model.TLSCertificate{
Name: "custom-cert",
Provider: "custom",
PrimaryDomain: "example.com",
Name: "custom-cert",
Provider: "custom",
PrimaryDomain: "example.com",
}
err := model.CreateTLSCertificateRecord(ctx, cert)
require.NoError(t, err)
@@ -175,4 +175,4 @@ func TestRiskControlMiddleware(t *testing.T) {
assert.NoError(t, err)
assert.Contains(t, resp["error_msg"], "系统繁忙")
})
}
}
+1 -1
View File
@@ -284,4 +284,4 @@ func TestGetUploadByIDWorksWithRedisDisabled(t *testing.T) {
if gotCached.ID != upload.ID {
t.Fatal("expected RAM cache hit when redis is disabled")
}
}
}
+1 -1
View File
@@ -55,4 +55,4 @@ func softDeleteUploadWithStats(ctx context.Context, upload *model.Upload) error
}
uploadcache.InvalidateUploadMetaCache(ctx, upload.ID)
return nil
}
}
-1
View File
@@ -410,4 +410,3 @@ func rotateAccessTokenLogic(ctx context.Context, id, userID uint64) (string, *mo
return newTokenStr, &tokenRecord, nil
}
+5 -5
View File
@@ -9,10 +9,10 @@ import (
)
const (
defaultQueueSize = 10_000
defaultMaxBatchSize = 1_000
defaultMinBatchSize = 50
defaultFlushEvery = time.Second
defaultQueueSize = 10_000
defaultMaxBatchSize = 1_000
defaultMinBatchSize = 50
defaultFlushEvery = time.Second
)
// Config controls queue capacity and flush thresholds for a Writer instance.
@@ -66,4 +66,4 @@ func (c Config) validate() error {
return fmt.Errorf("batchwriter: max flush wait must be non-negative")
}
return nil
}
}
+1 -1
View File
@@ -5,4 +5,4 @@ package batchwriter
import "errors"
var errNilFlushFunc = errors.New("batchwriter: flush func is required")
var errNilFlushFunc = errors.New("batchwriter: flush func is required")
+1 -1
View File
@@ -99,4 +99,4 @@ func ChConnReady() bool {
// SetChConnForTest sets the package-level native ClickHouse connection for testing.
func SetChConnForTest(c driver.Conn) {
ChConn = c
}
}
+1 -1
View File
@@ -52,4 +52,4 @@ func TestMigrateClickHouseSkipsWhenDisabled(t *testing.T) {
})
MigrateClickHouse()
}
}
+1 -1
View File
@@ -41,4 +41,4 @@ func (UserAccessLog) InsertColumns() string {
// BatchInsertSQL returns the INSERT prefix used by native batch writers.
func (UserAccessLog) BatchInsertSQL() string {
return fmt.Sprintf("INSERT INTO %s (%s)", userAccessLogTableName, userAccessLogInsertColumns)
}
}
+1 -1
View File
@@ -103,4 +103,4 @@ LIMIT ? OFFSET ?`, tableName, clause)
logs = append(logs, item)
}
return logs, total, nil
}
}
@@ -53,4 +53,4 @@ func buildUserAccessLogFilterClause(filter AccessLogFilter) (string, []any, bool
return "1", args, true
}
return strings.Join(parts, " AND "), args, true
}
}
@@ -167,4 +167,4 @@ func GetTopActiveUsers(ctx context.Context, startTime time.Time, limit int) ([]T
users = append(users, item)
}
return users, nil
}
}
@@ -258,4 +258,4 @@ func assignMockScanValue(dest any, value any) error {
}
}
return nil
}
}
@@ -46,4 +46,4 @@ func BatchInsert(ctx context.Context, logs []analyticsmodel.UserAccessLog) error
return fmt.Errorf("send clickhouse batch: %w", err)
}
return nil
}
}
+1 -1
View File
@@ -27,4 +27,4 @@ func ParseBrowserName(ua string) string {
return "Safari"
}
return "Other"
}
}
@@ -10,4 +10,4 @@ func safeInt64Count(count uint64) int64 {
return math.MaxInt64
}
return int64(count)
}
}
@@ -30,4 +30,3 @@ func TestSafeInt64Count(t *testing.T) {
})
}
}
@@ -354,7 +354,7 @@ ORDER BY hour ASC`, nodeTrafficHourlyTableName, clause)
result := make([]NodeTrafficHourly, 0)
for rows.Next() {
var (
item NodeTrafficHourly
item NodeTrafficHourly
requestCount, errorCount, uniqueVisitorCount uint64
)
if err := rows.Scan(&item.NodeID, &item.Hour, &requestCount, &errorCount, &uniqueVisitorCount); err != nil {
@@ -222,4 +222,3 @@ func TestListNodeOpenrestyHourly_FallsBackToRawOnEmptyRollup(t *testing.T) {
require.GreaterOrEqual(t, len(mock.queries), 2)
assert.Contains(t, mock.queries[1], "lagInFrame")
}
@@ -324,4 +324,4 @@ func BatchInsertNodeObsFrpc(ctx context.Context, observations []analyticsmodel.N
return fmt.Errorf("send clickhouse batch: %w", err)
}
return nil
}
}
@@ -38,7 +38,7 @@ func setupAuthSourceCacheTest(t *testing.T) (*gorm.DB, *miniredis.Miniredis, fun
db.SetDB(sqliteDB)
db.Redis = redis.NewClient(&redis.Options{
Addr: miniRedis.Addr(),
Addr: miniRedis.Addr(),
MaintNotificationsConfig: &maintnotifications.Config{
Mode: maintnotifications.ModeDisabled,
},
@@ -237,4 +237,4 @@ func TestAuthSourceInvalidationPubSubClearsPeerRAM(t *testing.T) {
if _, ok := authSourceActiveRAM.GetIfPresent(authSourceActiveRAMKey); ok {
t.Fatal("expected peer RAM cache to be cleared by pub/sub")
}
}
}