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

@@ -1,8 +1,9 @@
import type { Context } from 'koa';
import {prisma} from '../../libs/prisma.ts';
import { prisma } from '../../libs/prisma.ts';
import { log } from '../../libs/logger.ts';
import { BusinessError } from '../../middlewares/exception.ts';
import { Controller, Get, Post, Put, Delete } from '../../decorators/route.ts';
import { GitManager } from '../../libs/git-manager.ts';
import {
createProjectSchema,
updateProjectSchema,
@@ -37,7 +38,7 @@ export class ProjectController {
orderBy: {
createdAt: 'desc',
},
})
}),
]);
return {
@@ -47,7 +48,7 @@ export class ProjectController {
limit: query?.limit || 10,
total,
totalPages: Math.ceil(total / (query?.limit || 10)),
}
},
};
}
@@ -67,7 +68,48 @@ export class ProjectController {
throw new BusinessError('项目不存在', 1002, 404);
}
return project;
// 获取工作目录状态信息
let workspaceStatus = null;
if (project.projectDir) {
try {
const status = await GitManager.checkWorkspaceStatus(
project.projectDir,
);
let size = 0;
let gitInfo = null;
if (status.exists && !status.isEmpty) {
size = await GitManager.getDirectorySize(project.projectDir);
}
if (status.hasGit) {
gitInfo = await GitManager.getGitInfo(project.projectDir);
}
workspaceStatus = {
...status,
size,
gitInfo,
};
} catch (error) {
log.error(
'project',
'Failed to get workspace status for project %s: %s',
project.name,
(error as Error).message,
);
// 即使获取状态失败,也返回项目信息
workspaceStatus = {
status: 'error',
error: (error as Error).message,
};
}
}
return {
...project,
workspaceStatus,
};
}
// POST /api/projects - 创建项目
@@ -75,18 +117,36 @@ export class ProjectController {
async create(ctx: Context) {
const validatedData = createProjectSchema.parse(ctx.request.body);
// 检查工作目录是否已被其他项目使用
const existingProject = await prisma.project.findFirst({
where: {
projectDir: validatedData.projectDir,
valid: 1,
},
});
if (existingProject) {
throw new BusinessError('该工作目录已被其他项目使用', 1003, 400);
}
const project = await prisma.project.create({
data: {
name: validatedData.name,
description: validatedData.description || '',
repository: validatedData.repository,
projectDir: validatedData.projectDir,
createdBy: 'system',
updatedBy: 'system',
valid: 1,
},
});
log.info('project', 'Created new project: %s', project.name);
log.info(
'project',
'Created new project: %s with projectDir: %s',
project.name,
project.projectDir,
);
return project;
}