This commit is contained in:
Timothy Jaeryang Baek
2026-03-26 18:13:18 -05:00
parent 11f52921dc
commit a641325707
6 changed files with 98 additions and 15 deletions
+45
View File
@@ -269,6 +269,51 @@ async def set_terminal_servers_config(
}
@router.post('/terminal_servers/verify')
async def verify_terminal_server_connection(
request: Request, form_data: TerminalServerConnection, user=Depends(get_admin_user)
):
"""
Verify the connection to a terminal server by detecting its type.
Tries GET {url}/api/v1/policies (orchestrator) then GET {url}/api/config
(plain terminal). Returns ``{status: true, type: "orchestrator"|"terminal"}``.
"""
base_url = (form_data.url or '').rstrip('/')
if not base_url:
raise HTTPException(status_code=400, detail='Terminal server URL is required')
headers = {}
if form_data.auth_type == 'bearer' and form_data.key:
headers['Authorization'] = f'Bearer {form_data.key}'
try:
async with aiohttp.ClientSession(
trust_env=True,
timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT),
) as session:
# Orchestrators expose a policies API; plain terminals don't.
try:
async with session.get(f'{base_url}/api/v1/policies', headers=headers) as resp:
if resp.ok:
return {'status': True, 'type': 'orchestrator'}
except Exception:
pass
# Fall back to open-terminal config endpoint.
try:
async with session.get(f'{base_url}/api/config', headers=headers) as resp:
if resp.ok:
return {'status': True, 'type': 'terminal'}
except Exception:
pass
except Exception as e:
log.debug(f'Failed to connect to the terminal server: {e}')
raise HTTPException(status_code=400, detail='Failed to connect to the terminal server')
@router.post('/tool_servers/verify')
async def verify_tool_servers_config(request: Request, form_data: ToolServerConnection, user=Depends(get_admin_user)):
"""
+34
View File
@@ -308,6 +308,40 @@ export const putOrchestratorPolicy = async (
return res;
};
/**
* Verify a terminal server connection via the backend proxy.
* Used for system/admin connections to avoid CORS issues and API key exposure.
*/
export const verifyTerminalServerConnection = async (token: string, connection: object) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/configs/terminal_servers/verify`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
...connection
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.error(err);
error = err.detail;
return null;
});
if (error) {
throw error;
}
return res;
};
export const verifyToolServerConnection = async (token: string, connection: object) => {
let error = null;
@@ -12,12 +12,12 @@
import LockClosed from '$lib/components/icons/LockClosed.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import { detectTerminalServerType, putOrchestratorPolicy } from '$lib/apis/configs';
import { detectTerminalServerType, verifyTerminalServerConnection, putOrchestratorPolicy } from '$lib/apis/configs';
import { getTerminalConfig } from '$lib/apis/terminal';
export let show = false;
export let edit = false;
export let admin = false;
export let direct = false;
export let connection = null;
export let onSubmit: Function = () => {};
@@ -110,9 +110,13 @@
verifying = true;
try {
if (admin) {
// Admin: detect orchestrator vs terminal
const type = await detectTerminalServerType(_url, key);
if (!direct) {
// System connection: proxy through backend to avoid CORS / key exposure
const result = await verifyTerminalServerConnection(
localStorage.token,
{ url: _url, key, auth_type }
);
const type = result?.type ?? null;
if (type) {
serverType = type;
@@ -137,7 +141,7 @@
toast.error($i18n.t('Server connection failed'));
}
} else {
// Non-admin: simple terminal verification
// Direct connection: verify from browser
const res = await getTerminalConfig(_url, key);
if (res) {
toast.success($i18n.t('Server connection verified'));
@@ -192,7 +196,7 @@
url = url.replace(/\/$/, '');
// Save policy to orchestrator if applicable
if (serverType === 'orchestrator' && admin && policyId) {
if (serverType === 'orchestrator' && !direct && policyId) {
try {
await putOrchestratorPolicy(url, key, policyId, buildPolicyData());
} catch (err) {
@@ -202,7 +206,7 @@
}
const result = {
...(admin && id.trim() ? { id: id.trim() } : {}),
...(!direct && id.trim() ? { id: id.trim() } : {}),
url,
key,
name,
@@ -210,7 +214,7 @@
auth_type,
enabled: enabled,
config: {
...(admin ? { access_grants: accessGrants } : {})
...(!direct ? { access_grants: accessGrants } : {})
},
// Policy fields
...(serverType ? { server_type: serverType } : {}),
@@ -270,7 +274,7 @@
/>
</div>
</div>
{#if admin}
{#if !direct}
<div class="flex flex-col flex-1">
<div class="flex justify-between mb-0.5">
<label
@@ -368,7 +372,7 @@
</div>
<!-- Policy section (orchestrator only, admin only) -->
{#if serverType === 'orchestrator' && admin}
{#if serverType === 'orchestrator' && !direct}
<div class="flex gap-2 mt-2">
<div class="flex flex-col w-full">
<div class="flex justify-between mb-0.5">
@@ -580,7 +584,7 @@
{$i18n.t('Advanced')}
</button>
{#if admin}
{#if !direct}
<button
class="bg-gray-50 hover:bg-gray-100 text-black dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white transition px-2 py-1 object-cover rounded-full flex gap-1 items-center mt-2"
type="button"
@@ -662,7 +666,7 @@
>
<option value="none">{$i18n.t('None')}</option>
<option value="bearer">{$i18n.t('Bearer')}</option>
{#if admin}
{#if !direct}
<option value="session">{$i18n.t('Session')}</option>
<option value="system_oauth">{$i18n.t('OAuth')}</option>
{/if}
@@ -122,7 +122,6 @@
<AddToolServerModal bind:show={showConnectionModal} onSubmit={addConnectionHandler} />
<AddTerminalServerModal
admin
bind:show={showAddTerminalModal}
edit={editTerminalIdx !== null}
connection={editTerminalIdx !== null ? terminalConnections[editTerminalIdx] : null}
@@ -38,7 +38,7 @@
};
</script>
<AddTerminalServerModal bind:show={showAddModal} onSubmit={(server) => addServer(server)} />
<AddTerminalServerModal direct bind:show={showAddModal} onSubmit={(server) => addServer(server)} />
<div>
<div class="flex justify-between items-center mb-1">
@@ -20,6 +20,7 @@
</script>
<AddTerminalServerModal
direct
edit
bind:show={showConfigModal}
{connection}