mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 06:16:37 +08:00
feat: 添加节点自动更新功能,支持手动触发更新
This commit is contained in:
+21
-11
@@ -9,7 +9,14 @@ import NotFound from './pages/NotFound';
|
||||
import Setting from './pages/Setting';
|
||||
import EditUser from './pages/User/EditUser';
|
||||
import AddUser from './pages/User/AddUser';
|
||||
import { API, showError, showNotice } from './helpers';
|
||||
import {
|
||||
API,
|
||||
getBuildVersion,
|
||||
hasBuildVersion,
|
||||
normalizeVersion,
|
||||
showError,
|
||||
showNotice,
|
||||
} from './helpers';
|
||||
import PasswordResetForm from './components/PasswordResetForm';
|
||||
import GitHubOAuth from './components/GitHubOAuth';
|
||||
import PasswordResetConfirm from './components/PasswordResetConfirm';
|
||||
@@ -44,16 +51,19 @@ function App() {
|
||||
console.log(`GitHub 仓库地址:https://github.com/Rain-kl/ATSFlare`);
|
||||
localStorage.setItem('status', JSON.stringify(data));
|
||||
statusDispatch({ type: 'set', payload: data });
|
||||
localStorage.setItem('system_name', data.system_name);
|
||||
localStorage.setItem('footer_html', data.footer_html);
|
||||
localStorage.setItem('home_page_link', data.home_page_link);
|
||||
if (
|
||||
data.version !== process.env.REACT_APP_VERSION &&
|
||||
data.version !== 'v0.0.0' &&
|
||||
process.env.REACT_APP_VERSION !== ''
|
||||
) {
|
||||
showNotice(
|
||||
`新版本可用:${data.version},请使用快捷键 Shift + F5 刷新页面`
|
||||
localStorage.setItem('system_name', data.system_name);
|
||||
localStorage.setItem('footer_html', data.footer_html);
|
||||
localStorage.setItem('home_page_link', data.home_page_link);
|
||||
const currentBuildVersion = getBuildVersion();
|
||||
const latestServerVersion = normalizeVersion(data.version);
|
||||
if (
|
||||
hasBuildVersion() &&
|
||||
latestServerVersion !== '' &&
|
||||
latestServerVersion !== '0.0.0' &&
|
||||
latestServerVersion !== currentBuildVersion
|
||||
) {
|
||||
showNotice(
|
||||
`新版本可用:${data.version},请使用快捷键 Shift + F5 刷新页面`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,6 @@ const OperationSetting = () => {
|
||||
AgentHeartbeatInterval: '30000',
|
||||
AgentSyncInterval: '30000',
|
||||
NodeOfflineThreshold: '120000',
|
||||
AgentAutoUpdate: 'false',
|
||||
AgentUpdateRepo: 'Rain-kl/ATSFlare',
|
||||
ServerAddress: '',
|
||||
AgentDiscoveryToken: '',
|
||||
@@ -77,10 +76,6 @@ const OperationSetting = () => {
|
||||
setInputs((prev) => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (e, { name, checked }) => {
|
||||
setInputs((prev) => ({ ...prev, [name]: checked ? 'true' : 'false' }));
|
||||
};
|
||||
|
||||
const submitAgentIntervals = async () => {
|
||||
const hb = parseInt(inputs.AgentHeartbeatInterval, 10);
|
||||
const sync = parseInt(inputs.AgentSyncInterval, 10);
|
||||
@@ -102,8 +97,7 @@ const OperationSetting = () => {
|
||||
await updateOption('NodeOfflineThreshold', String(offline));
|
||||
};
|
||||
|
||||
const submitAutoUpdate = async () => {
|
||||
await updateOption('AgentAutoUpdate', inputs.AgentAutoUpdate);
|
||||
const submitUpdateRepo = async () => {
|
||||
await updateOption('AgentUpdateRepo', inputs.AgentUpdateRepo);
|
||||
};
|
||||
|
||||
@@ -178,15 +172,13 @@ const OperationSetting = () => {
|
||||
</Form.Button>
|
||||
|
||||
<Divider />
|
||||
<Header as='h3'>Agent 自动更新</Header>
|
||||
<Form.Group>
|
||||
<Form.Checkbox
|
||||
label='启用 Agent 自动更新'
|
||||
checked={inputs.AgentAutoUpdate === 'true'}
|
||||
name='AgentAutoUpdate'
|
||||
onChange={handleCheckboxChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Header as='h3'>Agent 更新源</Header>
|
||||
<Message info size='small'>
|
||||
<p>
|
||||
自动更新开关和手动更新由节点页单独控制,这里只配置 Agent
|
||||
更新仓库。
|
||||
</p>
|
||||
</Message>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.Input
|
||||
label='更新仓库'
|
||||
@@ -196,7 +188,7 @@ const OperationSetting = () => {
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Button onClick={submitAutoUpdate} primary>
|
||||
<Form.Button onClick={submitUpdateRepo} primary>
|
||||
保存更新设置
|
||||
</Form.Button>
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Divider, Form, Grid, Header, Modal } from 'semantic-ui-react';
|
||||
import { API, showError, showSuccess } from '../helpers';
|
||||
import {
|
||||
API,
|
||||
getBuildVersion,
|
||||
hasBuildVersion,
|
||||
normalizeVersion,
|
||||
showError,
|
||||
showSuccess,
|
||||
} from '../helpers';
|
||||
import { marked } from 'marked';
|
||||
|
||||
const OtherSetting = () => {
|
||||
@@ -81,15 +88,18 @@ const OtherSetting = () => {
|
||||
window.location = 'https://github.com/Rain-kl/ATSFlare/releases/latest';
|
||||
};
|
||||
|
||||
const checkUpdate = async () => {
|
||||
const res = await API.get(
|
||||
'https://api.github.com/repos/Rain-kl/ATSFlare/releases/latest'
|
||||
);
|
||||
const { tag_name, body } = res.data;
|
||||
if (tag_name === process.env.REACT_APP_VERSION) {
|
||||
showSuccess(`已是最新版本:${tag_name}`);
|
||||
} else {
|
||||
setUpdateData({
|
||||
const checkUpdate = async () => {
|
||||
const res = await API.get(
|
||||
'https://api.github.com/repos/Rain-kl/ATSFlare/releases/latest'
|
||||
);
|
||||
const { tag_name, body } = res.data;
|
||||
if (
|
||||
hasBuildVersion() &&
|
||||
normalizeVersion(tag_name) === getBuildVersion()
|
||||
) {
|
||||
showSuccess(`已是最新版本:${tag_name}`);
|
||||
} else {
|
||||
setUpdateData({
|
||||
tag_name: tag_name,
|
||||
content: marked.parse(body),
|
||||
});
|
||||
|
||||
@@ -25,6 +25,19 @@ export function getFooterHTML() {
|
||||
return localStorage.getItem('footer_html');
|
||||
}
|
||||
|
||||
export function normalizeVersion(value) {
|
||||
if (!value) return '';
|
||||
return String(value).trim().replace(/^v/i, '');
|
||||
}
|
||||
|
||||
export function getBuildVersion() {
|
||||
return normalizeVersion(process.env.REACT_APP_VERSION);
|
||||
}
|
||||
|
||||
export function hasBuildVersion() {
|
||||
return getBuildVersion() !== '';
|
||||
}
|
||||
|
||||
export async function copy(text, label) {
|
||||
let okay = true;
|
||||
try {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { API, showError, showSuccess, timeAgo } from '../../helpers';
|
||||
|
||||
const initialForm = {
|
||||
name: '',
|
||||
auto_update_enabled: false,
|
||||
};
|
||||
|
||||
const renderStatus = (status) => {
|
||||
@@ -26,6 +27,16 @@ const renderApply = (result) => {
|
||||
return <Label>暂无</Label>;
|
||||
};
|
||||
|
||||
const renderUpdateMode = (node) => {
|
||||
if (node.update_requested) {
|
||||
return <Label color='orange'>等待更新</Label>;
|
||||
}
|
||||
if (node.auto_update_enabled) {
|
||||
return <Label color='green'>自动更新</Label>;
|
||||
}
|
||||
return <Label>手动更新</Label>;
|
||||
};
|
||||
|
||||
const Node = () => {
|
||||
const [nodes, setNodes] = useState([]);
|
||||
const [bootstrap, setBootstrap] = useState({ discovery_token: '' });
|
||||
@@ -91,6 +102,7 @@ const Node = () => {
|
||||
setSubmitting(true);
|
||||
const payload = {
|
||||
name: form.name.trim(),
|
||||
auto_update_enabled: form.auto_update_enabled,
|
||||
};
|
||||
const res = editingId
|
||||
? await API.put(`/api/nodes/${editingId}`, payload)
|
||||
@@ -110,9 +122,21 @@ const Node = () => {
|
||||
setEditingId(node.id);
|
||||
setForm({
|
||||
name: node.name || '',
|
||||
auto_update_enabled: !!node.auto_update_enabled,
|
||||
});
|
||||
};
|
||||
|
||||
const requestAgentUpdate = async (node) => {
|
||||
const res = await API.post(`/api/nodes/${node.id}/agent-update`);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess(`已向节点 ${node.name} 下发更新指令`);
|
||||
await loadNodes(false);
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteNode = async (node) => {
|
||||
if (
|
||||
!window.confirm(
|
||||
@@ -163,6 +187,14 @@ const Node = () => {
|
||||
value={form.name}
|
||||
onChange={(e, { value }) => setForm({ ...form, name: value })}
|
||||
/>
|
||||
<Form.Checkbox
|
||||
label='启用该节点自动更新'
|
||||
checked={form.auto_update_enabled}
|
||||
onChange={(e, { checked }) =>
|
||||
setForm({ ...form, auto_update_enabled: !!checked })
|
||||
}
|
||||
style={{ alignSelf: 'flex-end', marginBottom: '0.5rem' }}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Button primary type='submit' loading={submitting}>
|
||||
{editingId ? '保存修改' : '新增节点'}
|
||||
@@ -182,6 +214,7 @@ const Node = () => {
|
||||
<Table.HeaderCell>Auth Token</Table.HeaderCell>
|
||||
<Table.HeaderCell>IP</Table.HeaderCell>
|
||||
<Table.HeaderCell>状态</Table.HeaderCell>
|
||||
<Table.HeaderCell>Agent 更新</Table.HeaderCell>
|
||||
<Table.HeaderCell>Agent / Nginx</Table.HeaderCell>
|
||||
<Table.HeaderCell>当前版本</Table.HeaderCell>
|
||||
<Table.HeaderCell>最近应用</Table.HeaderCell>
|
||||
@@ -216,6 +249,7 @@ const Node = () => {
|
||||
</Table.Cell>
|
||||
<Table.Cell>{node.ip}</Table.Cell>
|
||||
<Table.Cell>{renderStatus(node.status)}</Table.Cell>
|
||||
<Table.Cell>{renderUpdateMode(node)}</Table.Cell>
|
||||
<Table.Cell>
|
||||
{node.agent_version} / {node.nginx_version || 'unknown'}
|
||||
</Table.Cell>
|
||||
@@ -231,6 +265,14 @@ const Node = () => {
|
||||
</Table.Cell>
|
||||
<Table.Cell>{node.last_error || '无'}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
size='small'
|
||||
primary
|
||||
disabled={node.update_requested}
|
||||
onClick={() => requestAgentUpdate(node)}
|
||||
>
|
||||
立即更新
|
||||
</Button>
|
||||
<Button size='small' onClick={() => beginEdit(node)}>
|
||||
编辑
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user