🎉 first commit

This commit is contained in:
LIlGG
2025-09-24 13:06:25 +08:00
commit 1f4fb103e9
409 changed files with 61222 additions and 0 deletions

View File

@@ -0,0 +1,302 @@
import crypto from 'crypto';
import type { _1PanelPaginationResponse, _1PanelResponse, _1PanelWebsite } from '~/types/1panel';
import { isBinaryString } from '~/utils/file-utils';
import { generateUUID } from '~/utils/uuid';
import { request } from '../../lib/fetch';
export interface _1PanelBaseParams {
serverUrl: string;
apiKey: string;
version?: 'v2';
}
export interface CreateWebsiteParams extends _1PanelBaseParams {
alias: string;
primaryDomain?: string;
proxyProtocol?: string;
isSSL?: boolean;
}
export interface GetWebsiteParams extends _1PanelBaseParams {
siteId: number;
}
export interface UploadFileContent {
path: string;
data: string;
fileName: string;
}
export interface UploadFileParams extends _1PanelBaseParams {
path: string;
data: string;
fileName: string;
}
export interface UploadFilesParams extends _1PanelBaseParams {
files: UploadFileContent[];
}
export interface DeleteWebsiteParams extends _1PanelBaseParams {
siteId: number;
}
export interface ToggleAccessParams extends _1PanelBaseParams {
siteId: number;
operate: 'start' | 'stop';
}
function get1PanelHost(serverUrl: string, version = 'v2') {
return `${serverUrl.replace(/\/$/, '')}/api/${version}`;
}
export async function getWebsiteList(
serverUrl: string,
apiKey: string,
version = 'v2',
): Promise<_1PanelResponse<_1PanelWebsite[]>> {
const response = await request(`${get1PanelHost(serverUrl, version)}/websites/list`, {
method: 'GET',
headers: {
...getAuthHeaders(apiKey),
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Failed to fetch website list: ${response.statusText}`);
}
return (await response.json()) as _1PanelResponse<_1PanelWebsite[]>;
}
export async function createWebsite(params: CreateWebsiteParams) {
const { serverUrl, apiKey, version = 'v2', alias, primaryDomain, proxyProtocol, isSSL } = params;
const domain = primaryDomain || `${alias}.upage.ai`;
const response = await request(`${get1PanelHost(serverUrl, version)}/websites`, {
method: 'POST',
headers: {
...getAuthHeaders(apiKey),
'Content-Type': 'application/json',
},
body: JSON.stringify({
IPV6: false,
alias,
appType: 'installed',
domains: [
{
domain,
port: 80,
ssl: isSSL || false,
},
],
appinstall: {
appId: 0,
name: '',
appDetailId: 0,
params: {},
version: '',
appkey: '',
advanced: false,
cpuQuota: 0,
memoryLimit: 0,
memoryUnit: 'MB',
containerName: '',
allowPort: false,
},
createDb: false,
enableFtp: false,
enableSSL: false,
ftpPassword: '',
ftpUser: '',
otherDomains: '',
primaryDomain: domain || '',
proxy: '',
proxyAddress: '',
proxyProtocol: proxyProtocol || 'http://',
proxyType: 'tcp',
remark: '',
runtimeType: 'php',
port: 9000,
siteDir: '',
taskID: generateUUID(),
type: 'static',
webSiteGroupId: 1,
}),
});
if (!response.ok) {
return {
code: response.status,
data: {
message: response.statusText,
},
};
}
return {
code: response.status,
data: {
domain: domain,
},
};
}
export async function getWebsite(params: GetWebsiteParams) {
const { serverUrl, apiKey, version = 'v2', siteId } = params;
const response = await request(`${get1PanelHost(serverUrl, version)}/websites/${siteId}`, {
method: 'GET',
headers: {
...getAuthHeaders(apiKey),
},
});
if (!response.ok) {
throw new Error(`Failed to get website: ${response.statusText}`);
}
return response.json() as Promise<_1PanelResponse<_1PanelWebsite>>;
}
export async function getWebsiteByPrimaryDomain(
serverUrl: string,
apiKey: string,
primaryDomain: string,
version = 'v2',
) {
const response = await request(`${get1PanelHost(serverUrl, version)}/websites/search`, {
method: 'POST',
headers: {
...getAuthHeaders(apiKey),
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: primaryDomain,
order: 'descending',
orderBy: 'favorite',
page: 1,
pageSize: 10,
type: '',
websiteGroupId: 0,
}),
});
if (!response.ok) {
throw new Error(`Failed to get website by primary domain: ${response.statusText}`);
}
return response.json() as Promise<_1PanelResponse<_1PanelPaginationResponse<_1PanelWebsite>>>;
}
export async function uploadFiles(params: UploadFilesParams) {
const { serverUrl, apiKey, version = 'v2', files } = params;
try {
for (const file of files) {
await uploadSingleContent({
serverUrl,
apiKey,
version,
path: file.path,
data: file.data,
fileName: file.fileName,
});
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Upload files failed: ${errorMessage}`);
}
}
export async function uploadSingleContent(params: UploadFileParams) {
const { serverUrl, apiKey, version = 'v2', path, data, fileName } = params;
try {
const formData = new FormData();
const fileContent = isBinaryString(data) ? Buffer.from(data, 'binary') : data;
const fileBlob = new Blob([fileContent], { type: 'application/octet-stream' });
const file = new File([fileBlob], fileName, { type: 'application/octet-stream' });
formData.append('file', file);
formData.append('path', path);
formData.append('overwrite', 'True');
const response = await request(`${get1PanelHost(serverUrl, version)}/files/upload`, {
method: 'POST',
headers: {
...getAuthHeaders(apiKey),
},
body: formData,
});
if (!response.ok) {
throw new Error(`Upload failed with status: ${response.status} ${response.statusText}`);
}
const result = (await response.json()) as _1PanelResponse<{ message: string }>;
if (result.code !== 200) {
throw new Error(`Upload failed with status: ${result.data?.message || 'Unknown error'}`);
}
return result.data;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Upload file failed: ${path} - ${errorMessage}`);
}
}
export async function deleteWebsite(params: DeleteWebsiteParams) {
const { serverUrl, apiKey, version = 'v2', siteId } = params;
const deleteResponse = await request(`${get1PanelHost(serverUrl, version)}/websites/del`, {
method: 'POST',
headers: {
...getAuthHeaders(apiKey),
},
body: JSON.stringify({
deleteApp: false,
deleteBackup: false,
forceDelete: false,
id: siteId,
}),
});
if (!deleteResponse.ok) {
throw new Error(`Failed to delete website: ${deleteResponse.statusText}`);
}
return true;
}
export async function toggleAccessWebsite(params: ToggleAccessParams) {
const { serverUrl, apiKey, version = 'v2', siteId, operate } = params;
const response = await request(`${get1PanelHost(serverUrl, version)}/websites/operate`, {
method: 'POST',
headers: {
...getAuthHeaders(apiKey),
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: siteId,
operate,
}),
});
if (!response.ok) {
throw new Error(`Failed to toggle access: ${response.statusText}`);
}
const result = (await response.json()) as _1PanelResponse<{ message: string }>;
if (result.code !== 200) {
throw new Error(`Failed to toggle access: ${result.data?.message || 'Unknown error'}`);
}
return true;
}
function getAuthHeaders(apiKey: string) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const content = `1panel${apiKey}${timestamp}`;
const token = crypto.createHash('md5').update(content).digest('hex');
return {
'1Panel-Token': token,
'1Panel-Timestamp': timestamp,
'Accept-Language': 'zh',
};
}

View File

@@ -0,0 +1,51 @@
import { type ActionFunctionArgs } from '@remix-run/node';
import { delete1PanelConnectionSettings, save1PanelConnectionSettings } from '~/lib/.server/connectionSettings';
import { getWebsiteList } from '~/routes/api.1panel.$action/1panel.server';
import { errorResponse, successResponse } from '~/utils/api-response';
import { createScopedLogger } from '~/utils/logger';
const logger = createScopedLogger('api.1panel.auth');
export type HandleAuthArgs = ActionFunctionArgs & {
userId: string;
};
export async function handleAuth({ request, userId }: HandleAuthArgs) {
try {
const { serverUrl, apiKey } = await request.json();
if (!serverUrl) {
return errorResponse(400, '缺少服务器地址参数');
}
if (!apiKey) {
return errorResponse(400, '缺少API密钥参数');
}
const parsedServerUrl = serverUrl.replace(/\/$/, '');
const websitesResponse = await getWebsiteList(parsedServerUrl, apiKey);
if (websitesResponse.code !== 200) {
await delete1PanelConnectionSettings(userId);
return errorResponse(websitesResponse.code, websitesResponse.message || '连接1Panel失败');
}
await save1PanelConnectionSettings(userId, parsedServerUrl, apiKey);
logger.info(`用户 ${userId} 成功验证并保存了 1Panel 连接信息`);
const websites = websitesResponse.data || [];
websites.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
return successResponse(
{
websites,
totalWebsites: websites.length,
lastUpdated: new Date().toISOString(),
},
'1Panel 连接验证成功',
);
} catch (error) {
logger.error('验证 1Panel 连接失败:', error);
return errorResponse(500, '验证 1Panel 连接失败');
}
}

View File

@@ -0,0 +1,46 @@
import type { ActionFunctionArgs } from '@remix-run/node';
import { get1PanelConnectionSettings } from '~/lib/.server/connectionSettings';
import { deleteDeploymentById, getDeploymentById } from '~/lib/.server/deployment';
import { errorResponse, successResponse } from '~/utils/api-response';
import { createScopedLogger } from '~/utils/logger';
import { deleteWebsite } from './1panel.server';
const logger = createScopedLogger('api.1panel.delete');
export type DeletePageArgs = ActionFunctionArgs & {
userId: string;
};
export async function deletePage({ request, userId }: DeletePageArgs) {
const { id } = await request.json();
try {
// 查找部署记录
const deployment = await getDeploymentById(id);
if (!deployment) {
return errorResponse(404, '未找到部署记录');
}
const connectionSettings = await get1PanelConnectionSettings(userId);
if (!connectionSettings) {
return errorResponse(401, '未配置1Panel连接信息');
}
const { deploymentId: siteId } = deployment;
const { serverUrl, apiKey } = connectionSettings;
const result = await deleteWebsite({ serverUrl, apiKey, siteId: Number(siteId) });
if (!result) {
return errorResponse(500, '删除1Panel网站失败');
}
await deleteDeploymentById(id);
logger.info(`用户 ${userId} 已删除 1Panel 部署 ${id}`);
return successResponse(true, '页面已删除');
} catch (error) {
logger.error(`删除 1Panel 部署 ${id} 失败:`, error);
return errorResponse(500, error instanceof Error ? error.message : '删除失败');
}
}

View File

@@ -0,0 +1,217 @@
import { type ActionFunctionArgs } from '@remix-run/node';
import { get1PanelConnectionSettings, save1PanelConnectionSettings } from '~/lib/.server/connectionSettings';
import { createOrUpdateDeployment, getLatestDeployment } from '~/lib/.server/deployment';
import { createScopedLogger } from '~/lib/.server/logger';
import {
createWebsite,
getWebsite,
getWebsiteByPrimaryDomain,
type UploadFileContent,
uploadFiles,
} from '~/routes/api.1panel.$action/1panel.server';
import type { _1PanelWebsite, _1PanelWebsiteInfo } from '~/types/1panel';
import { DeploymentPlatformEnum, DeploymentStatusEnum } from '~/types/deployment';
import { errorResponse, successResponse } from '~/utils/api-response';
interface DeployRequestBody {
websiteId: number;
files: Record<string, string>;
chatId: string;
serverUrl?: string;
apiKey?: string;
websiteDomain?: string;
protocol?: string;
}
export type HandleDeployArgs = ActionFunctionArgs & {
userId: string;
};
const logger = createScopedLogger('api.1panel.deploy');
export async function handleDeploy({ request, userId }: HandleDeployArgs) {
try {
const {
websiteId,
files,
chatId,
serverUrl: requestServerUrl,
apiKey: requestApiKey,
websiteDomain,
protocol,
} = (await request.json()) as DeployRequestBody;
// 从用户设置中获取连接信息
let connectionSettings = await get1PanelConnectionSettings(userId);
// 如果请求体中提供了连接信息,优先使用请求体中的信息,并更新用户设置
if (requestServerUrl && requestApiKey) {
connectionSettings = {
serverUrl: requestServerUrl,
apiKey: requestApiKey,
};
// 更新用户设置
await save1PanelConnectionSettings(userId, requestServerUrl, requestApiKey);
}
// 如果没有连接信息,返回错误
if (!connectionSettings) {
logger.warn('未配置1Panel连接信息');
return errorResponse(401, '未配置1Panel连接信息请先设置服务器地址和API密钥');
}
const { serverUrl, apiKey } = connectionSettings;
logger.debug('action => request', { websiteId, files, chatId, serverUrl, websiteDomain, protocol });
const existingDeployment = await getLatestDeployment(userId, chatId, DeploymentPlatformEnum._1PANEL);
let targetWebsiteId;
if (websiteId) {
targetWebsiteId = websiteId;
} else if (existingDeployment?.deploymentId) {
targetWebsiteId = parseInt(existingDeployment.deploymentId);
} else {
targetWebsiteId = undefined;
}
let websiteInfo: _1PanelWebsiteInfo | undefined;
if (targetWebsiteId) {
const websiteResponse = await getWebsite({
serverUrl,
apiKey,
siteId: targetWebsiteId,
});
logger.debug('action => getWebsite', JSON.stringify(websiteResponse));
if (websiteResponse.data) {
const existingWebsite = websiteResponse.data as _1PanelWebsite;
websiteInfo = {
id: existingWebsite.id,
domain: existingWebsite.primaryDomain,
url: `${existingWebsite.protocol.toLowerCase()}://${existingWebsite.primaryDomain}`,
alias: existingWebsite.alias,
sitePath: existingWebsite.sitePath,
chatId,
};
}
}
if (!websiteInfo) {
// If no websiteId provided, create a new website
const alias = `upage-${chatId}-${Date.now()}`;
const createWebsiteResponse = await createWebsite({
serverUrl,
apiKey,
alias,
primaryDomain: websiteDomain,
proxyProtocol: `${protocol || 'http'}://`,
isSSL: protocol === 'https',
});
logger.debug('action => createWebsite', JSON.stringify(createWebsiteResponse));
if (createWebsiteResponse.code !== 200) {
logger.warn('无法创建网站', JSON.stringify(createWebsiteResponse));
return errorResponse(400, `无法创建网站: ${createWebsiteResponse.data?.message || 'Unknown error'}`);
}
const { domain } = createWebsiteResponse.data as { domain: string };
const webSiteInfo = await getWebsiteByPrimaryDomain(serverUrl, apiKey, domain);
if (webSiteInfo.code !== 200) {
logger.warn('无法获取网站信息', JSON.stringify(webSiteInfo));
return errorResponse(400, '无法获取网站信息');
}
if (webSiteInfo.data.items == null) {
logger.warn('获取网站失败,请检查 1Panel 日志', JSON.stringify(webSiteInfo));
return errorResponse(400, '获取网站失败,请检查 1Panel 日志');
}
const newWebsite = webSiteInfo.data.items.find((item) => item.alias === alias);
if (!newWebsite) {
logger.warn('无法获取网站信息', JSON.stringify(newWebsite));
return errorResponse(400, '无法获取网站信息');
}
targetWebsiteId = newWebsite.id;
websiteInfo = {
id: newWebsite.id,
domain: newWebsite.primaryDomain,
sitePath: newWebsite.sitePath,
alias: newWebsite.alias,
url: `${newWebsite.protocol.toLowerCase()}://${newWebsite.primaryDomain}`,
chatId,
};
}
logger.info('创建网站成功 => ', websiteInfo.id, websiteInfo.domain, websiteInfo.url);
if (!websiteInfo) {
return errorResponse(400, '无法创建网站');
}
const deploymentFiles: UploadFileContent[] = [];
for (const [filePath, content] of Object.entries(files)) {
const lastSlashIndex = filePath.lastIndexOf('/');
const folderPath = lastSlashIndex !== -1 ? filePath.substring(0, lastSlashIndex + 1) : '';
const fileName = lastSlashIndex !== -1 ? filePath.substring(lastSlashIndex + 1) : filePath;
// 获取 filename
deploymentFiles.push({
path: `${websiteInfo.sitePath}/index/${folderPath}`,
fileName,
data: content,
});
}
logger.debug('action => uploadFiles', JSON.stringify(deploymentFiles));
try {
await uploadFiles({
serverUrl,
apiKey,
version: 'v2',
files: deploymentFiles,
});
} catch (error) {
logger.warn('action => uploadFiles error', JSON.stringify(error));
const errorMessage = error instanceof Error ? error.message : String(error);
return errorResponse(400, `无法上传文件: ${errorMessage}`);
}
try {
await createOrUpdateDeployment({
userId,
chatId,
platform: DeploymentPlatformEnum._1PANEL,
deploymentId: String(websiteInfo.id),
url: websiteInfo.url,
status: DeploymentStatusEnum.SUCCESS,
metadata: {
domain: websiteInfo.domain,
alias: websiteInfo.alias,
sitePath: websiteInfo.sitePath,
serverUrl,
},
});
logger.info(`为用户 ${userId} 创建了 1Panel 部署记录`);
} catch (error) {
logger.error('创建部署记录失败:', error);
}
return successResponse(
{
deploy: {
id: websiteInfo.id,
domain: websiteInfo.domain,
url: websiteInfo.url,
},
},
'部署成功',
);
} catch (error) {
console.error('1Panel deploy error:', error);
return errorResponse(500, '部署到 1Panel 失败');
}
}

