mirror of
https://github.com//cppla/ServerStatus
synced 2026-08-06 10:13:57 +08:00
1.2.0
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
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"}}
|
||||
}
|
||||
Reference in New Issue
Block a user