migrate frontend

This commit is contained in:
ryan
2026-06-18 18:05:00 +08:00
parent 31b114b0cc
commit e7b8993181
27 changed files with 1836 additions and 383 deletions
+3
View File
@@ -27,6 +27,7 @@ import {PushService} from './push';
import {AdminUploadService, UploadService} from './upload';
import {UserService} from './user';
import {
AboutService,
AccessLogService,
ApplyLogService,
ConfigVersionService,
@@ -61,6 +62,7 @@ const services = {
adminUpload: AdminUploadService,
dbManage: DbManageService,
push: PushService,
openflareAbout: AboutService,
openflareNode: NodeService,
openflareProxyRoute: ProxyRouteService,
openflareConfigVersion: ConfigVersionService,
@@ -181,6 +183,7 @@ export type { PushEvent, PushHistory, PushChannelConfig, ListPushHistoriesReques
// ==================== OpenFlare 业务服务导出 ====================
export {
AboutService,
LegacyOpenFlareBaseService,
NodeService,
ProxyRouteService,
@@ -0,0 +1,9 @@
import {LegacyOpenFlareBaseService} from './legacy-base.service';
export class AboutService extends LegacyOpenFlareBaseService {
protected static override readonly basePath = '/api';
static getAboutContent(): Promise<string> {
return this.legacyGet<string>('/about');
}
}
@@ -1,3 +1,4 @@
export { AboutService } from './about.service';
export { LegacyOpenFlareBaseService } from './legacy-base.service';
export type { LegacyApiResponse } from './legacy-base.service';
@@ -87,6 +88,9 @@ export type {
DatabaseCleanupTarget,
LatestReleaseInfo,
OpenFlarePublicStatus,
UpgradeLogItem,
UpgradeStreamSnapshot,
UploadedServerBinaryInfo,
OriginDetail,
OriginItem,
OriginMutationPayload,
@@ -108,6 +112,7 @@ export type {
TlsCertificateMutationPayload,
} from './types';
import {AboutService} from './about.service';
import {AccessLogService} from './access-log.service';
import {ApplyLogService} from './apply-log.service';
import {ConfigVersionService} from './config-version.service';
@@ -126,6 +131,7 @@ import {WafService} from './waf.service';
import {WebsiteService} from './website.service';
export const openflareServices = {
about: AboutService,
node: NodeService,
proxyRoute: ProxyRouteService,
configVersion: ConfigVersionService,
@@ -669,10 +669,28 @@ export interface LatestReleaseInfo {
has_update: boolean;
upgrade_supported: boolean;
in_progress: boolean;
upgrade_status: string;
upgrade_status: 'idle' | 'running' | 'succeeded' | 'failed' | string;
upgrade_logs: UpgradeLogItem[];
}
export interface UpgradeStreamSnapshot {
in_progress: boolean;
upgrade_status: 'idle' | 'running' | 'succeeded' | 'failed' | string;
upgrade_logs: UpgradeLogItem[];
}
export interface UploadedServerBinaryInfo {
upload_token: string;
file_name: string;
detected_version: string;
current_version: string;
has_update: boolean;
upgrade_supported: boolean;
ready_to_upgrade: boolean;
comparison_message: string;
uploaded_at: string;
}
export interface WAFRuleGroup {
id: number;
name: string;
@@ -1,16 +1,135 @@
import type {InternalAxiosRequestConfig} from 'axios';
import {getApiBaseUrl} from '@/lib/services/core/config';
import {ApiErrorBase} from '@/lib/services/core/errors';
import type {LegacyApiResponse} from './legacy-base.service';
import {LegacyOpenFlareBaseService} from './legacy-base.service';
import type {LatestReleaseInfo, ReleaseChannel} from './types';
import type {LatestReleaseInfo, ReleaseChannel, UpgradeStreamSnapshot, UploadedServerBinaryInfo,} 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';
private static getLegacyApiUrl(path: string): string {
const baseURL = getApiBaseUrl();
const fullPath = this.getFullPath(path);
if (!baseURL) {
return fullPath;
}
return `${baseURL.replace(/\/$/, '')}${fullPath}`;
}
static getLatestRelease(channel: ReleaseChannel = 'stable'): Promise<LatestReleaseInfo> {
return this.legacyGet<LatestReleaseInfo>('/latest-release', { channel });
}
static upgradeServer(channel: ReleaseChannel = 'stable'): Promise<LatestReleaseInfo> {
return this.legacyPost<LatestReleaseInfo>('/upgrade', { channel });
}
static uploadServerBinary(
binary: File,
onProgress?: (progress: number) => void,
): Promise<UploadedServerBinaryInfo> {
const formData = new FormData();
formData.append('binary', binary);
if (!onProgress) {
return this.legacyPost<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.withCredentials = true;
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
onProgress(Math.round((event.loaded / event.total) * 100));
}
});
xhr.addEventListener('load', () => {
let payload: LegacyApiResponse<UploadedServerBinaryInfo> | null = null;
try {
payload = JSON.parse(
xhr.responseText,
) as LegacyApiResponse<UploadedServerBinaryInfo>;
} catch {
payload = null;
}
if (xhr.status < 200 || xhr.status >= 300) {
reject(
new ApiErrorBase(
payload?.message || `请求失败(${xhr.status})`,
undefined,
xhr.status,
),
);
return;
}
if (!payload) {
reject(new ApiErrorBase('响应格式无效', undefined, xhr.status));
return;
}
if (!payload.success) {
reject(new ApiErrorBase(payload.message || '请求失败', undefined, xhr.status));
return;
}
resolve(payload.data);
});
xhr.addEventListener('error', () => {
reject(new ApiErrorBase('上传过程中网络连接中断,请检查网络后重试', undefined, 0));
});
xhr.send(formData);
});
}
static confirmManualServerUpgrade(uploadToken: string): Promise<UploadedServerBinaryInfo> {
return this.legacyPost<UploadedServerBinaryInfo>('/manual-upgrade', {
upload_token: uploadToken,
});
}
static createUpgradeLogsWebSocket(): WebSocket | null {
if (typeof window === 'undefined') {
return null;
}
const apiUrl = this.getLegacyApiUrl('/logs/ws');
const resolvedUrl = apiUrl.startsWith('http://')
? `ws://${apiUrl.slice('http://'.length)}`
: apiUrl.startsWith('https://')
? `wss://${apiUrl.slice('https://'.length)}`
: `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}${apiUrl}`;
return new WebSocket(resolvedUrl);
}
static parseUpgradeStreamSnapshot(rawMessage: string): UpgradeStreamSnapshot | null {
try {
const parsed = JSON.parse(rawMessage) as Partial<UpgradeStreamSnapshot>;
if (
typeof parsed.in_progress !== 'boolean' ||
typeof parsed.upgrade_status !== 'string' ||
!Array.isArray(parsed.upgrade_logs)
) {
return null;
}
return {
in_progress: parsed.in_progress,
upgrade_status: parsed.upgrade_status,
upgrade_logs: parsed.upgrade_logs,
};
} catch {
return null;
}
}
}