View File

@@ -0,0 +1,64 @@
import { type ActionFunctionArgs, type LoaderFunctionArgs } from '@remix-run/node';
import { requireAuth } from '~/lib/.server/auth';
import { createScopedLogger } from '~/lib/.server/logger';
import { errorResponse } from '~/utils/api-response';
import { handleAuth } from './auth.server';
import { deletePage } from './delete.server';
import { handleDeploy } from './deploy.server';
import { getStats } from './stats.server';
import { toggleAccess } from './toggle-access.server';
import { handleWebsites } from './websites.server';
const logger = createScopedLogger('api.1panel.route');
export async function loader(args: LoaderFunctionArgs) {
const { request, params } = args;
const authResult = await requireAuth(request, { isApi: true });
if (authResult instanceof Response) {
return authResult;
}
const userId = authResult.userInfo?.sub;
if (!userId) {
return errorResponse(401, '用户未登录');
}
switch (params.action) {
case 'stats':
return getStats({ ...args, userId });
default:
return errorResponse(404, `未知的 API 操作: ${params.action}`);
}
}
export async function action(args: ActionFunctionArgs) {
const { request, params } = args;
const authResult = await requireAuth(request, { isApi: true });
if (authResult instanceof Response) {
return authResult;
}
const userId = authResult.userInfo?.sub;
if (!userId) {
return errorResponse(401, '用户未登录');
}
logger.debug('处理 1Panel API 请求', { action: params.action });
// 根据参数调用不同的处理函数
switch (params.action) {
case 'deploy':
return handleDeploy({ ...args, userId });
case 'websites':
return handleWebsites({ ...args, userId });
case 'auth':
return handleAuth({ ...args, userId });
case 'toggle-access':
return toggleAccess({ ...args, userId });
case 'delete':
return deletePage({ ...args, userId });
default:
logger.warn('未知的 API 操作', { action: params.action });
return errorResponse(404, `未知的 API 操作: ${params.action}`);
}
}

