feat: 项目中心简化为直接嵌入 Gitea iframe
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 10s
All checks were successful
Deploy Admin Frontend / build-and-deploy (push) Successful in 10s
- 移除仓库列表/Issues/里程碑/Release/Runner 等所有 API 数据面板 - 移除页面标题行和面包屑导航 - 侧边栏点击「项目中心」直接全屏展示 Gitea Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
6bcda17894
commit
7dbfb2751d
@ -88,6 +88,8 @@ export default function AdminLayout() {
|
|||||||
<a onClick={() => item.path && navigate(item.path)}>{dom}</a>
|
<a onClick={() => item.path && navigate(item.path)}>{dom}</a>
|
||||||
)}
|
)}
|
||||||
breadcrumbRender={(routers) => {
|
breadcrumbRender={(routers) => {
|
||||||
|
// Hide breadcrumbs for full-screen iframe pages
|
||||||
|
if (location.pathname === '/git') return []
|
||||||
return (routers ?? []).map((r) => {
|
return (routers ?? []).map((r) => {
|
||||||
const path = (r as any).path ?? ''
|
const path = (r as any).path ?? ''
|
||||||
const label = breadcrumbMap[path] || (r as any).breadcrumbName || path
|
const label = breadcrumbMap[path] || (r as any).breadcrumbName || path
|
||||||
|
|||||||
@ -1,165 +1,11 @@
|
|||||||
import { useState } from 'react'
|
|
||||||
import { Table, Tag, Tabs, Typography, Card, Row, Col, Select, Space, Spin } from 'antd'
|
|
||||||
import { CodeOutlined, RocketOutlined } from '@ant-design/icons'
|
|
||||||
import { useQuery } from '@tanstack/react-query'
|
|
||||||
import { api } from '@/services/http-client'
|
|
||||||
|
|
||||||
const { Title, Text } = Typography
|
|
||||||
|
|
||||||
export default function ProjectCenter() {
|
export default function ProjectCenter() {
|
||||||
const [selectedRepo, setSelectedRepo] = useState<string | null>(null)
|
|
||||||
const [tab, setTab] = useState('repos')
|
|
||||||
|
|
||||||
const { data: repos, isLoading } = useQuery({
|
|
||||||
queryKey: ['gitea', 'repos'],
|
|
||||||
queryFn: () => api.get<any[]>('/admin-api/projects/repos').then(d => d ?? []),
|
|
||||||
})
|
|
||||||
|
|
||||||
const repoParts = selectedRepo?.split('/') || []
|
|
||||||
|
|
||||||
const { data: issues } = useQuery({
|
|
||||||
queryKey: ['gitea', 'issues', selectedRepo],
|
|
||||||
queryFn: () => api.get<any[]>(`/admin-api/projects/repos/${repoParts[0]}/${repoParts[1]}/issues?state=all`).then(d => d ?? []),
|
|
||||||
enabled: tab === 'issues' && repoParts.length === 2,
|
|
||||||
})
|
|
||||||
|
|
||||||
const { data: milestones } = useQuery({
|
|
||||||
queryKey: ['gitea', 'milestones', selectedRepo],
|
|
||||||
queryFn: () => api.get<any[]>(`/admin-api/projects/repos/${repoParts[0]}/${repoParts[1]}/milestones`).then(d => d ?? []),
|
|
||||||
enabled: tab === 'milestones' && repoParts.length === 2,
|
|
||||||
})
|
|
||||||
|
|
||||||
const { data: releases } = useQuery({
|
|
||||||
queryKey: ['gitea', 'releases', selectedRepo],
|
|
||||||
queryFn: () => api.get<any[]>(`/admin-api/projects/repos/${repoParts[0]}/${repoParts[1]}/releases`).then(d => d ?? []),
|
|
||||||
enabled: tab === 'releases' && repoParts.length === 2,
|
|
||||||
})
|
|
||||||
|
|
||||||
const { data: runners } = useQuery({
|
|
||||||
queryKey: ['gitea', 'runners'],
|
|
||||||
queryFn: () => api.get<any[]>('/admin-api/projects/runners').then(d => d ?? []),
|
|
||||||
enabled: tab === 'runners',
|
|
||||||
})
|
|
||||||
|
|
||||||
const repoOptions = (repos || []).map((r: any) => ({ label: r.fullName, value: r.fullName }))
|
|
||||||
|
|
||||||
const RepoSelector = () => (
|
|
||||||
<Select
|
|
||||||
placeholder="选择仓库"
|
|
||||||
value={selectedRepo}
|
|
||||||
onChange={setSelectedRepo}
|
|
||||||
options={repoOptions}
|
|
||||||
style={{ width: 280 }}
|
|
||||||
showSearch
|
|
||||||
filterOption={(input, option) => (option?.label as string)?.toLowerCase().includes(input.toLowerCase())}
|
|
||||||
allowClear
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
const repoColumns = [
|
|
||||||
{ title: '仓库', dataIndex: 'fullName', width: 200, render: (n: string) => <a onClick={() => { setSelectedRepo(n); setTab('issues') }}>{n}</a> },
|
|
||||||
{ title: '描述', dataIndex: 'description', width: 200, ellipsis: true, render: (d: string) => d || '-' },
|
|
||||||
{ title: 'Issues', dataIndex: 'openIssues', width: 70 },
|
|
||||||
{ title: 'Stars', dataIndex: 'stars', width: 60 },
|
|
||||||
{ title: '分支', dataIndex: 'defaultBranch', width: 80, render: (b: string) => <Tag>{b}</Tag> },
|
|
||||||
{ title: '更新', dataIndex: 'updatedAt', width: 100, render: (d: string) => d ? new Date(d).toLocaleDateString() : '-' },
|
|
||||||
]
|
|
||||||
|
|
||||||
const issueColumns = [
|
|
||||||
{ title: '#', dataIndex: 'number', width: 60 },
|
|
||||||
{ title: '标题', dataIndex: 'title', width: 300, ellipsis: true },
|
|
||||||
{ title: '状态', dataIndex: 'state', width: 70, render: (s: string) => <Tag color={s === 'open' ? 'green' : 'default'}>{s}</Tag> },
|
|
||||||
{ title: '创建者', dataIndex: ['user', 'login'], width: 100 },
|
|
||||||
{ title: '更新', dataIndex: 'updated_at', width: 100, render: (d: string) => d ? new Date(d).toLocaleDateString() : '-' },
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ width: '100%', height: 'calc(100vh - 64px)' }}>
|
||||||
<Row justify="space-between" align="middle" style={{ marginBottom: 16 }}>
|
<iframe
|
||||||
<Col><Title level={4} style={{ margin: 0 }}><CodeOutlined /> 项目中心</Title></Col>
|
src="https://git.admin.longde.cloud"
|
||||||
{selectedRepo && <Col><Text strong>{selectedRepo}</Text></Col>}
|
style={{ width: '100%', height: '100%', border: 'none' }}
|
||||||
</Row>
|
title="Gitea"
|
||||||
|
/>
|
||||||
<Tabs activeKey={tab} onChange={setTab} items={[
|
|
||||||
{
|
|
||||||
key: 'repos', label: '仓库列表',
|
|
||||||
children: <Table dataSource={repos || []} columns={repoColumns} rowKey="id" loading={isLoading} size="small" pagination={false} scroll={{ x: 700 }} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'issues', label: 'Issues',
|
|
||||||
children: (
|
|
||||||
<div>
|
|
||||||
<Space style={{ marginBottom: 12 }}><RepoSelector /></Space>
|
|
||||||
{selectedRepo ? (
|
|
||||||
<Table dataSource={issues || []} columns={issueColumns} rowKey="id" size="small" pagination={{ pageSize: 30 }} scroll={{ x: 700 }} />
|
|
||||||
) : <Text type="secondary">请选择仓库</Text>}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'milestones', label: '里程碑',
|
|
||||||
children: (
|
|
||||||
<div>
|
|
||||||
<Space style={{ marginBottom: 12 }}><RepoSelector /></Space>
|
|
||||||
{selectedRepo ? (
|
|
||||||
<Row gutter={[16, 16]}>
|
|
||||||
{(milestones || []).map((m: any) => (
|
|
||||||
<Col xs={24} sm={12} lg={8} key={m.id}>
|
|
||||||
<Card size="small" title={m.title}>
|
|
||||||
<Text type="secondary">{m.description || '无描述'}</Text>
|
|
||||||
<br />
|
|
||||||
<Tag color={m.state === 'open' ? 'blue' : 'default'}>{m.state}</Tag>
|
|
||||||
{m.due_on && <Tag>截止 {new Date(m.due_on).toLocaleDateString()}</Tag>}
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
) : <Text type="secondary">请选择仓库</Text>}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'releases', label: 'Release',
|
|
||||||
children: (
|
|
||||||
<div>
|
|
||||||
<Space style={{ marginBottom: 12 }}><RepoSelector /></Space>
|
|
||||||
{selectedRepo ? (
|
|
||||||
<Table
|
|
||||||
dataSource={releases || []} rowKey="id" size="small" pagination={false}
|
|
||||||
columns={[
|
|
||||||
{ title: '标签', dataIndex: 'tag_name', width: 120, render: (t: string) => <Tag color="blue"><RocketOutlined /> {t}</Tag> },
|
|
||||||
{ title: '标题', dataIndex: 'name', width: 250, ellipsis: true },
|
|
||||||
{ title: '发布者', dataIndex: ['author', 'login'], width: 100 },
|
|
||||||
{ title: '发布时间', dataIndex: 'published_at', width: 120, render: (d: string) => d ? new Date(d).toLocaleDateString() : '-' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
) : <Text type="secondary">请选择仓库</Text>}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'runners', label: 'Runner 状态',
|
|
||||||
children: runners ? (
|
|
||||||
<Table
|
|
||||||
dataSource={runners} rowKey="id" size="small" pagination={false}
|
|
||||||
columns={[
|
|
||||||
{ title: '名称', dataIndex: 'name', width: 150 },
|
|
||||||
{ title: '状态', dataIndex: 'is_online', width: 80, render: (o: boolean) => <Tag color={o ? 'green' : 'red'}>{o ? '在线' : '离线'}</Tag> },
|
|
||||||
{ title: '标签', dataIndex: 'tags', width: 150, render: (t: string[]) => (t || []).join(', ') || '-' },
|
|
||||||
{ title: '最后活跃', dataIndex: 'last_active', width: 120, render: (d: string) => d ? new Date(d).toLocaleString() : '-' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
) : <Spin />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'gitea', label: 'Gitea 面板',
|
|
||||||
children: (
|
|
||||||
<div style={{ width: '100%', height: 'calc(100vh - 200px)' }}>
|
|
||||||
<iframe src="https://git.admin.longde.cloud" style={{ width: '100%', height: '100%', border: 'none' }} title="Gitea" />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]} />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user