diff --git a/.env b/.env index c56e29b..afb3775 100644 --- a/.env +++ b/.env @@ -2,4 +2,5 @@ VITE_APP_TITLE=楠溪屿后台管理系统 # API基础URL -VITE_API_BASE_URL=https://api.superwax.cn:4433/api +VITE_API_BASE_URL=https://apis.codeport.online/api +# VITE_API_BASE_URL=https://api.superwax.cn:4433/api diff --git a/.env.production b/.env.production index 725a2c1..be395e7 100644 --- a/.env.production +++ b/.env.production @@ -2,5 +2,6 @@ VITE_APP_TITLE=楠溪屿后台管理系统 # API基础URL - 生产环境 -VITE_API_BASE_URL=https://api.superwax.cn:4433/api +VITE_API_BASE_URL=https://apis.codeport.online/api +# VITE_API_BASE_URL=https://api.superwax.cn:4433/api diff --git a/src/api/database.ts b/src/api/database.ts index 3406c66..6aadc42 100644 --- a/src/api/database.ts +++ b/src/api/database.ts @@ -1,68 +1,364 @@ -import request from '@/utils/request' -import type { - DatabaseType, - DatabaseInfo, - DatabaseItem, - CreateDatabaseRequest, - UpdateDatabaseRequest, - DatabaseConnectionInfo, - DatabaseListParams, - DatabaseListResponse -} from '@/types/database' +/** + * 数据库管理 API + * 通过1Panel管理数据库 + */ +import { request } from '@/utils/request' -// 获取数据库信息 -export function getDatabaseInfo(type: DatabaseType): Promise { - return request.get(`/api/database/${type}/info`) +// ==================== 类型定义 ==================== + +/** + * 应用安装状态 + */ +export interface AppInstallStatus { + isExist: boolean + name: string + app: string + version: string + status: string + createdAt: string + lastBackupAt: string + appInstallId: number + containerName: string + installPath: string + httpPort: number + httpsPort: number } -// 启动数据库 -export function startDatabase(type: DatabaseType): Promise { - return request.post(`/api/database/${type}/start`) +/** + * 数据库记录 + */ +export interface DatabaseRecord { + id: number + createdAt: string + name: string + from: string + mysqlName: string + format: string + username: string + password: string + permission: string + isDelete: boolean + description: string + showPassword?: boolean // 前端控制是否显示密码 } -// 停止数据库 -export function stopDatabase(type: DatabaseType): Promise { - return request.post(`/api/database/${type}/stop`) +/** + * 数据库列表响应 + */ +export interface DatabaseListResponse { + total: number + items: DatabaseRecord[] } -// 重启数据库 -export function restartDatabase(type: DatabaseType): Promise { - return request.post(`/api/database/${type}/restart`) +/** + * 创建数据库参数 + */ +export interface CreateDatabaseParams { + serverId: number + name: string + username: string + password: string + format?: string // 字符集 format + collation?: string // 排序规则 collation (可选,后端接口没明确提到但通常MySQL需要) + permission?: string + permissionIPs?: string + type: string // 数据库类型,如mysql + database: string // 数据库名称,如mysql + from?: string // 固定 'local' + description?: string } -// 获取数据库列表 -export function getDatabaseList(type: DatabaseType, params?: DatabaseListParams): Promise { - return request.get(`/api/database/${type}/list`, { params }) +/** + * 删除数据库参数 + */ +export interface DeleteDatabaseParams { + serverId: number + id: number + type: string // 数据库类型,如mysql + database: string // 数据库名称/应用类型 + deleteBackup?: boolean + forceDelete?: boolean } -// 创建数据库 -export function createDatabase(type: DatabaseType, data: CreateDatabaseRequest): Promise { - return request.post(`/api/database/${type}`, data) +/** + * 修改密码参数 + */ +export interface ChangePasswordParams { + serverId: number + id: number + value: string // 新密码 (Base64) + type: string // 数据库类型 + database: string // 数据库名称 + from: string // 固定 'local' } -// 更新数据库 -export function updateDatabase(type: DatabaseType, id: number, data: UpdateDatabaseRequest): Promise { - return request.put(`/api/database/${type}/${id}`, data) +/** + * 更新描述参数 + */ +export interface UpdateDescriptionParams { + serverId: number + id: number + description: string } -// 删除数据库 -export function deleteDatabase(type: DatabaseType, id: number): Promise { - return request.delete(`/api/database/${type}/${id}`) +/** + * 操作应用参数 + */ +export interface OperateAppParams { + serverId: number + operate: 'start' | 'stop' | 'restart' + installId: number + [key: string]: any } -// 获取连接信息 -export function getDatabaseConnectionInfo(type: DatabaseType, id: number): Promise { - return request.get(`/api/database/${type}/${id}/connection`) +// ==================== API 方法 ==================== + +/** + * 检查应用安装状态(MySQL/PostgreSQL/Redis等) + */ +export async function checkAppInstalled( + serverId: number, + key: string, + name: string +): Promise { + const res = await request.post('/platform/database/app/check', { + serverId, + key, + name + }) + return res.data.data } -// 导出类型 -export type { - DatabaseType, - DatabaseInfo, - DatabaseItem, - CreateDatabaseRequest, - UpdateDatabaseRequest, - DatabaseConnectionInfo, - DatabaseListParams, - DatabaseListResponse +/** + * 查询数据库列表 + */ +export async function searchDatabases( + serverId: number, + database: string, + page: number = 1, + pageSize: number = 20 +): Promise { + const res = await request.post('/platform/database/search', { + serverId, + database, + page, + pageSize + }) + return res.data.data } + +/** + * 创建数据库 + */ +export async function createDatabase(params: CreateDatabaseParams): Promise { + await request.post('/platform/database/create', params) +} + +/** + * 删除数据库 + */ +export async function deleteDatabase(params: DeleteDatabaseParams): Promise { + await request.post('/platform/database/delete', params) +} + +/** + * 更新数据库描述 + */ +export async function updateDatabaseDescription(params: UpdateDescriptionParams): Promise { + await request.post('/platform/database/description/update', params) +} + +/** + * 修改数据库密码 + */ +export async function changeDatabasePassword(params: ChangePasswordParams): Promise { + await request.post('/platform/database/password/change', params) +} + +/** + * 操作应用(启动/停止/重启) + */ +export async function operateApp(params: OperateAppParams): Promise { + await request.post('/platform/database/app/operate', params) +} + +/** + * 获取数据库字符集排序规则选项 + */ +export async function getFormatOptions( + serverId: number, + type: string, + database: string, + format?: string +): Promise { + const res = await request.post('/platform/database/format/options', { + serverId, + type, + database, + format + }) + return res.data.data +} + +/** + * 应用信息 + */ +export interface AppInfo { + id: number + name: string + key: string + shortDescZh: string + shortDescEn: string + description: string + icon: string + type: string + status: string + website: string + github: string + document: string + versions: string[] + installed: boolean + [key: string]: any +} + +/** + * 应用版本详情 + */ +export interface AppDetail { + id: number + appId: number + version: string + dockerCompose: string + status: string + enable: boolean + params: { + formFields: Array<{ + default: any + envKey: string + label: Record + labelEn: string + labelZh: string + required: boolean + type: string + random?: boolean + rule?: string + }> + } + [key: string]: any +} + +/** + * 安装应用参数 + */ +export interface InstallAppParams { + serverId: number + appDetailId: number + name: string + version: string + params: Record + dockerCompose: string + taskID: string // 任务ID,用于查询安装日志 + // 高级设置 + advanced?: boolean + containerName?: string + allowPort?: boolean + specifyIP?: string + restartPolicy?: string + cpuQuota?: number + memoryLimit?: number + memoryUnit?: string + pullImage?: boolean + editCompose?: boolean + gpuConfig?: boolean + appID?: string + format?: string + collation?: string +} + +/** + * 安装应用返回结果 + */ +export interface InstallAppResult { + id: number + name: string + appId: number + appDetailId: number + version: string + status: string + containerName: string + httpPort: number + [key: string]: any +} + +/** + * 任务日志响应 + */ +export interface TaskLogResponse { + end: boolean + path: string + total: number + taskStatus: string + lines: string[] + totalLines: number +} + +/** + * 获取应用信息(如Redis) + */ +export async function getAppInfo( + serverId: number, + appKey: string +): Promise { + const res = await request.post('/platform/database/app/info', { + serverId, + appKey + }) + return res.data.data +} + +/** + * 获取应用版本详情 + */ +export async function getAppDetail( + serverId: number, + appId: number, + version: string +): Promise { + const res = await request.post('/platform/database/app/detail', { + serverId, + appId, + version + }) + return res.data.data +} + +/** + * 安装应用 + */ +export async function installApp(params: InstallAppParams): Promise { + const { serverId, ...restParams } = params + const res = await request.post('/platform/database/app/install', { + serverId, + ...restParams + }) + return res.data.data +} + +/** + * 读取任务日志 + */ +export async function readTaskLog( + serverId: number, + taskId: string, + page: number = 1, + pageSize: number = 500 +): Promise { + const res = await request.post('/platform/database/task/log', { + serverId, + taskId, + page, + pageSize + }) + return res.data.data +} + diff --git a/src/api/domain.ts b/src/api/domain.ts index 1886303..3e9429b 100644 --- a/src/api/domain.ts +++ b/src/api/domain.ts @@ -40,6 +40,12 @@ export interface DomainInfo { deployStatus?: DeployStatus lastDeployTime?: string lastDeployMessage?: string + // 运行环境关联 + runtimeId?: number + runtimeServerId?: number + runtimeName?: string + runtimeType?: string + runtimeDeployStatus?: DeployStatus // 时间 createdAt?: string updatedAt?: string @@ -135,6 +141,13 @@ export async function deployDomain(data: DomainDeployRequest): Promise(`/platform/domain/undeploy/${id}`) +} + /** * 检查域名 DNS 解析状态 */ @@ -173,3 +186,48 @@ export async function syncDomainsFromCertificates(serverId: number) { const res = await request.post<{ syncCount: number; message: string }>(`/platform/domain/sync-from-certificates/${serverId}`) return res.data.data } + +/** + * 部署运行环境到1Panel + */ +export async function deployRuntime(domainId: number) { + const res = await request.post(`/platform/domain/${domainId}/deploy-runtime`) + return res.data.data +} + +// ==================== Nginx配置相关 ==================== + +/** + * Nginx配置信息 + */ +export interface NginxConfigResult { + domain: string + path: string + content: string + name: string + error?: string +} + +/** + * 获取域名Nginx配置 + */ +export async function getNginxConfig(domainId: number): Promise { + const res = await request.get(`/platform/domain/${domainId}/nginx-config`) + return res.data.data +} + +/** + * 保存域名Nginx配置 + */ +export async function saveNginxConfig(domainId: number, content: string): Promise { + const res = await request.put(`/platform/domain/${domainId}/nginx-config`, { content }) + return res.data.data +} + +/** + * 重载Nginx + */ +export async function reloadNginx(domainId: number): Promise { + const res = await request.post(`/platform/domain/${domainId}/nginx-reload`) + return res.data.data +} diff --git a/src/api/file.ts b/src/api/file.ts new file mode 100644 index 0000000..9dc3777 --- /dev/null +++ b/src/api/file.ts @@ -0,0 +1,148 @@ +import { request } from '@/utils/request' + +export interface CheckFileResult { + path: string +} + +/** + * 批量检查文件是否存在 + * @param serverId 服务器ID + * @param paths 文件路径列表 + */ +export async function checkFileBatch(serverId: number, paths: string[]) { + const res = await request.post('/platform/files/check', { + serverId, + paths + }) + return res.data.data +} + +/** + * 分片上传文件(注意:1Panel 不支持真正的分片合并,此方法每次只上传一个分片作为完整文件) + * @deprecated 使用 uploadFile 代替 + */ +export async function uploadFileChunk( + serverId: number, + filename: string, + path: string, + chunkIndex: number, + chunkCount: number, + chunk: Blob, + onUploadProgress?: (progressEvent: { loaded: number; total?: number }) => void +) { + const formData = new FormData() + formData.append('serverId', serverId.toString()) + formData.append('filename', filename) + formData.append('path', path) + formData.append('chunkIndex', chunkIndex.toString()) + formData.append('chunkCount', chunkCount.toString()) + formData.append('chunk', chunk) + + const res = await request.post('/platform/files/upload/chunk', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + }, + timeout: 30 * 60 * 1000, // 30分钟超时,适合大文件分片 + onUploadProgress + }) + return res.data +} + +/** + * 直接上传完整文件 + * @param serverId 服务器ID + * @param path 目标目录路径 + * @param file 文件对象 + * @param onUploadProgress 上传进度回调 + */ +export async function uploadFile( + serverId: number, + path: string, + file: File, + onUploadProgress?: (progressEvent: { loaded: number; total?: number }) => void +) { + const formData = new FormData() + formData.append('serverId', serverId.toString()) + formData.append('path', path) + formData.append('file', file) + + const res = await request.post('/platform/files/upload', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + }, + timeout: 30 * 60 * 1000, // 30分钟超时,适合大文件 + onUploadProgress + }) + return res.data +} + +/** + * 分片上传 v2(支持真正的分片合并) + * @param serverId 服务器ID + * @param path 目标目录路径 + * @param filename 文件名 + * @param chunkIndex 分片索引(从0开始) + * @param chunkCount 分片总数 + * @param fileSize 文件总大小 + * @param uploadId 上传ID + * @param chunk 分片数据 + * @param onUploadProgress 上传进度回调 + */ +export async function uploadChunkV2( + serverId: number, + path: string, + filename: string, + chunkIndex: number, + chunkCount: number, + fileSize: number, + uploadId: string, + chunk: Blob, + onUploadProgress?: (progressEvent: { loaded: number; total?: number }) => void +) { + const formData = new FormData() + formData.append('serverId', serverId.toString()) + formData.append('path', path) + formData.append('filename', filename) + formData.append('chunkIndex', chunkIndex.toString()) + formData.append('chunkCount', chunkCount.toString()) + formData.append('fileSize', fileSize.toString()) + formData.append('uploadId', uploadId) + formData.append('chunk', chunk) + + const res = await request.post('/platform/files/upload/chunk/v2', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + }, + timeout: 10 * 60 * 1000, // 10分钟超时 + onUploadProgress + }) + return res.data +} + +/** + * 合并已上传的分片 + * @param serverId 服务器ID + * @param path 目标目录路径 + * @param filename 文件名 + * @param chunkCount 分片总数 + * @param fileSize 文件总大小 + * @param uploadId 上传ID + */ +export async function mergeChunks( + serverId: number, + path: string, + filename: string, + chunkCount: number, + fileSize: number, + uploadId: string +) { + const res = await request.post('/platform/files/upload/merge', { + serverId, + path, + filename, + chunkCount, + fileSize, + uploadId + }) + return res.data +} diff --git a/src/api/project.ts b/src/api/project.ts index df7cb41..d9bd9cb 100644 --- a/src/api/project.ts +++ b/src/api/project.ts @@ -248,3 +248,40 @@ export async function uploadFileChunk( return res.data.data } +// ==================== Nginx配置相关 ==================== + +/** + * Nginx配置信息 + */ +export interface NginxConfigResult { + domain: string + path: string + content: string + name: string + error?: string +} + +/** + * 获取项目Nginx配置 + */ +export async function getNginxConfig(projectId: number): Promise { + const res = await request.get(`/platform/project/${projectId}/nginx-config`) + return res.data.data +} + +/** + * 保存项目Nginx配置 + */ +export async function saveNginxConfig(projectId: number, content: string): Promise { + const res = await request.put(`/platform/project/${projectId}/nginx-config`, { content }) + return res.data.data +} + +/** + * 重载Nginx + */ +export async function reloadNginx(projectId: number): Promise { + const res = await request.post(`/platform/project/${projectId}/nginx-reload`) + return res.data.data +} + diff --git a/src/api/runtime.ts b/src/api/runtime.ts new file mode 100644 index 0000000..b2e14bb --- /dev/null +++ b/src/api/runtime.ts @@ -0,0 +1,310 @@ +import request from '@/utils/request' + +/** + * 运行时类型 + */ +export interface RuntimeType { + key: string + label: string +} + +/** + * 端口映射 + */ +export interface ExposedPort { + hostPort: number + containerPort: number + hostIP: string +} + +/** + * 运行时参数 + */ +export interface RuntimeParams { + CODE_DIR?: string + CONTAINER_NAME?: string + CONTAINER_PACKAGE_URL?: string + CONTAINER_PORT_0?: string + CUSTOM_SCRIPT?: string + EXEC_SCRIPT?: string + HOST_IP?: string + HOST_IP_0?: string + HOST_PORT_0?: string + NODE_VERSION?: string + PACKAGE_MANAGER?: string + RUN_INSTALL?: string + [key: string]: any +} + +/** + * 运行时记录 + */ +export interface RuntimeRecord { + id: number + name: string + resource: string + appDetailID: number + appID: number + source: string + status: string + type: string + image: string + params: RuntimeParams + message: string + version: string + createdAt: string + codeDir: string + appParams: any + port: string + path: string + exposedPorts: ExposedPort[] + taskId?: string // 任务ID + environments: any + volumes: any + extraHosts: any + containerStatus: string + container: string + remark: string +} + +/** + * 运行时列表响应 + */ +export interface RuntimeListResponse { + total: number + items: RuntimeRecord[] +} + +/** + * 搜索运行时列表 + */ +export async function searchRuntimes( + serverId: number, + type: string, + page: number = 1, + pageSize: number = 20 +): Promise { + const res = await request.post('/platform/runtime/search', { + serverId, + type, + page, + pageSize + }) + return res.data.data as RuntimeListResponse +} + +/** + * 搜索正在运行的运行时列表 + * @param serverId 服务器ID + * @param type 运行时类型(java/node) + * @param page 页码 + * @param pageSize 每页数量 + */ +export async function searchRunningRuntimes( + serverId: number, + type: string, + page: number = 1, + pageSize: number = 100 +): Promise { + const res = await request.post('/platform/runtime/search', { + serverId, + type, + page, + pageSize, + status: 'Running' + }) + return res.data.data as RuntimeListResponse +} + +/** + * 同步运行时状态 + */ +export async function syncRuntimes(serverId: number): Promise { + await request.post('/platform/runtime/sync', { + serverId + }) +} + +/** + * 操作运行时(启动/停止/重启) + */ +export async function operateRuntime(params: { + serverId: number + id: number + operate: 'start' | 'stop' | 'restart' +}): Promise { + await request.post('/platform/runtime/operate', params) +} + +/** + * 删除运行时 + */ +export async function deleteRuntime(params: { + serverId: number + id: number + forceDelete?: boolean + deleteFolder?: boolean + codeDir?: string +}): Promise { + await request.post('/platform/runtime/delete', params) +} + +/** + * 运行时应用信息 + */ +export interface RuntimeApp { + id: number + name: string + key: string + description: string + status: string + installed: boolean + type: string + versions?: string[] + tags?: string[] +} + +/** + * 运行时应用列表响应 + */ +export interface RuntimeAppListResponse { + total: number + items: RuntimeApp[] +} + +/** + * 搜索运行时应用列表 + */ +export async function searchRuntimeApps( + serverId: number, + type: string, + page: number = 1, + pageSize: number = 20 +): Promise { + const res = await request.post('/platform/runtime/apps/search', { + serverId, + type, + page, + pageSize + }) + return res.data.data as RuntimeAppListResponse +} + +/** + * 获取应用信息(含版本列表) + */ +export async function getRuntimeAppInfo( + serverId: number, + appKey: string +): Promise { + const res = await request.post('/platform/runtime/app/info', { + serverId, + appKey + }) + return res.data.data as RuntimeApp +} + +/** + * 运行时版本详情 + */ +export interface RuntimeVersionDetail { + id: number + appId: number + version: string + dockerCompose: string + status: string + enable: boolean + params: any +} + +/** + * 获取运行时版本详情 + */ +export async function getRuntimeDetail( + serverId: number, + appId: number, + version: string +): Promise { + const res = await request.post('/platform/runtime/detail', { + serverId, + appId, + version + }) + return res.data.data as RuntimeVersionDetail +} + +/** + * 创建运行时参数 + */ +export interface CreateRuntimeParams { + serverId: number + appDetailId: number + name: string + type: string + image: string + version: string + source: string + codeDir: string + params: Record + exposedPorts?: ExposedPort[] + remark?: string +} + +/** + * 创建运行时 + */ +export async function createRuntime(params: CreateRuntimeParams): Promise { + await request.post('/platform/runtime/create', params) +} + +/** + * 获取容器日志 + */ +export async function getContainerLog( + serverId: number, + containerName: string, + composePath?: string +): Promise<{ log: string }> { + // 注意:这里由于后端返回的是 Map {log: "..."},所以类型匹配 { log: string } + const res = await request.post('/platform/runtime/container/log', { + serverId, + containerName, + composePath + }) + return res.data.data +} + +/** + * 更新运行时参数 + */ +export interface UpdateRuntimeParams { + serverId: number + id: number + remark?: string + exposedPorts?: ExposedPort[] + params?: Record + codeDir?: string + [key: string]: any +} + +/** + * 更新运行时 + */ +export async function updateRuntime(params: UpdateRuntimeParams): Promise { + await request.post('/platform/runtime/update', params) +} + +export interface NodeScript { + name: string + script: string +} + +/** + * 获取Node脚本 + */ +export async function getNodeScripts(serverId: number, codeDir: string): Promise { + const res = await request.post('/platform/runtime/node/scripts', { serverId, codeDir }) + // @ts-ignore + return res.data.data +} + diff --git a/src/api/system/dept.ts b/src/api/system/dept.ts index 14922a0..556b8e8 100644 --- a/src/api/system/dept.ts +++ b/src/api/system/dept.ts @@ -1,35 +1,7 @@ import { request } from '@/utils/request' -// 部门类型定义 -export interface DeptRecord { - id: number - parentId: number - name: string - code?: string - leaderId?: number - leaderName?: string - phone?: string - email?: string - sort: number - status: number - remark?: string - createdAt?: string - children?: DeptRecord[] -} - -export interface DeptFormData { - id?: number - parentId: number - name: string - code?: string - leaderId?: number - leaderName?: string - phone?: string - email?: string - sort: number - status: number - remark?: string -} +import type { DeptRecord, DeptFormData } from '@/types/system/dept' +export type { DeptRecord, DeptFormData } /** * 获取部门树 diff --git a/src/components.d.ts b/src/components.d.ts index d407097..a17ad3a 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -33,6 +33,7 @@ declare module 'vue' { AFormItem: typeof import('ant-design-vue/es')['FormItem'] AFormItemRest: typeof import('ant-design-vue/es')['FormItemRest'] AInput: typeof import('ant-design-vue/es')['Input'] + AInputGroup: typeof import('ant-design-vue/es')['InputGroup'] AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] AInputSearch: typeof import('ant-design-vue/es')['InputSearch'] @@ -40,6 +41,9 @@ declare module 'vue' { ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent'] ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader'] ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider'] + AList: typeof import('ant-design-vue/es')['List'] + AListItem: typeof import('ant-design-vue/es')['ListItem'] + AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta'] AMenu: typeof import('ant-design-vue/es')['Menu'] AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider'] AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] @@ -47,9 +51,11 @@ declare module 'vue' { AModal: typeof import('ant-design-vue/es')['Modal'] APageHeader: typeof import('ant-design-vue/es')['PageHeader'] APagination: typeof import('ant-design-vue/es')['Pagination'] + ApartmentOutlined: typeof import('@ant-design/icons-vue')['ApartmentOutlined'] APopconfirm: typeof import('ant-design-vue/es')['Popconfirm'] APopover: typeof import('ant-design-vue/es')['Popover'] ApprovalDrawer: typeof import('./components/ApprovalDrawer/index.vue')['default'] + AppstoreOutlined: typeof import('@ant-design/icons-vue')['AppstoreOutlined'] AProgress: typeof import('ant-design-vue/es')['Progress'] ARadio: typeof import('ant-design-vue/es')['Radio'] ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] @@ -57,7 +63,10 @@ declare module 'vue' { ARangePicker: typeof import('ant-design-vue/es')['RangePicker'] AResult: typeof import('ant-design-vue/es')['Result'] ARow: typeof import('ant-design-vue/es')['Row'] + ArrowLeftOutlined: typeof import('@ant-design/icons-vue')['ArrowLeftOutlined'] + ArrowUpOutlined: typeof import('@ant-design/icons-vue')['ArrowUpOutlined'] ASelect: typeof import('ant-design-vue/es')['Select'] + ASelectOptGroup: typeof import('ant-design-vue/es')['SelectOptGroup'] ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] ASpace: typeof import('ant-design-vue/es')['Space'] ASpin: typeof import('ant-design-vue/es')['Spin'] @@ -76,23 +85,73 @@ declare module 'vue' { ATooltip: typeof import('ant-design-vue/es')['Tooltip'] ATree: typeof import('ant-design-vue/es')['Tree'] ATreeSelect: typeof import('ant-design-vue/es')['TreeSelect'] + AUpload: typeof import('ant-design-vue/es')['Upload'] AUploadDragger: typeof import('ant-design-vue/es')['UploadDragger'] BudgetDetailModal: typeof import('./components/finance/budget/BudgetDetailModal.vue')['default'] BudgetFormModal: typeof import('./components/finance/budget/BudgetFormModal.vue')['default'] + CheckCircleOutlined: typeof import('@ant-design/icons-vue')['CheckCircleOutlined'] + CheckOutlined: typeof import('@ant-design/icons-vue')['CheckOutlined'] + ClockCircleOutlined: typeof import('@ant-design/icons-vue')['ClockCircleOutlined'] + CloseCircleFilled: typeof import('@ant-design/icons-vue')['CloseCircleFilled'] + CloseCircleOutlined: typeof import('@ant-design/icons-vue')['CloseCircleOutlined'] + CloseOutlined: typeof import('@ant-design/icons-vue')['CloseOutlined'] + CloudServerOutlined: typeof import('@ant-design/icons-vue')['CloudServerOutlined'] + CloudUploadOutlined: typeof import('@ant-design/icons-vue')['CloudUploadOutlined'] + ClusterOutlined: typeof import('@ant-design/icons-vue')['ClusterOutlined'] + CopyOutlined: typeof import('@ant-design/icons-vue')['CopyOutlined'] + DatabaseOutlined: typeof import('@ant-design/icons-vue')['DatabaseOutlined'] + DeleteOutlined: typeof import('@ant-design/icons-vue')['DeleteOutlined'] + DesktopOutlined: typeof import('@ant-design/icons-vue')['DesktopOutlined'] DictFormModal: typeof import('./components/system/dict/DictFormModal.vue')['default'] DictItemDrawer: typeof import('./components/system/dict/DictItemDrawer.vue')['default'] + DislikeOutlined: typeof import('@ant-design/icons-vue')['DislikeOutlined'] + DownloadOutlined: typeof import('@ant-design/icons-vue')['DownloadOutlined'] DuplicateFileModal: typeof import('./components/DuplicateFileModal.vue')['default'] DynamicMenu: typeof import('./components/DynamicMenu/index.vue')['default'] + EditOutlined: typeof import('@ant-design/icons-vue')['EditOutlined'] + EyeInvisibleOutlined: typeof import('@ant-design/icons-vue')['EyeInvisibleOutlined'] + EyeOutlined: typeof import('@ant-design/icons-vue')['EyeOutlined'] + FileAddOutlined: typeof import('@ant-design/icons-vue')['FileAddOutlined'] + FileOutlined: typeof import('@ant-design/icons-vue')['FileOutlined'] + FileTextOutlined: typeof import('@ant-design/icons-vue')['FileTextOutlined'] + FileUploader: typeof import('./components/FileUploader.vue')['default'] FlowEditor: typeof import('./components/FlowEditor/index.vue')['default'] + FolderAddOutlined: typeof import('@ant-design/icons-vue')['FolderAddOutlined'] + FolderFilled: typeof import('@ant-design/icons-vue')['FolderFilled'] + FolderOpenOutlined: typeof import('@ant-design/icons-vue')['FolderOpenOutlined'] + FolderOutlined: typeof import('@ant-design/icons-vue')['FolderOutlined'] + FolderSelector: typeof import('./components/FolderSelector.vue')['default'] + GlobalOutlined: typeof import('@ant-design/icons-vue')['GlobalOutlined'] HelloWorld: typeof import('./components/HelloWorld.vue')['default'] + HistoryOutlined: typeof import('@ant-design/icons-vue')['HistoryOutlined'] IconPicker: typeof import('./components/common/IconPicker.vue')['default'] + LikeOutlined: typeof import('@ant-design/icons-vue')['LikeOutlined'] + LinkOutlined: typeof import('@ant-design/icons-vue')['LinkOutlined'] + LockOutlined: typeof import('@ant-design/icons-vue')['LockOutlined'] MenuFormModal: typeof import('./components/system/menu/MenuFormModal.vue')['default'] + MessageOutlined: typeof import('@ant-design/icons-vue')['MessageOutlined'] + MoreOutlined: typeof import('@ant-design/icons-vue')['MoreOutlined'] + PlusOutlined: typeof import('@ant-design/icons-vue')['PlusOutlined'] ProjectUpload: typeof import('./components/ProjectUpload.vue')['default'] + ReloadOutlined: typeof import('@ant-design/icons-vue')['ReloadOutlined'] ResetPasswordModal: typeof import('./components/system/user/ResetPasswordModal.vue')['default'] + RightOutlined: typeof import('@ant-design/icons-vue')['RightOutlined'] RoleFormModal: typeof import('./components/system/role/RoleFormModal.vue')['default'] + RollbackOutlined: typeof import('@ant-design/icons-vue')['RollbackOutlined'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + SafetyCertificateOutlined: typeof import('@ant-design/icons-vue')['SafetyCertificateOutlined'] + SaveOutlined: typeof import('@ant-design/icons-vue')['SaveOutlined'] + SearchOutlined: typeof import('@ant-design/icons-vue')['SearchOutlined'] + SwapOutlined: typeof import('@ant-design/icons-vue')['SwapOutlined'] + SyncOutlined: typeof import('@ant-design/icons-vue')['SyncOutlined'] + TagOutlined: typeof import('@ant-design/icons-vue')['TagOutlined'] + TaskLogViewer: typeof import('./components/TaskLogViewer.vue')['default'] + ThunderboltOutlined: typeof import('@ant-design/icons-vue')['ThunderboltOutlined'] UploadCore: typeof import('./components/UploadCore.vue')['default'] + UploadOutlined: typeof import('@ant-design/icons-vue')['UploadOutlined'] UserFormModal: typeof import('./components/system/user/UserFormModal.vue')['default'] + UserOutlined: typeof import('@ant-design/icons-vue')['UserOutlined'] + WarningOutlined: typeof import('@ant-design/icons-vue')['WarningOutlined'] } } diff --git a/src/components/FileUploader.vue b/src/components/FileUploader.vue new file mode 100644 index 0000000..d8134a3 --- /dev/null +++ b/src/components/FileUploader.vue @@ -0,0 +1,457 @@ + + + + + diff --git a/src/components/FolderSelector.vue b/src/components/FolderSelector.vue new file mode 100644 index 0000000..8763c5f --- /dev/null +++ b/src/components/FolderSelector.vue @@ -0,0 +1,412 @@ + + + + + diff --git a/src/components/TaskLogViewer.vue b/src/components/TaskLogViewer.vue new file mode 100644 index 0000000..9f26f93 --- /dev/null +++ b/src/components/TaskLogViewer.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index 0bbea7b..488cc5c 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -418,14 +418,34 @@ function updateMenuState() { projectStore.switchProject(projectId) } - const subPath = path.replace(`/app/${projectId}`, '') + // 同步子项目 iframe URL + const subPath = path.replace(`/app/${projectId}`, '') || '/dashboard' + const project = projectStore.getProjectById(projectId) + // 只有当项目已加载且 url 有效时才更新 + if (project?.baseUrl) { + const targetUrl = `${project.baseUrl}${subPath}?__embedded=true` + // 避免重复刷新 + if (subProjectUrl.value !== targetUrl) { + iframeLoading.value = true + subProjectUrl.value = targetUrl + } + } + const subMenuRouteMap = projectStore.getMenuRouteMap() for (const [menuKey, menuPath] of Object.entries(subMenuRouteMap)) { const relativePath = menuPath.replace(`/app/${projectId}`, '') if (subPath === relativePath || subPath.startsWith(relativePath + '/')) { selectedKeys.value = [menuKey] - // 展开逻辑略 + // 展开父级菜单 + const pathParts = menuKey.split('/').filter(Boolean) + if (pathParts.length > 1) { + const parentPath = `/${pathParts[0]}` + const openKey = `sub-${parentPath}` + if (!openKeys.value.includes(openKey)) { + openKeys.value.push(openKey) + } + } break } } diff --git a/src/types/database.ts b/src/types/database.ts deleted file mode 100644 index 76ed176..0000000 --- a/src/types/database.ts +++ /dev/null @@ -1,63 +0,0 @@ -// 数据库类型 -export type DatabaseType = 'mysql' | 'postgresql' | 'redis' - -// 数据库状态 -export type DatabaseStatus = 'running' | 'stopped' | 'error' - -// 数据库信息 -export interface DatabaseInfo { - type: DatabaseType - status: DatabaseStatus - version: string -} - -// 数据库实例 -export interface DatabaseItem { - id: number - name: string - username: string - password: string - description?: string - createdAt: string - updatedAt?: string -} - -// 创建数据库请求 -export interface CreateDatabaseRequest { - name: string - username: string - password: string - description?: string -} - -// 更新数据库请求 -export interface UpdateDatabaseRequest { - name?: string - username?: string - password?: string - description?: string -} - -// 数据库连接信息 -export interface DatabaseConnectionInfo { - host: string - port: number - username: string - password: string - database: string -} - -// 分页请求 -export interface DatabaseListParams { - keyword?: string - page?: number - pageSize?: number -} - -// 分页响应 -export interface DatabaseListResponse { - list: DatabaseItem[] - total: number - page: number - pageSize: number -} diff --git a/src/types/platform/certificates.ts b/src/types/platform/certificates.ts new file mode 100644 index 0000000..7e2ffe4 --- /dev/null +++ b/src/types/platform/certificates.ts @@ -0,0 +1,23 @@ +/** + * 证书管理类型定义 + */ + +export interface Certificate { + id: string + domain: string + otherDomain?: string + cn?: string + issuer: string + status: 'valid' | 'expired' | 'pending' + startDate: string + expireDate: string + autoRenew: boolean + verifyType: 'dns' + dnsAccount?: string + dnsAccountName?: string + dnsAccountType?: string + acmeAccount: string + remark?: string + certContent?: string + keyContent?: string +} diff --git a/src/types/platform/databases.ts b/src/types/platform/databases.ts new file mode 100644 index 0000000..79c9c17 --- /dev/null +++ b/src/types/platform/databases.ts @@ -0,0 +1,9 @@ +/** + * 数据库管理类型定义 + */ + +export interface DatabaseType { + key: string + label: string + defaultPort: number +} diff --git a/src/types/platform/files.ts b/src/types/platform/files.ts new file mode 100644 index 0000000..47929e7 --- /dev/null +++ b/src/types/platform/files.ts @@ -0,0 +1,11 @@ +/** + * 文件管理类型定义 + */ + +export interface FileItem { + name: string + isDir: boolean + size: number + modTime: string + mode?: string +} diff --git a/src/types/platform/menus.ts b/src/types/platform/menus.ts new file mode 100644 index 0000000..0d01a6c --- /dev/null +++ b/src/types/platform/menus.ts @@ -0,0 +1,17 @@ +/** + * 平台菜单管理类型定义 + */ + +import type { MenuItem } from '@/config' + +export interface PlatformProject { + id: string + name: string + shortName: string + logo: string + color?: string +} + +export interface MenuNode extends MenuItem { + order?: number +} diff --git a/src/types/platform/servers.ts b/src/types/platform/servers.ts new file mode 100644 index 0000000..d19d18c --- /dev/null +++ b/src/types/platform/servers.ts @@ -0,0 +1,36 @@ +/** + * 服务器管理类型定义 + */ + +export interface MonitorData { + cpu: number + memory: number + disk: number + networkIn: number + networkOut: number + time: string +} + +export type ServerStatus = 'online' | 'offline' | 'warning' | 'maintenance' +export type ServerType = 'cloud' | 'physical' | 'virtual' + +export interface ServerInfo { + id: number + name: string + ip: string + internalIp?: string + port?: number + user?: string + status: ServerStatus + type: ServerType + os?: string + cpu?: number + memory?: number + disk?: number + region?: string + expiredAt?: string + remark?: string + // 监控数据 + monitor?: MonitorData + tags?: string[] +} diff --git a/src/types/platform/upload-file.ts b/src/types/platform/upload-file.ts new file mode 100644 index 0000000..f96e48e --- /dev/null +++ b/src/types/platform/upload-file.ts @@ -0,0 +1,14 @@ +/** + * 上传文件类型定义 + */ + +export interface UploadFile { + uid: string + name: string + path: string // 相对路径 + size: number + type: string + status: 'pending' | 'uploading' | 'done' | 'error' + percent: number + file: File +} diff --git a/src/types/platform/upload.ts b/src/types/platform/upload.ts new file mode 100644 index 0000000..0a198f8 --- /dev/null +++ b/src/types/platform/upload.ts @@ -0,0 +1,9 @@ +/** + * 文件上传类型定义 + */ + +export interface UploadOptions { + path: string + overwrite: boolean + createDirs: boolean +} diff --git a/src/types/system/dept.ts b/src/types/system/dept.ts new file mode 100644 index 0000000..3d000d5 --- /dev/null +++ b/src/types/system/dept.ts @@ -0,0 +1,33 @@ +/** + * 部门管理类型定义 + */ + +export interface DeptRecord { + id: number + parentId: number + name: string + code?: string + leaderId?: number + leaderName?: string + phone?: string + email?: string + sort: number + status: number + remark?: string + createdAt?: string + children?: DeptRecord[] +} + +export interface DeptFormData { + id?: number + parentId: number + name: string + code?: string + leaderId?: number + leaderName?: string + phone?: string + email?: string + sort: number + status: number + remark?: string +} diff --git a/src/types/system/dict.ts b/src/types/system/dict.ts new file mode 100644 index 0000000..2972033 --- /dev/null +++ b/src/types/system/dict.ts @@ -0,0 +1,12 @@ +/** + * 字典管理类型定义 + */ + +export interface DictRecord { + id: number + name: string + code: string + remark?: string + status: number + createdAt?: string +} diff --git a/src/utils/request.ts b/src/utils/request.ts index c28fa31..07a953d 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -10,7 +10,7 @@ import type { ApiResponse } from '@/types/api/response' console.log('API Base URL:', import.meta.env.VITE_API_BASE_URL) const service: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || '/api', - timeout: 30000, + timeout: 5 * 60 * 1000, // 5分钟超时,适合大文件上传 headers: { 'Content-Type': 'application/json' } diff --git a/src/views/platform/certificates/index.vue b/src/views/platform/certificates/index.vue index 4bfedba..d9d9bab 100644 --- a/src/views/platform/certificates/index.vue +++ b/src/views/platform/certificates/index.vue @@ -262,15 +262,7 @@ + + + diff --git a/src/views/platform/domains/index.vue b/src/views/platform/domains/index.vue index 0214d5a..1884da1 100644 --- a/src/views/platform/domains/index.vue +++ b/src/views/platform/domains/index.vue @@ -172,6 +172,15 @@ 检测 + + + 删除网站 + + 删除 @@ -200,21 +209,75 @@ - - - {{ project.name }} - + + + + {{ project.name }} + + + + + {{ runtime.name }} ({{ runtime.serverName }}) + + + + + + + - + {{ server.name }} ({{ server.ip }}) + + 运行环境配置 + + + + + + + {{ type.label }} + + + + + + + + + {{ runtime.name }} + + + + + + 代理配置 @@ -268,8 +331,24 @@ @@ -279,84 +358,96 @@ - + + + + + +
+

