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
This commit was merged in pull request #1.
This commit is contained in:
2026-01-03 00:55:55 +08:00
parent 9897bd04c2
commit c40532c757
23 changed files with 1859 additions and 229 deletions

View File

@@ -4,7 +4,6 @@ import { useState } from 'react';
import { useAsyncEffect } from '../../../hooks/useAsyncEffect';
import type { Project } from '../types';
import CreateProjectModal from './components/CreateProjectModal';
import EditProjectModal from './components/EditProjectModal';
import ProjectCard from './components/ProjectCard';
import { projectService } from './service';
@@ -12,8 +11,6 @@ const { Text } = Typography;
function ProjectPage() {
const [projects, setProjects] = useState<Project[]>([]);
const [editModalVisible, setEditModalVisible] = useState(false);
const [editingProject, setEditingProject] = useState<Project | null>(null);
const [createModalVisible, setCreateModalVisible] = useState(false);
useAsyncEffect(async () => {
@@ -21,22 +18,6 @@ function ProjectPage() {
setProjects(response.data);
}, []);
const handleEditProject = (project: Project) => {
setEditingProject(project);
setEditModalVisible(true);
};
const handleEditSuccess = (updatedProject: Project) => {
setProjects((prev) =>
prev.map((p) => (p.id === updatedProject.id ? updatedProject : p)),
);
};
const handleEditCancel = () => {
setEditModalVisible(false);
setEditingProject(null);
};
const handleCreateProject = () => {
setCreateModalVisible(true);
};
@@ -49,17 +30,6 @@ function ProjectPage() {
setCreateModalVisible(false);
};
const handleDeleteProject = async (project: Project) => {
try {
await projectService.delete(project.id);
setProjects((prev) => prev.filter((p) => p.id !== project.id));
Message.success('项目删除成功');
} catch (error) {
console.error('删除项目失败:', error);
Message.error('删除项目失败,请稍后重试');
}
};
return (
<div className="p-6">
<div className="mb-6 flex items-center justify-between">
@@ -82,22 +52,11 @@ function ProjectPage() {
<Grid.Row gutter={[16, 16]}>
{projects.map((project) => (
<Grid.Col key={project.id} span={8}>
<ProjectCard
project={project}
onEdit={handleEditProject}
onDelete={handleDeleteProject}
/>
<ProjectCard project={project} />
</Grid.Col>
))}
</Grid.Row>
<EditProjectModal
visible={editModalVisible}
project={editingProject}
onCancel={handleEditCancel}
onSuccess={handleEditSuccess}
/>
<CreateProjectModal
visible={createModalVisible}
onCancel={handleCreateCancel}