Files
upage-git/app/routes/api.1panel.$action/1panel.server.ts

303 lines
7.9 KiB
TypeScript

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 '../../.server/utils/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',
};
}