diff --git a/web/.env.development b/web/.env.development new file mode 100644 index 0000000..260fc4b --- /dev/null +++ b/web/.env.development @@ -0,0 +1,3 @@ +VITE_SERVER_IP=192.168.1.1 +VITE_SERVER_PORT=80 +VITE_WITH_CREDENTIALS=false diff --git a/web/README.md b/web/README.md index 87202d7..e2dab9f 100644 --- a/web/README.md +++ b/web/README.md @@ -20,31 +20,18 @@ src └── types // types ``` -## Development +## Local Development -Here are some steps you can follow to develop based on your needs: +Due to CORS restrictions, authentication needs to be disabled during local development. -- Log in to NanoKVM via SSH: `ssh root@your-nanokvm-ip` (default password is root); -- Stop the current service: `killall NanoKVM-Server`. +To develop authentication features, you need to build the project and test in NanoKVM. -1. Build the frontend project: `pnpm build`; -2. Remove the files in NanoKVM: `rm -rf /kvmapp/server/web`; -3. Upload the new files: `scp -r dist root@your-nanokvm-ip:/kvmapp/server/web`; -4. Start the service: `/kvmapp/server/NanoKVM-Server`. +1. Log in to NanoKVM via SSH: `ssh root@your-nanokvm-ip` (default password is root). +2. Open the configuration file `/etc/kvm/server.yaml/` and add `authentication: disable`. ⚠️CAUTION: This option disables all authentication and should NOT be enabled in production environment! +3. Restart the service: `/etc/init.d/S95nanokvm restart`. +4. Edit the `.env.development` file and change `VITE_SERVER_IP` to your NanoKVM IP address. +5. Run `pnpm dev` to start the server and visit http://localhost:3001/ in browser. -Then, refresh the page in the browser to see the changes. - -### Configuration - -**Note: Do not enable these settings in production environment unless you fully understand their purpose!** - -In development mode, you can edit the configuration file `/etc/kvm/server.yaml` for easier development: - - -- Add `log: debug` to print service logs. This option may cause service slowdowns, so avoid using it in production. -- Add `authentication: disable` to disable all authentication, so don't need to log in again after restarting the service. This option should not be enabled in production! - -### Browser It's recommended to disable browser caching to avoid access issues during development: diff --git a/web/README_ZH.md b/web/README_ZH.md index 4a15fb8..f4c68c5 100644 --- a/web/README_ZH.md +++ b/web/README_ZH.md @@ -20,30 +20,18 @@ src └── types // 类型定义 ``` -## 开发 +## 本地开发 -这里给出一些参考,你可以根据自己的情况来开发。 +由于 CORS 的限制,在本地开发时,需要关闭鉴权功能。 -- 首先通过 ssh 登录到 NanoKVM:`ssh root@your-nanokvm-ip`(默认密码为 root); -- 停止当前服务:`killall NanoKVM-Server`。 +如果想要开发鉴权相关的功能,需要编译后在 NanoKVM 中进行测试。 -1. 编译前端项目:`pnpm build`; -2. 删除 NanoKVM 中前端文件:`rm -rf /kvmapp/server/web`; -3. 重新上传前端文件:`scp -r dist root@your-nanokvm-ip:/kvmapp/server/web`; -4. 启动服务:`/kvmapp/server/NanoKVM-Server` +1. 通过 ssh 登录到 NanoKVM:`ssh root@your-nanokvm-ip`(默认密码为 root); +2. 修改配置文件 `/etc/kvm/server.yaml`,添加一行 `authentication: disable`。⚠️注意:该选项会禁用所有鉴权功能,生产环境请勿开启该选项! +3. 执行 `/etc/init.d/S95nanokvm restart` 重启服务。 +4. 编辑 `.env.development` 文件,将 `VITE_SERVER_IP` 修改为你的 NanoKVM IP 地址。 +5. 执行 `pnpm dev` 启动服务,然后在浏览器中访问 http://localhost:3001/ 。 -最后,在浏览器中刷新页面即可。 - -### 配置文件 - -**注意:请勿在生产环境中开启这些配置项!除非你了解每个配置的用途。** - -在开发模式下,为了更方便的开发,可以修改配置文件 `/etc/kvm/server.yaml`。 - -- 添加 `log: debug`,会打印服务日志。该选项可能导致服务卡顿,生产环境中请勿开启。 -- 添加 `authentication: disable`,会禁用所有鉴权,服务重启后也不再需要重新登录。生产环境请勿开启该选项! - -### 浏览器设置 建议在浏览器中禁用缓存,防止在开发过程中出现无法访问的情况。 diff --git a/web/src/lib/http.ts b/web/src/lib/http.ts index 078b1e0..0653ac2 100644 --- a/web/src/lib/http.ts +++ b/web/src/lib/http.ts @@ -1,6 +1,7 @@ import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; import { removeToken } from '@/lib/cookie.ts'; +import { getBaseUrl } from '@/lib/service.ts'; type Response = { code: number; @@ -12,9 +13,12 @@ class Http { private instance: AxiosInstance; constructor() { + const baseURL = getBaseUrl('http'); + const withCredentials = (import.meta.env.VITE_WITH_CREDENTIALS as string) !== 'false'; + this.instance = axios.create({ - baseURL: this.getBaseURL(), - withCredentials: true, + baseURL, + withCredentials, timeout: 60 * 1000 }); @@ -46,10 +50,6 @@ class Http { ); } - public getBaseURL() { - return `${window.location.protocol}//${window.location.host}`; - } - public get(url: string, params?: any): Promise { return this.instance.request({ method: 'get', diff --git a/web/src/lib/service.ts b/web/src/lib/service.ts new file mode 100644 index 0000000..31dcfb7 --- /dev/null +++ b/web/src/lib/service.ts @@ -0,0 +1,21 @@ +export function getHostname(): string { + const ip = import.meta.env.VITE_SERVER_IP as string; + return ip ? ip : window.location.hostname; +} + +export function getPort(): string { + const port = import.meta.env.VITE_SERVER_PORT as string; + return port ? port : window.location.port; +} + +export function getBaseUrl(type: 'http' | 'ws'): string { + let protocol = window.location.protocol; + if (type === 'ws') { + protocol = protocol === 'https:' ? 'wss:' : 'ws:'; + } + + const hostname = getHostname(); + const port = getPort(); + + return `${protocol}//${hostname}:${port}`; +} diff --git a/web/src/lib/websocket.ts b/web/src/lib/websocket.ts index 55782a0..9e36f1e 100644 --- a/web/src/lib/websocket.ts +++ b/web/src/lib/websocket.ts @@ -1,23 +1,20 @@ import { w3cwebsocket as W3cWebSocket } from 'websocket'; +import { getBaseUrl } from '@/lib/service.ts'; + class WsClient { + private readonly url: string; private instance: W3cWebSocket; constructor() { - const url = this.getUrl(); - this.instance = new W3cWebSocket(url); - } - - private getUrl() { - const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - return `${protocol}//${window.location.host}/api/ws`; + this.url = `${getBaseUrl('ws')}/api/ws`; + this.instance = new W3cWebSocket(this.url); } public connect() { this.close(); - const url = this.getUrl(); - this.instance = new W3cWebSocket(url); + this.instance = new W3cWebSocket(this.url); } public send(data: number[]) { diff --git a/web/src/pages/desktop/screen/index.tsx b/web/src/pages/desktop/screen/index.tsx index 657ac00..bc871c2 100644 --- a/web/src/pages/desktop/screen/index.tsx +++ b/web/src/pages/desktop/screen/index.tsx @@ -5,6 +5,7 @@ import { useAtomValue } from 'jotai'; import MonitorXIcon from '@/assets/images/monitor-x.svg'; import { stopFrameDetect } from '@/api/stream.ts'; +import { getBaseUrl } from '@/lib/service.ts'; import { mouseStyleAtom } from '@/jotai/mouse.ts'; import { resolutionAtom } from '@/jotai/resolution.ts'; @@ -31,7 +32,7 @@ export const Screen = () => { )} width={resolution!.width} height={resolution!.height} - src={`${window.location.protocol}//${window.location.host}/api/stream/mjpeg`} + src={`${getBaseUrl('http')}/api/stream/mjpeg`} fallback={MonitorXIcon} preview={false} /> diff --git a/web/src/pages/terminal/xterm.tsx b/web/src/pages/terminal/xterm.tsx index c78bb40..e2c88df 100644 --- a/web/src/pages/terminal/xterm.tsx +++ b/web/src/pages/terminal/xterm.tsx @@ -5,6 +5,8 @@ import { Terminal as XtermTerminal } from '@xterm/xterm'; import '@xterm/xterm/css/xterm.css'; +import { getBaseUrl } from '@/lib/service.ts'; + type TerminalProps = { token: string; setToken: (token: string) => void; @@ -21,8 +23,7 @@ export const Xterm = ({ token, setToken }: TerminalProps) => { terminal.open(terminalEle); fitAddon.fit(); - const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - const url = `${protocol}//${window.location.host}/api/vm/terminal?${token}`; + const url = `${getBaseUrl('ws')}/api/vm/terminal?${token}`; const webSocket = new WebSocket(url); const sendSize = () => {