mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-04 23:16:37 +08:00
feat: openresty状态上报
This commit is contained in:
@@ -61,3 +61,9 @@ export function requestNodeAgentUpdate(
|
||||
body: JSON.stringify(payload ?? {}),
|
||||
});
|
||||
}
|
||||
|
||||
export function requestNodeOpenrestyRestart(id: number) {
|
||||
return apiRequest<NodeItem>(`/nodes/${id}/openresty-restart`, {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
deleteNode,
|
||||
getNodeAgentRelease,
|
||||
getNodes,
|
||||
requestNodeOpenrestyRestart,
|
||||
requestNodeAgentUpdate,
|
||||
updateNode,
|
||||
} from '@/features/nodes/api/nodes';
|
||||
@@ -45,6 +46,8 @@ import {
|
||||
getApplyVariant,
|
||||
getNodeStatusLabel,
|
||||
getNodeStatusVariant,
|
||||
getOpenrestyStatusLabel,
|
||||
getOpenrestyStatusVariant,
|
||||
getServerUrl,
|
||||
getUpdateMode,
|
||||
isMeaningfulTime,
|
||||
@@ -202,6 +205,20 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
|
||||
},
|
||||
});
|
||||
|
||||
const restartOpenrestyMutation = useMutation({
|
||||
mutationFn: () => requestNodeOpenrestyRestart(Number(nodeId)),
|
||||
onSuccess: async (updatedNode) => {
|
||||
setFeedback({
|
||||
tone: 'success',
|
||||
message: `已向节点 ${updatedNode.name} 下发 OpenResty 重启指令。`,
|
||||
});
|
||||
await queryClient.invalidateQueries({ queryKey: nodesQueryKey });
|
||||
},
|
||||
onError: (error) => {
|
||||
setFeedback({ tone: 'danger', message: getErrorMessage(error) });
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: () => deleteNode(Number(nodeId)),
|
||||
onSuccess: async () => {
|
||||
@@ -236,6 +253,23 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
|
||||
deleteMutation.mutate();
|
||||
};
|
||||
|
||||
const handleRestartOpenresty = () => {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!window.confirm(
|
||||
`确认向节点“${node.name}”下发 OpenResty 重启指令吗?该指令会在下一次心跳后执行。`,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFeedback(null);
|
||||
restartOpenrestyMutation.mutate();
|
||||
};
|
||||
|
||||
const handleCopy = async (value: string, message: string) => {
|
||||
try {
|
||||
await copyToClipboard(value);
|
||||
@@ -337,6 +371,17 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
|
||||
>
|
||||
{node.update_requested ? '查看 Agent 更新' : 'Agent 更新'}
|
||||
</PrimaryButton>
|
||||
<SecondaryButton
|
||||
type="button"
|
||||
onClick={handleRestartOpenresty}
|
||||
disabled={restartOpenrestyMutation.isPending}
|
||||
>
|
||||
{restartOpenrestyMutation.isPending
|
||||
? '下发重启中...'
|
||||
: node.restart_openresty_requested
|
||||
? '等待 OpenResty 重启'
|
||||
: '重启 OpenResty'}
|
||||
</SecondaryButton>
|
||||
<DangerButton
|
||||
type="button"
|
||||
onClick={handleDelete}
|
||||
@@ -378,6 +423,20 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
|
||||
</div>
|
||||
</AppCard>
|
||||
|
||||
<AppCard title="OpenResty 健康">
|
||||
<div className="space-y-3">
|
||||
<StatusBadge
|
||||
label={getOpenrestyStatusLabel(node.openresty_status)}
|
||||
variant={getOpenrestyStatusVariant(node.openresty_status)}
|
||||
/>
|
||||
<p className="text-sm text-[var(--foreground-secondary)]">
|
||||
{node.restart_openresty_requested
|
||||
? '已等待节点在下一次心跳后执行 OpenResty 重启。'
|
||||
: node.openresty_message || '当前未上报额外错误。'}
|
||||
</p>
|
||||
</div>
|
||||
</AppCard>
|
||||
|
||||
<AppCard title="更新模式">
|
||||
<div className="space-y-3">
|
||||
<StatusBadge
|
||||
@@ -485,6 +544,14 @@ export function NodeDetailPage({ nodeId }: { nodeId: string }) {
|
||||
{node.last_error || '无'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-[var(--foreground-primary)]">
|
||||
OpenResty 状态消息
|
||||
</p>
|
||||
<p className="mt-1 break-words whitespace-pre-wrap">
|
||||
{node.openresty_message || '无'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-[var(--foreground-primary)]">
|
||||
创建时间
|
||||
|
||||
@@ -36,6 +36,8 @@ import {
|
||||
getApplyVariant,
|
||||
getNodeStatusLabel,
|
||||
getNodeStatusVariant,
|
||||
getOpenrestyStatusLabel,
|
||||
getOpenrestyStatusVariant,
|
||||
isMeaningfulTime,
|
||||
} from '@/features/nodes/utils';
|
||||
|
||||
@@ -236,7 +238,8 @@ export function NodesPage() {
|
||||
<tr className="text-[var(--foreground-secondary)]">
|
||||
<th className="px-3 py-3 font-medium">节点</th>
|
||||
<th className="px-3 py-3 font-medium">状态</th>
|
||||
<th className="px-3 py-3 font-medium">Agent / Nginx</th>
|
||||
<th className="px-3 py-3 font-medium">Agent / OpenResty</th>
|
||||
<th className="px-3 py-3 font-medium">运行健康</th>
|
||||
<th className="px-3 py-3 font-medium">当前版本</th>
|
||||
<th className="px-3 py-3 font-medium">最近应用</th>
|
||||
<th className="px-3 py-3 font-medium">最近心跳</th>
|
||||
@@ -266,6 +269,21 @@ export function NodesPage() {
|
||||
{node.agent_version || 'unknown'} /{' '}
|
||||
{node.nginx_version || 'unknown'}
|
||||
</td>
|
||||
<td className="px-3 py-4">
|
||||
<div className="space-y-2">
|
||||
<StatusBadge
|
||||
label={getOpenrestyStatusLabel(
|
||||
node.openresty_status,
|
||||
)}
|
||||
variant={getOpenrestyStatusVariant(
|
||||
node.openresty_status,
|
||||
)}
|
||||
/>
|
||||
<p className="max-w-64 text-xs break-words whitespace-pre-wrap text-[var(--foreground-secondary)]">
|
||||
{node.openresty_message || '无额外错误'}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-4 text-[var(--foreground-secondary)]">
|
||||
{node.current_version || '未应用'}
|
||||
</td>
|
||||
|
||||
@@ -10,8 +10,11 @@ export interface NodeItem {
|
||||
update_requested: boolean;
|
||||
update_channel: ReleaseChannel;
|
||||
update_tag: string;
|
||||
restart_openresty_requested: boolean;
|
||||
agent_version: string;
|
||||
nginx_version: string;
|
||||
openresty_status: 'healthy' | 'unhealthy' | 'unknown';
|
||||
openresty_message: string;
|
||||
status: 'online' | 'offline' | 'pending';
|
||||
current_version: string;
|
||||
last_seen_at: string;
|
||||
|
||||
@@ -68,6 +68,30 @@ export function getUpdateMode(node: NodeItem) {
|
||||
return { label: '手动更新', variant: 'info' as const };
|
||||
}
|
||||
|
||||
export function getOpenrestyStatusVariant(status: NodeItem['openresty_status']) {
|
||||
if (status === 'healthy') {
|
||||
return 'success';
|
||||
}
|
||||
|
||||
if (status === 'unhealthy') {
|
||||
return 'danger';
|
||||
}
|
||||
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
export function getOpenrestyStatusLabel(status: NodeItem['openresty_status']) {
|
||||
if (status === 'healthy') {
|
||||
return '健康';
|
||||
}
|
||||
|
||||
if (status === 'unhealthy') {
|
||||
return '异常';
|
||||
}
|
||||
|
||||
return '未知';
|
||||
}
|
||||
|
||||
function parseVersionParts(version: string) {
|
||||
const normalized = version.trim().replace(/^v/i, '');
|
||||
if (!normalized || normalized.toLowerCase() === 'unknown') {
|
||||
|
||||
Reference in New Issue
Block a user