mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 00:26:37 +08:00
migrate frontend
This commit is contained in:
@@ -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;
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user