diff --git a/README_JA.md b/README_JA.md index 1bdb4a4..d3512e1 100644 --- a/README_JA.md +++ b/README_JA.md @@ -1,11 +1,13 @@ -# NanoKVM +NanoKVM +======
![](https://wiki.sipeed.com/hardware/assets/NanoKVM/introduce/NanoKVM_3.png)

- クイックスタート | + クイックスタート + | ハードウェア

@@ -13,18 +15,29 @@
-> Your NanoKVM Power by RISC-V ! +> NanoKVM は [RISC-V](https://en.wikipedia.org/wiki/RISC-V) を搭載しています! > 使用中に問題や提案がある場合は、ここで issue を提出するか、[MaixHub Discussion](https://maixhub.com/discussion/nanokvm) でお知らせください。 -## オープンソース & 貢献 & 出荷 +## オープンソース & コントリビューション -スターが2Kに近づいています!10月中旬にバックエンドのソースコードを整理してオープンソース化します!(また、10.1前のすべての予約注文が出荷される時期でもあります) +### 2025.02.19 更新 -### 10.18 更新 +NanoKVM のすべてのコンポーネントはオープンソース化されており、[front-end](https://github.com/sipeed/NanoKVM/tree/main/web)、[back-end](https://github.com/sipeed/NanoKVM/tree/main/server)、[kvm_vision](https://github.com/sipeed/NanoKVM/tree/main/vision/components/kvm)、[kvm_mmf](https://github.com/sipeed/NanoKVM/tree/main/vision/components/kvm_mmf)、[kvm_system](https://github.com/sipeed/NanoKVM/tree/main/support)、[kvmapp update package](https://github.com/sipeed/NanoKVM-System/tree/main/kvmapp)、[system sdk](https://github.com/sipeed/LicheeRV-Nano-Build/tree/NanoKVM)、[packaging methods](https://github.com/sipeed/LicheeRV-Nano-Build/blob/NanoKVM/kvm/NanoKVM_img.sh)が含まれます。 -10.8にバックエンドコードをオープンソース化しました。すぐにPRを提出してくれたCivilに感謝します。最新のNanoKVM-PCIeを報酬として提供します! -10月中旬のバッチはテストとパッケージングを開始しており、10.1前のほとんどの注文は次週と次々週に出荷されます。 -10.1以降、アリエクスプレスの出荷日は12月に変更されました。これは保守的な納期です。10月の注文は11.15〜12.15の間に発送される予定です。 +### 2025.02.14 更新 + +最新のアプリケーションバージョン v2.1.6 には、多くのセキュリティ強化とバグ修正が追加されているため、一般のユーザーはこのバージョン以降へのアップグレードをお勧めします。 + +### 2025.02.05 更新 + +セキュリティを懸念しているユーザーは、この issue をお読みください: 、セキュリティに関するすべての懸念事項について説明されています。 +GitHub は、オープンで透明性のあるプラットフォームとして、いわゆる「バックドア」の懸念事項を議論する場を提供しています。オープンソースであることにより、製品のセキュリティがさらに強化されます! + +### 2024.10.18 更新 + +10/08にバックエンドコードをオープンソース化しました。すぐにPRを提出してくれたCivilに感謝します。最新のNanoKVM-PCIeを報酬として提供します! +10月中旬のバッチはテストとパッケージングを開始しており、10/01以前のほとんどの注文は次週と次々週に出荷されます。 +10/01以降、アリエクスプレスの出荷日は12月に変更されました。これは保守的な納期です。10月の注文は11/15〜12/15の間に発送される予定です。 ## 紹介 @@ -33,35 +46,35 @@ Lichee NanoKVMは2つのバージョンがあります: NanoKVM Liteは基本構成で、一定のDIY能力を持つ個人ユーザーや大量のニーズを持つ企業ユーザーに適しています。 NanoKVM Fullは完全版で、洗練されたケースと完全なアクセサリーを備え、起動時にすぐに使用できるシステムミラーカードが内蔵されており、個人ユーザーに推奨されます。 -![](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_1.jpg) +![NanoKVM 分解画像](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_1.jpg) ## 技術仕様 -| 製品 | NanoKVM (Lite) | NanoKVM (Full) | PiKVM V4 | -| --- | --- | --- | --- | -| 計算ユニット | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) | -| 解像度 | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps | -| ビデオエンコーディング | MJPEG, H264 | MJPEG, H264 | MJPEG, H264 | -| ビデオ遅延 | 90~230ms | 90~230ms | 100~230ms | -| UEFI/BIOS | ✓ | ✓ | ✓ | -| 仮想HID | ✓ | ✓ | ✓ | -| 仮想CD-ROM | ✓ | ✓ | ✓ | -| IPMI | ✓ | ✓ | ✓ | -| Wake-on-LAN | ✓ | ✓ | ✓ | -| ETH | 100M/10M | 100M/10M | 1000M/100M/10M | -| ATX電源制御 | なし、ユーザーが自分で接続 | USBインターフェースIO制御ボード | RJ-45インターフェースIO制御ボード | -| OLED | なし、ユーザーが自分で接続 | 128x64 0.96" 白 | 128x32 0.91" 白 | -| UART | 2 | 2 | 1 | -| TFカード | なし | ✓ | ✓ | -| 拡張 | なし | PoE | WiFi/LTE | -| 消費電力 | 0.2A@5V | 0.2A@5V | ピーク2.6A@5V | -| 電源入力 | PC USBで給電可能 | PC USBまたは補助電源 | DC 5V 3A電源が必要 | -| 冷却 | 静音ファンレス | 静音ファンレス | ファン冷却 | -| サイズ | 23x37x15mm
~1/30 PiKVM V4サイズ | 40x36x36mm
~1/7 PiKVM V4サイズ | 120x68x44mm | +| 製品 | NanoKVM (Lite) | NanoKVM (Full) | PiKVM V4 | +|----------------------- |-------------------------------------- |---------------------------------- |----------------------------------- | +| 計算ユニット | LicheeRV Nano(RISCV) | LicheeRV Nano(RISCV) | CM4 (ARM) | +| 解像度 | 1080P @ 60fps | 1080P @ 60fps | 1080P @ 60fps | +| ビデオエンコーディング | MJPEG, H264 | MJPEG, H264 | MJPEG, H264 | +| ビデオ遅延 | 90~230ms | 90~230ms | 100~230ms | +| UEFI/BIOS | ✓ | ✓ | ✓ | +| 仮想HID | ✓ | ✓ | ✓ | +| 仮想CD-ROM | ✓ | ✓ | ✓ | +| IPMI | ✓ | ✓ | ✓ | +| Wake-on-LAN | ✓ | ✓ | ✓ | +| ETH | 100M/10M | 100M/10M | 1000M/100M/10M | +| ATX電源制御 | なし、ユーザーが自分で接続 | USBインターフェースIO制御ボード | RJ-45インターフェースIO制御ボード | +| OLED | なし、ユーザーが自分で接続 | 128x64 0.96" 白 | 128x32 0.91" 白 | +| UART | 2 | 2 | 1 | +| TFカード | なし | ✓ | ✓ | +| 拡張 | なし | PoE | WiFi/LTE | +| 消費電力 | 0.2A@5V | 0.2A@5V | ピーク時 2.6A@5V | +| 電源入力 | PC USBで給電可能 | PC USBまたは補助電源 | DC 5V 3A電源が必要 | +| 冷却 | 静音ファンレス | 静音ファンレス | ファン冷却 | +| サイズ | 23x37x15mm ~1/30 PiKVM V4サイズ | 40x36x36mm ~1/7 PiKVM V4サイズ | 120x68x44mm | -![](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg) +![NanoKVM PCB ピン配置](https://wiki.sipeed.com/hardware/zh/kvm/assets/NanoKVM/1_intro/NanoKVM_2.jpg) -## ハードウェアプラットフォーム NanoKVM +## NanoKVM ハードウェアプラットフォーム NanoKVMはSipeed [LicheeRV Nano](https://wiki.sipeed.com/hardware/zh/lichee/RV_Nano/1_intro.html)に基づいており、仕様、回路図、寸法図は[こちら](http://cn.dl.sipeed.com/shareURL/LICHEE/LicheeRV_Nano)で確認できます。 @@ -69,12 +82,41 @@ NanoKVM LiteはLicheeRV NanoとHDMItoCSIボードで構成され、NanoKVM FULL NanoKVMイメージはLicheeRV Nano SDKおよびMaixCDKに基づいて構築されており、LicheeRV Nanoを使用する資料と互換性があります。逆に、KVMソフトウェアはLicheeRV Nanoや他のSG2002製品では使用できません。LicheeRV NanoやMaixCamでHDMI入力アプリケーションを構築したい場合は、技術サポートを受けるためにお問い合わせください。 -注:SG2002の256MBメモリのうち、現在105MBがマルチメディアサブシステムに割り当てられており、NanoKVMはビデオ画像の取得と処理にこのメモリを使用します。 +注:SG2002の256MBメモリのうち、現在158MBがマルチメディアサブシステムに割り当てられており、NanoKVMはビデオ画像の取得と処理にこのメモリを使用します。 + [NanoKVM-A 回路図](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_RV_Nano_KVM_A_30111.pdf) + [NanoKVM-B 回路図](https://cn.dl.sipeed.com/fileList/KVM/nanoKVM/HDK/02_Schematic/SCH_HDMI_MIPI_31011.pdf) + [NanoKVM イメージダウンロード](https://github.com/sipeed/NanoKVM/releases/tag/NanoKVM) +## ロードマップ + +### Q1 2025 + +**改善:** + +- [x] HDMI モジュールをリファクタリングし、libmaixcam_lib.so の依存関係を削除 +- [x] SSH をデフォルトで無効にし、Web UI に有効/無効の切り替えを追加 +- [x] Tailscale をデフォルトで無効にし、Web UI に有効/無効の切り替えを追加 +- [x] 設定ファイルに JWT 設定オプションを追加 +- [x] bcrypt を利用したパスワードストレージのセキュリティ保護 + +**配布:** + +- [x] GitHub 経由でのアプリケーション配布 +- [x] オンラインでの更新の際に整合性チェックを実施 +- [ ] オフラインでの更新をサポート + +**バグ修正:** + +- [x] DNS の問題を修正 +- [x] CSRF の脆弱性を修正 + +**機能** + +- [ ] [79](https://github.com/sipeed/NanoKVM/issues/79) Zerotier をサポート +- [ ] [99](https://github.com/sipeed/NanoKVM/issues/99) WireGuard をサポート +- [ ] [249](https://github.com/sipeed/NanoKVM/issues/249) Mouse Jiggler を追加 + ## 購入方法 * [Aliexpress(グローバル、米国とロシアを除く)](https://www.aliexpress.com/item/1005007369816019.html) diff --git a/server/README_JA.md b/server/README_JA.md index 44ed82f..55a6f40 100644 --- a/server/README_JA.md +++ b/server/README_JA.md @@ -16,7 +16,7 @@ server ├── middleware // サーバーミドルウェアコンポーネント ├── proto // API リクエスト/レスポンス定義 ├── router // API ルートハンドラ -├── service // コアサービスの実�� +├── service // コアサービスの実装 ├── utils // ユーティリティ関数 └── main.go ``` @@ -39,17 +39,23 @@ cert: logger: level: info file: stdout - + # 認証設定 (enable/disable) # 注意: 認証を無効にするのは開発環境でのみ行ってください authentication: enable -# JWT 秘密鍵の設定 -# 空のままにすると、サーバー起動時にランダムな鍵が生成されます -secretKey: "" +jwt: + # JWT 秘密鍵の設定。 空のままにすると、サーバー起動時にランダムな 64 バイトの鍵が自動的に生成されます。 + secretKey: "" + # JWT トークンの有効期限(秒単位)。 デフォルト: 2678400 (31 日) + refreshTokenDuration: 2678400 + # ユーザーがログアウトすると、すべての JWT トークンが無効になります。 デフォルト: true + revokeTokensOnLogout: true +# カスタム STUN サーバーのアドレス stun: stun.l.google.com:19302 +# カスタム TURN サーバーのアドレスと認証情報 turn: turnAddr: turn.cloudflare.com:3478 turnUser: example_user @@ -80,3 +86,12 @@ turn: 1. デプロイ前に、ブラウザでアプリケーションを最新バージョンに更新します。手順は[こちら](https://wiki.sipeed.com/hardware/en/kvm/NanoKVM/system/updating.html)を参照してください。 2. コンパイルして生成された `NanoKVM-Server` ファイルを使用して、NanoKVM の `/kvmapp/server/` ディレクトリ内の元のファイルを置き換えます。 3. NanoKVM で `/etc/init.d/S95nanokvm restart` を実行してサービスを再起動します。 + +## 手動更新 + +> ファイルのアップロードには SSH が必要です。Web 設定で有効にしてください: `設定 > SSH` + +1. [GitHub](https://github.com/sipeed/NanoKVM/releases) から最新のアプリケーションをダウンロードします。 +2. ダウンロードしたファイルを解凍し、解凍したフォルダーの名前を `kvmapp` に変更します。 +3. NanoKVM 上の既存の `/kvmapp` ディレクトリをバックアップし、新しい `kvmapp` フォルダーに置き換えます。 +4. NanoKVM で `/etc/init.d/S95nanokvm restart` を実行してサービスを再起動します。 diff --git a/web/README_JA.md b/web/README_JA.md index 44aa2be..978c100 100644 --- a/web/README_JA.md +++ b/web/README_JA.md @@ -22,6 +22,8 @@ src ## ローカル開発 +> 開発には SSH が必要です。Web 設定で有効にすることができます: `設定 > SSH` + CORS 制限のため、ローカル開発中は認証を無効にする必要があります。 認証機能を開発するには、プロジェクトをビルドして NanoKVM でテストする必要があります。 @@ -55,4 +57,7 @@ pnpm build 3. `web` を NanoKVM の `/kvmapp/server/` にアップロードします。 4. NanoKVM で `/etc/init.d/S95nanokvm restart` を実行してサービスを再起動します。 -また、ブラウザに古いバージョンのキャッシュが残っている可能性があります。ページが開かない場合は、強制リフレッシュまたはキャッシュのクリアを試してください。 +Tips: + +1. ファイルのアップロードには SSH が必要です。Web 設定で有効にすることができます: `設定 > SSH` +2. ブラウザに古いバージョンのキャッシュが残っている可能性があります。ページが開かない場合は、強制リフレッシュまたはキャッシュのクリアを試してください。 diff --git a/web/src/i18n/locales/en.ts b/web/src/i18n/locales/en.ts index d776aa6..03b4290 100644 --- a/web/src/i18n/locales/en.ts +++ b/web/src/i18n/locales/en.ts @@ -47,6 +47,7 @@ const en = { finishBtn: 'Finished' }, screen: { + title: 'Screen', video: 'Video Mode', resolution: 'Resolution', auto: 'Automatic', @@ -65,6 +66,7 @@ const en = { resetHdmi: 'Reset HDMI' }, keyboard: { + title: 'Keyboard', paste: 'Paste', tips: 'Only standard keyboard letters and symbols are supported', placeholder: 'Please input', @@ -73,6 +75,7 @@ const en = { ctrlaltdel: 'Ctrl+Alt+Del' }, mouse: { + title: 'Mouse', default: 'Default cursor', pointer: 'Pointer cursor', cell: 'Cell cursor', @@ -86,7 +89,7 @@ const en = { resetHid: 'Reset HID' }, image: { - title: 'Image', + title: 'Images', loading: 'Loading...', empty: 'Nothing Found', cdrom: 'Mount the image in CD-ROM mode', @@ -110,7 +113,7 @@ const en = { } }, script: { - title: 'Script', + title: 'Scripts', upload: 'Upload', run: 'Run', runBackground: 'Run Background', @@ -139,7 +142,7 @@ const en = { ok: 'Ok' }, download: { - title: 'Download Image', + title: 'Image Downloader', input: 'Please enter a remote image URL', ok: 'Ok', disabled: '/data partition is RO, so we cannot download the image' @@ -257,8 +260,15 @@ const en = { } }, error: { - title: "We've ran into an issue", - refresh: 'Refresh' + title: 'We\'ve ran into an issue', + refresh: 'Refresh', + }, + fullscreen: { + toggle: 'Toggle Fullscreen', + }, + menu: { + collapse: 'Collapse Menu', + expand: 'Expand Menu', } } }; diff --git a/web/src/pages/desktop/menu/download.tsx b/web/src/pages/desktop/menu/download.tsx index 194000b..ec4494b 100644 --- a/web/src/pages/desktop/menu/download.tsx +++ b/web/src/pages/desktop/menu/download.tsx @@ -1,5 +1,5 @@ import { ChangeEvent, useEffect, useRef, useState } from 'react'; -import { Button, Divider, Input, Popover } from 'antd'; +import { Button, Divider, Input, Popover, Tooltip } from 'antd'; import type { InputRef } from 'antd'; import clsx from 'clsx'; import { useSetAtom } from 'jotai'; @@ -21,6 +21,9 @@ export const DownloadImage = () => { const [diskEnabled, setDiskEnabled] = useState(false); const [popoverKey, setPopoverKey] = useState(0); + const tooltip = t('download.title'); + const [tooltipValue, setTooltipValue] = useState(tooltip); + const inputRef = useRef(null); const intervalId = useRef(undefined); @@ -165,11 +168,16 @@ export const DownloadImage = () => { trigger="click" arrow={false} open={isPopoverOpen} - onOpenChange={handleOpenChange} + onOpenChange={(visible) => { + handleOpenChange(visible); + setTooltipValue(visible ? '' : tooltip); + }} > -
- -
+ +
+ +
+
); }; diff --git a/web/src/pages/desktop/menu/fullscreen/index.tsx b/web/src/pages/desktop/menu/fullscreen/index.tsx index defd237..da81b34 100644 --- a/web/src/pages/desktop/menu/fullscreen/index.tsx +++ b/web/src/pages/desktop/menu/fullscreen/index.tsx @@ -1,8 +1,12 @@ import { useEffect, useState } from 'react'; import { MaximizeIcon, MinimizeIcon } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { Tooltip } from 'antd'; export const Fullscreen = () => { + const { t } = useTranslation(); const [isFullscreen, setIsFullscreen] = useState(false); + const [tooltipValue, setTooltipValue] = useState(t('fullscreen.toggle')); useEffect(() => { function onFullscreenChange() { @@ -27,11 +31,13 @@ export const Fullscreen = () => { } return ( -
- {isFullscreen ? : } -
+ +
+ {isFullscreen ? : } +
+
); }; diff --git a/web/src/pages/desktop/menu/image/index.tsx b/web/src/pages/desktop/menu/image/index.tsx index d93da94..f918905 100644 --- a/web/src/pages/desktop/menu/image/index.tsx +++ b/web/src/pages/desktop/menu/image/index.tsx @@ -18,6 +18,9 @@ export const Image = () => { const [isMounted, setIsMounted] = useState(false); const [cdrom, setCdrom] = useState(false); + const tooltip = t('image.title'); + const [tooltipValue, setTooltipValue] = useState(tooltip); + useEffect(() => { getMountedImage().then((rsp) => { if (rsp.code !== 0) return; @@ -60,16 +63,21 @@ export const Image = () => { trigger="click" arrow={false} open={isPopoverOpen} - onOpenChange={setIsPopoverOpen} + onOpenChange={(visible) => { + setIsPopoverOpen(visible); + setTooltipValue(visible ? '' : tooltip); + }} > -
- -
+ +
+ +
+
); }; diff --git a/web/src/pages/desktop/menu/index.tsx b/web/src/pages/desktop/menu/index.tsx index fd8830f..6d21e93 100644 --- a/web/src/pages/desktop/menu/index.tsx +++ b/web/src/pages/desktop/menu/index.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { Divider } from 'antd'; +import { Divider, Tooltip } from 'antd'; import clsx from 'clsx'; import { useAtom } from 'jotai'; import { MenuIcon, XIcon } from 'lucide-react'; @@ -18,6 +18,7 @@ import { Script } from './script'; import { Settings } from './settings'; import { Terminal } from './terminal'; import { Wol } from './wol'; +import { t } from 'i18next'; export const Menu = () => { const [menuDisabledItems, setMenuDisabledItems] = useAtom(menuDisabledItemsAtom); @@ -67,21 +68,25 @@ export const Menu = () => { -
setIsMenuOpen((o) => !o)} - > - -
+ +
setIsMenuOpen((o) => !o)} + > + +
+
{!isMenuOpen && ( -
setIsMenuOpen((o) => !o)} - > - -
+ +
setIsMenuOpen((o) => !o)} + > + +
+
)} diff --git a/web/src/pages/desktop/menu/keyboard/index.tsx b/web/src/pages/desktop/menu/keyboard/index.tsx index e96f8d7..f29c2fe 100644 --- a/web/src/pages/desktop/menu/keyboard/index.tsx +++ b/web/src/pages/desktop/menu/keyboard/index.tsx @@ -1,14 +1,19 @@ import { useState } from 'react'; -import { Popover } from 'antd'; +import { Popover, Tooltip } from 'antd'; import { KeyboardIcon } from 'lucide-react'; +import { t } from 'i18next'; import { Paste } from './paste.tsx'; import { VirtualKeyboard } from './virtual-keyboard.tsx'; import { CtrlAltDel } from './ctrl-alt-del.tsx'; + export const Keyboard = () => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const tooltip = t('keyboard.title') + const [tooltipValue, setTooltipValue] = useState(tooltip); + const content = ( <> @@ -24,11 +29,16 @@ export const Keyboard = () => { trigger="click" arrow={false} open={isPopoverOpen} - onOpenChange={setIsPopoverOpen} + onOpenChange={(visible) => { + setIsPopoverOpen(visible); + setTooltipValue(visible ? "" : tooltip); + }} > -
- -
+ +
+ +
+
); }; diff --git a/web/src/pages/desktop/menu/mouse/index.tsx b/web/src/pages/desktop/menu/mouse/index.tsx index a46397a..ed2e199 100644 --- a/web/src/pages/desktop/menu/mouse/index.tsx +++ b/web/src/pages/desktop/menu/mouse/index.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { Divider, Popover } from 'antd'; +import { Divider, Popover, Tooltip } from 'antd'; import clsx from 'clsx'; import { useAtom } from 'jotai'; import { @@ -27,6 +27,9 @@ export const Mouse = () => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isResetting, setIsResetting] = useState(false); + const tooltip = t('mouse.title'); + const [tooltipValue, setTooltipValue] = useState(tooltip); + const mouseStyles = [ { name: t('mouse.default'), icon: , value: 'cursor-default' }, { name: t('mouse.grab'), icon: , value: 'cursor-grab' }, @@ -154,11 +157,16 @@ export const Mouse = () => { trigger="click" arrow={false} open={isPopoverOpen} - onOpenChange={setIsPopoverOpen} + onOpenChange={(visible) => { + setIsPopoverOpen(visible); + setTooltipValue(visible ? "" : tooltip); + }} > -
- -
+ +
+ +
+
); }; diff --git a/web/src/pages/desktop/menu/power/index.tsx b/web/src/pages/desktop/menu/power/index.tsx index 460ba71..b5d42ae 100644 --- a/web/src/pages/desktop/menu/power/index.tsx +++ b/web/src/pages/desktop/menu/power/index.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { Popover, Slider } from 'antd'; +import { Popover, Slider, Tooltip } from 'antd'; import clsx from 'clsx'; import { CirclePowerIcon, LoaderCircleIcon, PowerIcon, RotateCcwIcon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; @@ -13,6 +13,9 @@ export const Power = () => { const [isLoading, setIsLoading] = useState(false); const [powerDuration, setPowerDuration] = useState(8); + const tooltip = t('power.title'); + const [tooltipValue, setTooltipValue] = useState(tooltip); + useEffect(() => { getLed(); const interval = setInterval(getLed, 5000); @@ -95,19 +98,24 @@ export const Power = () => { trigger="click" arrow={false} open={isPopoverOpen} - onOpenChange={setIsPopoverOpen} + onOpenChange={(visible) => { + setIsPopoverOpen(visible); + setTooltipValue(visible ? '' : tooltip); + }} > -
-
- {isLoading ? ( - - ) : ( - - )} + +
+
+ {isLoading ? ( + + ) : ( + + )} +
-
+ ); }; diff --git a/web/src/pages/desktop/menu/screen/index.tsx b/web/src/pages/desktop/menu/screen/index.tsx index 12b70e2..9b291bb 100644 --- a/web/src/pages/desktop/menu/screen/index.tsx +++ b/web/src/pages/desktop/menu/screen/index.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { Popover } from 'antd'; +import { Popover, Tooltip } from 'antd'; import { useAtomValue } from 'jotai'; import { MonitorIcon } from 'lucide-react'; @@ -14,6 +14,7 @@ import { Quality } from './quality'; import { Reset } from './reset.tsx'; import { Resolution } from './resolution'; import { VideoMode } from './video-mode.tsx'; +import { t } from 'i18next'; export const Screen = () => { const videoMode = useAtomValue(videoModeAtom); @@ -21,10 +22,12 @@ export const Screen = () => { const [fps, setFps] = useState(30); const [quality, setQuality] = useState(2); + const tooltip = t('screen.title'); + const [tooltipValue, setTooltipValue] = useState(tooltip); + useEffect(() => { updateScreen('type', videoMode === 'mjpeg' ? 0 : 1); updateScreen('resolution', resolution!.height); - updateQuality(); updateFps(); }, []); @@ -69,10 +72,13 @@ export const Screen = () => { placement="bottomLeft" trigger="click" arrow={false} + onOpenChange={(visible) => setTooltipValue(visible ? '' : tooltip)} > -
- -
+ +
+ +
+
); }; diff --git a/web/src/pages/desktop/menu/script/index.tsx b/web/src/pages/desktop/menu/script/index.tsx index b079a83..00e6a16 100644 --- a/web/src/pages/desktop/menu/script/index.tsx +++ b/web/src/pages/desktop/menu/script/index.tsx @@ -1,6 +1,6 @@ import { ChangeEvent, useRef, useState } from 'react'; import { UploadOutlined } from '@ant-design/icons'; -import { Button, Divider, Popconfirm, Popover } from 'antd'; +import { Button, Divider, Popconfirm, Popover, Tooltip } from 'antd'; import clsx from 'clsx'; import { ChevronRightIcon, FileJsonIcon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; @@ -22,6 +22,9 @@ export const Script = () => { const [isUploading, setIsUploading] = useState(false); const inputRef = useRef(null); + const tooltip = t('script.title'); + const [tooltipValue, setTooltipValue] = useState(tooltip); + function handleOpenChange(open: boolean) { if (open) { getScripts(); @@ -194,11 +197,16 @@ export const Script = () => { trigger="click" arrow={false} open={isPopoverOpen} - onOpenChange={handleOpenChange} + onOpenChange={(visible) => { + handleOpenChange(visible); + setTooltipValue(visible ? '' : tooltip); + }} > -
- -
+ +
+ +
+
{isRunning && } diff --git a/web/src/pages/desktop/menu/settings/index.tsx b/web/src/pages/desktop/menu/settings/index.tsx index 9d15bf9..294c83a 100644 --- a/web/src/pages/desktop/menu/settings/index.tsx +++ b/web/src/pages/desktop/menu/settings/index.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { Badge, Modal } from 'antd'; +import { Badge, Modal, Tooltip } from 'antd'; import clsx from 'clsx'; import { BadgeInfoIcon, @@ -32,6 +32,9 @@ export const Settings = () => { const [isUpdateAvailable, setIsUpdateAvailable] = useState(false); + const tooltip = t('settings.title'); + const [tooltipValue, setTooltipValue] = useState(tooltip); + const tabs = [ { id: 'about', icon: , component: }, { id: 'appearance', icon: , component: }, @@ -92,16 +95,18 @@ export const Settings = () => { return ( <> -
setIsModalOpen(true)} - > - -
- -
-
-
+ +
setIsModalOpen(true)} + > + +
+ +
+
+
+
{ destroyOnClose={true} styles={{ content: { padding: 0 } }} onCancel={closeModal} + afterOpenChange={(visible) => setTooltipValue(visible ? '' : tooltip)} >
diff --git a/web/src/pages/desktop/menu/terminal/index.tsx b/web/src/pages/desktop/menu/terminal/index.tsx index 7f65584..82e525d 100644 --- a/web/src/pages/desktop/menu/terminal/index.tsx +++ b/web/src/pages/desktop/menu/terminal/index.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { Divider, Popover } from 'antd'; +import { Divider, Popover, Tooltip } from 'antd'; import { SquareTerminalIcon } from 'lucide-react'; import { useTranslation } from 'react-i18next'; @@ -10,6 +10,9 @@ export const Terminal = () => { const { t } = useTranslation(); const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const tooltip = t('terminal.title'); + const [tooltipValue, setTooltipValue] = useState(tooltip); + const content = (
@@ -30,11 +33,16 @@ export const Terminal = () => { trigger="click" arrow={false} open={isPopoverOpen} - onOpenChange={setIsPopoverOpen} + onOpenChange={(visible) => { + setIsPopoverOpen(visible); + setTooltipValue(visible ? '' : tooltip); + }} > -
- -
+ +
+ +
+
); }; diff --git a/web/src/pages/desktop/menu/wol/index.tsx b/web/src/pages/desktop/menu/wol/index.tsx index ca58d08..9d0a13d 100644 --- a/web/src/pages/desktop/menu/wol/index.tsx +++ b/web/src/pages/desktop/menu/wol/index.tsx @@ -1,5 +1,5 @@ import { ChangeEvent, useRef, useState } from 'react'; -import { Button, Divider, Input, List, Popover } from 'antd'; +import { Button, Divider, Input, List, Popover, Tooltip } from 'antd'; import type { InputRef } from 'antd'; import clsx from 'clsx'; import { useSetAtom } from 'jotai'; @@ -22,6 +22,9 @@ export const Wol = () => { const [status, setStatus] = useState(''); const [log, setLog] = useState(''); + const tooltip = t('wol.title'); + const [tooltipValue, setTooltipValue] = useState(tooltip); + const [macList, setMacList] = useState([]); const inputRef = useRef(null); @@ -153,11 +156,16 @@ export const Wol = () => { trigger="click" arrow={false} open={isPopoverOpen} - onOpenChange={handleOpenChange} + onOpenChange={(visible) => { + handleOpenChange(visible); + setTooltipValue(visible ? '' : tooltip); + }} > -
- -
+ +
+ +
+
); };