View File

@@ -0,0 +1,46 @@
import { type LoaderFunctionArgs } from '@remix-run/node';
import { get1PanelConnectionSettings } from '~/lib/.server/connectionSettings';
import { getWebsiteList } from '~/routes/api.1panel.$action/1panel.server';
import { errorResponse, successResponse } from '~/utils/api-response';
import { createScopedLogger } from '~/utils/logger';
const logger = createScopedLogger('api.1panel.stats');
export type GetStatsArgs = LoaderFunctionArgs & {
userId: string;
};
export async function getStats({ userId }: GetStatsArgs) {
try {
// 从用户设置中获取连接信息
const connectionSettings = await get1PanelConnectionSettings(userId);
if (!connectionSettings) {
return errorResponse(401, '未连接到1Panel请先设置服务器地址和API密钥');
}
const { serverUrl, apiKey } = connectionSettings;
// 获取网站列表
const websitesResponse = await getWebsiteList(serverUrl, apiKey);
if (websitesResponse.code !== 200) {
return errorResponse(websitesResponse.code, websitesResponse.message || '获取网站列表失败');
}
const websites = websitesResponse.data || [];
websites.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
return successResponse(
{
websites,
totalWebsites: websites.length,
lastUpdated: new Date().toISOString(),
},
'获取 1Panel 网站统计信息成功',
);
} catch (error) {
logger.error('获取 1Panel 网站统计信息失败:', error);
return errorResponse(500, '获取 1Panel 网站统计信息失败');
}
}

