用户问题: 有 list_clusters MCP tool 但没简易 admin HTML。 A + B 方案: - A: OpenAPI 文档 + Scalar UI(让人看 + 试 admin API) - B: Cluster CRUD HTML 页面(让人填表创建/改 cluster) 实现: - internal/admin/openapi.yaml: 手写 6 admin 端点 + Cluster/AuditEntry/Error schema。auth_password 不在 spec(因 json:"-" tag) - internal/admin/openapi.go: //go:embed openapi.yaml + Scalar HTML GET /admin/docs 返 Scalar UI (CDN), GET /admin/docs/spec 返 YAML - internal/admin/web/cluster.html: 单文件 401 行暗色 monospace 风格 cluster CRUD 页面, 14 字段表单, vanilla JS + localStorage token + 内嵌 CSS, 无前端框架, 无 bundler - internal/admin/web.go: //go:embed web/cluster.html - internal/admin/router.go: Mount 里追加 4 路由 (GET /admin, /admin/, /admin/docs, /admin/docs/spec), 全部在 AdminAuth 组内 Linus 视角决策: 整个 /admin 子树 (含 HTML) 统一用 AdminAuth — 用户进页面前输 token, JS 存 localStorage, 后续 fetch 自动加 header。 简单且一致, 不搞"白名单 + 不带 token 也能看页"这种特殊 case。 Constraint: - 不引第三方 Go 库 (不用 gofiber/swagger/scalar-go) - 不引前端框架 (no React/Vue) - 不开 JS bundler (no webpack/vite) - HTML/CSS/JS 全内嵌在 cluster.html 一个文件 - Scalar 走 CDN (@scalar/api-reference latest) Co-Authored-By: Claude <noreply@anthropic.com>
130 lines
4.3 KiB
YAML
130 lines
4.3 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: spark-mcp-go Admin API
|
|
version: 0.0.0
|
|
description: |
|
|
Admin API for cluster and audit log configuration. All endpoints
|
|
require `Authorization: Bearer <admin_token>` (matches any token in
|
|
the comma-separated ADMIN_TOKENS env var).
|
|
servers:
|
|
- url: http://localhost:8080
|
|
description: Default local dev
|
|
components:
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
schemas:
|
|
Cluster:
|
|
type: object
|
|
required: [id, name, rm_url, shs_url, spark_submit_execute_bin]
|
|
properties:
|
|
id: { type: string, example: prod }
|
|
name: { type: string, example: Production }
|
|
rm_url: { type: string, format: uri, example: http://rm:8088 }
|
|
shs_url: { type: string, format: uri, example: http://shs:18080 }
|
|
spark_submit_execute_bin:
|
|
type: string
|
|
example: /opt/spark/bin/spark-submit
|
|
description: Absolute path to spark-submit binary
|
|
is_active: { type: boolean, default: true }
|
|
auth_type:
|
|
type: string
|
|
enum: [none, simple, basic]
|
|
default: none
|
|
auth_username: { type: string, description: 'simple: user.name, basic: username' }
|
|
ssl_verify: { type: boolean, default: true }
|
|
ssl_ca_bundle: { type: string }
|
|
url_allowlist:
|
|
type: array
|
|
items: { type: string }
|
|
description: Additional host patterns (besides RM/SHS) permitted for fetch_url
|
|
default_submit_args:
|
|
type: array
|
|
items: { type: string }
|
|
description: Prepended to every spark_submit args
|
|
rate_limit_per_min:
|
|
type: integer
|
|
default: 10
|
|
minimum: 0
|
|
created_at: { type: string, format: date-time }
|
|
updated_at: { type: string, format: date-time }
|
|
AuditEntry:
|
|
type: object
|
|
properties:
|
|
id: { type: integer }
|
|
timestamp: { type: string, format: date-time }
|
|
actor: { type: string, example: 'admin:secret-a' }
|
|
action: { type: string, enum: [cluster.create, cluster.update, cluster.delete] }
|
|
cluster_id: { type: string, nullable: true }
|
|
details: { type: string, description: 'JSON-encoded before/after diff' }
|
|
Error:
|
|
type: object
|
|
properties:
|
|
error: { type: string }
|
|
security:
|
|
- bearerAuth: []
|
|
paths:
|
|
/admin/clusters:
|
|
get:
|
|
summary: List all clusters
|
|
responses:
|
|
'200':
|
|
description: Array of clusters (always [] even if empty)
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/Cluster' }
|
|
post:
|
|
summary: Create a cluster
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/Cluster' }
|
|
responses:
|
|
'201': { description: Created, content: { application/json: { schema: { $ref: '#/components/schemas/Cluster' } } } }
|
|
'400': { description: Validation error, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
|
|
/admin/clusters/{id}:
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema: { type: string }
|
|
get:
|
|
summary: Get a single cluster
|
|
responses:
|
|
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/Cluster' } } } }
|
|
'404': { description: Not found }
|
|
put:
|
|
summary: Update a cluster (password in body is ignored; set via dedicated endpoint)
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/Cluster' }
|
|
responses:
|
|
'200': { description: OK }
|
|
'404': { description: Not found }
|
|
delete:
|
|
summary: Delete a cluster
|
|
responses:
|
|
'204': { description: Deleted }
|
|
'404': { description: Not found }
|
|
/admin/audit:
|
|
get:
|
|
summary: Query audit log (most recent first)
|
|
parameters:
|
|
- name: limit
|
|
in: query
|
|
schema: { type: integer, default: 100, minimum: 1, maximum: 1000 }
|
|
responses:
|
|
'200':
|
|
description: Array of audit entries
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items: { $ref: '#/components/schemas/AuditEntry' }
|