mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-05 07:26:36 +08:00
[优化] 接口优化, 心跳请求返回规则摘要
This commit is contained in:
@@ -51,8 +51,7 @@ var AgentDiscoveryToken = ""
|
||||
var NodeOfflineThreshold = 2 * time.Minute
|
||||
|
||||
// V3 operational settings (hot-reloadable via Option table)
|
||||
var AgentHeartbeatInterval = 30000 // milliseconds
|
||||
var AgentSyncInterval = 30000 // milliseconds
|
||||
var AgentHeartbeatInterval = 10000 // milliseconds
|
||||
var AgentUpdateRepo = "Rain-kl/ATSFlare"
|
||||
|
||||
// V5 OpenResty performance settings (hot-reloadable via Option table)
|
||||
|
||||
@@ -91,6 +91,7 @@ func AgentHeartbeat(c *gin.Context) {
|
||||
"message": "",
|
||||
"data": node.Node,
|
||||
"agent_settings": node.AgentSettings,
|
||||
"active_config": node.ActiveConfig,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@ func InitOptionMap() {
|
||||
common.OptionMap["TurnstileSecretKey"] = ""
|
||||
common.OptionMap["AgentDiscoveryToken"] = ""
|
||||
common.OptionMap["AgentHeartbeatInterval"] = strconv.Itoa(common.AgentHeartbeatInterval)
|
||||
common.OptionMap["AgentSyncInterval"] = strconv.Itoa(common.AgentSyncInterval)
|
||||
common.OptionMap["NodeOfflineThreshold"] = strconv.Itoa(int(common.NodeOfflineThreshold.Milliseconds()))
|
||||
common.OptionMap["AgentUpdateRepo"] = common.AgentUpdateRepo
|
||||
common.OptionMap["OpenRestyWorkerProcesses"] = common.OpenRestyWorkerProcesses
|
||||
@@ -199,10 +198,6 @@ func updateOptionMap(key string, value string) {
|
||||
if v, err := strconv.Atoi(value); err == nil && v > 0 {
|
||||
common.AgentHeartbeatInterval = v
|
||||
}
|
||||
case "AgentSyncInterval":
|
||||
if v, err := strconv.Atoi(value); err == nil && v > 0 {
|
||||
common.AgentSyncInterval = v
|
||||
}
|
||||
case "NodeOfflineThreshold":
|
||||
if v, err := strconv.Atoi(value); err == nil && v > 0 {
|
||||
common.NodeOfflineThreshold = time.Duration(v) * time.Millisecond
|
||||
|
||||
@@ -284,9 +284,10 @@ func TestPhase2AgentLifecycle(t *testing.T) {
|
||||
t.Fatalf("unexpected heartbeat status %d: %s", restartHeartbeatRecorder.Code, restartHeartbeatRecorder.Body.String())
|
||||
}
|
||||
var restartHeartbeatBody struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
AgentSettings service.AgentSettings `json:"agent_settings"`
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
AgentSettings service.AgentSettings `json:"agent_settings"`
|
||||
ActiveConfig *service.ActiveConfigMeta `json:"active_config"`
|
||||
}
|
||||
if err = json.Unmarshal(restartHeartbeatRecorder.Body.Bytes(), &restartHeartbeatBody); err != nil {
|
||||
t.Fatalf("failed to decode heartbeat response: %v", err)
|
||||
@@ -297,6 +298,9 @@ func TestPhase2AgentLifecycle(t *testing.T) {
|
||||
if !restartHeartbeatBody.AgentSettings.RestartOpenrestyNow {
|
||||
t.Fatal("expected heartbeat response to instruct openresty restart")
|
||||
}
|
||||
if restartHeartbeatBody.ActiveConfig == nil || restartHeartbeatBody.ActiveConfig.Version == "" || restartHeartbeatBody.ActiveConfig.Checksum == "" {
|
||||
t.Fatal("expected heartbeat response to include active config summary")
|
||||
}
|
||||
|
||||
logsResp := performJSONRequest(t, engine, adminToken, http.MethodGet, "/api/apply-logs/?node_id="+createdNode.NodeID, nil)
|
||||
var logs []model.ApplyLog
|
||||
|
||||
@@ -57,7 +57,6 @@ type AgentConfigResponse struct {
|
||||
|
||||
type AgentSettings struct {
|
||||
HeartbeatInterval int `json:"heartbeat_interval"`
|
||||
SyncInterval int `json:"sync_interval"`
|
||||
AutoUpdate bool `json:"auto_update"`
|
||||
UpdateRepo string `json:"update_repo"`
|
||||
UpdateNow bool `json:"update_now"`
|
||||
@@ -66,9 +65,15 @@ type AgentSettings struct {
|
||||
RestartOpenrestyNow bool `json:"restart_openresty_now"`
|
||||
}
|
||||
|
||||
type ActiveConfigMeta struct {
|
||||
Version string `json:"version"`
|
||||
Checksum string `json:"checksum"`
|
||||
}
|
||||
|
||||
type HeartbeatResponse struct {
|
||||
Node *model.Node `json:"node"`
|
||||
AgentSettings *AgentSettings `json:"agent_settings"`
|
||||
Node *model.Node `json:"node"`
|
||||
AgentSettings *AgentSettings `json:"agent_settings"`
|
||||
ActiveConfig *ActiveConfigMeta `json:"active_config"`
|
||||
}
|
||||
|
||||
type NodeView struct {
|
||||
@@ -124,11 +129,14 @@ func HeartbeatNode(node *model.Node, payload AgentNodePayload) (*HeartbeatRespon
|
||||
if err := model.DB.Model(node).Select("ip", "agent_version", "nginx_version", "openresty_status", "openresty_message", "status", "current_version", "last_seen_at", "last_error", "update_requested", "update_channel", "update_tag", "restart_openresty_requested").Updates(node).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
activeConfig, err := GetActiveConfigMetaForAgent()
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
return &HeartbeatResponse{
|
||||
Node: node,
|
||||
AgentSettings: &AgentSettings{
|
||||
HeartbeatInterval: common.AgentHeartbeatInterval,
|
||||
SyncInterval: common.AgentSyncInterval,
|
||||
AutoUpdate: node.AutoUpdateEnabled,
|
||||
UpdateRepo: common.AgentUpdateRepo,
|
||||
UpdateNow: updateNow,
|
||||
@@ -136,6 +144,21 @@ func HeartbeatNode(node *model.Node, payload AgentNodePayload) (*HeartbeatRespon
|
||||
UpdateTag: updateTag,
|
||||
RestartOpenrestyNow: restartOpenrestyNow,
|
||||
},
|
||||
ActiveConfig: activeConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetActiveConfigMetaForAgent() (*ActiveConfigMeta, error) {
|
||||
version, err := model.GetActiveConfigVersion()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &ActiveConfigMeta{
|
||||
Version: version.Version,
|
||||
Checksum: version.Checksum,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,17 @@ func TestHeartbeatNodeReturnsPreviewUpdateSettings(t *testing.T) {
|
||||
if err := node.Insert(); err != nil {
|
||||
t.Fatalf("failed to seed node: %v", err)
|
||||
}
|
||||
if err := model.DB.Create(&model.ConfigVersion{
|
||||
Version: "20260313-001",
|
||||
SnapshotJSON: "{}",
|
||||
MainConfig: "worker_processes auto;",
|
||||
RenderedConfig: "server { listen 80; }",
|
||||
Checksum: "checksum-active-1",
|
||||
IsActive: true,
|
||||
CreatedBy: "root",
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("failed to seed active config version: %v", err)
|
||||
}
|
||||
|
||||
resp, err := HeartbeatNode(node, AgentNodePayload{
|
||||
NodeID: node.NodeID,
|
||||
@@ -94,6 +105,12 @@ func TestHeartbeatNodeReturnsPreviewUpdateSettings(t *testing.T) {
|
||||
if resp.AgentSettings == nil {
|
||||
t.Fatal("expected agent settings in heartbeat response")
|
||||
}
|
||||
if resp.ActiveConfig == nil {
|
||||
t.Fatal("expected active config summary in heartbeat response")
|
||||
}
|
||||
if resp.ActiveConfig.Version == "" || resp.ActiveConfig.Checksum == "" {
|
||||
t.Fatal("expected active config summary to include version and checksum")
|
||||
}
|
||||
if !resp.AgentSettings.UpdateNow {
|
||||
t.Fatal("expected update_now to be true")
|
||||
}
|
||||
|
||||
@@ -68,8 +68,7 @@ const defaultSystemFields = {
|
||||
};
|
||||
|
||||
const defaultOperationFields = {
|
||||
AgentHeartbeatInterval: '30000',
|
||||
AgentSyncInterval: '30000',
|
||||
AgentHeartbeatInterval: '10000',
|
||||
NodeOfflineThreshold: '120000',
|
||||
AgentUpdateRepo: 'Rain-kl/ATSFlare',
|
||||
OpenRestyWorkerProcesses: 'auto',
|
||||
@@ -335,8 +334,7 @@ export function SettingsPage() {
|
||||
});
|
||||
|
||||
setOperationFields({
|
||||
AgentHeartbeatInterval: optionMap.AgentHeartbeatInterval ?? '30000',
|
||||
AgentSyncInterval: optionMap.AgentSyncInterval ?? '30000',
|
||||
AgentHeartbeatInterval: optionMap.AgentHeartbeatInterval ?? '10000',
|
||||
NodeOfflineThreshold: optionMap.NodeOfflineThreshold ?? '120000',
|
||||
AgentUpdateRepo: optionMap.AgentUpdateRepo ?? 'Rain-kl/ATSFlare',
|
||||
OpenRestyWorkerProcesses: optionMap.OpenRestyWorkerProcesses ?? 'auto',
|
||||
@@ -942,10 +940,6 @@ export function SettingsPage() {
|
||||
operationFields.AgentHeartbeatInterval,
|
||||
10,
|
||||
);
|
||||
const sync = Number.parseInt(
|
||||
operationFields.AgentSyncInterval,
|
||||
10,
|
||||
);
|
||||
const offline = Number.parseInt(
|
||||
operationFields.NodeOfflineThreshold,
|
||||
10,
|
||||
@@ -954,9 +948,6 @@ export function SettingsPage() {
|
||||
if (Number.isNaN(heartbeat) || heartbeat < 5000) {
|
||||
throw new Error('心跳间隔不能小于 5000 毫秒。');
|
||||
}
|
||||
if (Number.isNaN(sync) || sync < 5000) {
|
||||
throw new Error('同步间隔不能小于 5000 毫秒。');
|
||||
}
|
||||
if (Number.isNaN(offline) || offline < 10000) {
|
||||
throw new Error('离线阈值不能小于 10000 毫秒。');
|
||||
}
|
||||
@@ -964,7 +955,6 @@ export function SettingsPage() {
|
||||
await saveOptionEntries(
|
||||
[
|
||||
['AgentHeartbeatInterval', String(heartbeat)],
|
||||
['AgentSyncInterval', String(sync)],
|
||||
['NodeOfflineThreshold', String(offline)],
|
||||
],
|
||||
'Agent 运行参数已保存。',
|
||||
@@ -980,7 +970,7 @@ export function SettingsPage() {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="grid gap-5 md:grid-cols-3">
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
<ResourceField
|
||||
label={`心跳间隔 (${formatDurationLabel(operationFields.AgentHeartbeatInterval)})`}
|
||||
>
|
||||
@@ -995,20 +985,6 @@ export function SettingsPage() {
|
||||
}
|
||||
/>
|
||||
</ResourceField>
|
||||
<ResourceField
|
||||
label={`同步间隔 (${formatDurationLabel(operationFields.AgentSyncInterval)})`}
|
||||
>
|
||||
<ResourceInput
|
||||
type="number"
|
||||
value={operationFields.AgentSyncInterval}
|
||||
onChange={(event) =>
|
||||
setOperationFields((previous) => ({
|
||||
...previous,
|
||||
AgentSyncInterval: event.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</ResourceField>
|
||||
<ResourceField
|
||||
label={`离线阈值 (${formatDurationLabel(operationFields.NodeOfflineThreshold)})`}
|
||||
>
|
||||
|
||||
@@ -422,7 +422,7 @@ export function WebsitesPage() {
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="网站"
|
||||
description="在同一页完成网站录入、默认证书绑定、证书导入和资产巡检,减少域名页与证书页之间的来回切换。"
|
||||
description="管理域名和TLS证书。"
|
||||
/>
|
||||
|
||||
{feedback ? (
|
||||
@@ -432,7 +432,6 @@ export function WebsitesPage() {
|
||||
<div className="grid gap-6 xl:grid-cols-[1.2fr_0.8fr]">
|
||||
<AppCard
|
||||
title={editingDomainId ? '编辑网站' : '快速添加网站'}
|
||||
description="录入域名后即可直接绑定默认证书。若证书尚未导入,可在右侧先导入再回到这里选择。"
|
||||
action={
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{editingDomainId ? (
|
||||
@@ -517,8 +516,7 @@ export function WebsitesPage() {
|
||||
</AppCard>
|
||||
|
||||
<AppCard
|
||||
title="证书快捷入口"
|
||||
description="先导入证书,再回到左侧立即绑定到网站。无需跳转到单独页面。"
|
||||
title="证书"
|
||||
action={
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<SecondaryButton
|
||||
|
||||
Reference in New Issue
Block a user