mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-29 07:56:37 +08:00
49ab2915ee
* fix: increase updateTunnel timeout to 120s Editing tunnel entry nodes triggers forward sync to all entry nodes. If nodes are offline or many forwards exist, the sync can exceed the default 30s timeout. Match the timeout used by other heavy operations like batchDeleteTunnels. * docs: add commercial white-label design spec * docs: add commercial white-label implementation plan * feat: add license activation endpoint and authorization check for commercial config keys * feat: add frontend api and update site config state for license * feat: conditionally hide flvx footer brand * feat: ui settings for commercial white-label and license activation * fix: add missing licenseActivateRequest and fix GetConfig in handler.go * docs: add keygen.sh license integration design spec * docs: add keygen.sh integration implementation plan * feat: add machine fingerprint generation * feat: add keygen.sh api client * feat: integrate keygen into license activation endpoint * feat: add periodic license validation job * fix: remove accidentally leaked dash kernel test codes that caused compilation failures * fix: correct keygen validation scope and binding logic * feat: hardcode Keygen.sh account ID * fix: relax strict validation matching after successful machine activation
532 lines
18 KiB
TypeScript
532 lines
18 KiB
TypeScript
import type {
|
|
BatchOperationResult,
|
|
ForwardDiagnosisApiData,
|
|
ForwardApiItem,
|
|
GroupPermissionApiItem,
|
|
NodeReleaseApiItem,
|
|
NodeApiItem,
|
|
SpeedLimitApiItem,
|
|
TunnelBatchDeletePreviewApiData,
|
|
TunnelBatchDeleteWithForwardsApiData,
|
|
TunnelDeletePreviewApiData,
|
|
TunnelDeleteWithForwardsApiData,
|
|
TunnelDiagnosisApiData,
|
|
TunnelGroupApiItem,
|
|
UserApiItem,
|
|
UserGroupApiItem,
|
|
UserListQuery,
|
|
UserPackageInfoApiData,
|
|
UserTunnelPermissionApiItem,
|
|
TunnelApiItem,
|
|
UserTunnelApiItem,
|
|
UserMutationPayload,
|
|
NodeMutationPayload,
|
|
TunnelMutationPayload,
|
|
UserQuotaResetPayload,
|
|
UserTunnelAssignPayload,
|
|
UserTunnelListQuery,
|
|
UserTunnelRemovePayload,
|
|
ForwardMutationPayload,
|
|
SpeedLimitMutationPayload,
|
|
UpdatePasswordPayload,
|
|
BackupImportPayload,
|
|
NodeMetricApiItem,
|
|
TunnelMetricApiItem,
|
|
ServiceMonitorApiItem,
|
|
ServiceMonitorResultApiItem,
|
|
ServiceMonitorLimitsApiData,
|
|
ServiceMonitorMutationPayload,
|
|
MonitorNodeApiItem,
|
|
MonitorTunnelApiItem,
|
|
MonitorPermissionApiItem,
|
|
MonitorAccessApiData,
|
|
TunnelQualityApiItem,
|
|
} from "./types";
|
|
|
|
import axios from "axios";
|
|
|
|
import Network from "./network";
|
|
|
|
export type ReleaseChannel = "stable" | "dev";
|
|
|
|
// 登陆相关接口
|
|
export interface LoginData {
|
|
username: string;
|
|
password: string;
|
|
captchaId: string;
|
|
}
|
|
|
|
export interface LoginResponse {
|
|
token: string;
|
|
role_id: number;
|
|
name: string;
|
|
requirePasswordChange?: boolean;
|
|
}
|
|
|
|
export const login = (data: LoginData) =>
|
|
Network.post<LoginResponse>("/user/login", data);
|
|
|
|
// 用户CRUD操作 - 全部使用POST请求
|
|
export const createUser = (data: UserMutationPayload) =>
|
|
Network.post("/user/create", data);
|
|
export const getAllUsers = (pageData: UserListQuery = {}) =>
|
|
Network.post<UserApiItem[]>("/user/list", pageData);
|
|
export const updateUser = (data: UserMutationPayload) =>
|
|
Network.post("/user/update", data);
|
|
export const deleteUser = (id: number) => Network.post("/user/delete", { id });
|
|
export const getUserPackageInfo = () =>
|
|
Network.post<UserPackageInfoApiData>("/user/package");
|
|
|
|
// 节点CRUD操作 - 全部使用POST请求
|
|
export const createNode = (data: NodeMutationPayload) =>
|
|
Network.post("/node/create", data);
|
|
export const getNodeList = () => Network.post<NodeApiItem[]>("/node/list");
|
|
export const getDashboardNodeExpiryList = () =>
|
|
Network.post<NodeApiItem[]>("/node/list", {});
|
|
export const updateNode = (data: NodeMutationPayload) =>
|
|
Network.post("/node/update", data);
|
|
export const deleteNode = (id: number) => Network.post("/node/delete", { id });
|
|
export const getNodeInstallCommand = (
|
|
id: number,
|
|
channel: ReleaseChannel = "stable",
|
|
) => Network.post<string>("/node/install", { id, channel });
|
|
export const updateNodeOrder = (data: {
|
|
nodes: Array<{ id: number; inx: number }>;
|
|
}) => Network.post("/node/update-order", data);
|
|
export const dismissNodeExpiryReminder = (id: number) =>
|
|
Network.post("/node/dismiss-expiry-reminder", { id });
|
|
export const checkNodeStatus = (nodeId?: number) => {
|
|
const params = nodeId ? { nodeId } : {};
|
|
|
|
return Network.post("/node/check-status", params);
|
|
};
|
|
|
|
export const upgradeNode = (
|
|
id: number,
|
|
version?: string,
|
|
channel: ReleaseChannel = "stable",
|
|
) =>
|
|
Network.post(
|
|
"/node/upgrade",
|
|
{ id, version: version || "", channel },
|
|
{ timeout: 5 * 60 * 1000 },
|
|
);
|
|
export const batchUpgradeNodes = (
|
|
ids: number[],
|
|
version?: string,
|
|
channel: ReleaseChannel = "stable",
|
|
) =>
|
|
Network.post(
|
|
"/node/batch-upgrade",
|
|
{ ids, version: version || "", channel },
|
|
{ timeout: 15 * 60 * 1000 },
|
|
);
|
|
export const getNodeReleases = (channel: ReleaseChannel = "stable") =>
|
|
Network.post<NodeReleaseApiItem[]>("/node/releases", { channel });
|
|
export const rollbackNode = (id: number) =>
|
|
Network.post("/node/rollback", { id });
|
|
|
|
// 隧道CRUD操作 - 全部使用POST请求
|
|
export const createTunnel = (data: TunnelMutationPayload) =>
|
|
Network.post("/tunnel/create", data);
|
|
export const getTunnelList = () =>
|
|
Network.post<TunnelApiItem[]>("/tunnel/list");
|
|
export const getTunnelById = (id: number) =>
|
|
Network.post<TunnelApiItem>("/tunnel/get", { id });
|
|
export const updateTunnel = (data: TunnelMutationPayload) =>
|
|
Network.post("/tunnel/update", data, { timeout: 120_000 });
|
|
export const deleteTunnel = (id: number) =>
|
|
Network.post("/tunnel/delete", { id });
|
|
export const previewTunnelDelete = (id: number) =>
|
|
Network.post<TunnelDeletePreviewApiData>("/tunnel/delete-preview", { id });
|
|
export const deleteTunnelWithForwards = (data: {
|
|
id: number;
|
|
action: "replace" | "delete_forwards";
|
|
targetTunnelId?: number;
|
|
}) =>
|
|
Network.post<TunnelDeleteWithForwardsApiData | BatchOperationResult>(
|
|
"/tunnel/delete-with-forwards",
|
|
data,
|
|
);
|
|
export const previewBatchTunnelDelete = (ids: number[]) =>
|
|
Network.post<TunnelBatchDeletePreviewApiData>("/tunnel/batch-delete-preview", {
|
|
ids,
|
|
});
|
|
export const batchDeleteTunnelsWithForwards = (data: {
|
|
ids: number[];
|
|
action: "replace" | "delete_forwards";
|
|
targetTunnelId?: number;
|
|
}) =>
|
|
Network.post<TunnelBatchDeleteWithForwardsApiData>(
|
|
"/tunnel/batch-delete-with-forwards",
|
|
data,
|
|
);
|
|
export const diagnoseTunnel = (tunnelId: number) =>
|
|
Network.post<TunnelDiagnosisApiData>(
|
|
"/tunnel/diagnose",
|
|
{ tunnelId },
|
|
{ timeout: 120 * 1000 },
|
|
);
|
|
export const updateTunnelOrder = (data: {
|
|
tunnels: Array<{ id: number; inx: number }>;
|
|
}) => Network.post("/tunnel/update-order", data);
|
|
|
|
// 用户隧道权限管理操作 - 全部使用POST请求
|
|
export const assignUserTunnel = (data: UserTunnelAssignPayload) =>
|
|
Network.post("/tunnel/user/assign", data);
|
|
export const batchAssignUserTunnel = (data: {
|
|
userId: number;
|
|
tunnels: Array<{ tunnelId: number; speedId?: number | null }>;
|
|
}) => Network.post("/tunnel/user/batch-assign", data);
|
|
export const getUserTunnelList = (queryData: UserTunnelListQuery = {}) =>
|
|
Network.post<UserTunnelPermissionApiItem[]>("/tunnel/user/list", queryData);
|
|
export const removeUserTunnel = (params: UserTunnelRemovePayload) =>
|
|
Network.post("/tunnel/user/remove", params);
|
|
export const updateUserTunnel = (data: UserTunnelAssignPayload) =>
|
|
Network.post("/tunnel/user/update", data);
|
|
export const userTunnel = () =>
|
|
Network.post<UserTunnelApiItem[]>("/tunnel/user/tunnel");
|
|
|
|
// 转发CRUD操作 - 全部使用POST请求
|
|
export const createForward = (data: ForwardMutationPayload) =>
|
|
Network.post("/forward/create", data);
|
|
export const getForwardList = () =>
|
|
Network.post<ForwardApiItem[]>("/forward/list");
|
|
export const updateForward = (data: ForwardMutationPayload) =>
|
|
Network.post("/forward/update", data);
|
|
export const deleteForward = (id: number) =>
|
|
Network.post("/forward/delete", { id });
|
|
export const forceDeleteForward = (id: number) =>
|
|
Network.post("/forward/force-delete", { id });
|
|
|
|
// 转发服务控制操作 - 通过Java后端接口
|
|
export const pauseForwardService = (forwardId: number) =>
|
|
Network.post("/forward/pause", { id: forwardId });
|
|
export const resumeForwardService = (forwardId: number) =>
|
|
Network.post("/forward/resume", { id: forwardId });
|
|
|
|
// 转发诊断操作
|
|
export const diagnoseForward = (forwardId: number) =>
|
|
Network.post<ForwardDiagnosisApiData>(
|
|
"/forward/diagnose",
|
|
{ forwardId },
|
|
{ timeout: 120 * 1000 },
|
|
);
|
|
|
|
// 转发排序操作
|
|
export const updateForwardOrder = (data: {
|
|
forwards: Array<{ id: number; inx: number }>;
|
|
}) => Network.post("/forward/update-order", data);
|
|
|
|
// 限速规则CRUD操作 - 全部使用POST请求
|
|
export const createSpeedLimit = (data: SpeedLimitMutationPayload) =>
|
|
Network.post("/speed-limit/create", data);
|
|
export const getSpeedLimitList = () =>
|
|
Network.post<SpeedLimitApiItem[]>("/speed-limit/list");
|
|
export const updateSpeedLimit = (data: SpeedLimitMutationPayload) =>
|
|
Network.post("/speed-limit/update", data);
|
|
export const deleteSpeedLimit = (id: number) =>
|
|
Network.post("/speed-limit/delete", { id });
|
|
|
|
// 修改密码接口
|
|
export const updatePassword = (data: UpdatePasswordPayload) =>
|
|
Network.post("/user/updatePassword", data);
|
|
|
|
// 重置流量接口
|
|
export const resetUserFlow = (data: { id: number; type: number }) =>
|
|
Network.post("/user/reset", data);
|
|
export const resetUserQuota = (data: UserQuotaResetPayload) =>
|
|
Network.post("/user/quota/reset", data);
|
|
|
|
export const getUserGroups = (id: number) =>
|
|
Network.post<number[]>("/user/groups", { id });
|
|
|
|
// 网站配置相关接口
|
|
export const getConfigs = () =>
|
|
Network.post<Record<string, string>>("/config/list");
|
|
export const getConfigByName = (name: string) =>
|
|
Network.post<{ name: string; value: string }>("/config/get", { name });
|
|
export const updateConfigs = (configMap: Record<string, string>) =>
|
|
Network.post("/config/update", configMap);
|
|
export const updateConfig = (name: string, value: string) =>
|
|
Network.post("/config/update-single", { name, value });
|
|
|
|
export const activateLicense = (licenseKey: string) =>
|
|
Network.post("/license/activate", { license_key: licenseKey });
|
|
|
|
export const exportBackupData = () => Network.post("/backup/export");
|
|
export const importBackupData = (data: BackupImportPayload) =>
|
|
Network.post("/backup/import", data);
|
|
export const restoreBackupData = (data: BackupImportPayload) =>
|
|
Network.post("/backup/restore", data);
|
|
|
|
// 验证码相关接口
|
|
export const checkCaptcha = () => Network.post("/captcha/check");
|
|
export const generateCaptcha = () => Network.post(`/captcha/generate`);
|
|
export const verifyCaptcha = (data: { captchaId: string; trackData: string }) =>
|
|
Network.post("/captcha/verify", data);
|
|
|
|
// 批量操作接口
|
|
export const batchDeleteForwards = (ids: number[]) =>
|
|
Network.post<BatchOperationResult>("/forward/batch-delete", { ids });
|
|
export const batchPauseForwards = (ids: number[]) =>
|
|
Network.post<BatchOperationResult>("/forward/batch-pause", { ids });
|
|
export const batchResumeForwards = (ids: number[]) =>
|
|
Network.post<BatchOperationResult>("/forward/batch-resume", { ids });
|
|
export const batchDeleteTunnels = (ids: number[]) =>
|
|
Network.post<BatchOperationResult>("/tunnel/batch-delete", { ids });
|
|
export const batchDeleteNodes = (ids: number[]) =>
|
|
Network.post<BatchOperationResult>("/node/batch-delete", { ids });
|
|
export const batchRedeployForwards = (ids: number[]) =>
|
|
Network.post<BatchOperationResult>("/forward/batch-redeploy", { ids });
|
|
export const batchRedeployTunnels = (ids: number[]) =>
|
|
Network.post<BatchOperationResult>("/tunnel/batch-redeploy", { ids });
|
|
export const batchChangeTunnel = (data: {
|
|
forwardIds: number[];
|
|
targetTunnelId: number;
|
|
}) => Network.post<BatchOperationResult>("/forward/batch-change-tunnel", data);
|
|
|
|
// 分组与权限分配接口
|
|
export const getTunnelGroupList = () =>
|
|
Network.post<TunnelGroupApiItem[]>("/group/tunnel/list");
|
|
export const createTunnelGroup = (data: { name: string; status?: number }) =>
|
|
Network.post("/group/tunnel/create", data);
|
|
export const updateTunnelGroup = (data: {
|
|
id: number;
|
|
name: string;
|
|
status?: number;
|
|
}) => Network.post("/group/tunnel/update", data);
|
|
export const deleteTunnelGroup = (id: number) =>
|
|
Network.post("/group/tunnel/delete", { id });
|
|
export const assignTunnelsToGroup = (data: {
|
|
groupId: number;
|
|
tunnelIds: number[];
|
|
}) => Network.post("/group/tunnel/assign", data);
|
|
|
|
export const getUserGroupList = () =>
|
|
Network.post<UserGroupApiItem[]>("/group/user/list");
|
|
export const createUserGroup = (data: { name: string; status?: number }) =>
|
|
Network.post("/group/user/create", data);
|
|
export const updateUserGroup = (data: {
|
|
id: number;
|
|
name: string;
|
|
status?: number;
|
|
}) => Network.post("/group/user/update", data);
|
|
export const deleteUserGroup = (id: number) =>
|
|
Network.post("/group/user/delete", { id });
|
|
export const assignUsersToGroup = (data: {
|
|
groupId: number;
|
|
userIds: number[];
|
|
}) => Network.post("/group/user/assign", data);
|
|
|
|
export const getGroupPermissionList = () =>
|
|
Network.post<GroupPermissionApiItem[]>("/group/permission/list");
|
|
export const assignGroupPermission = (data: {
|
|
userGroupId: number;
|
|
tunnelGroupId: number;
|
|
}) => Network.post("/group/permission/assign", data);
|
|
export const removeGroupPermission = (id: number) =>
|
|
Network.post("/group/permission/remove", { id });
|
|
|
|
// 面板共享 (Federation) 接口
|
|
export const getPeerShareList = () =>
|
|
Network.post<Array<Record<string, unknown>>>("/federation/share/list");
|
|
export const createPeerShare = (data: {
|
|
name: string;
|
|
nodeId: number;
|
|
maxBandwidth?: number;
|
|
expiryTime?: number;
|
|
portRangeStart?: number;
|
|
portRangeEnd?: number;
|
|
allowedDomains?: string;
|
|
allowedIps?: string;
|
|
}) => Network.post("/federation/share/create", data);
|
|
export const updatePeerShare = (data: {
|
|
id: number;
|
|
name: string;
|
|
maxBandwidth: number;
|
|
expiryTime: number;
|
|
portRangeStart: number;
|
|
portRangeEnd: number;
|
|
allowedDomains: string;
|
|
allowedIps: string;
|
|
}) => Network.post("/federation/share/update", data);
|
|
export const deletePeerShare = (id: number) =>
|
|
Network.post("/federation/share/delete", { id });
|
|
export const resetPeerShareFlow = (id: number) =>
|
|
Network.post("/federation/share/reset-flow", { id });
|
|
export const getPeerRemoteUsageList = () =>
|
|
Network.post<Array<Record<string, unknown>>>(
|
|
"/federation/share/remote-usage/list",
|
|
);
|
|
export const importRemoteNode = (data: { remoteUrl: string; token: string }) =>
|
|
Network.post("/federation/node/import", data);
|
|
|
|
export interface BackupTypes {
|
|
users?: boolean;
|
|
nodes?: boolean;
|
|
tunnels?: boolean;
|
|
forwards?: boolean;
|
|
userTunnels?: boolean;
|
|
speedLimits?: boolean;
|
|
tunnelGroups?: boolean;
|
|
userGroups?: boolean;
|
|
permissions?: boolean;
|
|
configs?: boolean;
|
|
}
|
|
|
|
export const exportBackup = async (types: string[] = []) => {
|
|
const token = window.localStorage.getItem("token");
|
|
const baseURL = axios.defaults.baseURL || "/api/v1/";
|
|
|
|
const response = await axios.post(
|
|
`${baseURL}/backup/export`,
|
|
{ types },
|
|
{
|
|
headers: {
|
|
Authorization: token,
|
|
"Content-Type": "application/json",
|
|
},
|
|
responseType: "blob",
|
|
},
|
|
);
|
|
|
|
const url = window.URL.createObjectURL(new Blob([response.data]));
|
|
const link = document.createElement("a");
|
|
|
|
link.href = url;
|
|
const timestamp = new Date().toISOString().slice(0, 19).replace(/[:-]/g, "");
|
|
|
|
link.setAttribute("download", `backup_${timestamp}.json`);
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
window.URL.revokeObjectURL(url);
|
|
};
|
|
|
|
export const importBackup = (data: BackupImportPayload) =>
|
|
Network.post("/backup/import", data);
|
|
|
|
export interface AnnouncementData {
|
|
content: string;
|
|
enabled: number;
|
|
}
|
|
|
|
export const getAnnouncement = () =>
|
|
Network.get<AnnouncementData>("/announcement/get");
|
|
export const updateAnnouncement = (data: AnnouncementData) =>
|
|
Network.post("/announcement/update", data);
|
|
|
|
export const getNodeMetrics = (
|
|
nodeId: number,
|
|
start?: number,
|
|
end?: number,
|
|
) => {
|
|
const params: Record<string, string> = {};
|
|
|
|
if (start) params.start = String(start);
|
|
if (end) params.end = String(end);
|
|
|
|
return Network.get<NodeMetricApiItem[]>(
|
|
`/monitor/nodes/${nodeId}/metrics`,
|
|
params,
|
|
{ timeout: 60_000 },
|
|
);
|
|
};
|
|
|
|
export const getNodeMetricsLatest = (nodeId: number) =>
|
|
Network.get<NodeMetricApiItem>(`/monitor/nodes/${nodeId}/metrics/latest`);
|
|
|
|
export const getTunnelMetrics = (
|
|
tunnelId: number,
|
|
start?: number,
|
|
end?: number,
|
|
) => {
|
|
const params: Record<string, string> = {};
|
|
|
|
if (start) params.start = String(start);
|
|
if (end) params.end = String(end);
|
|
|
|
return Network.get<TunnelMetricApiItem[]>(
|
|
`/monitor/tunnels/${tunnelId}/metrics`,
|
|
params,
|
|
);
|
|
};
|
|
|
|
export const getMonitorTunnels = () =>
|
|
Network.get<MonitorTunnelApiItem[]>("/monitor/tunnels");
|
|
|
|
export const getMonitorTunnelQuality = () =>
|
|
Network.get<TunnelQualityApiItem[]>("/monitor/tunnels/quality");
|
|
|
|
export const getMonitorTunnelQualityHistory = (
|
|
tunnelId: number,
|
|
start?: number,
|
|
end?: number,
|
|
) => {
|
|
const params: Record<string, string> = {};
|
|
|
|
if (start) params.start = String(start);
|
|
if (end) params.end = String(end);
|
|
|
|
return Network.get<TunnelQualityApiItem[]>(
|
|
`/monitor/tunnels/${tunnelId}/quality`,
|
|
params,
|
|
);
|
|
};
|
|
|
|
export const getServiceMonitorList = () =>
|
|
Network.get<ServiceMonitorApiItem[]>("/monitor/services");
|
|
|
|
export const getServiceMonitorLimits = () =>
|
|
Network.get<ServiceMonitorLimitsApiData>("/monitor/services/limits");
|
|
|
|
export const createServiceMonitor = (data: ServiceMonitorMutationPayload) =>
|
|
Network.post<ServiceMonitorApiItem>("/monitor/services/create", data);
|
|
|
|
export const updateServiceMonitor = (data: ServiceMonitorMutationPayload) =>
|
|
Network.post<ServiceMonitorApiItem>("/monitor/services/update", data);
|
|
|
|
export const deleteServiceMonitor = (id: number) =>
|
|
Network.post("/monitor/services/delete", { id });
|
|
|
|
export const getServiceMonitorResults = (
|
|
monitorId: number,
|
|
options?: { limit?: number; start?: number; end?: number },
|
|
) => {
|
|
const params: Record<string, string> = {};
|
|
if (options?.start != null && options?.end != null) {
|
|
params.start = String(options.start);
|
|
params.end = String(options.end);
|
|
} else if (options?.limit != null) {
|
|
params.limit = String(options.limit);
|
|
}
|
|
return Network.get<ServiceMonitorResultApiItem[]>(
|
|
`/monitor/services/${monitorId}/results`,
|
|
params,
|
|
);
|
|
};
|
|
|
|
export const getServiceMonitorLatestResults = () =>
|
|
Network.get<ServiceMonitorResultApiItem[]>(
|
|
"/monitor/services/latest-results",
|
|
);
|
|
|
|
export const runServiceMonitor = (id: number) =>
|
|
Network.post<ServiceMonitorResultApiItem>("/monitor/services/run", { id });
|
|
|
|
export const getMonitorNodes = () =>
|
|
Network.get<MonitorNodeApiItem[]>("/monitor/nodes");
|
|
|
|
export const getMonitorAccess = () =>
|
|
Network.get<MonitorAccessApiData>("/monitor/access");
|
|
|
|
export const getMonitorPermissionList = () =>
|
|
Network.get<MonitorPermissionApiItem[]>("/monitor/permission/list");
|
|
|
|
export const assignMonitorPermission = (userId: number) =>
|
|
Network.post("/monitor/permission/assign", { userId });
|
|
|
|
export const removeMonitorPermission = (userId: number) =>
|
|
Network.post("/monitor/permission/remove", { userId });
|