feat: 项目的增删改查
This commit is contained in:
102
apps/web/src/pages/project/components/CreateProjectModal.tsx
Normal file
102
apps/web/src/pages/project/components/CreateProjectModal.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { Modal, Form, Input, Button, Message } from '@arco-design/web-react';
|
||||
import React, { useState } from 'react';
|
||||
import type { Project } from '../types';
|
||||
import { projectService } from '../service';
|
||||
|
||||
interface CreateProjectModalProps {
|
||||
visible: boolean;
|
||||
onCancel: () => void;
|
||||
onSuccess: (newProject: Project) => void;
|
||||
}
|
||||
|
||||
function CreateProjectModal({ visible, onCancel, onSuccess }: CreateProjectModalProps) {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const values = await form.validate();
|
||||
setLoading(true);
|
||||
|
||||
const newProject = await projectService.create(values);
|
||||
|
||||
Message.success('项目创建成功');
|
||||
onSuccess(newProject);
|
||||
form.resetFields();
|
||||
onCancel();
|
||||
} catch (error) {
|
||||
console.error('创建项目失败:', error);
|
||||
Message.error('创建项目失败,请重试');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
form.resetFields();
|
||||
onCancel();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="新建项目"
|
||||
visible={visible}
|
||||
onCancel={handleCancel}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={handleCancel}>
|
||||
取消
|
||||
</Button>,
|
||||
<Button key="submit" type="primary" loading={loading} onClick={handleSubmit}>
|
||||
创建
|
||||
</Button>,
|
||||
]}
|
||||
style={{ width: 500 }}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item
|
||||
label="项目名称"
|
||||
field="name"
|
||||
rules={[
|
||||
{ required: true, message: '请输入项目名称' },
|
||||
{ minLength: 2, message: '项目名称至少2个字符' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入项目名称" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="项目描述"
|
||||
field="description"
|
||||
rules={[
|
||||
{ maxLength: 200, message: '项目描述不能超过200个字符' },
|
||||
]}
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder="请输入项目描述(可选)"
|
||||
autoSize={{ minRows: 3, maxRows: 6 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="仓库地址"
|
||||
field="repository"
|
||||
rules={[
|
||||
{ required: true, message: '请输入仓库地址' },
|
||||
{
|
||||
type: 'url',
|
||||
message: '请输入有效的仓库地址',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入仓库地址,如: https://github.com/user/repo" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateProjectModal;
|
||||
115
apps/web/src/pages/project/components/EditProjectModal.tsx
Normal file
115
apps/web/src/pages/project/components/EditProjectModal.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import { Modal, Form, Input, Button, Message } from '@arco-design/web-react';
|
||||
import React, { useState } from 'react';
|
||||
import type { Project } from '../types';
|
||||
import { projectService } from '../service';
|
||||
|
||||
interface EditProjectModalProps {
|
||||
visible: boolean;
|
||||
project: Project | null;
|
||||
onCancel: () => void;
|
||||
onSuccess: (updatedProject: Project) => void;
|
||||
}
|
||||
|
||||
function EditProjectModal({ visible, project, onCancel, onSuccess }: EditProjectModalProps) {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 当项目信息变化时,更新表单数据
|
||||
React.useEffect(() => {
|
||||
if (project && visible) {
|
||||
form.setFieldsValue({
|
||||
name: project.name,
|
||||
description: project.description,
|
||||
repository: project.repository,
|
||||
});
|
||||
}
|
||||
}, [project, visible, form]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const values = await form.validate();
|
||||
setLoading(true);
|
||||
|
||||
if (!project) return;
|
||||
|
||||
const updatedProject = await projectService.update(project.id, values);
|
||||
|
||||
Message.success('项目更新成功');
|
||||
onSuccess(updatedProject);
|
||||
onCancel();
|
||||
} catch (error) {
|
||||
console.error('更新项目失败:', error);
|
||||
Message.error('更新项目失败,请重试');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
form.resetFields();
|
||||
onCancel();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="编辑项目"
|
||||
visible={visible}
|
||||
onCancel={handleCancel}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={handleCancel}>
|
||||
取消
|
||||
</Button>,
|
||||
<Button key="submit" type="primary" loading={loading} onClick={handleSubmit}>
|
||||
保存
|
||||
</Button>,
|
||||
]}
|
||||
style={{ width: 500 }}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item
|
||||
label="项目名称"
|
||||
field="name"
|
||||
rules={[
|
||||
{ required: true, message: '请输入项目名称' },
|
||||
{ minLength: 2, message: '项目名称至少2个字符' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入项目名称" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="项目描述"
|
||||
field="description"
|
||||
rules={[
|
||||
{ maxLength: 200, message: '项目描述不能超过200个字符' },
|
||||
]}
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder="请输入项目描述"
|
||||
autoSize={{ minRows: 3, maxRows: 6 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="仓库地址"
|
||||
field="repository"
|
||||
rules={[
|
||||
{ required: true, message: '请输入仓库地址' },
|
||||
{
|
||||
type: 'url',
|
||||
message: '请输入有效的仓库地址',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入仓库地址,如: https://github.com/user/repo" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditProjectModal;
|
||||
178
apps/web/src/pages/project/components/ProjectCard.tsx
Normal file
178
apps/web/src/pages/project/components/ProjectCard.tsx
Normal file
@@ -0,0 +1,178 @@
|
||||
import { Card, Tag, Avatar, Space, Typography, Button, Tooltip, Dropdown, Menu, Modal } from '@arco-design/web-react';
|
||||
import { IconBranch, IconCalendar, IconEye, IconCloud, IconEdit, IconMore, IconDelete } from '@arco-design/web-react/icon';
|
||||
import type { Project } from '../types';
|
||||
import IconGitea from '@assets/images/gitea.svg?react';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
|
||||
interface ProjectCardProps {
|
||||
project: Project;
|
||||
onEdit?: (project: Project) => void;
|
||||
onDelete?: (project: Project) => void;
|
||||
}
|
||||
|
||||
function ProjectCard({ project, onEdit, onDelete }: ProjectCardProps) {
|
||||
// 处理删除操作
|
||||
const handleDelete = () => {
|
||||
Modal.confirm({
|
||||
title: '确认删除项目',
|
||||
content: `确定要删除项目 "${project.name}" 吗?此操作不可恢复。`,
|
||||
okText: '删除',
|
||||
cancelText: '取消',
|
||||
okButtonProps: {
|
||||
status: 'danger',
|
||||
},
|
||||
onOk: () => {
|
||||
onDelete?.(project);
|
||||
},
|
||||
});
|
||||
};
|
||||
// 获取环境信息
|
||||
const environments = [
|
||||
{ name: 'staging', color: 'orange', icon: '🚧' },
|
||||
{ name: 'production', color: 'green', icon: '🚀' }
|
||||
];
|
||||
|
||||
// 渲染环境标签
|
||||
const renderEnvironmentTags = () => {
|
||||
return (
|
||||
<div className="flex items-center space-x-1 mb-3">
|
||||
<IconCloud className="text-gray-400 text-xs mr-1" />
|
||||
<div className="flex space-x-1">
|
||||
{environments.map((env) => (
|
||||
<Tooltip key={env.name} content={`${env.name} 环境`}>
|
||||
<Tag
|
||||
size="small"
|
||||
color={env.color}
|
||||
className="text-xs px-2 py-0.5 rounded-full font-medium"
|
||||
>
|
||||
<span className="mr-1">{env.icon}</span>
|
||||
{env.name}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="foka-card !rounded-xl border border-gray-200 h-[320px] hover:border-blue-200 transition-all duration-300 hover:shadow-md"
|
||||
hoverable
|
||||
bodyStyle={{ padding: '20px' }}
|
||||
>
|
||||
{/* 项目头部 */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Avatar
|
||||
size={40}
|
||||
className="bg-blue-600 text-white text-base font-semibold"
|
||||
>
|
||||
{project.name.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<div className="ml-3">
|
||||
<Typography.Title
|
||||
heading={5}
|
||||
className="!m-0 !text-base !font-semibold"
|
||||
>
|
||||
{project.name}
|
||||
</Typography.Title>
|
||||
<Text type="secondary" className="text-xs">
|
||||
更新于 2天前
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Tag color="blue" size="small" className="font-medium">
|
||||
活跃
|
||||
</Tag>
|
||||
<Dropdown
|
||||
droplist={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
key="edit"
|
||||
onClick={() => onEdit?.(project)}
|
||||
>
|
||||
<IconEdit className="mr-2" />
|
||||
编辑
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="delete"
|
||||
onClick={() => handleDelete()}
|
||||
className="text-red-500"
|
||||
>
|
||||
<IconDelete className="mr-2" />
|
||||
删除
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
position="br"
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<IconMore />}
|
||||
className="text-gray-400 hover:text-blue-500 hover:bg-blue-50 transition-all duration-200 p-1 rounded-md"
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 项目描述 */}
|
||||
<Paragraph
|
||||
className="!m-0 !mb-4 !text-gray-600 !text-sm !leading-6 h-[42px] overflow-hidden line-clamp-2"
|
||||
>
|
||||
{project.description || '暂无描述'}
|
||||
</Paragraph>
|
||||
|
||||
{/* 环境信息 */}
|
||||
{renderEnvironmentTags()}
|
||||
|
||||
{/* 项目信息 */}
|
||||
<div className="mb-4">
|
||||
<div className="mb-2 flex items-center">
|
||||
<IconGitea className="mr-1.5 w-4 text-gray-500" />
|
||||
<Text
|
||||
type="secondary"
|
||||
className="text-xs truncate max-w-[200px]"
|
||||
title={project.repository}
|
||||
>
|
||||
{project.repository}
|
||||
</Text>
|
||||
</div>
|
||||
<Space size={16}>
|
||||
<div className="flex items-center">
|
||||
<IconBranch className="mr-1 text-gray-500 text-xs" />
|
||||
<Text className="text-xs text-gray-500">main</Text>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<IconCalendar className="mr-1 text-gray-500 text-xs" />
|
||||
<Text className="text-xs text-gray-500">3个提交</Text>
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex items-center justify-between pt-3 border-t border-gray-100">
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<IconEye />}
|
||||
className="text-gray-500 hover:text-blue-500 transition-colors"
|
||||
>
|
||||
查看详情
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
className="text-blue-500 hover:text-blue-600 font-medium transition-colors"
|
||||
>
|
||||
管理项目 →
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProjectCard;
|
||||
@@ -1,112 +1,108 @@
|
||||
import { Card, Grid, Link, Tag, Avatar, Space, Typography, Button } from '@arco-design/web-react';
|
||||
import { IconBranch, IconCalendar, IconEye } from '@arco-design/web-react/icon';
|
||||
import { Grid, Typography, Button, Message } from '@arco-design/web-react';
|
||||
import { IconPlus } from '@arco-design/web-react/icon';
|
||||
import { useState } from 'react';
|
||||
import type { Project } from './types';
|
||||
import { useAsyncEffect } from '../../hooks/useAsyncEffect';
|
||||
import { projectService } from './service';
|
||||
import IconGitea from '@assets/images/gitea.svg?react'
|
||||
import ProjectCard from './components/ProjectCard';
|
||||
import EditProjectModal from './components/EditProjectModal';
|
||||
import CreateProjectModal from './components/CreateProjectModal';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
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 () => {
|
||||
const list = await projectService.list();
|
||||
setProjects(list);
|
||||
const response = await projectService.list();
|
||||
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);
|
||||
};
|
||||
|
||||
const handleCreateSuccess = (newProject: Project) => {
|
||||
setProjects(prev => [newProject, ...prev]);
|
||||
};
|
||||
|
||||
const handleCreateCancel = () => {
|
||||
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 bg-gray-100 min-h-screen">
|
||||
<div className="mb-6">
|
||||
<Typography.Title heading={2} className="!m-0 !text-gray-900">
|
||||
我的项目
|
||||
</Typography.Title>
|
||||
<Text type="secondary">管理和查看您的所有项目</Text>
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<div>
|
||||
<Typography.Title heading={2} className="!m-0 !text-gray-900">
|
||||
我的项目
|
||||
</Typography.Title>
|
||||
<Text type="secondary">管理和查看您的所有项目</Text>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<IconPlus />}
|
||||
onClick={handleCreateProject}
|
||||
className="!rounded-lg"
|
||||
>
|
||||
新建项目
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Grid.Row gutter={[16, 16]}>
|
||||
{projects.map((project) => (
|
||||
<Grid.Col key={project.id} span={8}>
|
||||
<Card
|
||||
className="foka-card !rounded-xl border border-gray-200 h-[280px] hover:border-blue-200"
|
||||
hoverable
|
||||
bodyStyle={{ padding: '20px' }}
|
||||
>
|
||||
{/* 项目头部 */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Avatar
|
||||
size={40}
|
||||
className="bg-blue-600 text-white text-base font-semibold"
|
||||
>
|
||||
{project.name.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<div className="ml-3">
|
||||
<Typography.Title
|
||||
heading={5}
|
||||
className="!m-0 !text-base !font-semibold"
|
||||
>
|
||||
{project.name}
|
||||
</Typography.Title>
|
||||
<Text type="secondary" className="text-xs">
|
||||
更新于 2天前
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
<Tag color="blue" size="small">
|
||||
活跃
|
||||
</Tag>
|
||||
</div>
|
||||
|
||||
{/* 项目描述 */}
|
||||
<Paragraph
|
||||
className="!m-0 !mb-4 !text-gray-600 !text-sm !leading-6 h-[42px] overflow-hidden line-clamp-2"
|
||||
>
|
||||
{project.description || '暂无描述'}
|
||||
</Paragraph>
|
||||
|
||||
{/* 项目信息 */}
|
||||
<div className="mb-4">
|
||||
<div className="mb-2 flex items-center">
|
||||
<IconGitea className="mr-1.5 w-4" />
|
||||
<Text
|
||||
type="secondary"
|
||||
className="text-xs"
|
||||
>
|
||||
{project.repository}
|
||||
</Text>
|
||||
</div>
|
||||
<Space size={16}>
|
||||
<div className="flex items-center">
|
||||
<IconBranch className="mr-1 text-gray-500 text-xs" />
|
||||
<Text className="text-xs text-gray-500">main</Text>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<IconCalendar className="mr-1 text-gray-500 text-xs" />
|
||||
<Text className="text-xs text-gray-500">3个提交</Text>
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="flex items-center justify-between pt-3 border-t border-gray-100">
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<IconEye />}
|
||||
className="text-gray-500"
|
||||
>
|
||||
查看详情
|
||||
</Button>
|
||||
<Link className="text-xs font-medium">
|
||||
管理项目 →
|
||||
</Link>
|
||||
</div>
|
||||
</Card>
|
||||
<ProjectCard
|
||||
project={project}
|
||||
onEdit={handleEditProject}
|
||||
onDelete={handleDeleteProject}
|
||||
/>
|
||||
</Grid.Col>
|
||||
))}
|
||||
</Grid.Row>
|
||||
|
||||
<EditProjectModal
|
||||
visible={editModalVisible}
|
||||
project={editingProject}
|
||||
onCancel={handleEditCancel}
|
||||
onSuccess={handleEditSuccess}
|
||||
/>
|
||||
|
||||
<CreateProjectModal
|
||||
visible={createModalVisible}
|
||||
onCancel={handleCreateCancel}
|
||||
onSuccess={handleCreateSuccess}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,70 @@
|
||||
import { net, type APIResponse } from "@shared";
|
||||
import type { Project } from "./types";
|
||||
|
||||
interface ProjectListResponse {
|
||||
data: Project[];
|
||||
pagination: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface ProjectQueryParams {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
class ProjectService {
|
||||
|
||||
async list() {
|
||||
const { data } = await net.request<APIResponse<Project[]>>({
|
||||
// GET /api/projects - 获取项目列表
|
||||
async list(params?: ProjectQueryParams) {
|
||||
const { data } = await net.request<APIResponse<ProjectListResponse>>({
|
||||
method: 'GET',
|
||||
url: '/api/project/list',
|
||||
})
|
||||
url: '/api/projects',
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
// GET /api/projects/:id - 获取单个项目
|
||||
async show(id: string) {
|
||||
const { data } = await net.request<APIResponse<Project>>({
|
||||
method: 'GET',
|
||||
url: `/api/projects/${id}`,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
// POST /api/projects - 创建项目
|
||||
async create(project: { name: string; description?: string; repository: string }) {
|
||||
const { data } = await net.request<APIResponse<Project>>({
|
||||
method: 'POST',
|
||||
url: '/api/projects',
|
||||
data: project,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
// PUT /api/projects/:id - 更新项目
|
||||
async update(id: string, project: Partial<{ name: string; description: string; repository: string }>) {
|
||||
const { data } = await net.request<APIResponse<Project>>({
|
||||
method: 'PUT',
|
||||
url: `/api/projects/${id}`,
|
||||
data: project,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
// DELETE /api/projects/:id - 删除项目
|
||||
async delete(id: string) {
|
||||
await net.request({
|
||||
method: 'DELETE',
|
||||
url: `/api/projects/${id}`,
|
||||
});
|
||||
// DELETE 成功返回 204,无内容
|
||||
}
|
||||
}
|
||||
|
||||
export const projectService = new ProjectService();
|
||||
|
||||
@@ -9,7 +9,10 @@ export interface Project {
|
||||
name: string;
|
||||
description: string;
|
||||
repository: string;
|
||||
env: Record<string, string>;
|
||||
valid: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
createdBy: string;
|
||||
updatedBy: string;
|
||||
status: BuildStatus;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user