Skip to content

Commit 6890618

Browse files
committed
refac
1 parent a3238aa commit 6890618

4 files changed

Lines changed: 378 additions & 21 deletions

File tree

backend/open_webui/routers/terminals.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,20 @@ def _sanitize_proxy_path(path: str) -> str | None:
3131
"""Sanitize a proxy path to prevent directory traversal / SSRF.
3232
3333
Returns the cleaned path, or None if the path is invalid.
34+
Trailing slashes are preserved — many upstream frameworks treat
35+
``/path`` and ``/path/`` differently.
3436
"""
3537
decoded = unquote(path)
38+
had_trailing_slash = decoded.endswith('/')
3639
normalized = posixpath.normpath(decoded)
3740
# Remove any leading slashes that would reset the base
3841
cleaned = normalized.lstrip('/')
3942
# Reject if normpath resolved to parent traversal or current-dir only
4043
if cleaned.startswith('..') or cleaned == '.':
4144
return None
45+
# Restore trailing slash if the original path had one
46+
if had_trailing_slash and cleaned and not cleaned.endswith('/'):
47+
cleaned += '/'
4248
return cleaned
4349

4450

src/lib/components/chat/FileNav.svelte

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import FilePreview from './FileNav/FilePreview.svelte';
4141
import FileEntryRow from './FileNav/FileEntryRow.svelte';
4242
import PortList from './FileNav/PortList.svelte';
43+
import PortPreview from './FileNav/PortPreview.svelte';
4344
import XTerminal from './XTerminal.svelte';
4445
4546
const i18n = getContext('i18n');
@@ -90,6 +91,7 @@
9091
9192
// ── File preview state ───────────────────────────────────────────────
9293
let selectedFile: string | null = null;
94+
let previewPort: number | null = null;
9395
let fileContent: string | null = null;
9496
let fileImageUrl: string | null = null;
9597
let fileVideoUrl: string | null = null;
@@ -253,6 +255,7 @@
253255
loading = true;
254256
error = null;
255257
selectedFile = null;
258+
previewPort = null;
256259
clearFilePreview();
257260
currentPath = path;
258261
savedPath = path;
@@ -632,6 +635,7 @@
632635
</div>
633636
{/if}
634637

