mirror of
https://github.com//cppla/ServerStatus
synced 2026-08-06 08:13:56 +08:00
224 lines
9.5 KiB
Go
224 lines
9.5 KiB
Go
package main
|
|
|
|
import "strings"
|
|
|
|
func openAPISpec() map[string]any {
|
|
paths := map[string]any{
|
|
"/api/health": map[string]any{
|
|
"get": publicOperation("health", "服务健康状态", objectResponse("Go 服务、Agent TCP 与配置状态")),
|
|
},
|
|
"/api/schema": map[string]any{
|
|
"get": publicOperation("schema", "ServerStatus API 简表", objectResponse("端点和集合描述")),
|
|
},
|
|
"/api/openapi.json": map[string]any{
|
|
"get": publicOperation("openapi", "OpenAPI 3.1 文档", objectResponse("OpenAPI document")),
|
|
},
|
|
"/json/stats.json": map[string]any{
|
|
"get": publicOperation("stats", "实时状态快照", objectResponse("WebUI 状态数据")),
|
|
},
|
|
"/api/config": map[string]any{
|
|
"get": protectedOperation("getConfig", "读取完整配置", nil, configResponse("200", "配置文档")),
|
|
"put": protectedOperation("replaceConfig", "整体校验并替换配置", requestBody("Config"), configResponse("200", "已保存并热重载的配置")),
|
|
},
|
|
"/api/reload": map[string]any{
|
|
"post": protectedOperation("reloadConfig", "从磁盘热重载配置", nil, objectResponse("重载结果")),
|
|
},
|
|
"/api/restart": map[string]any{
|
|
"post": protectedOperation("restartRuntime", "在进程内重启采集运行时", nil, statusResponse("202", "重启结果", map[string]any{
|
|
"operation": map[string]any{"type": "string", "const": "restart"},
|
|
"mode": map[string]any{"type": "string", "const": "in-process"},
|
|
})),
|
|
},
|
|
"/api/servers/{username}/reset-traffic": map[string]any{
|
|
"post": withParameters(protectedOperation("resetServerTraffic", "重置节点本月流量基线", nil, objectResponse("流量重置结果")), pathParameter("username", "节点用户名")),
|
|
},
|
|
}
|
|
|
|
for _, key := range []string{"servers", "monitors", "sslcerts", "watchdog"} {
|
|
spec := collectionSpecs[key]
|
|
schemaName := map[string]string{"servers": "Server", "monitors": "Monitor", "sslcerts": "SSLCert", "watchdog": "Watchdog"}[key]
|
|
operationBase := strings.TrimSuffix(key, "s")
|
|
if key == "sslcerts" {
|
|
operationBase = "sslcert"
|
|
}
|
|
paths["/api/"+key] = map[string]any{
|
|
"get": protectedOperation("list"+titleWord(key), "查询 "+key, nil, collectionResponse(key, schemaName, "配置集合")),
|
|
"post": protectedOperation("create"+titleWord(operationBase), "新增 "+spec.itemName, requestBody(schemaName), itemResponse("201", spec.itemName, schemaName, "创建结果")),
|
|
}
|
|
parameterName := "id"
|
|
parameterDescription := "数字下标或唯一的 " + spec.idField
|
|
if key == "servers" {
|
|
parameterName = "username"
|
|
parameterDescription = "节点用户名"
|
|
}
|
|
paths["/api/"+key+"/{"+parameterName+"}"] = map[string]any{
|
|
"put": withParameters(protectedOperation("update"+titleWord(operationBase), "修改 "+spec.itemName, requestBody(schemaName), itemResponse("200", spec.itemName, schemaName, "修改结果")), pathParameter(parameterName, parameterDescription)),
|
|
"delete": withParameters(protectedOperation("delete"+titleWord(operationBase), "删除 "+spec.itemName, nil, itemResponse("200", "removed", schemaName, "删除结果")), pathParameter(parameterName, parameterDescription)),
|
|
}
|
|
}
|
|
|
|
return map[string]any{
|
|
"openapi": "3.1.0",
|
|
"info": map[string]any{
|
|
"title": "ServerStatus HTTP API",
|
|
"version": version,
|
|
"description": "单进程 Go ServerStatus 的配置、运行状态和采集控制 API。",
|
|
},
|
|
"servers": []any{map[string]any{"url": "/", "description": "当前 ServerStatus 实例"}},
|
|
"paths": paths,
|
|
"components": map[string]any{
|
|
"securitySchemes": map[string]any{
|
|
"bearerAuth": map[string]any{"type": "http", "scheme": "bearer", "bearerFormat": "ADMIN_TOKEN"},
|
|
},
|
|
"schemas": openAPISchemas(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func openAPISchemas() map[string]any {
|
|
stringProperty := func(description string) map[string]any {
|
|
return map[string]any{"type": "string", "description": description}
|
|
}
|
|
integerProperty := func(description string, minimum, maximum int) map[string]any {
|
|
property := map[string]any{"type": "integer", "description": description, "minimum": minimum}
|
|
if maximum > 0 {
|
|
property["maximum"] = maximum
|
|
}
|
|
return property
|
|
}
|
|
server := map[string]any{
|
|
"type": "object", "required": collectionSpecs["servers"].required,
|
|
"properties": map[string]any{
|
|
"username": stringProperty("唯一客户端用户名"), "name": stringProperty("节点显示名称"),
|
|
"type": stringProperty("虚拟化或节点类型"), "host": stringProperty("主机标识"),
|
|
"location": stringProperty("位置"), "password": stringProperty("客户端密码"),
|
|
"monthstart": integerProperty("月流量重置日", 1, 28), "disabled": map[string]any{"type": "boolean", "default": false},
|
|
},
|
|
}
|
|
monitor := map[string]any{
|
|
"type": "object", "required": collectionSpecs["monitors"].required,
|
|
"properties": map[string]any{
|
|
"name": stringProperty("监测名称"), "host": stringProperty("HTTP(S) URL 或 TCP 地址"),
|
|
"type": stringProperty("https、http 或 tcp"), "interval": integerProperty("客户端探测间隔秒数", 1, 0),
|
|
},
|
|
}
|
|
sslcert := map[string]any{
|
|
"type": "object", "required": collectionSpecs["sslcerts"].required,
|
|
"properties": map[string]any{
|
|
"name": stringProperty("证书名称"), "domain": stringProperty("域名或 URL"),
|
|
"port": integerProperty("TLS 端口", 1, 65535), "interval": integerProperty("检查间隔秒数", 1, 0),
|
|
"callback": stringProperty("告警回调 URL 前缀"),
|
|
},
|
|
}
|
|
watchdog := map[string]any{
|
|
"type": "object", "required": collectionSpecs["watchdog"].required,
|
|
"properties": map[string]any{
|
|
"name": stringProperty("告警名称"), "rule": stringProperty("兼容 Exprtk 的状态表达式"),
|
|
"interval": integerProperty("通知冷却秒数", 1, 0), "callback": stringProperty("告警回调 URL 前缀"),
|
|
},
|
|
}
|
|
return map[string]any{
|
|
"Server": server, "Monitor": monitor, "SSLCert": sslcert, "Watchdog": watchdog,
|
|
"Config": map[string]any{
|
|
"type": "object", "required": []string{"servers", "monitors", "sslcerts", "watchdog"},
|
|
"properties": map[string]any{
|
|
"servers": arraySchema("Server"), "monitors": arraySchema("Monitor"),
|
|
"sslcerts": arraySchema("SSLCert"), "watchdog": arraySchema("Watchdog"),
|
|
},
|
|
},
|
|
"Error": map[string]any{
|
|
"type": "object", "required": []string{"ok", "error"},
|
|
"properties": map[string]any{"ok": map[string]any{"type": "boolean", "const": false}, "error": map[string]any{"type": "string"}, "details": map[string]any{}},
|
|
},
|
|
}
|
|
}
|
|
|
|
func titleWord(value string) string {
|
|
if value == "" {
|
|
return ""
|
|
}
|
|
return strings.ToUpper(value[:1]) + value[1:]
|
|
}
|
|
|
|
func schemaRef(name string) map[string]any {
|
|
return map[string]any{"$ref": "#/components/schemas/" + name}
|
|
}
|
|
func arraySchema(name string) map[string]any {
|
|
return map[string]any{"type": "array", "items": schemaRef(name)}
|
|
}
|
|
|
|
func requestBody(schemaName string) map[string]any {
|
|
return map[string]any{
|
|
"required": true,
|
|
"content": map[string]any{"application/json": map[string]any{"schema": schemaRef(schemaName)}},
|
|
}
|
|
}
|
|
|
|
func objectResponse(description string) map[string]any {
|
|
return map[string]any{"200": jsonResponse(description, map[string]any{"type": "object"}), "4XX": jsonResponse("请求错误", schemaRef("Error"))}
|
|
}
|
|
|
|
func configResponse(status, description string) map[string]any {
|
|
return statusResponse(status, description, map[string]any{"config": schemaRef("Config")})
|
|
}
|
|
|
|
func collectionResponse(key, schemaName, description string) map[string]any {
|
|
return statusResponse("200", description, map[string]any{key: arraySchema(schemaName)})
|
|
}
|
|
|
|
func itemResponse(status, key, schemaName, description string) map[string]any {
|
|
return statusResponse(status, description, map[string]any{
|
|
key: schemaRef(schemaName),
|
|
"reloaded": map[string]any{"type": "boolean", "const": true},
|
|
"pid": map[string]any{"type": "integer", "minimum": 1},
|
|
"config": schemaRef("Config"),
|
|
})
|
|
}
|
|
|
|
func statusResponse(status, description string, properties map[string]any) map[string]any {
|
|
required := []string{"ok"}
|
|
allProperties := map[string]any{"ok": map[string]any{"type": "boolean", "const": true}}
|
|
for key, property := range properties {
|
|
allProperties[key] = property
|
|
required = append(required, key)
|
|
}
|
|
return map[string]any{
|
|
status: jsonResponse(description, map[string]any{
|
|
"type": "object", "required": required, "properties": allProperties,
|
|
}),
|
|
"4XX": jsonResponse("请求错误", schemaRef("Error")),
|
|
}
|
|
}
|
|
|
|
func jsonResponse(description string, schema map[string]any) map[string]any {
|
|
return map[string]any{"description": description, "content": map[string]any{"application/json": map[string]any{"schema": schema}}}
|
|
}
|
|
|
|
func publicOperation(operationID, summary string, responses map[string]any) map[string]any {
|
|
return map[string]any{"operationId": operationID, "summary": summary, "security": []any{}, "responses": responses}
|
|
}
|
|
|
|
func protectedOperation(operationID, summary string, body map[string]any, responses map[string]any) map[string]any {
|
|
operation := map[string]any{
|
|
"operationId": operationID, "summary": summary,
|
|
"security": []any{map[string]any{"bearerAuth": []any{}}}, "responses": responses,
|
|
}
|
|
if body != nil {
|
|
operation["requestBody"] = body
|
|
}
|
|
return operation
|
|
}
|
|
|
|
func withParameters(operation map[string]any, parameters ...map[string]any) map[string]any {
|
|
items := make([]any, 0, len(parameters))
|
|
for _, parameter := range parameters {
|
|
items = append(items, parameter)
|
|
}
|
|
operation["parameters"] = items
|
|
return operation
|
|
}
|
|
|
|
func pathParameter(name, description string) map[string]any {
|
|
return map[string]any{"name": name, "in": "path", "required": true, "description": description, "schema": map[string]any{"type": "string"}}
|
|
}
|