Files
foka-ci/apps/web/src/pages/project/detail/service.ts
hurole c40532c757 feat(project): add workspace directory configuration and management (#1)
- Add projectDir field to Project model for workspace directory management
- Implement workspace directory creation, validation and Git initialization
- Add workspace status query endpoint with directory info and Git status
- Create GitManager for Git repository operations (clone, branch, commit info)
- Add PathValidator for secure path validation and traversal attack prevention
- Implement execution queue with concurrency control for build tasks

- Refactor project list UI to remove edit/delete actions from cards
- Add project settings tab in detail page with edit/delete functionality
- Add icons to all tabs (History, Code, Settings)
- Implement time formatting with dayjs in YYYY-MM-DD HH:mm:ss format
- Display all timestamps using browser's local timezone

- Update PipelineRunner to use workspace directory for command execution
- Add workspace status card showing directory path, size, Git info
- Enhance CreateProjectModal with repository URL validation

Reviewed-on: #1
2026-01-03 00:55:55 +08:00

227 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { type APIResponse, net } from '@shared';
import type { Branch, Commit, Deployment, Pipeline, Project, Step, CreateDeploymentRequest } from '../types';
class DetailService {
async getProject(id: string) {
const { data } = await net.request<APIResponse<Project>>({
url: `/api/projects/${id}`,
});
return data;
}
// 获取项目的所有流水线
async getPipelines(projectId: number) {
const { data } = await net.request<APIResponse<Pipeline[]>>({
url: `/api/pipelines?projectId=${projectId}`,
});
return data;
}
// 获取可用的流水线模板
async getPipelineTemplates() {
const { data } = await net.request<APIResponse<{id: number, name: string, description: string}[]>>({
url: '/api/pipelines/templates',
});
return data;
}
// 获取项目的部署记录
async getDeployments(projectId: number) {
const { data } = await net.request<any>({
url: `/api/deployments?projectId=${projectId}`,
});
return data.data;
}
// 创建流水线
async createPipeline(
pipeline: Omit<
Pipeline,
| 'id'
| 'createdAt'
| 'updatedAt'
| 'createdBy'
| 'updatedBy'
| 'valid'
| 'steps'
>,
) {
const { data } = await net.request<APIResponse<Pipeline>>({
url: '/api/pipelines',
method: 'POST',
data: pipeline,
});
return data;
}
// 基于模板创建流水线
async createPipelineFromTemplate(
templateId: number,
projectId: number,
name: string,
description?: string
) {
const { data } = await net.request<APIResponse<Pipeline>>({
url: '/api/pipelines/from-template',
method: 'POST',
data: {
templateId,
projectId,
name,
description
},
});
return data;
}
// 更新流水线
async updatePipeline(
id: number,
pipeline: Partial<
Omit<
Pipeline,
| 'id'
| 'createdAt'
| 'updatedAt'
| 'createdBy'
| 'updatedBy'
| 'valid'
| 'steps'
>
>,
) {
const { data } = await net.request<APIResponse<Pipeline>>({
url: `/api/pipelines/${id}`,
method: 'PUT',
data: pipeline,
});
return data;
}
// 删除流水线
async deletePipeline(id: number) {
const { data } = await net.request<APIResponse<null>>({
url: `/api/pipelines/${id}`,
method: 'DELETE',
});
return data;
}
// 获取流水线的所有步骤
async getSteps(pipelineId: number) {
const { data } = await net.request<APIResponse<Step[]>>({
url: `/api/steps?pipelineId=${pipelineId}`,
});
return data;
}
// 创建步骤
async createStep(
step: Omit<
Step,
'id' | 'createdAt' | 'updatedAt' | 'createdBy' | 'updatedBy' | 'valid'
>,
) {
const { data } = await net.request<APIResponse<Step>>({
url: '/api/steps',
method: 'POST',
data: step,
});
return data;
}
// 更新步骤
async updateStep(
id: number,
step: Partial<
Omit<
Step,
'id' | 'createdAt' | 'updatedAt' | 'createdBy' | 'updatedBy' | 'valid'
>
>,
) {
const { data } = await net.request<APIResponse<Step>>({
url: `/api/steps/${id}`,
method: 'PUT',
data: step,
});
return data;
}
// 删除步骤
async deleteStep(id: number) {
// DELETE请求返回204状态码通过拦截器处理为成功响应
const { data } = await net.request<APIResponse<null>>({
url: `/api/steps/${id}`,
method: 'DELETE',
});
return data;
}
// 获取项目的提交记录
async getCommits(projectId: number, branch?: string) {
const { data } = await net.request<APIResponse<Commit[]>>({
url: `/api/git/commits?projectId=${projectId}${branch ? `&branch=${branch}` : ''}`,
});
return data;
}
// 获取项目的分支列表
async getBranches(projectId: number) {
const { data } = await net.request<APIResponse<Branch[]>>({
url: `/api/git/branches?projectId=${projectId}`,
});
return data;
}
// 创建部署
async createDeployment(deployment: CreateDeploymentRequest) {
const { data } = await net.request<APIResponse<Deployment>>({
url: '/api/deployments',
method: 'POST',
data: deployment,
});
return data;
}
// 重新执行部署
async retryDeployment(deploymentId: number) {
const { data } = await net.request<APIResponse<Deployment>>({
url: `/api/deployments/${deploymentId}/retry`,
method: 'POST',
});
return data;
}
// 获取项目详情(包含工作目录状态)
async getProjectDetail(id: number) {
const { data } = await net.request<APIResponse<Project>>({
url: `/api/projects/${id}`,
});
return data;
}
// 更新项目
async updateProject(
id: number,
project: Partial<{ name: string; description: string; repository: string }>,
) {
const { data } = await net.request<APIResponse<Project>>({
url: `/api/projects/${id}`,
method: 'PUT',
data: project,
});
return data;
}
// 删除项目
async deleteProject(id: number) {
await net.request({
url: `/api/projects/${id}`,
method: 'DELETE',
});
}
}
export const detailService = new DetailService();