mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 07:36:37 +08:00
feat(rate-limits): use 5m trend buckets and 24h/7d ranges
Overview API accepts bucket_minutes (5|60); rate-limit analysis uses 5-minute buckets and drops 15d/30d presets. Widen rank value column.
This commit is contained in:
@@ -30,7 +30,7 @@ sidebar: false
|
|||||||
|
|
||||||
- 站点流量限制语义调整为空或 `0` 继承全局默认、`-1` 关闭、大于 `0` 自定义;修改全局默认后需发布配置版本生效。
|
- 站点流量限制语义调整为空或 `0` 继承全局默认、`-1` 关闭、大于 `0` 自定义;修改全局默认后需发布配置版本生效。
|
||||||
- Agent Docker 部署命令默认挂载命名卷 `openflare-agent-pages` 持久化 Pages 目录,重建容器时无需重新拉取静态站点包。
|
- Agent Docker 部署命令默认挂载命名卷 `openflare-agent-pages` 持久化 Pages 目录,重建容器时无需重新拉取静态站点包。
|
||||||
- 限流页新增「分析」视图:默认展示近 24 小时请求压力(RPS)与独立访客双轴趋势,支持域名过滤与时间预设,并按窗口平均 RPS 排行域名与 IP;原全局默认配置迁入「配置」页签。
|
- 限流页新增「分析」视图:默认展示近 24 小时请求压力(RPS)与独立访客双轴趋势(5 分钟桶),支持域名过滤与 24 小时/7 天预设,并按窗口平均 RPS 排行域名与 IP;原全局默认配置迁入「配置」页签。
|
||||||
|
|
||||||
## [v3.4.1] - 2026-07-19
|
## [v3.4.1] - 2026-07-19
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ export type SearchDraft = {
|
|||||||
|
|
||||||
export type OverviewRangeHours = 24 | 168 | 360 | 720;
|
export type OverviewRangeHours = 24 | 168 | 360 | 720;
|
||||||
|
|
||||||
|
/** 限流分析等短窗口场景:仅 24 小时 / 7 天 */
|
||||||
|
export type RateLimitRangeHours = 24 | 168;
|
||||||
|
|
||||||
export const PAGE_SIZE_OPTIONS = [20, 50, 100, 200];
|
export const PAGE_SIZE_OPTIONS = [20, 50, 100, 200];
|
||||||
|
|
||||||
export const OVERVIEW_RANGE_OPTIONS: {
|
export const OVERVIEW_RANGE_OPTIONS: {
|
||||||
@@ -21,6 +24,14 @@ export const OVERVIEW_RANGE_OPTIONS: {
|
|||||||
{ value: 720, label: '30 天' },
|
{ value: 720, label: '30 天' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const RATE_LIMIT_RANGE_OPTIONS: {
|
||||||
|
value: RateLimitRangeHours;
|
||||||
|
label: string;
|
||||||
|
}[] = [
|
||||||
|
{ value: 24, label: '24 小时' },
|
||||||
|
{ value: 168, label: '7 天' },
|
||||||
|
];
|
||||||
|
|
||||||
export const DETAIL_SORT_OPTIONS = [
|
export const DETAIL_SORT_OPTIONS = [
|
||||||
{ value: 'logged_at:desc', label: '时间从新到旧' },
|
{ value: 'logged_at:desc', label: '时间从新到旧' },
|
||||||
{ value: 'logged_at:asc', label: '时间从旧到新' },
|
{ value: 'logged_at:asc', label: '时间从旧到新' },
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ import { cn } from '@/lib/utils';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
OVERVIEW_RANGE_OPTIONS,
|
OVERVIEW_RANGE_OPTIONS,
|
||||||
|
RATE_LIMIT_RANGE_OPTIONS,
|
||||||
type OverviewRangeHours,
|
type OverviewRangeHours,
|
||||||
|
type RateLimitRangeHours,
|
||||||
} from './access-log-utils';
|
} from './access-log-utils';
|
||||||
|
|
||||||
type ManagedZoneDomains = {
|
type ManagedZoneDomains = {
|
||||||
@@ -309,11 +311,13 @@ export function OverviewToolbar({
|
|||||||
hosts,
|
hosts,
|
||||||
onHoursChange,
|
onHoursChange,
|
||||||
onHostsChange,
|
onHostsChange,
|
||||||
|
rangeOptions = OVERVIEW_RANGE_OPTIONS,
|
||||||
}: {
|
}: {
|
||||||
hours: OverviewRangeHours;
|
hours: OverviewRangeHours | RateLimitRangeHours;
|
||||||
hosts: string[];
|
hosts: string[];
|
||||||
onHoursChange: (hours: OverviewRangeHours) => void;
|
onHoursChange: (hours: OverviewRangeHours) => void;
|
||||||
onHostsChange: (hosts: string[]) => void;
|
onHostsChange: (hosts: string[]) => void;
|
||||||
|
rangeOptions?: ReadonlyArray<{ value: number; label: string }>;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-wrap items-center justify-end gap-2'>
|
<div className='flex flex-wrap items-center justify-end gap-2'>
|
||||||
@@ -334,7 +338,7 @@ export function OverviewToolbar({
|
|||||||
size='sm'
|
size='sm'
|
||||||
className='justify-end'
|
className='justify-end'
|
||||||
>
|
>
|
||||||
{OVERVIEW_RANGE_OPTIONS.map((option) => (
|
{rangeOptions.map((option) => (
|
||||||
<ToggleGroupItem
|
<ToggleGroupItem
|
||||||
key={option.value}
|
key={option.value}
|
||||||
value={String(option.value)}
|
value={String(option.value)}
|
||||||
@@ -347,3 +351,5 @@ export function OverviewToolbar({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { RATE_LIMIT_RANGE_OPTIONS };
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import type {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
formatOverviewRangeHint,
|
formatOverviewRangeHint,
|
||||||
type OverviewRangeHours,
|
RATE_LIMIT_RANGE_OPTIONS,
|
||||||
|
type RateLimitRangeHours,
|
||||||
} from '../../access-logs/components/access-log-utils';
|
} from '../../access-logs/components/access-log-utils';
|
||||||
import { OverviewToolbar } from '../../access-logs/components/overview-toolbar';
|
import { OverviewToolbar } from '../../access-logs/components/overview-toolbar';
|
||||||
import { RatePressureChart } from './rate-pressure-chart';
|
import { RatePressureChart } from './rate-pressure-chart';
|
||||||
@@ -50,9 +51,9 @@ export function AnalysisTab({
|
|||||||
data?: AccessLogOverview;
|
data?: AccessLogOverview;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: Error | null;
|
error: Error | null;
|
||||||
hours: OverviewRangeHours;
|
hours: RateLimitRangeHours;
|
||||||
hosts: string[];
|
hosts: string[];
|
||||||
onHoursChange: (hours: OverviewRangeHours) => void;
|
onHoursChange: (hours: RateLimitRangeHours) => void;
|
||||||
onHostsChange: (hosts: string[]) => void;
|
onHostsChange: (hosts: string[]) => void;
|
||||||
onRetry: () => void;
|
onRetry: () => void;
|
||||||
}) {
|
}) {
|
||||||
@@ -65,8 +66,9 @@ export function AnalysisTab({
|
|||||||
<OverviewToolbar
|
<OverviewToolbar
|
||||||
hours={hours}
|
hours={hours}
|
||||||
hosts={hosts}
|
hosts={hosts}
|
||||||
onHoursChange={onHoursChange}
|
onHoursChange={(next) => onHoursChange(next as RateLimitRangeHours)}
|
||||||
onHostsChange={onHostsChange}
|
onHostsChange={onHostsChange}
|
||||||
|
rangeOptions={RATE_LIMIT_RANGE_OPTIONS}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
type OverviewRangeHours,
|
type OverviewRangeHours,
|
||||||
} from '../../access-logs/components/access-log-utils';
|
} from '../../access-logs/components/access-log-utils';
|
||||||
|
|
||||||
const BUCKET_SECONDS = 3600;
|
const DEFAULT_BUCKET_SECONDS = 300;
|
||||||
const RPS_COLOR = '#38bdf8';
|
const RPS_COLOR = '#38bdf8';
|
||||||
const VISITS_COLOR = '#a78bfa';
|
const VISITS_COLOR = '#a78bfa';
|
||||||
|
|
||||||
@@ -58,6 +58,11 @@ type RatePressureChartProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function RatePressureChart({ data, hours }: RatePressureChartProps) {
|
export function RatePressureChart({ data, hours }: RatePressureChartProps) {
|
||||||
|
const bucketSeconds =
|
||||||
|
(data?.bucket_minutes && data.bucket_minutes > 0
|
||||||
|
? data.bucket_minutes
|
||||||
|
: 5) * 60 || DEFAULT_BUCKET_SECONDS;
|
||||||
|
|
||||||
const rangeLabel = useMemo(() => {
|
const rangeLabel = useMemo(() => {
|
||||||
const end = data?.generated_at ? new Date(data.generated_at) : new Date();
|
const end = data?.generated_at ? new Date(data.generated_at) : new Date();
|
||||||
const start = new Date(end.getTime() - hours * 3600 * 1000);
|
const start = new Date(end.getTime() - hours * 3600 * 1000);
|
||||||
@@ -82,12 +87,12 @@ export function RatePressureChart({ data, hours }: RatePressureChartProps) {
|
|||||||
const time = req?.bucket_started_at ?? visit?.bucket_started_at ?? '';
|
const time = req?.bucket_started_at ?? visit?.bucket_started_at ?? '';
|
||||||
rawTimes.push(time);
|
rawTimes.push(time);
|
||||||
labels.push(formatAxisTime(time, hours));
|
labels.push(formatAxisTime(time, hours));
|
||||||
rpsValues.push((req?.value ?? 0) / BUCKET_SECONDS);
|
rpsValues.push((req?.value ?? 0) / bucketSeconds);
|
||||||
visitValues.push(visit?.value ?? 0);
|
visitValues.push(visit?.value ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { labels, rpsValues, visitValues, rawTimes };
|
return { labels, rpsValues, visitValues, rawTimes };
|
||||||
}, [data?.trends.requests, data?.trends.visits, hours]);
|
}, [bucketSeconds, data?.trends.requests, data?.trends.visits, hours]);
|
||||||
|
|
||||||
const option = useMemo<EChartsOption>(() => {
|
const option = useMemo<EChartsOption>(() => {
|
||||||
const rpsMax = calculateNiceAxisMax(chartModel.rpsValues);
|
const rpsMax = calculateNiceAxisMax(chartModel.rpsValues);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { LoadingStateWithBorder } from '@/components/layout/loading';
|
|||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { AccessLogService } from '@/lib/services/openflare';
|
import { AccessLogService } from '@/lib/services/openflare';
|
||||||
|
|
||||||
import type { OverviewRangeHours } from '../access-logs/components/access-log-utils';
|
import type { RateLimitRangeHours } from '../access-logs/components/access-log-utils';
|
||||||
import { AnalysisTab } from './components/analysis-tab';
|
import { AnalysisTab } from './components/analysis-tab';
|
||||||
import { ConfigTab } from './components/config-tab';
|
import { ConfigTab } from './components/config-tab';
|
||||||
|
|
||||||
@@ -27,15 +27,16 @@ function RateLimitsPageContent() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const tab = resolveTab(searchParams.get('tab'));
|
const tab = resolveTab(searchParams.get('tab'));
|
||||||
const [hours, setHours] = useState<OverviewRangeHours>(24);
|
const [hours, setHours] = useState<RateLimitRangeHours>(24);
|
||||||
const [hosts, setHosts] = useState<string[]>([]);
|
const [hosts, setHosts] = useState<string[]>([]);
|
||||||
|
|
||||||
const overviewQuery = useQuery({
|
const overviewQuery = useQuery({
|
||||||
queryKey: ['openflare', 'rate-limits', 'overview', hours, hosts],
|
queryKey: ['openflare', 'rate-limits', 'overview', hours, hosts, 5],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
AccessLogService.getOverview({
|
AccessLogService.getOverview({
|
||||||
hours,
|
hours,
|
||||||
hosts: hosts.length > 0 ? hosts : undefined,
|
hosts: hosts.length > 0 ? hosts : undefined,
|
||||||
|
bucket_minutes: 5,
|
||||||
}),
|
}),
|
||||||
enabled: !!user?.is_admin && tab === 'analysis',
|
enabled: !!user?.is_admin && tab === 'analysis',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export function RankChart({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className='w-14 text-right tabular-nums text-foreground/80 text-[13px] font-medium'>
|
<span className='min-w-14 max-w-28 text-right tabular-nums text-foreground/80 text-[13px] font-medium'>
|
||||||
{valueFormatter(item.value)}
|
{valueFormatter(item.value)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -505,6 +505,7 @@ export interface AccessLogOverviewFilters {
|
|||||||
host?: string;
|
host?: string;
|
||||||
hosts?: string[];
|
hosts?: string[];
|
||||||
hours?: number;
|
hours?: number;
|
||||||
|
bucket_minutes?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AccessLogOverviewMetricPoint {
|
export interface AccessLogOverviewMetricPoint {
|
||||||
@@ -515,6 +516,7 @@ export interface AccessLogOverviewMetricPoint {
|
|||||||
export interface AccessLogOverview {
|
export interface AccessLogOverview {
|
||||||
generated_at: string;
|
generated_at: string;
|
||||||
hours: number;
|
hours: number;
|
||||||
|
bucket_minutes?: number;
|
||||||
summary: {
|
summary: {
|
||||||
total_requests: number;
|
total_requests: number;
|
||||||
total_visits: number;
|
total_visits: number;
|
||||||
|
|||||||
@@ -232,10 +232,11 @@ type AccessLogIPAnalysisView struct {
|
|||||||
|
|
||||||
// AccessLogOverviewQuery filters access log overview queries.
|
// AccessLogOverviewQuery filters access log overview queries.
|
||||||
type AccessLogOverviewQuery struct {
|
type AccessLogOverviewQuery struct {
|
||||||
NodeID string `json:"node_id"`
|
NodeID string `json:"node_id"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
Hosts []string `json:"hosts"`
|
Hosts []string `json:"hosts"`
|
||||||
Hours int `json:"hours"`
|
Hours int `json:"hours"`
|
||||||
|
BucketMinutes int `json:"bucket_minutes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccessLogOverviewMetricPoint is a single overview trend bucket.
|
// AccessLogOverviewMetricPoint is a single overview trend bucket.
|
||||||
@@ -262,6 +263,7 @@ type AccessLogOverviewTrends struct {
|
|||||||
type AccessLogOverview struct {
|
type AccessLogOverview struct {
|
||||||
GeneratedAt time.Time `json:"generated_at"`
|
GeneratedAt time.Time `json:"generated_at"`
|
||||||
Hours int `json:"hours"`
|
Hours int `json:"hours"`
|
||||||
|
BucketMinutes int `json:"bucket_minutes"`
|
||||||
Summary AccessLogOverviewSummary `json:"summary"`
|
Summary AccessLogOverviewSummary `json:"summary"`
|
||||||
Trends AccessLogOverviewTrends `json:"trends"`
|
Trends AccessLogOverviewTrends `json:"trends"`
|
||||||
TopPaths []DistributionItem `json:"top_paths"`
|
TopPaths []DistributionItem `json:"top_paths"`
|
||||||
@@ -287,10 +289,11 @@ type AccessLogCleanupResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultAccessLogOverviewHours = 24
|
defaultAccessLogOverviewHours = 24
|
||||||
maxAccessLogOverviewHours = 24 * 30
|
maxAccessLogOverviewHours = 24 * 30
|
||||||
accessLogOverviewTopLimit = 10
|
defaultAccessLogOverviewBucketMinutes = 60
|
||||||
accessLogOverviewUASampleLimit = 200
|
accessLogOverviewTopLimit = 10
|
||||||
|
accessLogOverviewUASampleLimit = 200
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetAccessLogOverview returns summary metrics, trends, and top rankings.
|
// GetAccessLogOverview returns summary metrics, trends, and top rankings.
|
||||||
@@ -312,13 +315,14 @@ func GetAccessLogOverview(ctx context.Context, input AccessLogOverviewQuery) (*A
|
|||||||
}
|
}
|
||||||
|
|
||||||
requestPoints, visitPoints, bandwidthPoints := buildAccessLogOverviewTrends(
|
requestPoints, visitPoints, bandwidthPoints := buildAccessLogOverviewTrends(
|
||||||
ctx, now, normalized.Hours, query,
|
ctx, now, normalized.Hours, normalized.BucketMinutes, query,
|
||||||
)
|
)
|
||||||
deviceTypes, topBrowsers, topOSes, topUserAgents := buildAccessLogUADistributions(ctx, query)
|
deviceTypes, topBrowsers, topOSes, topUserAgents := buildAccessLogUADistributions(ctx, query)
|
||||||
|
|
||||||
return &AccessLogOverview{
|
return &AccessLogOverview{
|
||||||
GeneratedAt: now,
|
GeneratedAt: now,
|
||||||
Hours: normalized.Hours,
|
Hours: normalized.Hours,
|
||||||
|
BucketMinutes: normalized.BucketMinutes,
|
||||||
Summary: AccessLogOverviewSummary{
|
Summary: AccessLogOverviewSummary{
|
||||||
TotalRequests: summaryRow.RequestCount,
|
TotalRequests: summaryRow.RequestCount,
|
||||||
TotalVisits: summaryRow.UniqueIPCount,
|
TotalVisits: summaryRow.UniqueIPCount,
|
||||||
@@ -348,6 +352,7 @@ func normalizeAccessLogOverviewQuery(input AccessLogOverviewQuery) AccessLogOver
|
|||||||
if hours > maxAccessLogOverviewHours {
|
if hours > maxAccessLogOverviewHours {
|
||||||
hours = maxAccessLogOverviewHours
|
hours = maxAccessLogOverviewHours
|
||||||
}
|
}
|
||||||
|
bucketMinutes := normalizeAccessLogOverviewBucketMinutes(input.BucketMinutes)
|
||||||
hosts := normalizeAccessLogOverviewHosts(input.Hosts)
|
hosts := normalizeAccessLogOverviewHosts(input.Hosts)
|
||||||
host := strings.TrimSpace(input.Host)
|
host := strings.TrimSpace(input.Host)
|
||||||
if len(hosts) == 0 && host != "" {
|
if len(hosts) == 0 && host != "" {
|
||||||
@@ -359,10 +364,20 @@ func normalizeAccessLogOverviewQuery(input AccessLogOverviewQuery) AccessLogOver
|
|||||||
host = ""
|
host = ""
|
||||||
}
|
}
|
||||||
return AccessLogOverviewQuery{
|
return AccessLogOverviewQuery{
|
||||||
NodeID: strings.TrimSpace(input.NodeID),
|
NodeID: strings.TrimSpace(input.NodeID),
|
||||||
Host: host,
|
Host: host,
|
||||||
Hosts: hosts,
|
Hosts: hosts,
|
||||||
Hours: hours,
|
Hours: hours,
|
||||||
|
BucketMinutes: bucketMinutes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeAccessLogOverviewBucketMinutes(value int) int {
|
||||||
|
switch value {
|
||||||
|
case 5, 60:
|
||||||
|
return value
|
||||||
|
default:
|
||||||
|
return defaultAccessLogOverviewBucketMinutes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,6 +469,7 @@ func buildAccessLogOverviewTrends(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
now time.Time,
|
now time.Time,
|
||||||
hours int,
|
hours int,
|
||||||
|
bucketMinutes int,
|
||||||
query model.OpenFlareAccessLogQuery,
|
query model.OpenFlareAccessLogQuery,
|
||||||
) (
|
) (
|
||||||
requests []AccessLogOverviewMetricPoint,
|
requests []AccessLogOverviewMetricPoint,
|
||||||
@@ -463,12 +479,18 @@ func buildAccessLogOverviewTrends(
|
|||||||
if hours <= 0 {
|
if hours <= 0 {
|
||||||
hours = defaultAccessLogOverviewHours
|
hours = defaultAccessLogOverviewHours
|
||||||
}
|
}
|
||||||
start := now.Truncate(time.Hour).Add(-time.Duration(hours-1) * time.Hour)
|
bucketMinutes = normalizeAccessLogOverviewBucketMinutes(bucketMinutes)
|
||||||
requests = make([]AccessLogOverviewMetricPoint, hours)
|
bucketDuration := time.Duration(bucketMinutes) * time.Minute
|
||||||
visits = make([]AccessLogOverviewMetricPoint, hours)
|
bucketCount := hours * 60 / bucketMinutes
|
||||||
bandwidth = make([]AccessLogOverviewMetricPoint, hours)
|
if bucketCount <= 0 {
|
||||||
|
bucketCount = 1
|
||||||
|
}
|
||||||
|
start := now.Truncate(bucketDuration).Add(-time.Duration(bucketCount-1) * bucketDuration)
|
||||||
|
requests = make([]AccessLogOverviewMetricPoint, bucketCount)
|
||||||
|
visits = make([]AccessLogOverviewMetricPoint, bucketCount)
|
||||||
|
bandwidth = make([]AccessLogOverviewMetricPoint, bucketCount)
|
||||||
for index := range requests {
|
for index := range requests {
|
||||||
bucketAt := start.Add(time.Duration(index) * time.Hour)
|
bucketAt := start.Add(time.Duration(index) * bucketDuration)
|
||||||
requests[index].BucketStartedAt = bucketAt
|
requests[index].BucketStartedAt = bucketAt
|
||||||
visits[index].BucketStartedAt = bucketAt
|
visits[index].BucketStartedAt = bucketAt
|
||||||
bandwidth[index].BucketStartedAt = bucketAt
|
bandwidth[index].BucketStartedAt = bucketAt
|
||||||
@@ -480,7 +502,7 @@ func buildAccessLogOverviewTrends(
|
|||||||
Hosts: query.Hosts,
|
Hosts: query.Hosts,
|
||||||
Since: query.Since,
|
Since: query.Since,
|
||||||
Until: query.Until,
|
Until: query.Until,
|
||||||
FoldMinutes: 60,
|
FoldMinutes: bucketMinutes,
|
||||||
SortBy: defaultAccessLogSortBy,
|
SortBy: defaultAccessLogSortBy,
|
||||||
SortOrder: sortOrderAsc,
|
SortOrder: sortOrderAsc,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
// @Param host query string false "请求 Host(单域名)"
|
// @Param host query string false "请求 Host(单域名)"
|
||||||
// @Param hosts query []string false "请求 Host 列表(多域名精确匹配)"
|
// @Param hosts query []string false "请求 Host 列表(多域名精确匹配)"
|
||||||
// @Param hours query int false "统计时间范围(小时)"
|
// @Param hours query int false "统计时间范围(小时)"
|
||||||
|
// @Param bucket_minutes query int false "趋势桶分钟数(5 或 60,默认 60)"
|
||||||
// @Success 200 {object} response.Any{data=observability.AccessLogOverview} "访问日志概览"
|
// @Success 200 {object} response.Any{data=observability.AccessLogOverview} "访问日志概览"
|
||||||
// @Failure 400 {object} response.Any "参数错误"
|
// @Failure 400 {object} response.Any "参数错误"
|
||||||
// @Failure 401 {object} response.Any "未登录"
|
// @Failure 401 {object} response.Any "未登录"
|
||||||
@@ -30,10 +31,11 @@ import (
|
|||||||
// @Router /api/v1/d/access-logs/overview [get]
|
// @Router /api/v1/d/access-logs/overview [get]
|
||||||
func GetAccessLogOverviewHandler(c *gin.Context) {
|
func GetAccessLogOverviewHandler(c *gin.Context) {
|
||||||
result, err := GetAccessLogOverview(c.Request.Context(), AccessLogOverviewQuery{
|
result, err := GetAccessLogOverview(c.Request.Context(), AccessLogOverviewQuery{
|
||||||
NodeID: c.Query("node_id"),
|
NodeID: c.Query("node_id"),
|
||||||
Host: c.Query("host"),
|
Host: c.Query("host"),
|
||||||
Hosts: readQueryStringArray(c, "hosts"),
|
Hosts: readQueryStringArray(c, "hosts"),
|
||||||
Hours: readQueryInt(c, "hours"),
|
Hours: readQueryInt(c, "hours"),
|
||||||
|
BucketMinutes: readQueryInt(c, "bucket_minutes"),
|
||||||
})
|
})
|
||||||
if apiutil.AbortBadRequestOnError(c, err) {
|
if apiutil.AbortBadRequestOnError(c, err) {
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user