638+
{#if previewPort === null}
635639
<FileNavToolbar
636640
breadcrumbs={buildBreadcrumbs(currentPath)}
637641
{selectedFile}
@@ -911,10 +915,17 @@
911915
</button>
912916
</Tooltip>
913917
</FileNavToolbar>
918+
{/if}
914919

915920
<!-- Content -->
916921
<div class="flex-1 overflow-y-auto min-h-0 min-w-0">
917-
{#if selectedFile !== null}
922+
{#if previewPort !== null}
923+
<PortPreview
924+
baseUrl={selectedTerminal?.url ?? ''}
925+
port={previewPort}
926+
onClose={() => { previewPort = null; }}
927+
/>
928+
{:else if selectedFile !== null}
918929
<FilePreview
919930
bind:this={filePreviewRef}
920931
bind:editing
@@ -1039,9 +1050,17 @@
10391050
</div>
10401051

10411052
<!-- Port detection -->
1042-
{#if selectedTerminal && !selectedFile}
1053+
{#if selectedTerminal && !selectedFile && previewPort === null}
10431054
<div class="shrink-0 border-t border-gray-100 dark:border-gray-800">
1044-
<PortList baseUrl={selectedTerminal.url} apiKey={selectedTerminal.key} />
1055+
<PortList
1056+
baseUrl={selectedTerminal.url}
1057+
apiKey={selectedTerminal.key}
1058+
on:previewPort={(e) => {
1059+
selectedFile = null;
1060+
clearFilePreview();
1061+
previewPort = e.detail;
1062+
}}
1063+
/>
10451064
</div>
10461065
{/if}
10471066

src/lib/components/chat/FileNav/PortList.svelte

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
<script lang="ts">
2-
import { onDestroy, getContext } from 'svelte';
2+
import { onDestroy, getContext, createEventDispatcher } from 'svelte';
33
import type { ListeningPort } from '$lib/apis/terminal';
44
import { getListeningPorts, getPortProxyUrl } from '$lib/apis/terminal';
55
import Tooltip from '$lib/components/common/Tooltip.svelte';
66
77
const i18n = getContext('i18n');
8+
const dispatch = createEventDispatcher<{ previewPort: number }>();
89
910
export let baseUrl: string;
1011
export let apiKey: string;
@@ -33,7 +34,11 @@
3334
}
3435
};
3536
36-
const openPort = (port: number) => {
37+
const previewPort = (port: number) => {
38+
dispatch('previewPort', port);
39+
};
40+
41+
const openPortExternal = (port: number) => {
3742
const url = getPortProxyUrl(baseUrl, port);
3843
window.open(url, '_blank', 'noopener,noreferrer');
3944
};
@@ -107,30 +112,36 @@
107112
{#each ports as port}
108113
<button
109114
class="flex items-center w-full gap-2 px-1.5 py-1 text-xs rounded hover:bg-gray-100 dark:hover:bg-gray-800 transition group"
110-
on:click={() => openPort(port.port)}
115+
on:click={() => previewPort(port.port)}
111116
>
112117
<span class="font-mono text-blue-500 dark:text-blue-400 shrink-0">
113118
:{port.port}
114119
</span>
115120
<span class="text-gray-500 dark:text-gray-400 truncate flex-1 text-left">
116121
{port.process ?? ''}
117122
</span>
118-
<span
119-
class="text-gray-400 dark:text-gray-500 opacity-0 group-hover:opacity-100 transition shrink-0"
120-
>
121-
<svg
122-
xmlns="http://www.w3.org/2000/svg"
123-
viewBox="0 0 20 20"
124-
fill="currentColor"
125-
class="size-3"
123+
<Tooltip content={$i18n.t('Open in new tab')}>
124+
<!-- svelte-ignore a11y-click-events-have-key-events -->
125+
<span
126+
role="button"
127+
tabindex="-1"
128+
class="text-gray-400 dark:text-gray-500 opacity-0 group-hover:opacity-100 transition shrink-0 p-0.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
129+
on:click|stopPropagation={() => openPortExternal(port.port)}
126130
>
127-
<path
128-
fill-rule="evenodd"
129-
d="M4.25 5.5a.75.75 0 0 0-.75.75v8.5c0 .414.336.75.75.75h8.5a.75.75 0 0 0 .75-.75v-4a.75.75 0 0 1 1.5 0v4A2.25 2.25 0 0 1 12.75 17h-8.5A2.25 2.25 0 0 1 2 14.75v-8.5A2.25 2.25 0 0 1 4.25 4h5a.75.75 0 0 1 0 1.5h-5Zm7.5-3.5a.75.75 0 0 0 0 1.5h2.69l-4.72 4.72a.75.75 0 0 0 1.06 1.06l4.72-4.72v2.69a.75.75 0 0 0 1.5 0v-5.25a.75.75 0 0 0-.75-.75h-5.25Z"
130-
clip-rule="evenodd"
131-
/>
132-
</svg>
133-
</span>
131+
<svg
132+
xmlns="http://www.w3.org/2000/svg"
133+
viewBox="0 0 20 20"
134+
fill="currentColor"
135+
class="size-3"
136+
>
137+
<path
138+
fill-rule="evenodd"
139+
d="M4.25 5.5a.75.75 0 0 0-.75.75v8.5c0 .414.336.75.75.75h8.5a.75.75 0 0 0 .75-.75v-4a.75.75 0 0 1 1.5 0v4A2.25 2.25 0 0 1 12.75 17h-8.5A2.25 2.25 0 0 1 2 14.75v-8.5A2.25 2.25 0 0 1 4.25 4h5a.75.75 0 0 1 0 1.5h-5Zm7.5-3.5a.75.75 0 0 0 0 1.5h2.69l-4.72 4.72a.75.75 0 0 0 1.06 1.06l4.72-4.72v2.69a.75.75 0 0 0 1.5 0v-5.25a.75.75 0 0 0-.75-.75h-5.25Z"
140+
clip-rule="evenodd"
141+
/>
142+
</svg>
143+
</span>
144+
</Tooltip>
134145
</button>
135146
{/each}
136147
{/if}

0 commit comments

Comments
 (0)