diff --git a/src/console/app.controller.ts b/src/console/app.controller.ts
index 8bef2135..999878ed 100644
--- a/src/console/app.controller.ts
+++ b/src/console/app.controller.ts
@@ -2,6 +2,7 @@ import {
Controller,
Get,
Post,
+ Put,
Delete,
Param,
Body,
@@ -23,6 +24,15 @@ export class AppController {
};
}
+ @Put("hub/gateway")
+ updateGateway(@Body() body: { url: string }) {
+ this.hub.reconnect(body.url);
+ return {
+ url: this.hub.url,
+ connectionState: this.hub.connectionState,
+ };
+ }
+
@Get("agents")
listAgents() {
return this.hub.listAgents().map((id) => {
diff --git a/src/console/public/index.html b/src/console/public/index.html
index 720439db..f07f0cf9 100644
--- a/src/console/public/index.html
+++ b/src/console/public/index.html
@@ -15,6 +15,9 @@
.agent-list { list-style: none; }
.agent-list li { display: flex; justify-content: space-between; align-items: center; padding: 0.6rem 0; border-bottom: 1px solid #222; font-family: monospace; font-size: 0.85rem; }
.agent-list li:last-child { border-bottom: none; }
+ .gateway-form { display: flex; gap: 0.5rem; align-items: center; margin-top: 0.8rem; }
+ .gateway-form input { background: #0a0a0a; color: #e0e0e0; border: 1px solid #444; border-radius: 4px; padding: 0.4rem 0.6rem; font-size: 0.85rem; font-family: monospace; flex: 1; }
+ .gateway-form input:focus { outline: none; border-color: #666; }
button { background: #2a2a2a; color: #e0e0e0; border: 1px solid #444; border-radius: 4px; padding: 0.4rem 0.8rem; cursor: pointer; font-size: 0.8rem; }
button:hover { background: #3a3a3a; }
.btn-create { background: #1a3a1a; border-color: #2a5a2a; }
@@ -30,6 +33,10 @@
Hub Status
Loading...
+
+
+
+
@@ -56,6 +63,11 @@
`State: ${hubRes.connectionState}` +
`Agents: ${hubRes.agentCount}`;
+ const input = document.getElementById('gateway-url');
+ if (!input.dataset.edited) {
+ input.value = hubRes.url;
+ }
+
const list = document.getElementById('agent-list');
if (agentsRes.length === 0) {
list.innerHTML = '
No agents';
@@ -66,6 +78,23 @@
}
}
+ document.getElementById('gateway-url').addEventListener('input', function() {
+ this.dataset.edited = '1';
+ });
+
+ async function updateGateway() {
+ const input = document.getElementById('gateway-url');
+ const url = input.value.trim();
+ if (!url) return;
+ await fetch(`${API}/hub/gateway`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ url }),
+ });
+ input.dataset.edited = '';
+ refresh();
+ }
+
async function createAgent() {
await fetch(`${API}/agents`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' });
refresh();
diff --git a/src/hub/hub.ts b/src/hub/hub.ts
index 2d39b6f6..ae6c5cfe 100644
--- a/src/hub/hub.ts
+++ b/src/hub/hub.ts
@@ -7,8 +7,8 @@ import { GatewayClient } from "../shared/gateway-sdk/client.js";
export class Hub {
private readonly agents = new Map();
private readonly agentSenders = new Map();
- private readonly client: GatewayClient;
- readonly url: string;
+ private client: GatewayClient;
+ url: string;
readonly path: string;
readonly deviceId: string;
@@ -21,9 +21,13 @@ export class Hub {
this.url = url;
this.path = path ?? "/ws";
this.deviceId = getDeviceId();
+ this.client = this.createClient(this.url);
+ this.client.connect();
+ }
- this.client = new GatewayClient({
- url: this.url,
+ private createClient(url: string): GatewayClient {
+ const client = new GatewayClient({
+ url,
path: this.path,
deviceId: this.deviceId,
deviceType: "client",
@@ -31,19 +35,19 @@ export class Hub {
reconnectDelay: 1000,
});
- this.client.onStateChange((state) => {
+ client.onStateChange((state) => {
console.log(`[Hub] Connection state: ${state}`);
});
- this.client.onRegistered((deviceId) => {
+ client.onRegistered((deviceId) => {
console.log(`[Hub] Registered as: ${deviceId}`);
});
- this.client.onError((err) => {
+ client.onError((err) => {
console.error(`[Hub] Connection error:`, err.message);
});
- this.client.onMessage((msg) => {
+ client.onMessage((msg) => {
console.log(`[Hub] Received message: id=${msg.id} from=${msg.from} to=${msg.to} action=${msg.action} payload=${JSON.stringify(msg.payload)}`);
const payload = msg.payload as { agentId?: string; content?: string } | undefined;
const agentId = payload?.agentId;
@@ -61,10 +65,19 @@ export class Hub {
}
});
- this.client.onSendError((err) => {
+ client.onSendError((err) => {
console.error(`[Hub] Send error: messageId=${err.messageId} code=${err.code} error=${err.error}`);
});
+ return client;
+ }
+
+ /** 重连到新的 Gateway 地址 */
+ reconnect(url: string): void {
+ console.log(`[Hub] Reconnecting to ${url}`);
+ this.client.disconnect();
+ this.url = url;
+ this.client = this.createClient(url);
this.client.connect();
}