refactor(openflare): migrate console APIs to v1 and centralize routing

Move OpenFlare management endpoints to /api/v1/custom/openflare with
Wavelet response envelopes and Abort* error handling. Keep agent, relay,
and flared protocol routes on /api/* with the legacy compat format.

- Add apiutil helpers and Swagger annotations for console handlers
- Register all OpenFlare routes in internal/router/openflare (not apps)
- Switch frontend services to OpenFlareBaseService and v1 paths
- Remove dead auth/compat code and legacy-base.service.ts
- Update integration tests and changelog
This commit is contained in:
ryan
2026-06-18 20:34:54 +08:00
parent aa348a1b48
commit 399c1bc88d
104 changed files with 28541 additions and 3879 deletions
@@ -40,12 +40,12 @@ export function OriginDetailPageClient() {
const originQuery = useQuery({
queryKey: ["openflare", "origins", originId],
queryFn: () => OriginService.get(parsedId),
queryFn: () => OriginService.getById(parsedId),
enabled,
})
const deleteMutation = useMutation({
mutationFn: () => OriginService.delete(parsedId),
mutationFn: () => OriginService.deleteById(parsedId),
onSuccess: async () => {
toast.success("源站已删除")
await queryClient.invalidateQueries({ queryKey: ["openflare", "origins"] })
+1 -1
View File
@@ -42,7 +42,7 @@ export default function OriginsPage() {
const origins = useMemo(() => originsQuery.data ?? [], [originsQuery.data])
const deleteMutation = useMutation({
mutationFn: (id: number) => OriginService.delete(id),
mutationFn: (id: number) => OriginService.deleteById(id),
onSuccess: async () => {
toast.success("源站已删除")
setDeleteTarget(null)
@@ -83,7 +83,7 @@ export function ProxyRoutesPageClient() {
setDeleting(true);
try {
await ProxyRouteService.delete(deleteTarget.id);
await ProxyRouteService.deleteById(deleteTarget.id);
toast.success('网站已删除');
setDeleteTarget(null);
await fetchRoutes();
@@ -52,7 +52,7 @@ export default function CertificatesPage() {
});
const deleteMutation = useMutation({
mutationFn: (id: number) => TlsCertificateService.delete(id),
mutationFn: (id: number) => TlsCertificateService.deleteById(id),
onSuccess: async () => {
toast.success('证书已删除');
setDeleteTarget(null);
@@ -102,7 +102,7 @@ export function WebsiteDetailPageClient() {
}, [routesQuery.data, website]);
const deleteDomainMutation = useMutation({
mutationFn: (id: number) => WebsiteService.delete(id),
mutationFn: (id: number) => WebsiteService.deleteById(id),
onSuccess: async () => {
await queryClient.invalidateQueries({queryKey: domainsQueryKey});
router.push('/websites');
@@ -111,7 +111,7 @@ export function WebsiteDetailPageClient() {
});
const deleteCertificateMutation = useMutation({
mutationFn: (id: number) => TlsCertificateService.delete(id),
mutationFn: (id: number) => TlsCertificateService.deleteById(id),
onSuccess: async () => {
toast.success('证书已删除');
setDeleteCertOpen(false);
@@ -40,7 +40,7 @@ export default function DnsAccountsPage() {
});
const deleteMutation = useMutation({
mutationFn: (id: number) => DnsAccountService.delete(id),
mutationFn: (id: number) => DnsAccountService.deleteById(id),
onSuccess: async () => {
toast.success('DNS 账号已删除');
setDeleteTarget(null);
@@ -50,7 +50,7 @@ export default function WebsitesPage() {
});
const deleteMutation = useMutation({
mutationFn: (id: number) => WebsiteService.delete(id),
mutationFn: (id: number) => WebsiteService.deleteById(id),
onSuccess: async () => {
toast.success('网站已删除');
setDeleteTarget(null);
@@ -73,7 +73,7 @@ export function LoginForm({ onOTPStateChange }: { onOTPStateChange?: (show: bool
queryFn: () => AuthService.getAuthSources(),
})
const capEnabled = configBool(publicConfigQuery.data?.cap_login_enabled, false)
const capEnabled = configBool(publicConfigQuery.data?.cap_login_enabled, true)
const capAutoSolve = configBool(publicConfigQuery.data?.cap_auto_solve, true)
const loginMutation = useMutation({
@@ -190,8 +190,8 @@ export function LoginForm({ onOTPStateChange }: { onOTPStateChange?: (show: bool
}
const registrationEnabled =
configBool(publicConfigQuery.data?.registration_enabled, true) &&
configBool(publicConfigQuery.data?.password_register_enabled, true)
configBool(publicConfigQuery.data?.registration_enabled, false) &&
configBool(publicConfigQuery.data?.password_register_enabled, false)
const passwordLoginEnabled = configBool(publicConfigQuery.data?.password_login_enabled, true)
const oidcLoginEnabled = configBool(publicConfigQuery.data?.oidc_login_enabled, true)
@@ -63,12 +63,12 @@ export function RegisterForm() {
)
const registrationEnabled =
configBool(publicConfigQuery.data?.registration_enabled, true) &&
configBool(publicConfigQuery.data?.password_register_enabled, true)
configBool(publicConfigQuery.data?.registration_enabled, false) &&
configBool(publicConfigQuery.data?.password_register_enabled, false)
const emailRegisterEnabled = configBool(publicConfigQuery.data?.email_register_verification_enabled, false)
const capEnabled = configBool(publicConfigQuery.data?.cap_login_enabled, false)
const capEnabled = configBool(publicConfigQuery.data?.cap_login_enabled, true)
const capAutoSolve = configBool(publicConfigQuery.data?.cap_auto_solve, true)
const [capScope, setCapScope] = useState<'send_email_code' | 'register'>('send_email_code')
@@ -242,9 +242,10 @@ export const apiSections: PolicySection[] = [
"error_msg": "",
"data": {
"site_name": "OpenFlare",
"registration_enabled": "true",
"registration_enabled": "false",
"password_login_enabled": "true",
"password_register_enabled": "true",
"password_register_enabled": "false",
"cap_login_enabled": "true",
"oidc_login_enabled": "true"
}
}`}
+1 -2
View File
@@ -184,7 +184,7 @@ export type { PushEvent, PushHistory, PushChannelConfig, ListPushHistoriesReques
export {
AboutService,
LegacyOpenFlareBaseService,
OpenFlareBaseService,
NodeService,
ProxyRouteService,
ConfigVersionService,
@@ -204,7 +204,6 @@ export {
} from './openflare';
export type {
LegacyApiResponse,
NodeItem,
ProxyRouteItem,
ProxyRouteConfigSection,
@@ -1,9 +1,7 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
export class AboutService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api';
import {OpenFlareBaseService} from './base.service';
export class AboutService extends OpenFlareBaseService {
static getAboutContent(): Promise<string> {
return this.legacyGet<string>('/about');
return this.get<string>('/about');
}
}
@@ -1,4 +1,4 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import {OpenFlareBaseService} from './base.service';
import type {
AccessLogCleanupPayload,
AccessLogCleanupResult,
@@ -25,17 +25,17 @@ function buildSearchParams(filters: object): Record<string, unknown> {
return params;
}
export class AccessLogService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/access-logs';
export class AccessLogService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/access-logs';
static list(filters: AccessLogFilters = {}): Promise<AccessLogList> {
return this.legacyGet<AccessLogList>('/', buildSearchParams(filters));
return this.get<AccessLogList>('/', buildSearchParams(filters));
}
static listFolds(
filters: FoldedAccessLogFilters,
): Promise<FoldedAccessLogList> {
return this.legacyGet<FoldedAccessLogList>(
return this.get<FoldedAccessLogList>(
'/folds',
buildSearchParams(filters),
);
@@ -44,7 +44,7 @@ export class AccessLogService extends LegacyOpenFlareBaseService {
static listFoldIPs(
filters: FoldedAccessLogIPFilters,
): Promise<FoldedAccessLogIPList> {
return this.legacyGet<FoldedAccessLogIPList>(
return this.get<FoldedAccessLogIPList>(
'/folds/ip-summary',
buildSearchParams(filters),
);
@@ -53,7 +53,7 @@ export class AccessLogService extends LegacyOpenFlareBaseService {
static listIPSummaries(
filters: AccessLogIPSummaryFilters = {},
): Promise<AccessLogIPSummaryList> {
return this.legacyGet<AccessLogIPSummaryList>(
return this.get<AccessLogIPSummaryList>(
'/ip-summary',
buildSearchParams(filters),
);
@@ -62,7 +62,7 @@ export class AccessLogService extends LegacyOpenFlareBaseService {
static getIPTrend(
filters: AccessLogIPTrendFilters,
): Promise<AccessLogIPTrend> {
return this.legacyGet<AccessLogIPTrend>(
return this.get<AccessLogIPTrend>(
'/ip-summary/trend',
buildSearchParams(filters),
);
@@ -71,6 +71,6 @@ export class AccessLogService extends LegacyOpenFlareBaseService {
static cleanup(
payload: AccessLogCleanupPayload,
): Promise<AccessLogCleanupResult> {
return this.legacyPost<AccessLogCleanupResult>('/cleanup', payload);
return this.post<AccessLogCleanupResult>('/cleanup', payload);
}
}
}
@@ -1,8 +1,13 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {ApplyLogCleanupPayload, ApplyLogCleanupResult, ApplyLogList, ApplyLogListQuery,} from './types';
import {OpenFlareBaseService} from './base.service';
import type {
ApplyLogCleanupPayload,
ApplyLogCleanupResult,
ApplyLogList,
ApplyLogListQuery,
} from './types';
export class ApplyLogService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/apply-logs';
export class ApplyLogService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/apply-logs';
static list(query: ApplyLogListQuery = {}): Promise<ApplyLogList> {
const params: Record<string, unknown> = {};
@@ -18,10 +23,10 @@ export class ApplyLogService extends LegacyOpenFlareBaseService {
params.pageSize = query.pageSize;
}
return this.legacyGet<ApplyLogList>('/', params);
return this.get<ApplyLogList>('/', params);
}
static cleanup(payload: ApplyLogCleanupPayload): Promise<ApplyLogCleanupResult> {
return this.legacyPost<ApplyLogCleanupResult>('/cleanup', payload);
return this.post<ApplyLogCleanupResult>('/cleanup', payload);
}
}
}
@@ -0,0 +1,5 @@
import {BaseService} from '@/lib/services/core';
export class OpenFlareBaseService extends BaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare';
}
@@ -1,6 +1,6 @@
import type {InternalAxiosRequestConfig} from 'axios';
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import {OpenFlareBaseService} from './base.service';
import type {
ConfigDiffResult,
ConfigPreviewResult,
@@ -10,31 +10,31 @@ import type {
ConfigVersionSummary,
} from './types';
export class ConfigVersionService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/config-versions';
export class ConfigVersionService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/config-versions';
static list(): Promise<ConfigVersionSummary[]> {
return this.legacyGet<ConfigVersionSummary[]>('/');
return this.get<ConfigVersionSummary[]>('/');
}
static getActive(): Promise<ConfigVersionDetail> {
return this.legacyGet<ConfigVersionDetail>('/active');
return this.get<ConfigVersionDetail>('/active');
}
static preview(): Promise<ConfigPreviewResult> {
return this.legacyGet<ConfigPreviewResult>('/preview');
return this.get<ConfigPreviewResult>('/preview');
}
static diff(): Promise<ConfigDiffResult> {
return this.legacyGet<ConfigDiffResult>('/diff');
return this.get<ConfigDiffResult>('/diff');
}
static getById(id: number): Promise<ConfigVersionDetail> {
return this.legacyGet<ConfigVersionDetail>(`/${id}`);
return this.get<ConfigVersionDetail>(`/${id}`);
}
static publish(force?: boolean): Promise<ConfigVersionDetail> {
return this.legacyPost<ConfigVersionDetail>(
return this.post<ConfigVersionDetail>(
'/publish',
undefined,
force
@@ -44,12 +44,12 @@ export class ConfigVersionService extends LegacyOpenFlareBaseService {
}
static activate(id: number): Promise<ConfigVersionDetail> {
return this.legacyPost<ConfigVersionDetail>(`/${id}/activate`);
return this.post<ConfigVersionDetail>(`/${id}/activate`);
}
static cleanup(
payload: ConfigVersionCleanupPayload,
): Promise<ConfigVersionCleanupResult> {
return this.legacyPost<ConfigVersionCleanupResult>('/cleanup', payload);
return this.post<ConfigVersionCleanupResult>('/cleanup', payload);
}
}
}
@@ -1,4 +1,4 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import {OpenFlareBaseService} from './base.service';
import type {
CompactCapacityTrendPoint,
CompactDashboardNodeHealth,
@@ -230,11 +230,11 @@ function normalizeDashboardOverview(
};
}
export class DashboardService extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/dashboard';
export class DashboardService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/dashboard';
static async getOverview(): Promise<DashboardOverview | null> {
const overview = await this.legacyGet<
const overview = await this.get<
DashboardOverview | DashboardOverviewCompact
>('/overview');
return normalizeDashboardOverview(overview);
@@ -1,27 +1,27 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import {OpenFlareBaseService} from './base.service';
import type {DnsAccountItem, DnsAccountMutationPayload} from './types';
export class DnsAccountService extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/dns-accounts';
export class DnsAccountService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/dns-accounts';
static async list(): Promise<DnsAccountItem[]> {
return this.legacyGet<DnsAccountItem[]>('/');
return this.get<DnsAccountItem[]>('/');
}
static async create(
payload: DnsAccountMutationPayload,
): Promise<DnsAccountItem> {
return this.legacyPost<DnsAccountItem>('/', payload);
return this.post<DnsAccountItem>('/', payload);
}
static async update(
id: number,
payload: DnsAccountMutationPayload,
): Promise<DnsAccountItem> {
return this.legacyPost<DnsAccountItem>(`/${id}/update`, payload);
return this.post<DnsAccountItem>(`/${id}/update`, payload);
}
static async delete(id: number): Promise<void> {
return this.legacyPost<void>(`/${id}/delete`);
static async deleteById(id: number): Promise<void> {
return this.post<void>(`/${id}/delete`);
}
}
@@ -1,6 +1,5 @@
export { AboutService } from './about.service';
export { LegacyOpenFlareBaseService } from './legacy-base.service';
export type { LegacyApiResponse } from './legacy-base.service';
export { OpenFlareBaseService } from './base.service';
export { NodeService } from './node.service';
export { ProxyRouteService } from './proxy-route.service';
@@ -1,58 +0,0 @@
import apiClient from '@/lib/services/core/api-client';
import {ApiErrorBase} from '@/lib/services/core/errors';
import type {InternalAxiosRequestConfig} from 'axios';
export interface LegacyApiResponse<T> {
success: boolean;
message: string;
data: T;
}
/**
* OpenFlare legacy 业务 API 基类
* 解析 { success, message, data } 响应格式
*/
export class LegacyOpenFlareBaseService {
protected static readonly basePath: string = '';
protected static getFullPath(path: string): string {
const full = `${this.basePath}${path}`;
// Avoid Next.js 308 (strip slash) <-> Gin 301 (add slash) redirect loops in dev proxy.
if (full.length > 1 && full.endsWith('/')) {
return full.slice(0, -1);
}
return full;
}
protected static parseLegacyResponse<T>(body: LegacyApiResponse<T>): T {
if (!body.success) {
throw new ApiErrorBase(body.message || '请求失败');
}
return body.data;
}
protected static async legacyGet<T>(
path: string,
params?: Record<string, unknown>,
config?: InternalAxiosRequestConfig,
): Promise<T> {
const response = await apiClient.get<LegacyApiResponse<T>>(
this.getFullPath(path),
{ ...config, params } as InternalAxiosRequestConfig,
);
return this.parseLegacyResponse(response.data);
}
protected static async legacyPost<T>(
path: string,
data?: unknown,
config?: InternalAxiosRequestConfig,
): Promise<T> {
const response = await apiClient.post<LegacyApiResponse<T>>(
this.getFullPath(path),
data,
config,
);
return this.parseLegacyResponse(response.data);
}
}
@@ -1,4 +1,4 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import {OpenFlareBaseService} from './base.service';
import type {
NodeAgentReleaseInfo,
NodeAgentUpdatePayload,
@@ -9,60 +9,60 @@ import type {
ReleaseChannel,
} from './types';
export class NodeService extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/nodes';
export class NodeService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/nodes';
static async listNodes(): Promise<NodeItem[]> {
return this.legacyGet<NodeItem[]>('/');
return this.get<NodeItem[]>('/');
}
static async createNode(payload: NodeMutationPayload): Promise<NodeItem> {
return this.legacyPost<NodeItem>('/', payload);
return this.post<NodeItem>('/', payload);
}
static async updateNode(id: number, payload: NodeMutationPayload): Promise<NodeItem> {
return this.legacyPost<NodeItem>(`/${id}/update`, payload);
return this.post<NodeItem>(`/${id}/update`, payload);
}
static async deleteNode(id: number): Promise<void> {
return this.legacyPost<void>(`/${id}/delete`);
return this.post<void>(`/${id}/delete`);
}
static async getBootstrapToken(): Promise<NodeBootstrapToken> {
return this.legacyGet<NodeBootstrapToken>('/bootstrap-token');
return this.get<NodeBootstrapToken>('/bootstrap-token');
}
static async rotateBootstrapToken(): Promise<NodeBootstrapToken> {
return this.legacyPost<NodeBootstrapToken>('/bootstrap-token/rotate');
return this.post<NodeBootstrapToken>('/bootstrap-token/rotate');
}
static async requestAgentUpdate(
id: number,
payload?: NodeAgentUpdatePayload,
): Promise<NodeItem> {
return this.legacyPost<NodeItem>(`/${id}/agent-update`, payload ?? {});
return this.post<NodeItem>(`/${id}/agent-update`, payload ?? {});
}
static async requestForceSync(id: number): Promise<NodeItem> {
return this.legacyPost<NodeItem>(`/${id}/force-sync`);
return this.post<NodeItem>(`/${id}/force-sync`);
}
static async requestOpenrestyRestart(id: number): Promise<NodeItem> {
return this.legacyPost<NodeItem>(`/${id}/openresty-restart`);
return this.post<NodeItem>(`/${id}/openresty-restart`);
}
static async getAgentRelease(
id: number,
channel: ReleaseChannel = 'stable',
): Promise<NodeAgentReleaseInfo> {
return this.legacyGet<NodeAgentReleaseInfo>(`/${id}/agent-release`, { channel });
return this.get<NodeAgentReleaseInfo>(`/${id}/agent-release`, { channel });
}
static async getObservability(
id: number,
options?: { hours?: number; limit?: number },
): Promise<NodeObservability> {
return this.legacyGet<NodeObservability>(`/${id}/observability`, {
return this.get<NodeObservability>(`/${id}/observability`, {
hours: options?.hours,
limit: options?.limit,
});
@@ -71,8 +71,8 @@ export class NodeService extends LegacyOpenFlareBaseService {
static async cleanupHealthEvents(
id: number,
): Promise<{ node_id: string; deleted_count: number }> {
return this.legacyPost<{ node_id: string; deleted_count: number }>(
return this.post<{ node_id: string; deleted_count: number }>(
`/${id}/observability/cleanup`,
);
}
}
}
@@ -1,4 +1,4 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import {OpenFlareBaseService} from './base.service';
import type {
DatabaseCleanupPayload,
DatabaseCleanupResult,
@@ -7,29 +7,29 @@ import type {
OptionItem,
} from './types';
export class OptionService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/option';
export class OptionService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/option';
static list(): Promise<OptionItem[]> {
return this.legacyGet<OptionItem[]>('/');
return this.get<OptionItem[]>('/');
}
static update(key: string, value: string): Promise<void> {
return this.legacyPost<void>('/update', { key, value });
return this.post<void>('/update', { key, value });
}
static updateBatch(options: OptionItem[]): Promise<void> {
const payload: OptionBatchPayload = { options };
return this.legacyPost<void>('/update-batch', payload);
return this.post<void>('/update-batch', payload);
}
static lookupGeoIP(provider: string, ip: string): Promise<GeoIPLookupResult> {
return this.legacyPost<GeoIPLookupResult>('/geoip/lookup', { provider, ip });
return this.post<GeoIPLookupResult>('/geoip/lookup', { provider, ip });
}
static cleanupDatabase(
payload: DatabaseCleanupPayload,
): Promise<DatabaseCleanupResult> {
return this.legacyPost<DatabaseCleanupResult>('/database/cleanup', payload);
return this.post<DatabaseCleanupResult>('/database/cleanup', payload);
}
}
}
@@ -1,29 +1,29 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {OriginDetail, OriginItem, OriginMutationPayload,} from './types';
import {OpenFlareBaseService} from './base.service';
import type {OriginDetail, OriginItem, OriginMutationPayload} from './types';
export class OriginService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/origins';
export class OriginService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/origins';
static list(): Promise<OriginItem[]> {
return this.legacyGet<OriginItem[]>('/');
return this.get<OriginItem[]>('/');
}
static get(id: number): Promise<OriginDetail> {
return this.legacyGet<OriginDetail>(`/${id}`);
static getById(id: number): Promise<OriginDetail> {
return this.get<OriginDetail>(`/${id}`);
}
static create(payload: OriginMutationPayload): Promise<OriginItem> {
return this.legacyPost<OriginItem>('/', payload);
return this.post<OriginItem>('/', payload);
}
static update(
id: number,
payload: OriginMutationPayload,
): Promise<OriginItem> {
return this.legacyPost<OriginItem>(`/${id}/update`, payload);
return this.post<OriginItem>(`/${id}/update`, payload);
}
static delete(id: number): Promise<void> {
return this.legacyPost<void>(`/${id}/delete`);
static deleteById(id: number): Promise<void> {
return this.post<void>(`/${id}/delete`);
}
}
}
@@ -1,11 +1,10 @@
import type {AxiosProgressEvent, InternalAxiosRequestConfig} from 'axios';
import apiClient from '@/lib/services/core/api-client';
import {ApiErrorBase} from '@/lib/services/core/errors';
import type {ApiResponse} from '@/lib/services/core';
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import {OpenFlareBaseService} from './base.service';
import type {
LegacyApiResponse,
PagesDeployment,
PagesDeploymentFile,
PagesDeploymentUploadPayload,
@@ -13,41 +12,41 @@ import type {
PagesProjectPayload,
} from './types';
export class PagesService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/pages';
export class PagesService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/pages';
static listProjects(): Promise<PagesProject[]> {
return this.legacyGet<PagesProject[]>('/');
return this.get<PagesProject[]>('/');
}
static getProject(id: number): Promise<PagesProject> {
return this.legacyGet<PagesProject>(`/${id}`);
return this.get<PagesProject>(`/${id}`);
}
static createProject(payload: PagesProjectPayload): Promise<PagesProject> {
return this.legacyPost<PagesProject>('/', payload);
return this.post<PagesProject>('/', payload);
}
static updateProject(
id: number,
payload: PagesProjectPayload,
): Promise<PagesProject> {
return this.legacyPost<PagesProject>(`/${id}/update`, payload);
return this.post<PagesProject>(`/${id}/update`, payload);
}
static deleteProject(id: number): Promise<void> {
return this.legacyPost<void>(`/${id}/delete`);
return this.post<void>(`/${id}/delete`);
}
static listDeployments(projectId: number): Promise<PagesDeployment[]> {
return this.legacyGet<PagesDeployment[]>(`/${projectId}/deployments`);
return this.get<PagesDeployment[]>(`/${projectId}/deployments`);
}
static listDeploymentFiles(
projectId: number,
deploymentId: number,
): Promise<PagesDeploymentFile[]> {
return this.legacyGet<PagesDeploymentFile[]>(
return this.get<PagesDeploymentFile[]>(
`/${projectId}/deployments/${deploymentId}/files`,
);
}
@@ -72,7 +71,7 @@ export class PagesService extends LegacyOpenFlareBaseService {
projectId: number,
deploymentId: number,
): Promise<PagesProject> {
return this.legacyPost<PagesProject>(
return this.post<PagesProject>(
`/${projectId}/deployments/${deploymentId}/activate`,
);
}
@@ -81,7 +80,7 @@ export class PagesService extends LegacyOpenFlareBaseService {
projectId: number,
deploymentId: number,
): Promise<void> {
return this.legacyPost<void>(
return this.post<void>(
`/${projectId}/deployments/${deploymentId}/delete`,
);
}
@@ -91,7 +90,7 @@ export class PagesService extends LegacyOpenFlareBaseService {
formData: FormData,
onProgress?: (percent: number) => void,
): Promise<T> {
const response = await apiClient.post<LegacyApiResponse<T>>(
const response = await apiClient.post<ApiResponse<T>>(
this.getFullPath(path),
formData,
{
@@ -104,10 +103,6 @@ export class PagesService extends LegacyOpenFlareBaseService {
} as InternalAxiosRequestConfig,
);
if (!response.data.success) {
throw new ApiErrorBase(response.data.message || '请求失败');
}
return response.data.data;
}
}
}
@@ -1,29 +1,29 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import {OpenFlareBaseService} from './base.service';
import type {ProxyRouteItem, ProxyRouteMutationPayload} from './types';
export class ProxyRouteService extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/proxy-routes';
export class ProxyRouteService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/proxy-routes';
static async list(): Promise<ProxyRouteItem[]> {
return this.legacyGet<ProxyRouteItem[]>('/');
return this.get<ProxyRouteItem[]>('/');
}
static async getById(id: number): Promise<ProxyRouteItem> {
return this.legacyGet<ProxyRouteItem>(`/${id}`);
return this.get<ProxyRouteItem>(`/${id}`);
}
static async create(payload: ProxyRouteMutationPayload): Promise<ProxyRouteItem> {
return this.legacyPost<ProxyRouteItem>('/', payload);
return this.post<ProxyRouteItem>('/', payload);
}
static async update(
id: number,
payload: ProxyRouteMutationPayload,
): Promise<ProxyRouteItem> {
return this.legacyPost<ProxyRouteItem>(`/${id}/update`, payload);
return this.post<ProxyRouteItem>(`/${id}/update`, payload);
}
static async delete(id: number): Promise<void> {
return this.legacyPost<void>(`/${id}/delete`);
static async deleteById(id: number): Promise<void> {
return this.post<void>(`/${id}/delete`);
}
}
}
@@ -1,10 +1,8 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import {OpenFlareBaseService} from './base.service';
import type {OpenFlarePublicStatus} from './types';
export class StatusService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api';
export class StatusService extends OpenFlareBaseService {
static getPublicStatus(): Promise<OpenFlarePublicStatus> {
return this.legacyGet<OpenFlarePublicStatus>('/status');
return this.get<OpenFlarePublicStatus>('/status');
}
}
}
@@ -1,4 +1,4 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import {OpenFlareBaseService} from './base.service';
import type {
AcmeAccountItem,
TlsCertificateApplyPayload,
@@ -9,68 +9,68 @@ import type {
TlsCertificateMutationPayload,
} from './types';
class AcmeAccountApi extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/acme-accounts';
class AcmeAccountApi extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/acme-accounts';
static getDefault(): Promise<AcmeAccountItem> {
return this.legacyGet<AcmeAccountItem>('/default');
return this.get<AcmeAccountItem>('/default');
}
}
export class TlsCertificateService extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/tls-certificates';
export class TlsCertificateService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/tls-certificates';
static async list(): Promise<TlsCertificateItem[]> {
return this.legacyGet<TlsCertificateItem[]>('/');
return this.get<TlsCertificateItem[]>('/');
}
static async getById(id: number): Promise<TlsCertificateDetailItem> {
return this.legacyGet<TlsCertificateDetailItem>(`/${id}`);
return this.get<TlsCertificateDetailItem>(`/${id}`);
}
static async getContent(id: number): Promise<TlsCertificateContentItem> {
return this.legacyGet<TlsCertificateContentItem>(`/${id}/content`);
return this.get<TlsCertificateContentItem>(`/${id}/content`);
}
static async create(
payload: TlsCertificateMutationPayload,
): Promise<TlsCertificateItem> {
return this.legacyPost<TlsCertificateItem>('/', payload);
return this.post<TlsCertificateItem>('/', payload);
}
static async update(
id: number,
payload: TlsCertificateMutationPayload,
): Promise<TlsCertificateItem> {
return this.legacyPost<TlsCertificateItem>(`/${id}/update`, payload);
return this.post<TlsCertificateItem>(`/${id}/update`, payload);
}
static async delete(id: number): Promise<void> {
return this.legacyPost<void>(`/${id}/delete`);
static async deleteById(id: number): Promise<void> {
return this.post<void>(`/${id}/delete`);
}
static async apply(
payload: TlsCertificateApplyPayload,
): Promise<TlsCertificateItem> {
return this.legacyPost<TlsCertificateItem>('/apply', payload);
return this.post<TlsCertificateItem>('/apply', payload);
}
static async renew(id: number): Promise<TlsCertificateItem> {
return this.legacyPost<TlsCertificateItem>(`/${id}/renew`);
return this.post<TlsCertificateItem>(`/${id}/renew`);
}
static async updateAcme(
id: number,
payload: TlsCertificateApplyPayload,
): Promise<TlsCertificateItem> {
return this.legacyPost<TlsCertificateItem>(`/${id}/update-acme`, payload);
return this.post<TlsCertificateItem>(`/${id}/update-acme`, payload);
}
static async convertToAcme(
id: number,
payload: TlsCertificateApplyPayload,
): Promise<TlsCertificateItem> {
return this.legacyPost<TlsCertificateItem>(`/${id}/convert-acme`, payload);
return this.post<TlsCertificateItem>(`/${id}/convert-acme`, payload);
}
static async importFile(
@@ -82,7 +82,7 @@ export class TlsCertificateService extends LegacyOpenFlareBaseService {
formData.append('cert_file', payload.certFile);
formData.append('key_file', payload.keyFile);
return this.legacyPost<TlsCertificateItem>('/import-file', formData);
return this.post<TlsCertificateItem>('/import-file', formData);
}
static getDefaultAcmeAccount(): Promise<AcmeAccountItem> {
@@ -1,13 +1,3 @@
/**
* OpenFlare 遗留业务 API 响应信封
* 阶段一 `/api/*` 端点使用此格式,与 Wavelet `/api/v1/*` 的 `{error_msg,data}` 不同
*/
export interface LegacyApiResponse<T = unknown> {
success: boolean;
message: string;
data: T;
}
export type ReleaseChannel = 'stable' | 'preview';
export type NodeType = 'edge_node' | 'tunnel_relay' | 'tunnel_client';
@@ -2,18 +2,18 @@ import type {InternalAxiosRequestConfig} from 'axios';
import {getApiBaseUrl} from '@/lib/services/core/config';
import {ApiErrorBase} from '@/lib/services/core/errors';
import type {ApiResponse} from '@/lib/services/core';
import type {LegacyApiResponse} from './legacy-base.service';
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {LatestReleaseInfo, ReleaseChannel, UpgradeStreamSnapshot, UploadedServerBinaryInfo,} from './types';
import {OpenFlareBaseService} from './base.service';
import type {LatestReleaseInfo, ReleaseChannel, UpgradeStreamSnapshot, UploadedServerBinaryInfo} from './types';
/**
* OpenFlare 服务端升级 API(遗留 `/api/update/*`)。
* OpenFlare 服务端升级 API(`/api/v1/custom/openflare/update/*`)。
*/
export class UpdateService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/update';
export class UpdateService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/update';
private static getLegacyApiUrl(path: string): string {
private static getApiUrl(path: string): string {
const baseURL = getApiBaseUrl();
const fullPath = this.getFullPath(path);
if (!baseURL) {
@@ -23,11 +23,11 @@ export class UpdateService extends LegacyOpenFlareBaseService {
}
static getLatestRelease(channel: ReleaseChannel = 'stable'): Promise<LatestReleaseInfo> {
return this.legacyGet<LatestReleaseInfo>('/latest-release', { channel });
return this.get<LatestReleaseInfo>('/latest-release', { channel });
}
static upgradeServer(channel: ReleaseChannel = 'stable'): Promise<LatestReleaseInfo> {
return this.legacyPost<LatestReleaseInfo>('/upgrade', { channel });
return this.post<LatestReleaseInfo>('/upgrade', { channel });
}
static uploadServerBinary(
@@ -38,14 +38,14 @@ export class UpdateService extends LegacyOpenFlareBaseService {
formData.append('binary', binary);
if (!onProgress) {
return this.legacyPost<UploadedServerBinaryInfo>('/manual-upload', formData, {
return this.post<UploadedServerBinaryInfo>('/manual-upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
} as InternalAxiosRequestConfig);
}
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', this.getLegacyApiUrl('/manual-upload'));
xhr.open('POST', this.getApiUrl('/manual-upload'));
xhr.withCredentials = true;
xhr.upload.addEventListener('progress', (event) => {
@@ -55,11 +55,11 @@ export class UpdateService extends LegacyOpenFlareBaseService {
});
xhr.addEventListener('load', () => {
let payload: LegacyApiResponse<UploadedServerBinaryInfo> | null = null;
let payload: ApiResponse<UploadedServerBinaryInfo> | null = null;
try {
payload = JSON.parse(
xhr.responseText,
) as LegacyApiResponse<UploadedServerBinaryInfo>;
) as ApiResponse<UploadedServerBinaryInfo>;
} catch {
payload = null;
}
@@ -77,10 +77,6 @@ export class UpdateService extends LegacyOpenFlareBaseService {
reject(new ApiErrorBase('响应格式无效', undefined, xhr.status));
return;
}
if (!payload.success) {
reject(new ApiErrorBase(payload.message || '请求失败', undefined, xhr.status));
return;
}
resolve(payload.data);
});
@@ -93,7 +89,7 @@ export class UpdateService extends LegacyOpenFlareBaseService {
}
static confirmManualServerUpgrade(uploadToken: string): Promise<UploadedServerBinaryInfo> {
return this.legacyPost<UploadedServerBinaryInfo>('/manual-upgrade', {
return this.post<UploadedServerBinaryInfo>('/manual-upgrade', {
upload_token: uploadToken,
});
}
@@ -103,7 +99,7 @@ export class UpdateService extends LegacyOpenFlareBaseService {
return null;
}
const apiUrl = this.getLegacyApiUrl('/logs/ws');
const apiUrl = this.getApiUrl('/logs/ws');
const resolvedUrl = apiUrl.startsWith('http://')
? `ws://${apiUrl.slice('http://'.length)}`
: apiUrl.startsWith('https://')
@@ -132,4 +128,4 @@ export class UpdateService extends LegacyOpenFlareBaseService {
return null;
}
}
}
}
@@ -1,9 +1,9 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import {OpenFlareBaseService} from './base.service';
export class UptimeKumaService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api/uptimekuma';
export class UptimeKumaService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/uptimekuma';
static sync(): Promise<void> {
return this.legacyPost<void>('/sync');
return this.post<void>('/sync');
}
}
}
@@ -1,4 +1,4 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import {OpenFlareBaseService} from './base.service';
import type {
WAFIPGroup,
WAFIPGroupAutoTestPayload,
@@ -10,76 +10,76 @@ import type {
WAFSiteRuleGroups,
} from './types';
export class WafService extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/waf';
export class WafService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/waf';
static async listRuleGroups(): Promise<WAFRuleGroup[]> {
return this.legacyGet<WAFRuleGroup[]>('/rule-groups');
return this.get<WAFRuleGroup[]>('/rule-groups');
}
static async getRuleGroup(id: number): Promise<WAFRuleGroup> {
return this.legacyGet<WAFRuleGroup>(`/rule-groups/${id}`);
return this.get<WAFRuleGroup>(`/rule-groups/${id}`);
}
static async createRuleGroup(payload: WAFRuleGroupPayload): Promise<WAFRuleGroup> {
return this.legacyPost<WAFRuleGroup>('/rule-groups', payload);
return this.post<WAFRuleGroup>('/rule-groups', payload);
}
static async updateRuleGroup(
id: number,
payload: WAFRuleGroupPayload,
): Promise<WAFRuleGroup> {
return this.legacyPost<WAFRuleGroup>(`/rule-groups/${id}/update`, payload);
return this.post<WAFRuleGroup>(`/rule-groups/${id}/update`, payload);
}
static async deleteRuleGroup(id: number): Promise<void> {
return this.legacyPost<void>(`/rule-groups/${id}/delete`);
return this.post<void>(`/rule-groups/${id}/delete`);
}
static async updateRuleGroupSites(id: number, ids: number[]): Promise<WAFRuleGroup> {
return this.legacyPost<WAFRuleGroup>(`/rule-groups/${id}/sites`, { ids });
return this.post<WAFRuleGroup>(`/rule-groups/${id}/sites`, { ids });
}
static async listSiteRuleGroups(routeId: number): Promise<WAFSiteRuleGroups> {
return this.legacyGet<WAFSiteRuleGroups>(`/sites/${routeId}/rule-groups`);
return this.get<WAFSiteRuleGroups>(`/sites/${routeId}/rule-groups`);
}
static async updateSiteRuleGroups(
routeId: number,
ids: number[],
): Promise<WAFSiteRuleGroups> {
return this.legacyPost<WAFSiteRuleGroups>(`/sites/${routeId}/rule-groups`, {
return this.post<WAFSiteRuleGroups>(`/sites/${routeId}/rule-groups`, {
ids,
});
}
static async listIPGroups(): Promise<WAFIPGroup[]> {
return this.legacyGet<WAFIPGroup[]>('/ip-groups');
return this.get<WAFIPGroup[]>('/ip-groups');
}
static async getIPGroup(id: number): Promise<WAFIPGroup> {
return this.legacyGet<WAFIPGroup>(`/ip-groups/${id}`);
return this.get<WAFIPGroup>(`/ip-groups/${id}`);
}
static async createIPGroup(payload: WAFIPGroupPayload): Promise<WAFIPGroup> {
return this.legacyPost<WAFIPGroup>('/ip-groups', payload);
return this.post<WAFIPGroup>('/ip-groups', payload);
}
static async updateIPGroup(id: number, payload: WAFIPGroupPayload): Promise<WAFIPGroup> {
return this.legacyPost<WAFIPGroup>(`/ip-groups/${id}/update`, payload);
return this.post<WAFIPGroup>(`/ip-groups/${id}/update`, payload);
}
static async deleteIPGroup(id: number): Promise<void> {
return this.legacyPost<void>(`/ip-groups/${id}/delete`);
return this.post<void>(`/ip-groups/${id}/delete`);
}
static async testIPGroup(
payload: WAFIPGroupAutoTestPayload,
): Promise<WAFIPGroupAutoTestResult> {
return this.legacyPost<WAFIPGroupAutoTestResult>('/ip-groups/test', payload);
return this.post<WAFIPGroupAutoTestResult>('/ip-groups/test', payload);
}
static async syncIPGroup(id: number): Promise<WAFIPGroupSyncResult> {
return this.legacyPost<WAFIPGroupSyncResult>(`/ip-groups/${id}/sync`);
return this.post<WAFIPGroupSyncResult>(`/ip-groups/${id}/sync`);
}
}
}
@@ -1,31 +1,35 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {ManagedDomainItem, ManagedDomainMatchResult, ManagedDomainMutationPayload,} from './types';
import {OpenFlareBaseService} from './base.service';
import type {
ManagedDomainItem,
ManagedDomainMatchResult,
ManagedDomainMutationPayload,
} from './types';
export class WebsiteService extends LegacyOpenFlareBaseService {
protected static readonly basePath = '/api/managed-domains';
export class WebsiteService extends OpenFlareBaseService {
protected static override readonly basePath: string = '/api/v1/custom/openflare/managed-domains';
static async list(): Promise<ManagedDomainItem[]> {
return this.legacyGet<ManagedDomainItem[]>('/');
return this.get<ManagedDomainItem[]>('/');
}
static async create(
payload: ManagedDomainMutationPayload,
): Promise<ManagedDomainItem> {
return this.legacyPost<ManagedDomainItem>('/', payload);
return this.post<ManagedDomainItem>('/', payload);
}
static async update(
id: number,
payload: ManagedDomainMutationPayload,
): Promise<ManagedDomainItem> {
return this.legacyPost<ManagedDomainItem>(`/${id}/update`, payload);
return this.post<ManagedDomainItem>(`/${id}/update`, payload);
}
static async delete(id: number): Promise<void> {
return this.legacyPost<void>(`/${id}/delete`);
static async deleteById(id: number): Promise<void> {
return this.post<void>(`/${id}/delete`);
}
static async match(domain: string): Promise<ManagedDomainMatchResult> {
return this.legacyGet<ManagedDomainMatchResult>('/match', {domain});
return this.get<ManagedDomainMatchResult>('/match', {domain});
}
}
}