migrate frontend

This commit is contained in:
ryan
2026-06-18 17:26:57 +08:00
parent d7fe4ef8be
commit a9fd0bd128
53 changed files with 4765 additions and 19 deletions
@@ -0,0 +1,35 @@
import type {LucideIcon} from 'lucide-react';
import {
ClipboardList,
FileText,
Gauge,
GitBranch,
Globe,
LayoutDashboard,
MapPin,
Route,
ScrollText,
Server,
Shield,
} from 'lucide-react';
export interface OpenFlareNavItem {
title: string;
url: string;
icon: LucideIcon;
}
/** OpenFlare 业务控制台侧栏导航 */
export const openflareNavItems: OpenFlareNavItem[] = [
{title: '总览', url: '/openflare', icon: LayoutDashboard},
{title: '节点', url: '/openflare/nodes', icon: Server},
{title: '规则', url: '/openflare/proxy-routes', icon: Route},
{title: 'Pages', url: '/openflare/pages', icon: FileText},
{title: '网站', url: '/openflare/websites', icon: Globe},
{title: 'WAF', url: '/openflare/waf', icon: Shield},
{title: '源站', url: '/openflare/origins', icon: MapPin},
{title: '发布', url: '/openflare/config-versions', icon: GitBranch},
{title: '访问日志', url: '/openflare/access-logs', icon: ScrollText},
{title: '应用日志', url: '/openflare/apply-logs', icon: ClipboardList},
{title: '性能', url: '/openflare/performance', icon: Gauge},
];
+28 -1
View File
@@ -26,6 +26,7 @@ import {DbManageService} from './db-manage';
import {PushService} from './push';
import {AdminUploadService, UploadService} from './upload';
import {UserService} from './user';
import {ApplyLogService, ConfigVersionService, NodeService, ProxyRouteService,} from './openflare';
const services = {
auth: AuthService,
@@ -43,6 +44,10 @@ const services = {
adminUpload: AdminUploadService,
dbManage: DbManageService,
push: PushService,
openflareNode: NodeService,
openflareProxyRoute: ProxyRouteService,
openflareConfigVersion: ConfigVersionService,
openflareApplyLog: ApplyLogService,
} as const;
export default services;
@@ -142,4 +147,26 @@ export { DbManageService } from './db-manage';
export type { DBOverview, TableDataResponse, ExecuteSQLResponse } from './db-manage';
export { PushService } from './push';
export type { PushEvent, PushHistory, PushChannelConfig, ListPushHistoriesRequest, ListPushHistoriesResponse, UpdatePushEventRequest, TestPushRequest } from './push';
export type { PushEvent, PushHistory, PushChannelConfig, ListPushHistoriesRequest, ListPushHistoriesResponse, UpdatePushEventRequest, TestPushRequest } from './push';
// ==================== OpenFlare 业务服务导出 ====================
export {
LegacyOpenFlareBaseService,
NodeService,
ProxyRouteService,
ConfigVersionService,
ApplyLogService,
} from './openflare';
export type {
LegacyApiResponse,
NodeItem,
ProxyRouteItem,
ProxyRouteConfigSection,
ConfigVersionSummary,
ConfigVersionDetail,
ConfigDiffResult,
ConfigPreviewResult,
ApplyLogList,
} from './openflare';
@@ -0,0 +1,27 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {ApplyLogCleanupPayload, ApplyLogCleanupResult, ApplyLogList, ApplyLogListQuery,} from './types';
export class ApplyLogService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/apply-logs';
static list(query: ApplyLogListQuery = {}): Promise<ApplyLogList> {
const params: Record<string, unknown> = {};
const normalizedNodeId = query.node_id?.trim();
if (normalizedNodeId) {
params.node_id = normalizedNodeId;
}
if (query.pageNo) {
params.pageNo = query.pageNo;
}
if (query.pageSize) {
params.pageSize = query.pageSize;
}
return this.legacyGet<ApplyLogList>('/', params);
}
static cleanup(payload: ApplyLogCleanupPayload): Promise<ApplyLogCleanupResult> {
return this.legacyPost<ApplyLogCleanupResult>('/cleanup', payload);
}
}
@@ -0,0 +1,55 @@
import type {InternalAxiosRequestConfig} from 'axios';
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {
ConfigDiffResult,
ConfigPreviewResult,
ConfigVersionCleanupPayload,
ConfigVersionCleanupResult,
ConfigVersionDetail,
ConfigVersionSummary,
} from './types';
export class ConfigVersionService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/config-versions';
static list(): Promise<ConfigVersionSummary[]> {
return this.legacyGet<ConfigVersionSummary[]>('/');
}
static getActive(): Promise<ConfigVersionDetail> {
return this.legacyGet<ConfigVersionDetail>('/active');
}
static preview(): Promise<ConfigPreviewResult> {
return this.legacyGet<ConfigPreviewResult>('/preview');
}
static diff(): Promise<ConfigDiffResult> {
return this.legacyGet<ConfigDiffResult>('/diff');
}
static getById(id: number): Promise<ConfigVersionDetail> {
return this.legacyGet<ConfigVersionDetail>(`/${id}`);
}
static publish(force?: boolean): Promise<ConfigVersionDetail> {
return this.legacyPost<ConfigVersionDetail>(
'/publish',
undefined,
force
? ({ params: { force: true } } as InternalAxiosRequestConfig)
: undefined,
);
}
static activate(id: number): Promise<ConfigVersionDetail> {
return this.legacyPost<ConfigVersionDetail>(`/${id}/activate`);
}
static cleanup(
payload: ConfigVersionCleanupPayload,
): Promise<ConfigVersionCleanupResult> {
return this.legacyPost<ConfigVersionCleanupResult>('/cleanup', payload);
}
}
@@ -0,0 +1,51 @@
export { LegacyOpenFlareBaseService } from './legacy-base.service';
export type { LegacyApiResponse } from './legacy-base.service';
export { NodeService } from './node.service';
export { ProxyRouteService } from './proxy-route.service';
export { ConfigVersionService } from './config-version.service';
export { ApplyLogService } from './apply-log.service';
export type {
ApplyLogCleanupPayload,
ApplyLogCleanupResult,
ApplyLogItem,
ApplyLogList,
ApplyLogListQuery,
ApplyResult,
ConfigDiffResult,
ConfigOptionDiffItem,
ConfigPreviewResult,
ConfigVersionCleanupPayload,
ConfigVersionCleanupResult,
ConfigVersionDetail,
ConfigVersionSummary,
NodeAgentReleaseInfo,
NodeAgentUpdatePayload,
NodeBootstrapToken,
NodeItem,
NodeMutationPayload,
NodeObservability,
NodeStatus,
NodeType,
OpenrestyStatus,
ProxyRouteConfigSection,
ProxyRouteCustomHeader,
ProxyRouteItem,
ProxyRouteMutationPayload,
ProxyRoutePoWConfig,
ReleaseChannel,
SupportFile,
} from './types';
import {ApplyLogService} from './apply-log.service';
import {ConfigVersionService} from './config-version.service';
import {NodeService} from './node.service';
import {ProxyRouteService} from './proxy-route.service';
export const openflareServices = {
node: NodeService,
proxyRoute: ProxyRouteService,
configVersion: ConfigVersionService,
applyLog: ApplyLogService,
} as const;
@@ -0,0 +1,53 @@
import apiClient from '@/lib/services/core/api-client';
import {ApiErrorBase} from '@/lib/services/core/errors';
import type {InternalAxiosRequestConfig} from 'axios';
export interface LegacyApiResponse<T> {
success: boolean;
message: string;
data: T;
}
/**
* OpenFlare legacy 业务 API 基类
* 解析 { success, message, data } 响应格式
*/
export class LegacyOpenFlareBaseService {
protected static readonly basePath: string = '';
protected static getFullPath(path: string): string {
return `${this.basePath}${path}`;
}
protected static parseLegacyResponse<T>(body: LegacyApiResponse<T>): T {
if (!body.success) {
throw new ApiErrorBase(body.message || '请求失败');
}
return body.data;
}
protected static async legacyGet<T>(
path: string,
params?: Record<string, unknown>,
config?: InternalAxiosRequestConfig,
): Promise<T> {
const response = await apiClient.get<LegacyApiResponse<T>>(
this.getFullPath(path),
{ ...config, params } as InternalAxiosRequestConfig,
);
return this.parseLegacyResponse(response.data);
}
protected static async legacyPost<T>(
path: string,
data?: unknown,
config?: InternalAxiosRequestConfig,
): Promise<T> {
const response = await apiClient.post<LegacyApiResponse<T>>(
this.getFullPath(path),
data,
config,
);
return this.parseLegacyResponse(response.data);
}
}
@@ -0,0 +1,70 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {
NodeAgentReleaseInfo,
NodeAgentUpdatePayload,
NodeBootstrapToken,
NodeItem,
NodeMutationPayload,
NodeObservability,
ReleaseChannel,
} from './types';
export class NodeService extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/nodes';
static async listNodes(): Promise<NodeItem[]> {
return this.legacyGet<NodeItem[]>('/');
}
static async createNode(payload: NodeMutationPayload): Promise<NodeItem> {
return this.legacyPost<NodeItem>('/', payload);
}
static async updateNode(id: number, payload: NodeMutationPayload): Promise<NodeItem> {
return this.legacyPost<NodeItem>(`/${id}/update`, payload);
}
static async deleteNode(id: number): Promise<void> {
return this.legacyPost<void>(`/${id}/delete`);
}
static async getBootstrapToken(): Promise<NodeBootstrapToken> {
return this.legacyGet<NodeBootstrapToken>('/bootstrap-token');
}
static async rotateBootstrapToken(): Promise<NodeBootstrapToken> {
return this.legacyPost<NodeBootstrapToken>('/bootstrap-token/rotate');
}
static async requestAgentUpdate(
id: number,
payload?: NodeAgentUpdatePayload,
): Promise<NodeItem> {
return this.legacyPost<NodeItem>(`/${id}/agent-update`, payload ?? {});
}
static async requestForceSync(id: number): Promise<NodeItem> {
return this.legacyPost<NodeItem>(`/${id}/force-sync`);
}
static async requestOpenrestyRestart(id: number): Promise<NodeItem> {
return this.legacyPost<NodeItem>(`/${id}/openresty-restart`);
}
static async getAgentRelease(
id: number,
channel: ReleaseChannel = 'stable',
): Promise<NodeAgentReleaseInfo> {
return this.legacyGet<NodeAgentReleaseInfo>(`/${id}/agent-release`, { channel });
}
static async getObservability(
id: number,
options?: { hours?: number; limit?: number },
): Promise<NodeObservability> {
return this.legacyGet<NodeObservability>(`/${id}/observability`, {
hours: options?.hours,
limit: options?.limit,
});
}
}
@@ -0,0 +1,29 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {ProxyRouteItem, ProxyRouteMutationPayload} from './types';
export class ProxyRouteService extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/proxy-routes';
static async list(): Promise<ProxyRouteItem[]> {
return this.legacyGet<ProxyRouteItem[]>('/');
}
static async getById(id: number): Promise<ProxyRouteItem> {
return this.legacyGet<ProxyRouteItem>(`/${id}`);
}
static async create(payload: ProxyRouteMutationPayload): Promise<ProxyRouteItem> {
return this.legacyPost<ProxyRouteItem>('/', payload);
}
static async update(
id: number,
payload: ProxyRouteMutationPayload,
): Promise<ProxyRouteItem> {
return this.legacyPost<ProxyRouteItem>(`/${id}/update`, payload);
}
static async delete(id: number): Promise<void> {
return this.legacyPost<void>(`/${id}/delete`);
}
}
@@ -0,0 +1,367 @@
/**
* OpenFlare 遗留业务 API 响应信封
* 阶段一 `/api/*` 端点使用此格式,与 Wavelet `/api/v1/*` 的 `{error_msg,data}` 不同
*/
export interface LegacyApiResponse<T = unknown> {
success: boolean;
message: string;
data: T;
}
export type ReleaseChannel = 'stable' | 'preview';
export type NodeType = 'edge_node' | 'tunnel_relay' | 'tunnel_client';
export type NodeStatus = 'online' | 'offline' | 'pending';
export type OpenrestyStatus = 'healthy' | 'unhealthy' | 'unknown';
export type ApplyResult = 'success' | 'warning' | 'failed' | '';
export interface NodeItem {
id: number;
node_id: string;
node_type: NodeType;
name: string;
ip: string;
ip_manual_override: boolean;
relay_bind_port: number;
relay_vhost_http_port: number;
relay_client_access_addr: string;
relay_agent_access_addr: string;
relay_client_proxy_url: string;
relay_auth_token: string;
relay_status: string;
relay_web_server_enabled: boolean;
relay_frps_connections: number;
relay_frps_proxy_count: number;
geo_name: string;
geo_latitude?: number | null;
geo_longitude?: number | null;
geo_manual_override: boolean;
access_token: string;
auto_update_enabled: boolean;
update_requested: boolean;
update_channel: ReleaseChannel;
update_tag: string;
restart_openresty_requested: boolean;
version: string;
ext_version: string;
openresty_status: OpenrestyStatus;
openresty_message: string;
status: NodeStatus;
current_version: string;
last_seen_at: string;
last_error: string;
latest_apply_result: ApplyResult;
latest_apply_message: string;
latest_apply_checksum: string;
latest_main_config_checksum: string;
latest_route_config_checksum: string;
latest_support_file_count: number;
latest_apply_at?: string | null;
created_at: string;
updated_at: string;
}
export interface NodeBootstrapToken {
discovery_token: string;
}
export interface NodeMutationPayload {
node_type: NodeType;
name: string;
ip: string;
ip_manual_override: boolean;
relay_bind_port?: number;
relay_vhost_http_port?: number;
relay_client_access_addr?: string;
relay_agent_access_addr?: string;
relay_client_proxy_url?: string;
relay_web_server_enabled?: boolean;
auto_update_enabled: boolean;
geo_name: string;
geo_latitude?: number | null;
geo_longitude?: number | null;
geo_manual_override: boolean;
}
export interface NodeAgentReleaseInfo {
tag_name: string;
body: string;
html_url: string;
published_at: string;
current_version: string;
has_update: boolean;
channel: ReleaseChannel;
prerelease: boolean;
update_requested: boolean;
requested_channel: ReleaseChannel;
requested_tag: string;
}
export interface NodeAgentUpdatePayload {
channel?: ReleaseChannel;
tag_name?: string;
}
export interface NodeSystemProfile {
hostname: string;
os_name: string;
os_version: string;
kernel_version: string;
architecture: string;
cpu_model: string;
cpu_cores: number;
total_memory_bytes: number;
total_disk_bytes: number;
uptime_seconds: number;
reported_at: string;
}
export interface NodeMetricSnapshot {
captured_at: string;
cpu_usage_percent: number;
memory_used_bytes: number;
memory_total_bytes: number;
storage_used_bytes: number;
storage_total_bytes: number;
disk_read_bytes: number;
disk_write_bytes: number;
network_rx_bytes: number;
network_tx_bytes: number;
openresty_rx_bytes: number;
openresty_tx_bytes: number;
openresty_connections: number;
}
export interface NodeHealthEvent {
event_type: string;
severity: string;
status: string;
message: string;
metadata_json?: string;
first_triggered_at: string;
last_triggered_at: string;
reported_at: string;
resolved_at?: string | null;
}
export interface NodeObservability {
node_id: string;
profile: NodeSystemProfile | null;
metric_snapshots: NodeMetricSnapshot[];
health_events: NodeHealthEvent[];
}
export type ProxyRouteConfigSection =
| 'domains'
| 'limits'
| 'proxy'
| 'cache'
| 'waf'
| 'auth';
export interface ProxyRouteCustomHeader {
key: string;
value: string;
}
export interface ProxyRoutePoWListConfig {
ips: string[];
ip_cidrs: string[];
paths: string[];
path_regexes: string[];
user_agents: string[];
}
export interface ProxyRoutePoWConfig {
difficulty: number;
algorithm: 'fast' | 'slow';
session_ttl: number;
challenge_ttl: number;
whitelist: ProxyRoutePoWListConfig;
blacklist: ProxyRoutePoWListConfig;
}
export interface ProxyRouteItem {
id: number;
site_name: string;
domain: string;
domains: string[];
primary_domain: string;
domain_count: number;
origin_id: number | null;
origin_url: string;
origin_host: string;
upstreams: string;
upstream_list: string[];
enabled: boolean;
enable_https: boolean;
cert_id: number | null;
cert_ids: number[];
domain_cert_ids: number[];
redirect_http: boolean;
limit_conn_per_server: number;
limit_conn_per_ip: number;
limit_rate: string;
cache_enabled: boolean;
cache_policy: string;
cache_rules: string;
cache_rule_list: string[];
custom_headers: string;
custom_header_list: ProxyRouteCustomHeader[];
pow_enabled: boolean;
pow_config: ProxyRoutePoWConfig;
basic_auth_enabled: boolean;
basic_auth_username: string;
basic_auth_password: string;
remark: string;
upstream_type: 'direct' | 'tunnel' | 'pages';
tunnel_node_id?: number | null;
tunnel_id?: number | null;
tunnel_target_addr?: string;
tunnel_target_protocol?: string;
pages_project_id?: number | null;
created_at: string;
updated_at: string;
}
export interface ProxyRouteMutationPayload {
site_name?: string;
domain: string;
domains?: string[];
origin_id: number | null;
origin_url: string;
origin_scheme: 'http' | 'https';
origin_address: string;
origin_port: string;
origin_uri: string;
origin_host: string;
upstreams: string[];
enabled: boolean;
enable_https: boolean;
cert_id: number | null;
cert_ids?: number[];
domain_cert_ids?: number[];
redirect_http: boolean;
limit_conn_per_server?: number;
limit_conn_per_ip?: number;
limit_rate?: string;
cache_enabled: boolean;
cache_policy: string;
cache_rules: string[];
custom_headers: ProxyRouteCustomHeader[];
pow_enabled: boolean;
pow_config: string;
basic_auth_enabled: boolean;
basic_auth_username?: string;
basic_auth_password?: string;
remark: string;
upstream_type?: 'direct' | 'tunnel' | 'pages';
tunnel_node_id?: number | null;
tunnel_id?: number | null;
tunnel_target_addr?: string;
tunnel_target_protocol?: string;
pages_project_id?: number | null;
}
export interface ConfigVersionSummary {
id: number;
version: string;
checksum: string;
is_active: boolean;
created_by: string;
created_at: string;
}
export interface ConfigVersionDetail extends ConfigVersionSummary {
snapshot_json: string;
main_config: string;
rendered_config: string;
support_files_json: string;
}
export interface SupportFile {
path: string;
content: string;
}
export interface ConfigPreviewResult {
snapshot_json: string;
main_config: string;
route_config: string;
rendered_config: string;
support_files: SupportFile[];
checksum: string;
route_count: number;
website_count: number;
}
export interface ConfigOptionDiffItem {
key: string;
previous_value: string;
current_value: string;
}
export interface ConfigDiffResult {
active_version?: string;
added_sites: string[];
removed_sites: string[];
modified_sites: string[];
added_domains: string[];
removed_domains: string[];
modified_domains: string[];
main_config_changed: boolean;
waf_config_changed: boolean;
changed_option_keys: string[];
changed_option_details: ConfigOptionDiffItem[];
current_website_count: number;
active_website_count: number;
}
export interface ConfigVersionCleanupPayload {
keep_count: number;
}
export interface ConfigVersionCleanupResult {
deleted_count: number;
}
export interface ApplyLogItem {
id: number;
node_id: string;
version: string;
result: 'success' | 'failed' | string;
message: string;
checksum: string;
main_config_checksum: string;
route_config_checksum: string;
support_file_count: number;
created_at: string;
}
export interface ApplyLogList {
rows: ApplyLogItem[];
current: number;
total: number;
totalPage: number;
}
export interface ApplyLogListQuery {
node_id?: string;
pageNo?: number;
pageSize?: number;
}
export interface ApplyLogCleanupPayload {
delete_all?: boolean;
retention_days?: number;
}
export interface ApplyLogCleanupResult {
delete_all: boolean;
retention_days: number;
deleted_count: number;
cutoff?: string;
}