From 86678e368e9309d23257d1b86fabc04877b12eec Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 11 Aug 2026 22:51:17 +0800 Subject: [PATCH] feat: complete remote session UI integration for lion --- .env.development | 3 +- nuxt.config.ts | 39 +- src-tauri/capabilities/main.json | 1 + ui/components/lion/LionProvider.vue | 32 +- ui/lion/README.md | 4 +- ui/lion/api/index.ts | 102 ++++- ui/lion/components/CardContainer/index.vue | 8 +- ui/lion/components/ClipBoardText.vue | 40 +- ui/lion/components/FileManager.vue | 312 +++++++++++---- ui/lion/components/Osk.vue | 245 ++++++------ ui/lion/components/OtherOption.vue | 34 +- ui/lion/components/SessionShare/index.vue | 39 +- .../SessionShare/widget/CreateLink.vue | 259 +++++++------ .../SessionShare/widget/UserItem.vue | 10 +- ui/lion/hooks/useColor.ts | 205 ---------- ui/lion/hooks/useGuacamoleClient.ts | 360 +++++++++++------- ui/lion/hooks/useLionConnectTicket.ts | 22 ++ ui/lion/hooks/useLionEndpoint.ts | 17 + ui/lion/locales/index.ts | 27 +- ui/lion/locales/modules/en.json | 16 +- ui/lion/locales/modules/ja.json | 16 +- ui/lion/locales/modules/zh.json | 16 +- ui/lion/locales/modules/zh_Hant.json | 16 +- ui/lion/styles/base.css | 50 +-- ui/lion/styles/keyboard.css | 26 +- ui/lion/utils/clipboard.ts | 13 +- ui/lion/utils/config.ts | 160 -------- ui/lion/utils/lunaBus.ts | 34 +- ui/lion/views/ConnectView.vue | 210 +++++++--- ui/lion/views/MonitorView.vue | 63 ++- ui/lion/views/ShareView.vue | 160 +++++--- ui/lion/workspaces/RemoteSessionSurface.vue | 23 +- ui/lion/workspaces/useBaseWorkspaceSession.ts | 41 +- ui/utils/clipboard.ts | 51 ++- 34 files changed, 1503 insertions(+), 1151 deletions(-) delete mode 100644 ui/lion/hooks/useColor.ts create mode 100644 ui/lion/hooks/useLionConnectTicket.ts create mode 100644 ui/lion/hooks/useLionEndpoint.ts diff --git a/.env.development b/.env.development index 7c8d4b543..f5de60af9 100644 --- a/.env.development +++ b/.env.development @@ -3,5 +3,6 @@ JMS_KOKO_DEV_URL=http://localhost:5050 -# JMS_LION_DEV_URL=http://localhost:8081 +# Lion 默认复用 JMS_KOKO_DEV_URL;仅兼容独立调试时覆盖 +# JMS_LION_DEV_URL=http://localhost:5050 # JMS_CHEN_DEV_URL=http://localhost:8082 diff --git a/nuxt.config.ts b/nuxt.config.ts index 98b0c8e80..4138e9ff4 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,6 +1,7 @@ const jumpServerTarget = process.env.JMS_CORE_DEV_URL || "http://localhost:8080"; const kokoTarget = process.env.JMS_KOKO_DEV_URL || "http://localhost:5050"; -const lionTarget = process.env.JMS_LION_DEV_URL || "http://localhost:8081"; +// JMS_LION_DEV_URL remains a compatibility override; Lion is served by Koko by default. +const lionTarget = process.env.JMS_LION_DEV_URL || kokoTarget; const chenTarget = process.env.JMS_CHEN_DEV_URL || "http://localhost:8082"; const faceliveTarget = process.env.JMS_FACELIVE_DEV_URL || "http://localhost:5173"; const kaelTarget = process.env.JMS_KAEL_DEV_URL || "http://localhost:5172"; @@ -163,13 +164,26 @@ export default defineNuxtConfig({ rewrite: (path) => path.replace(/^\/luna/, ""), configure: bindProxyErrorHandler("luna-lion-ws") }, - "/luna/lion/": { + "/luna/lion/api/": { + target: lionTarget, + secure: false, + changeOrigin: true, + rewrite: (path) => path.replace(/^\/luna/, ""), + configure: bindProxyErrorHandler("luna-lion-api") + }, + "/luna/lion/token/": { + target: lionTarget, + secure: false, + changeOrigin: true, + rewrite: (path) => path.replace(/^\/luna/, ""), + configure: bindProxyErrorHandler("luna-lion-token") + }, + "/luna/lion/health/": { target: lionTarget, secure: false, - ws: true, changeOrigin: true, rewrite: (path) => path.replace(/^\/luna/, ""), - configure: bindProxyErrorHandler("luna-lion-http") + configure: bindProxyErrorHandler("luna-lion-health") }, "/lion/ws/": { target: lionTarget.replace(/^http/i, "ws"), @@ -178,12 +192,23 @@ export default defineNuxtConfig({ changeOrigin: true, configure: bindProxyErrorHandler("lion-ws") }, - "/lion/": { + "/lion/api/": { + target: lionTarget, + secure: false, + changeOrigin: true, + configure: bindProxyErrorHandler("lion-api") + }, + "/lion/token/": { + target: lionTarget, + secure: false, + changeOrigin: true, + configure: bindProxyErrorHandler("lion-token") + }, + "/lion/health/": { target: lionTarget, secure: false, - ws: true, changeOrigin: true, - configure: bindProxyErrorHandler("lion-http") + configure: bindProxyErrorHandler("lion-health") }, "/chen/ws/": { target: chenTarget.replace(/^http/i, "ws"), diff --git a/src-tauri/capabilities/main.json b/src-tauri/capabilities/main.json index 1516776a6..8ba4eb9de 100644 --- a/src-tauri/capabilities/main.json +++ b/src-tauri/capabilities/main.json @@ -84,6 +84,7 @@ "window-state:default", "clipboard-manager:allow-read-text", "clipboard-manager:allow-write-text", + "clipboard-manager:allow-write-image", "updater:allow-check", "updater:allow-download", "updater:allow-install", diff --git a/ui/components/lion/LionProvider.vue b/ui/components/lion/LionProvider.vue index 2d9e34c93..cc9415a58 100644 --- a/ui/components/lion/LionProvider.vue +++ b/ui/components/lion/LionProvider.vue @@ -1,44 +1,28 @@ diff --git a/ui/lion/README.md b/ui/lion/README.md index 491b623b8..bd22c1015 100644 --- a/ui/lion/README.md +++ b/ui/lion/README.md @@ -2,9 +2,9 @@ Lion 是 RDP/VNC 远程桌面 connector,结构与 koko 对齐: -- `views/` — 独立页面(`/lion/connect`、`/lion/share`、`/lion/monitor`) +- `views/` — 独立页面(`/lion/connect`、`/lion/share/:id`、`/lion/monitor`) - `workspaces/` — 内嵌到 Luna workspace 的 session surface - `shared/connectors/capabilities.ts` — 声明 `web_rdp_native` 连接方式 - `shared/connectors/registry.ts` — 解析到 `RemoteSessionSurface.vue` -开发代理见根目录 `nuxt.config.ts`:`/lion/`、`/lion/ws/` 转发到 `localhost:8081`。API/WS 固定走站点根路径 `/lion/*`(不带 `/luna` 前缀)。 +开发代理见根目录 `nuxt.config.ts`:Lion 的 API、Token、Health 和 WebSocket 路径默认转发到 Koko(`JMS_KOKO_DEV_URL`,默认 `localhost:5050`)。`JMS_LION_DEV_URL` 仅作为兼容覆盖保留。页面路径由 Client 自己渲染,不转发给 Koko;API/WS 固定走站点根路径 `/lion/*`(不带 `/luna` 前缀)。 diff --git a/ui/lion/api/index.ts b/ui/lion/api/index.ts index 263cc2b9d..67adc62d9 100644 --- a/ui/lion/api/index.ts +++ b/ui/lion/api/index.ts @@ -1,7 +1,75 @@ -import { withBasePath, withLionUrl } from "@/lion/utils/base"; +import { apiRequest } from "@/composables/useApiRequest"; +import { withLionUrl } from "@/lion/utils/base"; -const postJson = (url: string, data: any) => { - return fetch(url, { +export interface SuggestionUser { + id: string; + name: string; + username: string; +} + +export interface SuggestionUserPage { + count?: number; + next?: string | null; + previous?: string | null; + results: SuggestionUser[]; +} + +export interface ShareApiResponse { + success?: boolean; + message?: string; + id?: string; + verify_code?: string; + [key: string]: unknown; +} + +export interface ShareSessionResponse extends ShareApiResponse { + action_permission?: { + value?: string; + }; + session?: { + id?: string; + [key: string]: unknown; + }; +} + +export interface LionRequestAuth { + ticket?: string; + token?: string; +} + +const withRequestAuth = (url: string, auth?: LionRequestAuth) => { + if (!auth?.ticket && !auth?.token) return url; + const target = new URL(url, window.location.origin); + if (auth.ticket) target.searchParams.set("ticket", auth.ticket); + if (auth.token) target.searchParams.set("token", auth.token); + return target.toString(); +}; + +const parseResponse = async (response: Response): Promise => { + const text = await response.text(); + let data: unknown = null; + + if (text) { + try { + data = JSON.parse(text); + } catch { + data = text; + } + } + + if (!response.ok) { + const message = + data && typeof data === "object" && "message" in data + ? String((data as { message?: unknown }).message || "") + : text; + throw new Error(message || `HTTP ${response.status}`); + } + + return data as T; +}; + +const postJson = async (url: string, data: unknown): Promise => { + const response = await fetch(url, { method: "POST", credentials: "include", headers: { @@ -9,18 +77,26 @@ const postJson = (url: string, data: any) => { }, body: JSON.stringify(data) }); -}; -const getSuggestionUsers = (query: string) => { - const url = new URL(withBasePath("/api/v1/users/users/suggestions/"), window.location.origin); - url.searchParams.set("search", query); - return fetch(url.toString(), { credentials: "include" }); + return await parseResponse(response); }; -const createShareURL = (data: any, endpointUrl?: string) => postJson(withLionUrl("/api/share/", endpointUrl), data); -const getShareSession = (id: string, data: any, endpointUrl?: string) => - postJson(withLionUrl(`/api/share/${id}/`, endpointUrl), data); -const removeShareUser = (data: any, endpointUrl?: string) => - postJson(withLionUrl("/api/share/remove/", endpointUrl), data); +const getSuggestionUsers = (query: string, page = 1, limit = 10) => + apiRequest({ + method: "GET", + path: "/api/v1/users/users/suggestions/", + query: { + search: query, + page, + limit + } + }); + +const createShareURL = (data: unknown, endpointUrl?: string, auth?: LionRequestAuth) => + postJson(withRequestAuth(withLionUrl("/api/share/", endpointUrl), auth), data); +const getShareSession = (id: string, data: unknown, endpointUrl?: string, auth?: LionRequestAuth) => + postJson(withRequestAuth(withLionUrl(`/api/share/${id}/`, endpointUrl), auth), data); +const removeShareUser = (data: unknown, endpointUrl?: string, auth?: LionRequestAuth) => + postJson(withRequestAuth(withLionUrl("/api/share/remove/", endpointUrl), auth), data); export { createShareURL, getShareSession, getSuggestionUsers, removeShareUser }; diff --git a/ui/lion/components/CardContainer/index.vue b/ui/lion/components/CardContainer/index.vue index 8a0541dc0..151c2af22 100644 --- a/ui/lion/components/CardContainer/index.vue +++ b/ui/lion/components/CardContainer/index.vue @@ -8,15 +8,11 @@ const expanded = ref(true); diff --git a/ui/lion/components/SessionShare/widget/CreateLink.vue b/ui/lion/components/SessionShare/widget/CreateLink.vue index 6a2abfd03..25584f4c3 100644 --- a/ui/lion/components/SessionShare/widget/CreateLink.vue +++ b/ui/lion/components/SessionShare/widget/CreateLink.vue @@ -1,11 +1,13 @@ @@ -230,16 +262,16 @@ const handleBack = () => { {{ t("ExpiredTime") }}
- +
@@ -250,16 +282,16 @@ const handleBack = () => { {{ t("ActionPerm") }}
- +
@@ -269,28 +301,41 @@ const handleBack = () => {
{{ t("ShareUser") }}
- + @focus="userOptions.length || searchLoading ? undefined : searchUsers(searchTerm)" + @update:open="(open) => open && !userOptions.length && !searchLoading && searchUsers(searchTerm)" + > + + - + {{ t("CreateLink") }} diff --git a/ui/lion/components/SessionShare/widget/UserItem.vue b/ui/lion/components/SessionShare/widget/UserItem.vue index a36a8cd71..6fc2c4084 100644 --- a/ui/lion/components/SessionShare/widget/UserItem.vue +++ b/ui/lion/components/SessionShare/widget/UserItem.vue @@ -1,7 +1,6 @@