migrate frontend

This commit is contained in:
ryan
2026-06-18 17:43:11 +08:00
parent a9fd0bd128
commit 31b114b0cc
104 changed files with 16735 additions and 874 deletions
@@ -17,19 +17,37 @@ export interface OpenFlareNavItem {
title: string;
url: string;
icon: LucideIcon;
/** 子页面在侧栏中仍高亮父级菜单项 */
childUrls?: string[];
}
/** OpenFlare 业务控制台侧栏导航 */
/** 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/nodes', icon: Server, childUrls: ['/openflare/nodes/detail']},
{title: '规则', url: '/openflare/proxy-routes', icon: Route, childUrls: ['/openflare/proxy-routes/detail']},
{title: 'Pages', url: '/openflare/pages', icon: FileText, childUrls: ['/openflare/pages/detail']},
{
title: '网站',
url: '/openflare/websites',
icon: Globe,
childUrls: [
'/openflare/websites/detail',
'/openflare/websites/certificates',
'/openflare/websites/dns-accounts',
],
},
{title: 'WAF', url: '/openflare/waf', icon: Shield, childUrls: ['/openflare/waf/ip-groups']},
{title: '源站', url: '/openflare/origins', icon: MapPin, childUrls: ['/openflare/origins/detail']},
{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},
];
];
/** 网站模块页内二级导航 */
export const openflareWebsiteSubNav = [
{title: '网站列表', url: '/openflare/websites'},
{title: '证书', url: '/openflare/websites/certificates'},
{title: 'DNS 账号', url: '/openflare/websites/dns-accounts'},
] as const;
+59 -1
View File
@@ -26,7 +26,24 @@ 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';
import {
AccessLogService,
ApplyLogService,
ConfigVersionService,
DashboardService,
DnsAccountService,
NodeService,
OptionService,
OriginService,
PagesService,
ProxyRouteService,
StatusService,
TlsCertificateService,
UpdateService,
UptimeKumaService,
WafService,
WebsiteService,
} from './openflare';
const services = {
auth: AuthService,
@@ -48,6 +65,18 @@ const services = {
openflareProxyRoute: ProxyRouteService,
openflareConfigVersion: ConfigVersionService,
openflareApplyLog: ApplyLogService,
openflareDashboard: DashboardService,
openflareWaf: WafService,
openflareWebsite: WebsiteService,
openflareTls: TlsCertificateService,
openflareDns: DnsAccountService,
openflarePages: PagesService,
openflareOrigin: OriginService,
openflareAccessLog: AccessLogService,
openflareOption: OptionService,
openflareUptimeKuma: UptimeKumaService,
openflareUpdate: UpdateService,
openflareStatus: StatusService,
} as const;
export default services;
@@ -157,6 +186,18 @@ export {
ProxyRouteService,
ConfigVersionService,
ApplyLogService,
DashboardService,
WafService,
WebsiteService,
TlsCertificateService,
DnsAccountService,
PagesService,
OriginService,
AccessLogService,
OptionService,
StatusService,
UpdateService,
UptimeKumaService,
} from './openflare';
export type {
@@ -169,4 +210,21 @@ export type {
ConfigDiffResult,
ConfigPreviewResult,
ApplyLogList,
DashboardOverview,
WAFIPGroup,
WAFRuleGroup,
WAFSiteRuleGroups,
ManagedDomainItem,
TlsCertificateItem,
DnsAccountItem,
PagesProject,
PagesDeployment,
OriginItem,
OriginDetail,
AccessLogList,
OptionItem,
GeoIPLookupResult,
DatabaseCleanupResult,
LatestReleaseInfo,
OpenFlarePublicStatus,
} from './openflare';
@@ -0,0 +1,76 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {
AccessLogCleanupPayload,
AccessLogCleanupResult,
AccessLogFilters,
AccessLogIPSummaryFilters,
AccessLogIPSummaryList,
AccessLogIPTrend,
AccessLogIPTrendFilters,
AccessLogList,
FoldedAccessLogFilters,
FoldedAccessLogIPFilters,
FoldedAccessLogIPList,
FoldedAccessLogList,
} from './types';
function buildSearchParams(filters: object): Record<string, unknown> {
const params: Record<string, unknown> = {};
Object.entries(filters as Record<string, string | number | undefined>).forEach(
([key, value]) => {
if (value === undefined || value === null || value === '') return;
params[key] = value;
},
);
return params;
}
export class AccessLogService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/access-logs';
static list(filters: AccessLogFilters = {}): Promise<AccessLogList> {
return this.legacyGet<AccessLogList>('/', buildSearchParams(filters));
}
static listFolds(
filters: FoldedAccessLogFilters,
): Promise<FoldedAccessLogList> {
return this.legacyGet<FoldedAccessLogList>(
'/folds',
buildSearchParams(filters),
);
}
static listFoldIPs(
filters: FoldedAccessLogIPFilters,
): Promise<FoldedAccessLogIPList> {
return this.legacyGet<FoldedAccessLogIPList>(
'/folds/ip-summary',
buildSearchParams(filters),
);
}
static listIPSummaries(
filters: AccessLogIPSummaryFilters = {},
): Promise<AccessLogIPSummaryList> {
return this.legacyGet<AccessLogIPSummaryList>(
'/ip-summary',
buildSearchParams(filters),
);
}
static getIPTrend(
filters: AccessLogIPTrendFilters,
): Promise<AccessLogIPTrend> {
return this.legacyGet<AccessLogIPTrend>(
'/ip-summary/trend',
buildSearchParams(filters),
);
}
static cleanup(
payload: AccessLogCleanupPayload,
): Promise<AccessLogCleanupResult> {
return this.legacyPost<AccessLogCleanupResult>('/cleanup', payload);
}
}
@@ -0,0 +1,242 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {
CompactCapacityTrendPoint,
CompactDashboardNodeHealth,
CompactDiskIOTrendPoint,
CompactDistributionItem,
CompactNetworkTrendPoint,
CompactTrafficTrendPoint,
DashboardCapacity,
DashboardNodeHealth,
DashboardOverview,
DashboardOverviewCompact,
DashboardSummary,
DashboardTraffic,
DistributionItem,
} from './types';
function arrayOrEmpty<T>(value: T[] | null | undefined) {
return Array.isArray(value) ? value : [];
}
function isCompactDistributionItem(
value: DistributionItem | CompactDistributionItem,
): value is CompactDistributionItem {
return Array.isArray(value);
}
function isCompactTrafficTrendPoint(
value:
| DashboardOverview['trends']['traffic_24h'][number]
| CompactTrafficTrendPoint,
): value is CompactTrafficTrendPoint {
return Array.isArray(value);
}
function isCompactCapacityTrendPoint(
value:
| DashboardOverview['trends']['capacity_24h'][number]
| CompactCapacityTrendPoint,
): value is CompactCapacityTrendPoint {
return Array.isArray(value);
}
function isCompactNetworkTrendPoint(
value:
| DashboardOverview['trends']['network_24h'][number]
| CompactNetworkTrendPoint,
): value is CompactNetworkTrendPoint {
return Array.isArray(value);
}
function isCompactDiskIOTrendPoint(
value:
| DashboardOverview['trends']['disk_io_24h'][number]
| CompactDiskIOTrendPoint,
): value is CompactDiskIOTrendPoint {
return Array.isArray(value);
}
function isCompactDashboardNode(
value: DashboardNodeHealth | CompactDashboardNodeHealth,
): value is CompactDashboardNodeHealth {
return Array.isArray(value);
}
function normalizeDistributionItems(
items: Array<DistributionItem | CompactDistributionItem> | null | undefined,
): DistributionItem[] {
return arrayOrEmpty(items).map((item) =>
isCompactDistributionItem(item)
? { key: String(item[0] ?? ''), value: Number(item[1] ?? 0) }
: item,
);
}
function normalizeTrafficTrendPoints(
items:
| Array<
| DashboardOverview['trends']['traffic_24h'][number]
| CompactTrafficTrendPoint
>
| null
| undefined,
) {
return arrayOrEmpty(items).map((item) =>
isCompactTrafficTrendPoint(item)
? {
bucket_started_at: String(item[0] ?? ''),
request_count: Number(item[1] ?? 0),
error_count: Number(item[2] ?? 0),
unique_visitor_count: Number(item[3] ?? 0),
}
: item,
);
}
function normalizeCapacityTrendPoints(
items:
| Array<
| DashboardOverview['trends']['capacity_24h'][number]
| CompactCapacityTrendPoint
>
| null
| undefined,
) {
return arrayOrEmpty(items).map((item) =>
isCompactCapacityTrendPoint(item)
? {
bucket_started_at: String(item[0] ?? ''),
average_cpu_usage_percent: Number(item[1] ?? 0),
average_memory_usage_percent: Number(item[2] ?? 0),
reported_nodes: Number(item[3] ?? 0),
}
: item,
);
}
function normalizeNetworkTrendPoints(
items:
| Array<
| DashboardOverview['trends']['network_24h'][number]
| CompactNetworkTrendPoint
>
| null
| undefined,
) {
return arrayOrEmpty(items).map((item) =>
isCompactNetworkTrendPoint(item)
? {
bucket_started_at: String(item[0] ?? ''),
network_rx_bytes: Number(item[1] ?? 0),
network_tx_bytes: Number(item[2] ?? 0),
openresty_rx_bytes: Number(item[3] ?? 0),
openresty_tx_bytes: Number(item[4] ?? 0),
reported_nodes: Number(item[5] ?? 0),
}
: item,
);
}
function normalizeDiskIOTrendPoints(
items:
| Array<
| DashboardOverview['trends']['disk_io_24h'][number]
| CompactDiskIOTrendPoint
>
| null
| undefined,
) {
return arrayOrEmpty(items).map((item) =>
isCompactDiskIOTrendPoint(item)
? {
bucket_started_at: String(item[0] ?? ''),
disk_read_bytes: Number(item[1] ?? 0),
disk_write_bytes: Number(item[2] ?? 0),
reported_nodes: Number(item[3] ?? 0),
}
: item,
);
}
function normalizeDashboardNodes(
items:
| Array<DashboardNodeHealth | CompactDashboardNodeHealth>
| null
| undefined,
): DashboardNodeHealth[] {
return arrayOrEmpty(items).map((item) =>
isCompactDashboardNode(item)
? {
id: Number(item[0] ?? 0),
node_id: String(item[1] ?? ''),
name: String(item[2] ?? ''),
geo_name: String(item[3] ?? ''),
geo_latitude:
item[4] === null || item[4] === undefined ? null : Number(item[4]),
geo_longitude:
item[5] === null || item[5] === undefined ? null : Number(item[5]),
status: (item[6] ?? 'pending') as DashboardNodeHealth['status'],
openresty_status: (item[7] ??
'unknown') as DashboardNodeHealth['openresty_status'],
current_version: String(item[8] ?? ''),
last_seen_at: String(item[9] ?? ''),
active_event_count: Number(item[10] ?? 0),
cpu_usage_percent: Number(item[11] ?? 0),
memory_usage_percent: Number(item[12] ?? 0),
storage_usage_percent: Number(item[13] ?? 0),
request_count: Number(item[14] ?? 0),
error_count: Number(item[15] ?? 0),
unique_visitor_count: Number(item[16] ?? 0),
}
: item,
);
}
function normalizeDashboardOverview(
overview: DashboardOverview | DashboardOverviewCompact | null | undefined,
): DashboardOverview | null {
if (!overview) {
return null;
}
const summary = (overview.summary ?? {}) as DashboardSummary;
const traffic = (overview.traffic ?? {}) as DashboardTraffic;
const capacity = (overview.capacity ?? {}) as DashboardCapacity;
return {
generated_at: String(overview.generated_at ?? ''),
summary,
traffic,
capacity,
nodes: normalizeDashboardNodes(overview.nodes),
distributions: {
source_countries: normalizeDistributionItems(
overview.distributions?.source_countries,
),
status_codes: normalizeDistributionItems(
overview.distributions?.status_codes,
),
top_domains: normalizeDistributionItems(
overview.distributions?.top_domains,
),
},
trends: {
traffic_24h: normalizeTrafficTrendPoints(overview.trends?.traffic_24h),
capacity_24h: normalizeCapacityTrendPoints(overview.trends?.capacity_24h),
network_24h: normalizeNetworkTrendPoints(overview.trends?.network_24h),
disk_io_24h: normalizeDiskIOTrendPoints(overview.trends?.disk_io_24h),
},
};
}
export class DashboardService extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/dashboard';
static async getOverview(): Promise<DashboardOverview | null> {
const overview = await this.legacyGet<
DashboardOverview | DashboardOverviewCompact
>('/overview');
return normalizeDashboardOverview(overview);
}
}
@@ -0,0 +1,27 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {DnsAccountItem, DnsAccountMutationPayload} from './types';
export class DnsAccountService extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/dns-accounts';
static async list(): Promise<DnsAccountItem[]> {
return this.legacyGet<DnsAccountItem[]>('/');
}
static async create(
payload: DnsAccountMutationPayload,
): Promise<DnsAccountItem> {
return this.legacyPost<DnsAccountItem>('/', payload);
}
static async update(
id: number,
payload: DnsAccountMutationPayload,
): Promise<DnsAccountItem> {
return this.legacyPost<DnsAccountItem>(`/${id}/update`, payload);
}
static async delete(id: number): Promise<void> {
return this.legacyPost<void>(`/${id}/delete`);
}
}
@@ -5,6 +5,18 @@ export { NodeService } from './node.service';
export { ProxyRouteService } from './proxy-route.service';
export { ConfigVersionService } from './config-version.service';
export { ApplyLogService } from './apply-log.service';
export { DashboardService } from './dashboard.service';
export { WafService } from './waf.service';
export { WebsiteService } from './website.service';
export { TlsCertificateService } from './tls-certificate.service';
export { DnsAccountService } from './dns-account.service';
export { PagesService } from './pages.service';
export { OriginService } from './origin.service';
export { AccessLogService } from './access-log.service';
export { OptionService } from './option.service';
export { UptimeKumaService } from './uptimekuma.service';
export { UpdateService } from './update.service';
export { StatusService } from './status.service';
export type {
ApplyLogCleanupPayload,
@@ -36,16 +48,98 @@ export type {
ProxyRoutePoWConfig,
ReleaseChannel,
SupportFile,
DashboardOverview,
DashboardNodeHealth,
DashboardSummary,
DashboardTraffic,
DashboardCapacity,
DistributionItem,
TrafficTrendPoint,
CapacityTrendPoint,
WAFIPGroup,
WAFIPGroupAutoTestPayload,
WAFIPGroupAutoTestResult,
WAFIPGroupPayload,
WAFIPGroupSyncResult,
WAFIPGroupSubscriptionFormat,
WAFIPGroupType,
WAFRuleGroup,
WAFRuleGroupPayload,
WAFSiteRuleGroups,
AccessLogCleanupPayload,
AccessLogCleanupResult,
AccessLogFilters,
AccessLogIPSummaryFilters,
AccessLogIPSummaryItem,
AccessLogIPSummaryList,
AccessLogIPTrend,
AccessLogIPTrendFilters,
AccessLogItem,
AccessLogList,
FoldedAccessLogFilters,
FoldedAccessLogIPFilters,
FoldedAccessLogIPList,
FoldedAccessLogList,
OptionItem,
GeoIPLookupResult,
DatabaseCleanupPayload,
DatabaseCleanupResult,
DatabaseCleanupTarget,
LatestReleaseInfo,
OpenFlarePublicStatus,
OriginDetail,
OriginItem,
OriginMutationPayload,
PagesDeployment,
PagesDeploymentFile,
PagesProject,
PagesProjectPayload,
AcmeAccountItem,
DnsAccountItem,
DnsAccountMutationPayload,
ManagedDomainItem,
ManagedDomainMatchResult,
ManagedDomainMutationPayload,
TlsCertificateApplyPayload,
TlsCertificateContentItem,
TlsCertificateDetailItem,
TlsCertificateFileImportPayload,
TlsCertificateItem,
TlsCertificateMutationPayload,
} from './types';
import {AccessLogService} from './access-log.service';
import {ApplyLogService} from './apply-log.service';
import {ConfigVersionService} from './config-version.service';
import {DashboardService} from './dashboard.service';
import {DnsAccountService} from './dns-account.service';
import {NodeService} from './node.service';
import {OptionService} from './option.service';
import {StatusService} from './status.service';
import {UpdateService} from './update.service';
import {UptimeKumaService} from './uptimekuma.service';
import {OriginService} from './origin.service';
import {PagesService} from './pages.service';
import {ProxyRouteService} from './proxy-route.service';
import {TlsCertificateService} from './tls-certificate.service';
import {WafService} from './waf.service';
import {WebsiteService} from './website.service';
export const openflareServices = {
node: NodeService,
proxyRoute: ProxyRouteService,
configVersion: ConfigVersionService,
applyLog: ApplyLogService,
dashboard: DashboardService,
waf: WafService,
website: WebsiteService,
tlsCertificate: TlsCertificateService,
dnsAccount: DnsAccountService,
pages: PagesService,
origin: OriginService,
accessLog: AccessLogService,
option: OptionService,
uptimeKuma: UptimeKumaService,
update: UpdateService,
status: StatusService,
} as const;
@@ -67,4 +67,12 @@ export class NodeService extends LegacyOpenFlareBaseService {
limit: options?.limit,
});
}
static async cleanupHealthEvents(
id: number,
): Promise<{ node_id: string; deleted_count: number }> {
return this.legacyPost<{ node_id: string; deleted_count: number }>(
`/${id}/observability/cleanup`,
);
}
}
@@ -0,0 +1,35 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {
DatabaseCleanupPayload,
DatabaseCleanupResult,
GeoIPLookupResult,
OptionBatchPayload,
OptionItem,
} from './types';
export class OptionService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/option';
static list(): Promise<OptionItem[]> {
return this.legacyGet<OptionItem[]>('/');
}
static update(key: string, value: string): Promise<void> {
return this.legacyPost<void>('/update', { key, value });
}
static updateBatch(options: OptionItem[]): Promise<void> {
const payload: OptionBatchPayload = { options };
return this.legacyPost<void>('/update-batch', payload);
}
static lookupGeoIP(provider: string, ip: string): Promise<GeoIPLookupResult> {
return this.legacyPost<GeoIPLookupResult>('/geoip/lookup', { provider, ip });
}
static cleanupDatabase(
payload: DatabaseCleanupPayload,
): Promise<DatabaseCleanupResult> {
return this.legacyPost<DatabaseCleanupResult>('/database/cleanup', payload);
}
}
@@ -0,0 +1,29 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {OriginDetail, OriginItem, OriginMutationPayload,} from './types';
export class OriginService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/origins';
static list(): Promise<OriginItem[]> {
return this.legacyGet<OriginItem[]>('/');
}
static get(id: number): Promise<OriginDetail> {
return this.legacyGet<OriginDetail>(`/${id}`);
}
static create(payload: OriginMutationPayload): Promise<OriginItem> {
return this.legacyPost<OriginItem>('/', payload);
}
static update(
id: number,
payload: OriginMutationPayload,
): Promise<OriginItem> {
return this.legacyPost<OriginItem>(`/${id}/update`, payload);
}
static delete(id: number): Promise<void> {
return this.legacyPost<void>(`/${id}/delete`);
}
}
@@ -0,0 +1,113 @@
import type {AxiosProgressEvent, InternalAxiosRequestConfig} from 'axios';
import apiClient from '@/lib/services/core/api-client';
import {ApiErrorBase} from '@/lib/services/core/errors';
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {
LegacyApiResponse,
PagesDeployment,
PagesDeploymentFile,
PagesDeploymentUploadPayload,
PagesProject,
PagesProjectPayload,
} from './types';
export class PagesService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/pages';
static listProjects(): Promise<PagesProject[]> {
return this.legacyGet<PagesProject[]>('/');
}
static getProject(id: number): Promise<PagesProject> {
return this.legacyGet<PagesProject>(`/${id}`);
}
static createProject(payload: PagesProjectPayload): Promise<PagesProject> {
return this.legacyPost<PagesProject>('/', payload);
}
static updateProject(
id: number,
payload: PagesProjectPayload,
): Promise<PagesProject> {
return this.legacyPost<PagesProject>(`/${id}/update`, payload);
}
static deleteProject(id: number): Promise<void> {
return this.legacyPost<void>(`/${id}/delete`);
}
static listDeployments(projectId: number): Promise<PagesDeployment[]> {
return this.legacyGet<PagesDeployment[]>(`/${projectId}/deployments`);
}
static listDeploymentFiles(
projectId: number,
deploymentId: number,
): Promise<PagesDeploymentFile[]> {
return this.legacyGet<PagesDeploymentFile[]>(
`/${projectId}/deployments/${deploymentId}/files`,
);
}
static uploadDeployment(
projectId: number,
payload: PagesDeploymentUploadPayload,
): Promise<PagesDeployment> {
const formData = new FormData();
formData.append('package', payload.file);
formData.append('root_dir', payload.rootDir ?? '');
formData.append('entry_file', payload.entryFile ?? 'index.html');
return this.postFormData<PagesDeployment>(
`/${projectId}/deployments/upload`,
formData,
payload.onProgress,
);
}
static activateDeployment(
projectId: number,
deploymentId: number,
): Promise<PagesProject> {
return this.legacyPost<PagesProject>(
`/${projectId}/deployments/${deploymentId}/activate`,
);
}
static deleteDeployment(
projectId: number,
deploymentId: number,
): Promise<void> {
return this.legacyPost<void>(
`/${projectId}/deployments/${deploymentId}/delete`,
);
}
private static async postFormData<T>(
path: string,
formData: FormData,
onProgress?: (percent: number) => void,
): Promise<T> {
const response = await apiClient.post<LegacyApiResponse<T>>(
this.getFullPath(path),
formData,
{
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (event: AxiosProgressEvent) => {
if (!onProgress || !event.total) return;
const percent = Math.round((event.loaded / event.total) * 100);
onProgress(percent);
},
} as InternalAxiosRequestConfig,
);
if (!response.data.success) {
throw new ApiErrorBase(response.data.message || '请求失败');
}
return response.data.data;
}
}
@@ -0,0 +1,10 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {OpenFlarePublicStatus} from './types';
export class StatusService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api';
static getPublicStatus(): Promise<OpenFlarePublicStatus> {
return this.legacyGet<OpenFlarePublicStatus>('/status');
}
}
@@ -0,0 +1,91 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {
AcmeAccountItem,
TlsCertificateApplyPayload,
TlsCertificateContentItem,
TlsCertificateDetailItem,
TlsCertificateFileImportPayload,
TlsCertificateItem,
TlsCertificateMutationPayload,
} from './types';
class AcmeAccountApi extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/acme-accounts';
static getDefault(): Promise<AcmeAccountItem> {
return this.legacyGet<AcmeAccountItem>('/default');
}
}
export class TlsCertificateService extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/tls-certificates';
static async list(): Promise<TlsCertificateItem[]> {
return this.legacyGet<TlsCertificateItem[]>('/');
}
static async getById(id: number): Promise<TlsCertificateDetailItem> {
return this.legacyGet<TlsCertificateDetailItem>(`/${id}`);
}
static async getContent(id: number): Promise<TlsCertificateContentItem> {
return this.legacyGet<TlsCertificateContentItem>(`/${id}/content`);
}
static async create(
payload: TlsCertificateMutationPayload,
): Promise<TlsCertificateItem> {
return this.legacyPost<TlsCertificateItem>('/', payload);
}
static async update(
id: number,
payload: TlsCertificateMutationPayload,
): Promise<TlsCertificateItem> {
return this.legacyPost<TlsCertificateItem>(`/${id}/update`, payload);
}
static async delete(id: number): Promise<void> {
return this.legacyPost<void>(`/${id}/delete`);
}
static async apply(
payload: TlsCertificateApplyPayload,
): Promise<TlsCertificateItem> {
return this.legacyPost<TlsCertificateItem>('/apply', payload);
}
static async renew(id: number): Promise<TlsCertificateItem> {
return this.legacyPost<TlsCertificateItem>(`/${id}/renew`);
}
static async updateAcme(
id: number,
payload: TlsCertificateApplyPayload,
): Promise<TlsCertificateItem> {
return this.legacyPost<TlsCertificateItem>(`/${id}/update-acme`, payload);
}
static async convertToAcme(
id: number,
payload: TlsCertificateApplyPayload,
): Promise<TlsCertificateItem> {
return this.legacyPost<TlsCertificateItem>(`/${id}/convert-acme`, payload);
}
static async importFile(
payload: TlsCertificateFileImportPayload,
): Promise<TlsCertificateItem> {
const formData = new FormData();
formData.append('name', payload.name);
formData.append('remark', payload.remark);
formData.append('cert_file', payload.certFile);
formData.append('key_file', payload.keyFile);
return this.legacyPost<TlsCertificateItem>('/import-file', formData);
}
static getDefaultAcmeAccount(): Promise<AcmeAccountItem> {
return AcmeAccountApi.getDefault();
}
}
@@ -364,4 +364,699 @@ export interface ApplyLogCleanupResult {
retention_days: number;
deleted_count: number;
cutoff?: string;
}
// ==================== Pages ====================
export interface PagesDeployment {
id: number;
project_id: number;
deployment_number: number;
checksum: string;
status: 'uploaded' | 'active';
file_count: number;
total_size: number;
root_dir?: string;
entry_file: string;
created_by: string;
created_at: string;
activated_at?: string | null;
}
export interface PagesDeploymentFile {
id: number;
deployment_id: number;
path: string;
size: number;
checksum: string;
created_at: string;
}
export interface PagesProject {
id: number;
name: string;
slug: string;
description: string;
enabled: boolean;
spa_fallback_enabled: boolean;
spa_fallback_path: string;
api_proxy_enabled: boolean;
api_proxy_path: string;
api_proxy_pass: string;
api_proxy_rewrite: string;
root_dir?: string;
entry_file: string;
active_deployment_id?: number | null;
active_deployment?: PagesDeployment | null;
deployment_count: number;
created_at: string;
updated_at: string;
}
export interface PagesProjectPayload {
name: string;
slug: string;
description: string;
enabled: boolean;
spa_fallback_enabled: boolean;
spa_fallback_path: string;
api_proxy_enabled: boolean;
api_proxy_path: string;
api_proxy_pass: string;
api_proxy_rewrite: string;
root_dir?: string;
entry_file: string;
}
export interface PagesDeploymentUploadPayload {
file: File;
rootDir?: string;
entryFile?: string;
onProgress?: (percent: number) => void;
}
// ==================== Origins ====================
export interface OriginItem {
id: number;
name: string;
address: string;
remark: string;
route_count: number;
created_at: string;
updated_at: string;
}
export interface OriginRouteSummary {
id: number;
domain: string;
origin_url: string;
enabled: boolean;
updated_at: string;
}
export interface OriginDetail extends OriginItem {
routes: OriginRouteSummary[];
}
export interface OriginMutationPayload {
name: string;
address: string;
remark: string;
}
// ==================== Access Logs ====================
export interface AccessLogFilters {
node_id?: string;
remote_addr?: string;
host?: string;
path?: string;
p?: number;
page_size?: number;
sort_by?: string;
sort_order?: 'asc' | 'desc';
}
export interface AccessLogItem {
id: number;
node_id: string;
node_name: string;
logged_at: string;
remote_addr: string;
region: string;
host: string;
path: string;
status_code: number;
}
export interface AccessLogList {
items: AccessLogItem[];
page: number;
page_size: number;
has_more: boolean;
total_record: number;
total_ip: number;
}
export interface FoldedAccessLogFilters extends AccessLogFilters {
fold_minutes: 3 | 5;
}
export interface FoldedAccessLogItem {
bucket_started_at: string;
request_count: number;
unique_ip_count: number;
unique_host_count: number;
success_count: number;
client_error_count: number;
server_error_count: number;
}
export interface FoldedAccessLogList {
items: FoldedAccessLogItem[];
page: number;
page_size: number;
has_more: boolean;
total_bucket: number;
total_record: number;
total_ip: number;
fold_minutes: number;
}
export interface FoldedAccessLogIPFilters extends FoldedAccessLogFilters {
bucket_started_at: string;
}
export interface FoldedAccessLogIPItem {
remote_addr: string;
request_count: number;
success_count: number;
client_error_count: number;
server_error_count: number;
last_seen_at: string;
}
export interface FoldedAccessLogIPList {
items: FoldedAccessLogIPItem[];
page: number;
page_size: number;
has_more: boolean;
total_ip: number;
bucket_started_at: string;
fold_minutes: number;
sort_by: string;
sort_order: 'asc' | 'desc';
}
export interface AccessLogIPSummaryFilters {
node_id?: string;
remote_addr?: string;
host?: string;
p?: number;
page_size?: number;
sort_by?: string;
sort_order?: 'asc' | 'desc';
}
export interface AccessLogIPSummaryItem {
remote_addr: string;
total_requests: number;
recent_requests: number;
last_seen_at: string;
}
export interface AccessLogIPSummaryList {
items: AccessLogIPSummaryItem[];
page: number;
page_size: number;
has_more: boolean;
total_ip: number;
sort_by: string;
sort_order: 'asc' | 'desc';
}
export interface AccessLogIPTrendFilters {
node_id?: string;
remote_addr: string;
host?: string;
hours?: number;
bucket_minutes?: number;
}
export interface AccessLogIPTrendPoint {
bucket_started_at: string;
request_count: number;
}
export interface AccessLogIPTrend {
remote_addr: string;
hours: number;
bucket_minutes: number;
points: AccessLogIPTrendPoint[];
}
export interface AccessLogCleanupPayload {
retention_days: number;
}
export interface AccessLogCleanupResult {
retention_days: number;
deleted_count: number;
cutoff: string;
}
// ==================== Options (Performance) ====================
export interface OptionItem {
key: string;
value: string;
}
export interface OptionBatchPayload {
options: OptionItem[];
}
export interface GeoIPLookupResult {
provider: string;
ip: string;
iso_code: string;
name: string;
latitude?: number | null;
longitude?: number | null;
}
export type DatabaseCleanupTarget =
| 'node_access_logs'
| 'node_metric_snapshots'
| 'node_request_reports';
export interface DatabaseCleanupPayload {
target: DatabaseCleanupTarget;
retention_days?: number;
}
export interface DatabaseCleanupResult {
target: DatabaseCleanupTarget;
target_label: string;
deleted_count: number;
delete_all: boolean;
retention_days?: number;
cutoff?: string;
}
export interface OpenFlarePublicStatus {
version: string;
start_time: number;
server_address: string;
system_name: string;
}
export interface UpgradeLogItem {
level: string;
message: string;
created_at: string;
}
export interface LatestReleaseInfo {
tag_name: string;
body: string;
html_url: string;
published_at: string;
channel: ReleaseChannel;
prerelease: boolean;
current_version: string;
has_update: boolean;
upgrade_supported: boolean;
in_progress: boolean;
upgrade_status: string;
upgrade_logs: UpgradeLogItem[];
}
export interface WAFRuleGroup {
id: number;
name: string;
enabled: boolean;
is_global: boolean;
block_status_code: number;
block_response_body: string;
ip_whitelist: string[];
ip_blacklist: string[];
ip_whitelist_group_ids: number[];
ip_blacklist_group_ids: number[];
country_whitelist: string[];
country_blacklist: string[];
region_whitelist: string[];
region_blacklist: string[];
pow_enabled: boolean;
pow_config: ProxyRoutePoWConfig;
remark: string;
applied_site_ids: number[];
applied_site_count: number;
created_at: string;
updated_at: string;
}
export interface WAFRuleGroupPayload {
name: string;
enabled: boolean;
block_status_code: number;
block_response_body: string;
ip_whitelist: string[];
ip_blacklist: string[];
ip_whitelist_group_ids: number[];
ip_blacklist_group_ids: number[];
country_whitelist: string[];
country_blacklist: string[];
region_whitelist: string[];
region_blacklist: string[];
pow_enabled: boolean;
pow_config: ProxyRoutePoWConfig;
remark: string;
}
export interface WAFSiteRuleGroups {
route_id: number;
global_rule_group: WAFRuleGroup | null;
rule_groups: WAFRuleGroup[];
applied_rule_groups: WAFRuleGroup[];
applied_ids: number[];
}
export type WAFIPGroupType = 'manual' | 'automatic' | 'subscription';
export type WAFIPGroupSubscriptionFormat = 'text' | 'json';
export interface WAFIPGroupExtIP {
ip: string;
captured_at: string;
}
export interface WAFIPGroup {
id: number;
name: string;
type: WAFIPGroupType;
enabled: boolean;
ip_list: string[];
auto_config: Record<string, unknown>;
ext_ips?: WAFIPGroupExtIP[];
subscription_url: string;
subscription_format: WAFIPGroupSubscriptionFormat;
subscription_mapping_rule: string;
sync_interval_minutes: number;
last_synced_at?: string;
next_sync_at?: string;
last_sync_status: string;
last_sync_message: string;
remark: string;
referenced_by_rule_count: number;
created_at: string;
updated_at: string;
}
export interface WAFIPGroupPayload {
name: string;
type: WAFIPGroupType;
enabled: boolean;
ip_list: string[];
auto_config: Record<string, unknown>;
subscription_url: string;
subscription_format: WAFIPGroupSubscriptionFormat;
subscription_mapping_rule: string;
sync_interval_minutes: number;
remark: string;
}
export interface WAFIPGroupSyncResult {
group: WAFIPGroup;
ip_count: number;
synced_at: string;
next_sync_at: string;
status: string;
message: string;
}
export interface WAFIPGroupAutoTestPayload {
auto_config: Record<string, unknown>;
}
export interface WAFIPGroupAutoTestResult {
matched_ips: string[];
matched_count: number;
lookback_minutes: number;
rule_count: number;
tested_at: string;
}
export interface DashboardSummary {
total_nodes: number;
online_nodes: number;
offline_nodes: number;
pending_nodes: number;
unhealthy_nodes: number;
}
export interface DashboardTraffic {
request_count: number;
unique_visitors: number;
error_count: number;
estimated_qps: number;
reported_nodes: number;
}
export interface DashboardCapacity {
average_cpu_usage_percent: number;
average_memory_usage_percent: number;
high_cpu_nodes: number;
high_memory_nodes: number;
high_storage_nodes: number;
}
export interface DistributionItem {
key: string;
value: number;
}
export type CompactDistributionItem = [string, number];
export interface TrafficTrendPoint {
bucket_started_at: string;
request_count: number;
error_count: number;
unique_visitor_count: number;
}
export interface CapacityTrendPoint {
bucket_started_at: string;
average_cpu_usage_percent: number;
average_memory_usage_percent: number;
reported_nodes: number;
}
export interface NetworkTrendPoint {
bucket_started_at: string;
network_rx_bytes: number;
network_tx_bytes: number;
openresty_rx_bytes: number;
openresty_tx_bytes: number;
reported_nodes: number;
}
export interface DiskIOTrendPoint {
bucket_started_at: string;
disk_read_bytes: number;
disk_write_bytes: number;
reported_nodes: number;
}
export interface TrafficDistributions {
status_codes: DistributionItem[];
top_domains: DistributionItem[];
source_countries: DistributionItem[];
}
export interface DashboardTrends {
traffic_24h: TrafficTrendPoint[];
capacity_24h: CapacityTrendPoint[];
network_24h: NetworkTrendPoint[];
disk_io_24h: DiskIOTrendPoint[];
}
export interface DashboardNodeHealth {
id: number;
node_id: string;
name: string;
geo_name: string;
geo_latitude?: number | null;
geo_longitude?: number | null;
status: NodeStatus;
openresty_status: OpenrestyStatus;
current_version: string;
last_seen_at: string;
active_event_count: number;
cpu_usage_percent: number;
memory_usage_percent: number;
storage_usage_percent: number;
request_count: number;
error_count: number;
unique_visitor_count: number;
}
export interface DashboardOverview {
generated_at: string;
summary: DashboardSummary;
traffic: DashboardTraffic;
capacity: DashboardCapacity;
distributions: TrafficDistributions;
trends: DashboardTrends;
nodes: DashboardNodeHealth[];
}
export type CompactTrafficTrendPoint = [string, number, number, number];
export type CompactCapacityTrendPoint = [string, number, number, number];
export type CompactNetworkTrendPoint = [
string,
number,
number,
number,
number,
number,
];
export type CompactDiskIOTrendPoint = [string, number, number, number];
export type CompactDashboardNodeHealth = [
number,
string,
string,
string,
number | null,
number | null,
DashboardNodeHealth['status'],
DashboardNodeHealth['openresty_status'],
string,
string,
number,
number,
number,
number,
number,
number,
number,
];
export interface DashboardOverviewCompact {
generated_at: string;
summary: DashboardSummary;
traffic: DashboardTraffic;
capacity: DashboardCapacity;
distributions: {
status_codes: CompactDistributionItem[];
top_domains: CompactDistributionItem[];
source_countries: CompactDistributionItem[];
};
trends: {
traffic_24h: CompactTrafficTrendPoint[];
capacity_24h: CompactCapacityTrendPoint[];
network_24h: CompactNetworkTrendPoint[];
disk_io_24h: CompactDiskIOTrendPoint[];
};
nodes: CompactDashboardNodeHealth[];
}
// ==================== Websites / TLS / DNS ====================
export interface ManagedDomainItem {
id: number;
domain: string;
cert_id: number | null;
enabled: boolean;
remark: string;
created_at: string;
updated_at: string;
}
export interface ManagedDomainMutationPayload {
domain: string;
cert_id: number | null;
enabled: boolean;
remark: string;
}
export interface ManagedDomainMatchCandidate {
managed_domain_id: number;
domain: string;
match_type: 'exact' | 'wildcard' | string;
certificate_id: number;
certificate_name: string;
}
export interface ManagedDomainMatchResult {
domain: string;
matched: boolean;
candidate?: ManagedDomainMatchCandidate;
candidates: ManagedDomainMatchCandidate[];
}
export interface TlsCertificateItem {
id: number;
name: string;
cert_pem?: string;
key_pem?: string;
provider: string;
acme_account_id: number;
dns_account_id: number;
key_algorithm: string;
auto_renew: boolean;
primary_domain: string;
other_domains: string;
disable_cname: boolean;
skip_dns: boolean;
dns1: string;
dns2: string;
apply_status: string;
apply_message: string;
not_before: string;
not_after: string;
remark: string;
created_at: string;
updated_at: string;
}
export interface TlsCertificateDetailItem extends TlsCertificateItem {
cert_pem?: never;
key_pem?: never;
}
export interface TlsCertificateContentItem extends TlsCertificateItem {
cert_pem: string;
key_pem: string;
}
export interface TlsCertificateMutationPayload {
name: string;
cert_pem: string;
key_pem: string;
remark: string;
}
export interface TlsCertificateApplyPayload {
name: string;
remark: string;
acme_account_id: number;
dns_account_id: number;
key_algorithm: string;
auto_renew: boolean;
primary_domain: string;
other_domains: string;
disable_cname: boolean;
skip_dns: boolean;
dns1: string;
dns2: string;
}
export interface TlsCertificateFileImportPayload {
name: string;
remark: string;
certFile: File;
keyFile: File;
}
export interface AcmeAccountItem {
id: number;
email: string;
url: string;
created_at: string;
updated_at: string;
}
export interface DnsAccountItem {
id: number;
name: string;
type: string;
created_at: string;
updated_at: string;
}
export interface DnsAccountMutationPayload {
name: string;
type: string;
authorization: string;
}
@@ -0,0 +1,16 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {LatestReleaseInfo, ReleaseChannel} from './types';
/**
* OpenFlare 服务端升级 API(遗留 `/api/update/*`)。
*
* 可选集成:在顶栏或 Admin 升级入口调用 `getLatestRelease()` 展示版本与更新状态;
* 完整升级流程(upgrade / manual-upload / logs WebSocket)可对接 Wavelet `admin/updater`。
*/
export class UpdateService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/update';
static getLatestRelease(channel: ReleaseChannel = 'stable'): Promise<LatestReleaseInfo> {
return this.legacyGet<LatestReleaseInfo>('/latest-release', { channel });
}
}
@@ -0,0 +1,9 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
export class UptimeKumaService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/uptimekuma';
static sync(): Promise<void> {
return this.legacyPost<void>('/sync');
}
}
@@ -0,0 +1,85 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {
WAFIPGroup,
WAFIPGroupAutoTestPayload,
WAFIPGroupAutoTestResult,
WAFIPGroupPayload,
WAFIPGroupSyncResult,
WAFRuleGroup,
WAFRuleGroupPayload,
WAFSiteRuleGroups,
} from './types';
export class WafService extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/waf';
static async listRuleGroups(): Promise<WAFRuleGroup[]> {
return this.legacyGet<WAFRuleGroup[]>('/rule-groups');
}
static async getRuleGroup(id: number): Promise<WAFRuleGroup> {
return this.legacyGet<WAFRuleGroup>(`/rule-groups/${id}`);
}
static async createRuleGroup(payload: WAFRuleGroupPayload): Promise<WAFRuleGroup> {
return this.legacyPost<WAFRuleGroup>('/rule-groups', payload);
}
static async updateRuleGroup(
id: number,
payload: WAFRuleGroupPayload,
): Promise<WAFRuleGroup> {
return this.legacyPost<WAFRuleGroup>(`/rule-groups/${id}/update`, payload);
}
static async deleteRuleGroup(id: number): Promise<void> {
return this.legacyPost<void>(`/rule-groups/${id}/delete`);
}
static async updateRuleGroupSites(id: number, ids: number[]): Promise<WAFRuleGroup> {
return this.legacyPost<WAFRuleGroup>(`/rule-groups/${id}/sites`, { ids });
}
static async listSiteRuleGroups(routeId: number): Promise<WAFSiteRuleGroups> {
return this.legacyGet<WAFSiteRuleGroups>(`/sites/${routeId}/rule-groups`);
}
static async updateSiteRuleGroups(
routeId: number,
ids: number[],
): Promise<WAFSiteRuleGroups> {
return this.legacyPost<WAFSiteRuleGroups>(`/sites/${routeId}/rule-groups`, {
ids,
});
}
static async listIPGroups(): Promise<WAFIPGroup[]> {
return this.legacyGet<WAFIPGroup[]>('/ip-groups');
}
static async getIPGroup(id: number): Promise<WAFIPGroup> {
return this.legacyGet<WAFIPGroup>(`/ip-groups/${id}`);
}
static async createIPGroup(payload: WAFIPGroupPayload): Promise<WAFIPGroup> {
return this.legacyPost<WAFIPGroup>('/ip-groups', payload);
}
static async updateIPGroup(id: number, payload: WAFIPGroupPayload): Promise<WAFIPGroup> {
return this.legacyPost<WAFIPGroup>(`/ip-groups/${id}/update`, payload);
}
static async deleteIPGroup(id: number): Promise<void> {
return this.legacyPost<void>(`/ip-groups/${id}/delete`);
}
static async testIPGroup(
payload: WAFIPGroupAutoTestPayload,
): Promise<WAFIPGroupAutoTestResult> {
return this.legacyPost<WAFIPGroupAutoTestResult>('/ip-groups/test', payload);
}
static async syncIPGroup(id: number): Promise<WAFIPGroupSyncResult> {
return this.legacyPost<WAFIPGroupSyncResult>(`/ip-groups/${id}/sync`);
}
}
@@ -0,0 +1,31 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {ManagedDomainItem, ManagedDomainMatchResult, ManagedDomainMutationPayload,} from './types';
export class WebsiteService extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/managed-domains';
static async list(): Promise<ManagedDomainItem[]> {
return this.legacyGet<ManagedDomainItem[]>('/');
}
static async create(
payload: ManagedDomainMutationPayload,
): Promise<ManagedDomainItem> {
return this.legacyPost<ManagedDomainItem>('/', payload);
}
static async update(
id: number,
payload: ManagedDomainMutationPayload,
): Promise<ManagedDomainItem> {
return this.legacyPost<ManagedDomainItem>(`/${id}/update`, payload);
}
static async delete(id: number): Promise<void> {
return this.legacyPost<void>(`/${id}/delete`);
}
static async match(domain: string): Promise<ManagedDomainMatchResult> {
return this.legacyGet<ManagedDomainMatchResult>('/match', {domain});
}
}