Add admin shard control: config, status, town crier (phase 5)

- admin/uoLink.controller.js: GET /admin/uo-link/config (masked config + live
  health + ingestion stats from the socket/broadcaster); PUT to save base/ws
  URL + write-only token + protocol + enabled, which (re)starts or stops the WS
  ingest client and activity-logs the change; POST/DELETE /uo-link/towncrier to
  publish/remove town-crier messages; GET /uo-link/stream (admin SSE channel,
  full feed incl. audit/cheat). Mounted adminOnly with express-validator guards
  + #swagger annotations (new "Admin · Shard" tag, TownCrierRequest schema).
- server.js: startup probe (checkUoLink) that logs reachability and warns
  loudly on a protocol mismatch when the integration is enabled.
- client: api.admin uo-link methods; ShardAdmin.jsx control panel (status
  panel with ingestion stats, config form, town crier) modeled on
  DiscordBotAdmin; wired into AdminLayout nav/titles + the /admin/shard route.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011qPmpmVH1xGCiZoz9m9vW3
This commit is contained in:
2026-07-11 02:22:17 -05:00
parent 1c9a9d26e1
commit e7bc316863
9 changed files with 825 additions and 0 deletions

View File

@@ -80,6 +80,10 @@
"name": "Admin · Discord Bot",
"description": "Discord bot token/config and live status (admin only)"
},
{
"name": "Admin · Shard",
"description": "uo-link sidecar connection config, live status and town crier (admin only)"
},
{
"name": "Admin · Auth Providers",
"description": "SSO provider configuration (admin only)"
@@ -5761,6 +5765,270 @@
]
}
},
"/api/v1/admin/uo-link/config": {
"get": {
"tags": [
"Admin · Shard"
],
"summary": "Get uo-link config + live status + ingestion stats (admin only)",
"description": "",
"responses": {
"200": {
"description": "Masked config, health and ingestion stats",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
},
"put": {
"tags": [
"Admin · Shard"
],
"summary": "Save uo-link connection config (admin only)",
"description": "token is write-only — omit/blank it to keep the existing one. Saving (re)starts the WS ingest client.",
"responses": {
"200": {
"description": "Updated config + live status",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Validation error, or missing token while enabling",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Admin role required",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"baseUrl": {
"type": "string"
},
"wsUrl": {
"type": "string"
},
"token": {
"type": "string"
},
"protocol": {
"type": "integer"
},
"enabled": {
"type": "boolean"
}
}
}
}
}
}
}
},
"/api/v1/admin/uo-link/towncrier": {
"post": {
"tags": [
"Admin · Shard"
],
"summary": "Publish / replace a town-crier message (admin only)",
"description": "",
"responses": {
"200": {
"description": "Posted",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Rejected (over caps)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"502": {
"description": "Bad Gateway"
},
"503": {
"description": "Shard unavailable",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TownCrierRequest"
}
}
}
}
}
},
"/api/v1/admin/uo-link/towncrier/{id}": {
"delete": {
"tags": [
"Admin · Shard"
],
"summary": "Remove a town-crier message (admin only)",
"description": "",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "Town-crier message id."
}
],
"responses": {
"200": {
"description": "Removed",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"400": {
"description": "Bad Request"
},
"404": {
"description": "Unknown id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"500": {
"description": "Internal Server Error"
},
"502": {
"description": "Bad Gateway"
},
"503": {
"description": "Service Unavailable"
}
},
"security": [
{
"cookieAuth": []
},
{
"bearerAuth": []
}
]
}
},
"/api/v1/admin/uo-link/stream": {
"get": {
"tags": [
"Admin · Shard"
],
"summary": "Full live shard event stream incl. audit/cheat (SSE, admin only)",
"description": "",
"responses": {
"200": {
"description": "An SSE stream (Content-Type: text/event-stream)."
}
}
}
},
"/api/v1/player/account": {
"get": {
"tags": [
@@ -10408,6 +10676,104 @@
}
}
}
},
"TownCrierRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "object"
},
"required": {
"type": "array",
"example": [
"id",
"lines"
],
"items": {
"type": "string"
}
},
"properties": {
"type": "object",
"properties": {
"id": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"maxLength": {
"type": "number",
"example": 64
},
"description": {
"type": "string",
"example": "Re-posting the same id replaces the prior entry."
},
"example": {
"type": "string",
"example": "news-42"
}
}
},
"lines": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "array"
},
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "string"
},
"maxLength": {
"type": "number",
"example": 200
}
}
},
"example": {
"type": "array",
"example": [
"Hear ye!",
"Market tax is now 5%."
],
"items": {
"type": "string"
}
}
}
},
"durationSec": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "integer"
},
"minimum": {
"type": "number",
"example": 1
},
"maximum": {
"type": "number",
"example": 86400
},
"example": {
"type": "number",
"example": 3600
}
}
}
}
}
}
}
}
}