请选择要从证书同步域名的目标服务器:

+ +
+
diff --git a/src/views/platform/environments/index.vue b/src/views/platform/environments/index.vue index fcd16ab..fcfb253 100644 --- a/src/views/platform/environments/index.vue +++ b/src/views/platform/environments/index.vue @@ -361,22 +361,7 @@ + + diff --git a/src/views/platform/log-center/index.vue b/src/views/platform/log-center/index.vue index 0e7c491..bd10103 100644 --- a/src/views/platform/log-center/index.vue +++ b/src/views/platform/log-center/index.vue @@ -242,17 +242,8 @@ + + + diff --git a/src/views/platform/servers/index.vue b/src/views/platform/servers/index.vue index 641b9ba..4731349 100644 --- a/src/views/platform/servers/index.vue +++ b/src/views/platform/servers/index.vue @@ -8,17 +8,16 @@ - 在线 - 离线 - 告警 - 维护中 + + {{ item.label }} + - 云服务器 - 物理机 - 虚拟机 + + {{ item.label }} + @@ -195,9 +194,9 @@ - 云服务器 - 物理机 - 虚拟机 + + {{ item.label }} + @@ -225,10 +224,9 @@ - 生产 - 测试 - 开发 - 备份 + + {{ item.label }} + @@ -347,21 +345,7 @@ diff --git a/src/views/platform/upload/index.vue b/src/views/platform/upload/index.vue index e223472..0bde906 100644 --- a/src/views/platform/upload/index.vue +++ b/src/views/platform/upload/index.vue @@ -105,18 +105,7 @@