Fix settings layout and frontend startup checks
This commit is contained in:
@@ -340,6 +340,98 @@ body {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.settings-shell,
|
||||
.settings-tabs-shell,
|
||||
.settings-tabs,
|
||||
.settings-tabs .ant-tabs-content-holder,
|
||||
.settings-tabs .ant-tabs-content,
|
||||
.settings-tabs .ant-tabs-tabpane {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.settings-tabs-shell {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.settings-tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.settings-tabs .ant-tabs-nav {
|
||||
flex: 0 0 auto;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.settings-tabs .ant-tabs-content-holder,
|
||||
.settings-tabs .ant-tabs-content,
|
||||
.settings-tabs .ant-tabs-tabpane {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.settings-tabs .ant-tabs-tabpane-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.settings-tab-panel {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.settings-panel-card,
|
||||
.settings-panel-card .ant-card-body {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.settings-panel-card {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.settings-panel-card .ant-card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.settings-panel-scroll {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 6px;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.settings-panel-scroll::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.settings-panel-scroll::-webkit-scrollbar-thumb {
|
||||
background: rgba(148, 163, 184, 0.8);
|
||||
border-radius: 999px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.settings-panel-scroll::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.settings-table-scroll-region {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
@@ -59,6 +59,8 @@ function Settings() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [savingCollectorId, setSavingCollectorId] = useState<number | null>(null)
|
||||
const [collectors, setCollectors] = useState<CollectorSettings[]>([])
|
||||
const collectorTableRegionRef = useRef<HTMLDivElement | null>(null)
|
||||
const [collectorTableHeight, setCollectorTableHeight] = useState(360)
|
||||
const [systemForm] = Form.useForm<SystemSettings>()
|
||||
const [notificationForm] = Form.useForm<NotificationSettings>()
|
||||
const [securityForm] = Form.useForm<SecuritySettings>()
|
||||
@@ -83,6 +85,24 @@ function Settings() {
|
||||
fetchSettings()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const updateTableHeight = () => {
|
||||
const regionHeight = collectorTableRegionRef.current?.offsetHeight || 0
|
||||
setCollectorTableHeight(Math.max(220, regionHeight - 56))
|
||||
}
|
||||
|
||||
updateTableHeight()
|
||||
|
||||
if (typeof ResizeObserver === 'undefined') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver(updateTableHeight)
|
||||
if (collectorTableRegionRef.current) observer.observe(collectorTableRegionRef.current)
|
||||
|
||||
return () => observer.disconnect()
|
||||
}, [collectors.length])
|
||||
|
||||
const saveSection = async (section: 'system' | 'notifications' | 'security', values: object) => {
|
||||
try {
|
||||
await axios.put(`/api/v1/settings/${section}`, values)
|
||||
@@ -219,115 +239,142 @@ function Settings() {
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
<Space direction="vertical" size={16} style={{ width: '100%' }}>
|
||||
<div>
|
||||
<Title level={3} style={{ marginBottom: 4 }}>系统配置中心</Title>
|
||||
<Text type="secondary">这一页现在已经直接连接数据库配置和采集调度,不再只是演示表单。</Text>
|
||||
<div className="page-shell settings-shell">
|
||||
<div className="page-shell__header">
|
||||
<div>
|
||||
<Title level={3} style={{ marginBottom: 4 }}>系统配置中心</Title>
|
||||
<Text type="secondary">这一页现在已经直接连接数据库配置和采集调度,不再只是演示表单。</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs
|
||||
items={[
|
||||
{
|
||||
key: 'system',
|
||||
label: '系统显示',
|
||||
children: (
|
||||
<Card loading={loading}>
|
||||
<Form form={systemForm} layout="vertical" onFinish={(values) => saveSection('system', values)}>
|
||||
<Form.Item name="system_name" label="系统名称" rules={[{ required: true, message: '请输入系统名称' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="refresh_interval" label="默认刷新间隔(秒)">
|
||||
<InputNumber min={10} max={3600} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="data_retention_days" label="数据保留天数">
|
||||
<InputNumber min={1} max={3650} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="max_concurrent_tasks" label="最大并发任务数">
|
||||
<InputNumber min={1} max={50} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="auto_refresh" label="自动刷新" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit">保存系统配置</Button>
|
||||
</Form>
|
||||
</Card>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'notifications',
|
||||
label: '通知策略',
|
||||
children: (
|
||||
<Card loading={loading}>
|
||||
<Form form={notificationForm} layout="vertical" onFinish={(values) => saveSection('notifications', values)}>
|
||||
<Form.Item name="email_enabled" label="启用邮件通知" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="email_address" label="通知邮箱">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="critical_alerts" label="严重告警通知" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="warning_alerts" label="警告告警通知" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="daily_summary" label="每日摘要" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit">保存通知配置</Button>
|
||||
</Form>
|
||||
</Card>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'security',
|
||||
label: '安全策略',
|
||||
children: (
|
||||
<Card loading={loading}>
|
||||
<Form form={securityForm} layout="vertical" onFinish={(values) => saveSection('security', values)}>
|
||||
<Form.Item name="session_timeout" label="会话超时(分钟)">
|
||||
<InputNumber min={5} max={1440} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="max_login_attempts" label="最大登录尝试次数">
|
||||
<InputNumber min={1} max={20} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="password_policy" label="密码策略">
|
||||
<Select
|
||||
options={[
|
||||
{ value: 'low', label: '简单' },
|
||||
{ value: 'medium', label: '中等' },
|
||||
{ value: 'high', label: '严格' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit">保存安全配置</Button>
|
||||
</Form>
|
||||
</Card>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'collectors',
|
||||
label: '采集调度',
|
||||
children: (
|
||||
<Card loading={loading}>
|
||||
<div className="table-scroll-region">
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={collectorColumns}
|
||||
dataSource={collectors}
|
||||
pagination={false}
|
||||
scroll={{ x: 1200, y: 'calc(100% - 360px)' }}
|
||||
/>
|
||||
<div className="page-shell__body settings-tabs-shell">
|
||||
<Tabs
|
||||
className="settings-tabs"
|
||||
items={[
|
||||
{
|
||||
key: 'system',
|
||||
label: '系统显示',
|
||||
children: (
|
||||
<div className="settings-tab-panel">
|
||||
<Card className="settings-panel-card" loading={loading}>
|
||||
<div className="settings-panel-scroll">
|
||||
<Form form={systemForm} layout="vertical" onFinish={(values) => saveSection('system', values)}>
|
||||
<Form.Item name="system_name" label="系统名称" rules={[{ required: true, message: '请输入系统名称' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="refresh_interval" label="默认刷新间隔(秒)">
|
||||
<InputNumber min={10} max={3600} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="data_retention_days" label="数据保留天数">
|
||||
<InputNumber min={1} max={3650} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="max_concurrent_tasks" label="最大并发任务数">
|
||||
<InputNumber min={1} max={50} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="auto_refresh" label="自动刷新" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit">保存系统配置</Button>
|
||||
</Form>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Card>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'notifications',
|
||||
label: '通知策略',
|
||||
children: (
|
||||
<div className="settings-tab-panel">
|
||||
<Card className="settings-panel-card" loading={loading}>
|
||||
<div className="settings-panel-scroll">
|
||||
<Form form={notificationForm} layout="vertical" onFinish={(values) => saveSection('notifications', values)}>
|
||||
<Form.Item name="email_enabled" label="启用邮件通知" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="email_address" label="通知邮箱">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="critical_alerts" label="严重告警通知" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="warning_alerts" label="警告告警通知" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="daily_summary" label="每日摘要" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit">保存通知配置</Button>
|
||||
</Form>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'security',
|
||||
label: '安全策略',
|
||||
children: (
|
||||
<div className="settings-tab-panel">
|
||||
<Card className="settings-panel-card" loading={loading}>
|
||||
<div className="settings-panel-scroll">
|
||||
<Form form={securityForm} layout="vertical" onFinish={(values) => saveSection('security', values)}>
|
||||
<Form.Item name="session_timeout" label="会话超时(分钟)">
|
||||
<InputNumber min={5} max={1440} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="max_login_attempts" label="最大登录尝试次数">
|
||||
<InputNumber min={1} max={20} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="password_policy" label="密码策略">
|
||||
<Select
|
||||
options={[
|
||||
{ value: 'low', label: '简单' },
|
||||
{ value: 'medium', label: '中等' },
|
||||
{ value: 'high', label: '严格' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit">保存安全配置</Button>
|
||||
</Form>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'collectors',
|
||||
label: '采集调度',
|
||||
children: (
|
||||
<div className="settings-tab-panel">
|
||||
<Card
|
||||
className="settings-panel-card settings-panel-card--table"
|
||||
loading={loading}
|
||||
bodyStyle={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}
|
||||
>
|
||||
<div
|
||||
ref={collectorTableRegionRef}
|
||||
className="table-scroll-region data-source-table-region settings-table-scroll-region"
|
||||
style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}
|
||||
>
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={collectorColumns}
|
||||
dataSource={collectors}
|
||||
pagination={false}
|
||||
scroll={{ x: 1200, y: collectorTableHeight }}
|
||||
virtual
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default Settings
|
||||
|
||||
|
||||
42
planet.sh
42
planet.sh
@@ -11,6 +11,27 @@ YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
ensure_frontend_deps() {
|
||||
echo -e "${BLUE}📦 检查前端依赖...${NC}"
|
||||
|
||||
if ! command -v bun >/dev/null 2>&1; then
|
||||
echo -e "${RED}❌ 未找到 bun,请先安装或加载 bun 到 PATH${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$SCRIPT_DIR/frontend"
|
||||
|
||||
if [ ! -x "$SCRIPT_DIR/frontend/node_modules/.bin/vite" ]; then
|
||||
echo -e "${YELLOW}⚠️ 前端依赖缺失,正在执行 bun install...${NC}"
|
||||
bun install
|
||||
fi
|
||||
|
||||
if [ ! -x "$SCRIPT_DIR/frontend/node_modules/.bin/vite" ]; then
|
||||
echo -e "${RED}❌ 前端依赖安装失败,未找到 vite${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
start() {
|
||||
echo -e "${BLUE}🚀 启动智能星球计划...${NC}"
|
||||
|
||||
@@ -25,21 +46,16 @@ start() {
|
||||
PYTHONPATH="$SCRIPT_DIR/backend" nohup python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload > /tmp/planet_backend.log 2>&1 &
|
||||
BACKEND_PID=$!
|
||||
|
||||
echo " 等待后端启动..."
|
||||
for i in {1..10}; do
|
||||
sleep 2
|
||||
if curl -s http://localhost:8000/health > /dev/null 2>&1; then
|
||||
echo -e " ${GREEN}✅ 后端已就绪${NC}"
|
||||
break
|
||||
fi
|
||||
if [ $i -eq 10 ]; then
|
||||
echo -e "${RED}❌ 后端启动失败${NC}"
|
||||
tail -10 /tmp/planet_backend.log
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
sleep 3
|
||||
|
||||
if ! curl -s http://localhost:8000/health > /dev/null 2>&1; then
|
||||
echo -e "${RED}❌ 后端启动失败${NC}"
|
||||
tail -10 /tmp/planet_backend.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}🌐 启动前端...${NC}"
|
||||
ensure_frontend_deps
|
||||
pkill -f "vite" 2>/dev/null || true
|
||||
pkill -f "bun run dev" 2>/dev/null || true
|
||||
cd "$SCRIPT_DIR/frontend"
|
||||
|
||||
Reference in New Issue
Block a user