mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 16:46:37 +08:00
fix: resolve all build-test and code-check failures
- Fix Go backend mnd (magic number) and revive lint issues in zone stats. - Remove unused React/Lucide imports and variables in zone overview. - Add generateStaticParams and Suspense wrapper for websites/[zoneId] page to support Next.js static HTML export. - Remove next/font/google dependency to allow fully offline frontend compilation. - Fix hardcoded time dependency in ssl_renew_test.go. - Fix async_tasks_test.go to respect minimum 90-day retention clamping in database auto-cleanup. - Add Zone and ZoneDomain models to test database AutoMigrate schemas. - Update integration tests to use the new zone_domain_ids route binding scheme.
This commit is contained in:
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
import {useMemo, useState} from 'react';
|
import {useMemo, useState} from 'react';
|
||||||
import {useQuery} from '@tanstack/react-query';
|
import {useQuery} from '@tanstack/react-query';
|
||||||
import {Activity, HardDrive, Link2, ShieldCheck, Users} from 'lucide-react';
|
import {Activity, HardDrive, Users} from 'lucide-react';
|
||||||
import {Area, AreaChart, CartesianGrid, XAxis, YAxis} from 'recharts';
|
import {Area, AreaChart, CartesianGrid, XAxis, YAxis} from 'recharts';
|
||||||
|
|
||||||
import {Button} from '@/components/ui/button';
|
import {Button} from '@/components/ui/button';
|
||||||
import {Card, CardContent, CardDescription, CardHeader, CardTitle} from '@/components/ui/card';
|
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card';
|
||||||
import {
|
import {
|
||||||
ChartContainer,
|
ChartContainer,
|
||||||
ChartTooltip,
|
ChartTooltip,
|
||||||
@@ -84,14 +84,6 @@ export function ZoneOverviewPanel({
|
|||||||
zoneId: number;
|
zoneId: number;
|
||||||
}) {
|
}) {
|
||||||
const [range, setRange] = useState<ZoneStatsRange>('24h');
|
const [range, setRange] = useState<ZoneStatsRange>('24h');
|
||||||
const boundRoutes = new Set(
|
|
||||||
overview.domains.flatMap((domain) =>
|
|
||||||
domain.proxy_route_id ? [domain.proxy_route_id] : [],
|
|
||||||
),
|
|
||||||
).size;
|
|
||||||
const certificates = overview.domains.filter(
|
|
||||||
(domain) => domain.cert_id !== null,
|
|
||||||
).length;
|
|
||||||
|
|
||||||
const statsQuery = useQuery({
|
const statsQuery = useQuery({
|
||||||
queryKey: [...zoneQueryKey, zoneId, 'stats', range],
|
queryKey: [...zoneQueryKey, zoneId, 'stats', range],
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
|
import {Suspense} from 'react'
|
||||||
|
import {Globe} from 'lucide-react'
|
||||||
|
import {LoadingStateWithBorder} from '@/components/layout/loading'
|
||||||
import {ZonePageClient} from './page-client'
|
import {ZonePageClient} from './page-client'
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
return [{zoneId: '1'}];
|
||||||
|
}
|
||||||
|
|
||||||
export default async function ZonePage({params}: PageProps<'/websites/[zoneId]'>) {
|
export default async function ZonePage({params}: PageProps<'/websites/[zoneId]'>) {
|
||||||
const {zoneId} = await params
|
const {zoneId} = await params
|
||||||
return <ZonePageClient zoneId={Number(zoneId)} />
|
return (
|
||||||
|
<Suspense fallback={
|
||||||
|
<div className="py-6 px-1">
|
||||||
|
<LoadingStateWithBorder icon={Globe} description="加载 Zone 详情中..." />
|
||||||
|
</div>
|
||||||
|
}>
|
||||||
|
<ZonePageClient zoneId={Number(zoneId)} />
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
@theme inline {
|
@theme inline {
|
||||||
--color-background: var(--background);
|
--color-background: var(--background);
|
||||||
--color-foreground: var(--foreground);
|
--color-foreground: var(--foreground);
|
||||||
--font-sans: var(--font-inter), 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
--font-sans: 'Inter', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||||
--font-mono: var(--font-geist-mono), 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'PingFang SC', 'Microsoft YaHei', monospace;
|
--font-mono: var(--font-geist-mono), 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'PingFang SC', 'Microsoft YaHei', monospace;
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type {Metadata} from "next";
|
import type {Metadata} from "next";
|
||||||
import {Inter} from "next/font/google";
|
|
||||||
import {Toaster} from "@/components/ui/sonner";
|
import {Toaster} from "@/components/ui/sonner";
|
||||||
import {ThemeProvider} from "@/components/layout/theme-provider";
|
import {ThemeProvider} from "@/components/layout/theme-provider";
|
||||||
import {CustomThemeProvider} from "@/lib/theme";
|
import {CustomThemeProvider} from "@/lib/theme";
|
||||||
@@ -11,12 +10,6 @@ import {SiteTitleUpdater} from "@/components/providers/title-updater";
|
|||||||
import {RobotsMeta} from "@/components/layout/robots-meta";
|
import {RobotsMeta} from "@/components/layout/robots-meta";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
const inter = Inter({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-inter",
|
|
||||||
display: "swap",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "OpenFlare",
|
title: "OpenFlare",
|
||||||
description: "OpenFlare 边缘节点与反向代理管理平台",
|
description: "OpenFlare 边缘节点与反向代理管理平台",
|
||||||
@@ -30,7 +23,7 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html
|
<html
|
||||||
lang="zh-CN"
|
lang="zh-CN"
|
||||||
className={`hide-scrollbar font-sans ${inter.variable}`}
|
className="hide-scrollbar font-sans"
|
||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
>
|
>
|
||||||
<body
|
<body
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func TestDatabaseAutoCleanupHandlerDeletesRowsWhenEnabled(t *testing.T) {
|
|||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
require.NoError(t, model.InsertOpenFlareAccessLogsBatch(ctx, []*model.OpenFlareAccessLog{{
|
require.NoError(t, model.InsertOpenFlareAccessLogsBatch(ctx, []*model.OpenFlareAccessLog{{
|
||||||
NodeID: "node-a",
|
NodeID: "node-a",
|
||||||
LoggedAt: now.Add(-48 * time.Hour),
|
LoggedAt: now.Add(-95 * 24 * time.Hour),
|
||||||
RemoteAddr: "203.0.113.10",
|
RemoteAddr: "203.0.113.10",
|
||||||
Host: "example.com",
|
Host: "example.com",
|
||||||
Path: "/access",
|
Path: "/access",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -49,6 +50,8 @@ func setupCoreChainTest(t *testing.T) (*gin.Engine, adminSeed, func()) {
|
|||||||
&model.OpenFlareNode{},
|
&model.OpenFlareNode{},
|
||||||
&model.SystemConfig{},
|
&model.SystemConfig{},
|
||||||
&model.OpenFlareApplyLog{},
|
&model.OpenFlareApplyLog{},
|
||||||
|
&model.Zone{},
|
||||||
|
&model.ZoneDomain{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
db.SetDB(sqliteDB)
|
||||||
@@ -138,13 +141,22 @@ func TestCoreChainMigrationFlow(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("create proxy route linked to origin", func(t *testing.T) {
|
t.Run("create proxy route linked to origin", func(t *testing.T) {
|
||||||
|
// Create Zone and ZoneDomain directly in the DB
|
||||||
|
zone := model.Zone{Domain: "example.com"}
|
||||||
|
require.NoError(t, db.DB(context.Background()).Create(&zone).Error)
|
||||||
|
zoneDomain := model.ZoneDomain{
|
||||||
|
ZoneID: zone.ID,
|
||||||
|
Domain: "core-chain.example.com",
|
||||||
|
}
|
||||||
|
require.NoError(t, db.DB(context.Background()).Create(&zoneDomain).Error)
|
||||||
|
|
||||||
rec := performJSONRequest(t, engine, http.MethodPost, apiPath("/proxy-routes/"), map[string]any{
|
rec := performJSONRequest(t, engine, http.MethodPost, apiPath("/proxy-routes/"), map[string]any{
|
||||||
"site_name": "core-chain-site",
|
"site_name": "core-chain-site",
|
||||||
"domain": "core-chain.example.com",
|
"zone_domain_ids": []uint{zoneDomain.ID},
|
||||||
"origin_id": originID,
|
"origin_id": originID,
|
||||||
"origin_scheme": "http",
|
"origin_scheme": "http",
|
||||||
"origin_port": "8080",
|
"origin_port": "8080",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
}, map[string]string{
|
}, map[string]string{
|
||||||
"X-Access-Token": seed.Token,
|
"X-Access-Token": seed.Token,
|
||||||
})
|
})
|
||||||
@@ -155,7 +167,10 @@ func TestCoreChainMigrationFlow(t *testing.T) {
|
|||||||
proxyRouteID = uint(data["id"].(float64))
|
proxyRouteID = uint(data["id"].(float64))
|
||||||
assert.NotZero(t, proxyRouteID)
|
assert.NotZero(t, proxyRouteID)
|
||||||
assert.Equal(t, "core-chain-site", data["site_name"])
|
assert.Equal(t, "core-chain-site", data["site_name"])
|
||||||
assert.Equal(t, "core-chain.example.com", data["domain"])
|
assert.NotEmpty(t, data["zone_domains"])
|
||||||
|
zoneDomains := data["zone_domains"].([]any)
|
||||||
|
assert.Len(t, zoneDomains, 1)
|
||||||
|
assert.Equal(t, "core-chain.example.com", zoneDomains[0].(map[string]any)["domain"])
|
||||||
assert.Equal(t, float64(originID), data["origin_id"])
|
assert.Equal(t, float64(originID), data["origin_id"])
|
||||||
assert.Equal(t, "http://origin.core-chain.internal:8080", data["origin_url"])
|
assert.Equal(t, "http://origin.core-chain.internal:8080", data["origin_url"])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
@@ -207,11 +208,20 @@ func TestSecurityWAFTLSMigrationFlow(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("create proxy route for WAF binding", func(t *testing.T) {
|
t.Run("create proxy route for WAF binding", func(t *testing.T) {
|
||||||
|
// Create Zone and ZoneDomain directly in the DB
|
||||||
|
routeZone := model.Zone{Domain: "example-route.com"}
|
||||||
|
require.NoError(t, db.DB(context.Background()).Create(&routeZone).Error)
|
||||||
|
routeZoneDomain := model.ZoneDomain{
|
||||||
|
ZoneID: routeZone.ID,
|
||||||
|
Domain: "route.example-route.com",
|
||||||
|
}
|
||||||
|
require.NoError(t, db.DB(context.Background()).Create(&routeZoneDomain).Error)
|
||||||
|
|
||||||
rec := performJSONRequest(t, engine, http.MethodPost, apiPath("/proxy-routes/"), map[string]any{
|
rec := performJSONRequest(t, engine, http.MethodPost, apiPath("/proxy-routes/"), map[string]any{
|
||||||
"site_name": "security-site",
|
"site_name": "security-site",
|
||||||
"domain": "security.example.com",
|
"zone_domain_ids": []uint{routeZoneDomain.ID},
|
||||||
"origin_url": "http://origin.security.internal:8080",
|
"origin_url": "http://origin.security.internal:8080",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
}, adminAuthHeaders(seed.Token))
|
}, adminAuthHeaders(seed.Token))
|
||||||
require.Equal(t, http.StatusOK, rec.Code)
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
@@ -219,7 +229,10 @@ func TestSecurityWAFTLSMigrationFlow(t *testing.T) {
|
|||||||
data := unmarshalAPIMap(t, resp.Data)
|
data := unmarshalAPIMap(t, resp.Data)
|
||||||
proxyRouteID = uint(data["id"].(float64))
|
proxyRouteID = uint(data["id"].(float64))
|
||||||
assert.NotZero(t, proxyRouteID)
|
assert.NotZero(t, proxyRouteID)
|
||||||
assert.Equal(t, "security.example.com", data["domain"])
|
assert.NotEmpty(t, data["zone_domains"])
|
||||||
|
zoneDomains := data["zone_domains"].([]any)
|
||||||
|
assert.Len(t, zoneDomains, 1)
|
||||||
|
assert.Equal(t, "route.example-route.com", zoneDomains[0].(map[string]any)["domain"])
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("bind WAF rule group to proxy route", func(t *testing.T) {
|
t.Run("bind WAF rule group to proxy route", func(t *testing.T) {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func TestRunSSLRenewJobTriggersDueCertificates(t *testing.T) {
|
|||||||
})
|
})
|
||||||
defer restore()
|
defer restore()
|
||||||
|
|
||||||
now := time.Date(2026, 6, 18, 12, 0, 0, 0, time.UTC)
|
now := time.Now().UTC()
|
||||||
due := &model.TLSCertificate{
|
due := &model.TLSCertificate{
|
||||||
Name: "due-cert",
|
Name: "due-cert",
|
||||||
Provider: "acme",
|
Provider: "acme",
|
||||||
|
|||||||
@@ -17,12 +17,26 @@ import (
|
|||||||
// StatsRange is a supported traffic window for Zone analytics.
|
// StatsRange is a supported traffic window for Zone analytics.
|
||||||
type StatsRange string
|
type StatsRange string
|
||||||
|
|
||||||
|
// StatsRange constants representing supported analytics windows.
|
||||||
const (
|
const (
|
||||||
|
// StatsRange24h represents a 24-hour time window.
|
||||||
StatsRange24h StatsRange = "24h"
|
StatsRange24h StatsRange = "24h"
|
||||||
|
// StatsRange7d represents a 7-day time window.
|
||||||
StatsRange7d StatsRange = "7d"
|
StatsRange7d StatsRange = "7d"
|
||||||
|
// StatsRange30d represents a 30-day time window.
|
||||||
StatsRange30d StatsRange = "30d"
|
StatsRange30d StatsRange = "30d"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
hoursPerDay = 24
|
||||||
|
daysPerWeek = 7
|
||||||
|
daysPerMonth = 30
|
||||||
|
minutesPerHour = 60
|
||||||
|
bucketMinutes24h = 60
|
||||||
|
bucketMinutes7d = 6 * minutesPerHour
|
||||||
|
bucketMinutes30d = 24 * minutesPerHour
|
||||||
|
)
|
||||||
|
|
||||||
// StatsPoint is one bucket on a Zone traffic chart.
|
// StatsPoint is one bucket on a Zone traffic chart.
|
||||||
type StatsPoint struct {
|
type StatsPoint struct {
|
||||||
BucketStartedAt time.Time `json:"bucket_started_at"`
|
BucketStartedAt time.Time `json:"bucket_started_at"`
|
||||||
@@ -49,11 +63,11 @@ type Stats struct {
|
|||||||
func parseStatsRange(raw string) (StatsRange, time.Duration, int, error) {
|
func parseStatsRange(raw string) (StatsRange, time.Duration, int, error) {
|
||||||
switch StatsRange(strings.TrimSpace(raw)) {
|
switch StatsRange(strings.TrimSpace(raw)) {
|
||||||
case "", StatsRange24h:
|
case "", StatsRange24h:
|
||||||
return StatsRange24h, 24 * time.Hour, 60, nil
|
return StatsRange24h, hoursPerDay * time.Hour, bucketMinutes24h, nil
|
||||||
case StatsRange7d:
|
case StatsRange7d:
|
||||||
return StatsRange7d, 7 * 24 * time.Hour, 6 * 60, nil
|
return StatsRange7d, daysPerWeek * hoursPerDay * time.Hour, bucketMinutes7d, nil
|
||||||
case StatsRange30d:
|
case StatsRange30d:
|
||||||
return StatsRange30d, 30 * 24 * time.Hour, 24 * 60, nil
|
return StatsRange30d, daysPerMonth * hoursPerDay * time.Hour, bucketMinutes30d, nil
|
||||||
default:
|
default:
|
||||||
return "", 0, 0, errors.New(errStatsRangeInvalid)
|
return "", 0, 0, errors.New(errStatsRangeInvalid)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user