feat: 完善项目架构和功能

- 修复路由配置,实现根路径自动重定向到/project
- 新增Gitea OAuth认证系统和相关组件
- 完善日志系统实现,包含pino日志工具和中间件
- 重构页面结构,分离项目管理和环境管理页面
- 新增CORS、Session等关键中间件
- 优化前端请求封装和类型定义
- 修复TypeScript类型错误和参数传递问题
This commit is contained in:
2025-09-04 23:19:52 +08:00
parent d178df54da
commit ef473d6084
34 changed files with 1022 additions and 196 deletions

View File

@@ -1,14 +1,17 @@
import { Route, Routes } from 'react-router';
import { Route, Routes, Navigate } from 'react-router';
import Home from '@pages/home';
import Login from '@pages/login';
import Application from '@pages/application';
import Project from '@pages/project';
import Env from '@pages/env';
import '@styles/index.css';
const App = () => {
return (
<Routes>
<Route path="/" element={<Home />}>
<Route path="application" element={<Application />} index />
<Route index element={<Navigate to="project" replace />} />
<Route path="project" element={<Project />} />
<Route path="env" element={<Env />} />
</Route>
<Route path="/login" element={<Login />} />
</Routes>

View File

@@ -1,12 +0,0 @@
import { useState } from "react";
function Application() {
const [apps, setApps] = useState<Application[]>([]);
return (
<div>
application
</div>
)
}
export default Application;

12
apps/web/src/pages/env/index.tsx vendored Normal file
View File

@@ -0,0 +1,12 @@
import { useState } from "react";
function Env() {
const [env, setEnv] = useState([]);
return (
<div>
env page
</div>
)
}
export default Env;

View File

@@ -11,7 +11,7 @@ import {
} from '@arco-design/web-react/icon';
import { useState } from 'react';
import Logo from '@assets/images/logo.svg?react';
import { Outlet } from 'react-router';
import { Link, Outlet } from 'react-router';
function Home() {
const [collapsed, setCollapsed] = useState(false);
@@ -33,24 +33,16 @@ function Home() {
defaultSelectedKeys={['0_1']}
>
<Menu.Item key="0">
<IconApps />
Navigation 1
<Link to="/project" className="flex flex-row items-center">
<IconApps fontSize={18} />
</Link>
</Menu.Item>
<Menu.Item key="1">
<IconRobot />
Navigation 2
</Menu.Item>
<Menu.Item key="2">
<IconBulb />
Navigation 3
</Menu.Item>
<Menu.Item key="3">
<IconSafe />
Navigation 4
</Menu.Item>
<Menu.Item key="4">
<IconFire />
Navigation 5
<Link to="/env" className="flex flex-row items-center">
<IconRobot fontSize={18} />
</Link>
</Menu.Item>
</Menu>
</Layout.Sider>

View File

@@ -1,16 +1,41 @@
import { Input, Space } from '@arco-design/web-react';
import { IconUser, IconInfoCircle } from '@arco-design/web-react/icon';
import { Button } from '@arco-design/web-react';
import Gitea from '@assets/images/gitea.svg?react';
import { loginService } from './service';
import { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router';
function Login() {
const [ searchParams ] = useSearchParams();
const navigate = useNavigate();
const authCode = searchParams.get('code');
const onLoginClick = async () => {
const url = await loginService.getAuthUrl();
if (url) {
window.location.href = url;
}
};
useEffect(() => {
if (authCode) {
loginService.login(authCode, navigate);
}
}, [authCode, navigate]);
return (
<div>
<Space direction='vertical'>
<Input placeholder="username" prefix={<IconUser />} size="large" />
<Input.Password
placeholder="password"
prefix={<IconInfoCircle />}
size="large"
/>
</Space>
<div className="flex justify-center items-center h-[100vh]">
<Button
type="primary"
color="green"
shape="round"
size="large"
onClick={onLoginClick}
>
<span className="flex items-center gap-2">
<Gitea className="w-5 h-5" />
<span>Gitea </span>
</span>
</Button>
</div>
);
}

View File

@@ -0,0 +1,39 @@
import { net } from '@shared';
import type { AuthURLResponse } from './types';
import type { NavigateFunction } from 'react-router';
import { Notification } from '@arco-design/web-react';
class LoginService {
async getAuthUrl() {
const { code, data } = await net.request<AuthURLResponse>({
method: 'GET',
url: '/api/auth/url',
params: {
redirect: encodeURIComponent(`${location.origin}/login`),
},
});
if (code === 0) {
return data.url;
}
}
async login(authCode: string, navigate: NavigateFunction) {
const { data, code } = await net.request<AuthURLResponse>({
method: 'POST',
url: '/api/auth/login',
data: {
code: authCode,
},
});
if (code === 0) {
localStorage.setItem('user', JSON.stringify(data));
navigate('/');
Notification.success({
title: '提示',
content: '登录成功'
});
}
}
}
export const loginService = new LoginService();

View File

@@ -0,0 +1,5 @@
import type { APIResponse } from '@shared';
export type AuthURLResponse = APIResponse<{
url: string;
}>

View File

@@ -0,0 +1,8 @@
import { useState } from 'react';
function Project() {
const [projects, setProjects] = useState([]);
return <div>project page</div>;
}
export default Project;

View File

@@ -1,15 +1,15 @@
enum AppStatus {
enum BuildStatus {
Idle = "Pending",
Running = "Running",
Stopped = "Stopped",
}
export interface Application {
export interface Project {
id: string;
name: string;
description: string;
git: string;
env: Record<string, string>;
createdAt: string;
status: AppStatus;
status: BuildStatus;
}