Skip to content

Commit d85b52b

Browse files
committed
refac
1 parent 05f314b commit d85b52b

5 files changed

Lines changed: 461 additions & 23 deletions

File tree

backend/open_webui/routers/configs.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,11 @@ class TerminalServerConnection(BaseModel):
229229

230230
config: Optional[dict] = None
231231

232+
# Orchestrator policy fields
233+
server_type: Optional[str] = None # "orchestrator", "terminal"
234+
policy_id: Optional[str] = None
235+
policy: Optional[dict] = None # cached policy data
236+
232237
model_config = ConfigDict(extra="allow")
233238

234239

backend/open_webui/routers/terminals.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ async def proxy_terminal(
7575
)
7676

7777
target_url = f"{base_url}/{path}"
78+
79+
# Route through orchestrator policy endpoint if policy_id is set
80+
policy_id = connection.get("policy_id")
81+
if policy_id:
82+
target_url = f"{base_url}/p/{policy_id}/{path}"
83+
7884
if request.query_params:
7985
target_url += f"?{request.query_params}"
8086

@@ -236,14 +242,18 @@ async def ws_terminal(
236242
# Build upstream WebSocket URL (no token in URL)
237243
ws_base = base_url.replace("https://", "wss://").replace("http://", "ws://")
238244

239-
auth_type = connection.get("auth_type", "bearer")
245+
# Route through orchestrator policy endpoint if policy_id is set
246+
policy_id = connection.get("policy_id")
240247
upstream_params = {}
241248
# For orchestrator-backed servers, pass user_id
242249
upstream_params["user_id"] = user.id
243250

244251
import urllib.parse
245252

246-
upstream_url = f"{ws_base}/api/terminals/{session_id}"
253+
if policy_id:
254+
upstream_url = f"{ws_base}/p/{policy_id}/api/terminals/{session_id}"
255+
else:
256+
upstream_url = f"{ws_base}/api/terminals/{session_id}"
247257
if upstream_params:
248258
upstream_url += f"?{urllib.parse.urlencode(upstream_params)}"
249259

backend/open_webui/utils/tools.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -914,9 +914,17 @@ async def set_terminal_servers(request: Request):
914914

915915
enabled = connection.get("enabled", True)
916916

917+
base_url = connection.get("url", "").rstrip("/")
918+
policy_id = connection.get("policy_id", "")
919+
920+
# Orchestrator connections route through /p/{policy_id}/ — the
921+
# OpenAPI spec lives on the proxied terminal, not the orchestrator.
922+
if connection.get("server_type") == "orchestrator" and policy_id:
923+
base_url = f"{base_url}/p/{policy_id}"
924+
917925
server_configs.append(
918926
{
919-
"url": connection.get("url", ""),
927+
"url": base_url,
920928
"key": connection.get("key", ""),
921929
"auth_type": connection.get("auth_type", "bearer"),
922930
"path": connection.get("path", "/openapi.json"),

src/lib/apis/configs/index.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,85 @@ export const setTerminalServerConnections = async (token: string, connections: o
229229
return res;
230230
};
231231

232+
/**
233+
* Detect whether a terminal server URL points to an Orchestrator or a direct
234+
* Open Terminal instance.
235+
*
236+
* - GET {url}/api/v1/policies → 200 → "orchestrator"
237+
* - GET {url}/api/config → 200 → "terminal"
238+
* - Neither → null
239+
*/
240+
export const detectTerminalServerType = async (
241+
url: string,
242+
key: string
243+
): Promise<'orchestrator' | 'terminal' | null> => {
244+
const baseUrl = url.replace(/\/$/, '');
245+
const headers: Record<string, string> = {};
246+
if (key) {
247+
headers['Authorization'] = `Bearer ${key}`;
248+
}
249+
250+
// Orchestrators expose a policies API; plain terminals don't.
251+
try {
252+
const res = await fetch(`${baseUrl}/api/v1/policies`, { headers });
253+
if (res.ok) return 'orchestrator';
254+
} catch {
255+
// ignore
256+
}
257+
258+
// Fall back to open-terminal config endpoint.
259+
try {
260+
const res = await fetch(`${baseUrl}/api/config`, { headers });
261+
if (res.ok) return 'terminal';
262+
} catch {
263+
// ignore
264+
}
265+
266+
return null;
267+
};
268+
269+
/**
270+
* Create or update a policy on the orchestrator.
271+
* PUT {url}/api/v1/policies/{policyId}
272+
*/
273+
export const putOrchestratorPolicy = async (
274+
url: string,
275+
key: string,
276+
policyId: string,
277+
policyData: object
278+
): Promise<object | null> => {
279+
let error = null;
280+
281+
const baseUrl = url.replace(/\/$/, '');
282+
const headers: Record<string, string> = {
283+
'Content-Type': 'application/json'
284+
};
285+
if (key) {
286+
headers['Authorization'] = `Bearer ${key}`;
287+
}
288+
289+
const res = await fetch(`${baseUrl}/api/v1/policies/${encodeURIComponent(policyId)}`, {
290+
method: 'PUT',
291+
headers,
292+
body: JSON.stringify(policyData)
293+
})
294+
.then(async (res) => {
295+
if (!res.ok) throw await res.json();
296+
return res.json();
297+
})
298+
.catch((err) => {
299+
console.error(err);
300+
error = err.detail;
301+
return null;
302+
});
303+
304+
if (error) {
305+
throw error;
306+
}
307+
308+
return res;
309+
};
310+
232311
export const verifyToolServerConnection = async (token: string, connection: object) => {
233312
let error = null;
234313

0 commit comments

Comments
 (0)