View File

@@ -0,0 +1,54 @@
import type { ActionFunctionArgs } from '@remix-run/node';
import { get1PanelConnectionSettings } from '~/lib/.server/connectionSettings';
import { getDeploymentById, updateDeploymentStatus } from '~/lib/.server/deployment';
import { errorResponse, successResponse } from '~/utils/api-response';
import { createScopedLogger } from '~/utils/logger';
import { toggleAccessWebsite } from './1panel.server';
const logger = createScopedLogger('api.1panel.toggle-access');
export type ToggleAccessArgs = ActionFunctionArgs & {
userId: string;
};
export async function toggleAccess({ request, userId }: ToggleAccessArgs) {
const { id } = await request.json();
try {
const deployment = await getDeploymentById(id);
if (!deployment) {
return errorResponse(404, '未找到部署记录');
}
const connectionSettings = await get1PanelConnectionSettings(userId);
if (!connectionSettings) {
return errorResponse(401, '未配置1Panel连接信息');
}
const currentStatus = deployment.status;
const newStatus = currentStatus !== 'inactive' ? 'inactive' : 'success';
const { deploymentId: siteId } = deployment;
const { serverUrl, apiKey } = connectionSettings;
const operate = newStatus === 'success' ? 'start' : 'stop';
const result = await toggleAccessWebsite({ serverUrl, apiKey, siteId: Number(siteId), operate });
if (!result) {
return errorResponse(500, '切换访问状态失败');
}
await updateDeploymentStatus(id, newStatus);
logger.info(`用户 ${userId}${newStatus === 'success' ? '开启' : '停止'} 1Panel 网站 ${siteId} 的访问`);
return successResponse(
{
status: newStatus,
},
`${newStatus === 'success' ? '开启' : '停止'}访问`,
);
} catch (error) {
logger.error(`切换1Panel部署 ${id} 访问状态失败:`, error);
return errorResponse(500, error instanceof Error ? error.message : '操作失败');
}
}

