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:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ const doc = {
|
||||
{ name: 'Admin · Activity', description: 'Admin activity log' },
|
||||
{ name: 'Admin · Bot Activity', description: 'Bot-scoring/ban state and emergency unban (admin only)' },
|
||||
{ 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)' },
|
||||
{ name: 'Admin · Users', description: 'User management (admin only)' },
|
||||
],
|
||||
@@ -587,6 +588,15 @@ const doc = {
|
||||
linkedAt: { type: 'string', format: 'date-time' },
|
||||
},
|
||||
},
|
||||
TownCrierRequest: {
|
||||
type: 'object',
|
||||
required: ['id', 'lines'],
|
||||
properties: {
|
||||
id: { type: 'string', maxLength: 64, description: 'Re-posting the same id replaces the prior entry.', example: 'news-42' },
|
||||
lines: { type: 'array', items: { type: 'string', maxLength: 200 }, example: ['Hear ye!', 'Market tax is now 5%.'] },
|
||||
durationSec: { type: 'integer', minimum: 1, maximum: 86400, example: 3600 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user