View File

@@ -0,0 +1,88 @@
import { type ActionFunctionArgs } from '@remix-run/node';
import { get1PanelConnectionSettings, save1PanelConnectionSettings } from '~/lib/.server/connectionSettings';
import { deleteDeploymentsByPlatformAndId } from '~/lib/.server/deployment';
import { createScopedLogger } from '~/lib/.server/logger';
import { deleteWebsite, getWebsiteList } from '~/routes/api.1panel.$action/1panel.server';
import { DeploymentPlatformEnum } from '~/types/deployment';
import { errorResponse, successResponse } from '~/utils/api-response';
interface WebsiteListRequestBody {
serverUrl?: string;
apiKey?: string;
}
interface DeleteWebsiteRequestBody {
serverUrl?: string;
apiKey?: string;
siteId: number;
}
export type HandleWebsitesArgs = ActionFunctionArgs & {
userId: string;
};
const logger = createScopedLogger('api.1panel.websites');
export async function handleWebsites({ request, userId }: HandleWebsitesArgs) {
try {
if (request.method === 'POST') {
const requestBody = (await request.json()) as WebsiteListRequestBody;
let connectionSettings = await get1PanelConnectionSettings(userId);
if (requestBody.serverUrl && requestBody.apiKey) {
connectionSettings = {
serverUrl: requestBody.serverUrl,
apiKey: requestBody.apiKey,
};
await save1PanelConnectionSettings(userId, requestBody.serverUrl, requestBody.apiKey);
}
if (!connectionSettings) {
return errorResponse(401, '未配置1Panel连接信息请先设置服务器地址和API密钥');
}
const { serverUrl, apiKey } = connectionSettings;
const websites = await getWebsiteList(serverUrl, apiKey);
if (websites.code !== 200) {
logger.warn('获取网站列表失败', JSON.stringify(websites));
return errorResponse(websites.code, websites.message);
}
return successResponse(websites.data ?? [], '获取网站列表成功');
}
if (request.method === 'DELETE') {
const requestBody = (await request.json()) as DeleteWebsiteRequestBody;
if (!requestBody.siteId) {
return errorResponse(400, '未提供网站ID');
}
const connectionSettings = await get1PanelConnectionSettings(userId);
if (!connectionSettings) {
return errorResponse(401, '未配置1Panel连接信息请先设置服务器地址和API密钥');
}
const { serverUrl, apiKey } = connectionSettings;
await deleteWebsite({
serverUrl,
apiKey,
siteId: requestBody.siteId,
});
await deleteDeploymentsByPlatformAndId(DeploymentPlatformEnum._1PANEL, requestBody.siteId);
return successResponse(true, '网站删除成功');
}
logger.warn('不支持的 HTTP 方法', JSON.stringify({ url: request.url, method: request.method }));
return errorResponse(405, '不支持的 HTTP 方法');
} catch (error) {
logger.error('处理 1Panel 网站请求错误:', error);
return errorResponse(500, '处理请求失败 - ' + (error instanceof Error ? error.message : String(error)));
}
}