mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
Merge pull request #348 from forumi0721/20250223/ImageRefresh
Add Refresh Button for Image List
This commit is contained in:
@@ -96,6 +96,7 @@ const en = {
|
||||
mountFailed: 'Mount Failed',
|
||||
mountDesc:
|
||||
"In some systems, it's necessary to eject the virtual disk on the remote host before mounting the image.",
|
||||
refresh: 'Refresh the image list',
|
||||
tips: {
|
||||
title: 'How to upload',
|
||||
usb1: 'Connect the NanoKVM to your computer via USB.',
|
||||
|
||||
@@ -14,8 +14,7 @@ const ko = {
|
||||
placeholderPassword2: '비밀번호를 다시 입력하세요.',
|
||||
noEmptyUsername: '사용자 이름은 비어있을 수 없습니다.',
|
||||
noEmptyPassword: '비밀번호는 비어있을 수 없습니다.',
|
||||
noAccount:
|
||||
'사용자 정보를 불러오는 데 실패했습니다. 페이지를 새로고침하거나 비밀번호를 초기화하세요.',
|
||||
noAccount: '사용자 정보를 불러오는 데 실패했습니다. 페이지를 새로고침하거나 비밀번호를 초기화하세요.',
|
||||
invalidUser: '사용자 이름이나 비밀번호가 틀렸습니다.',
|
||||
error: '알 수 없는 오류',
|
||||
changePassword: '비밀번호 변경',
|
||||
@@ -28,7 +27,8 @@ const ko = {
|
||||
cancel: '취소',
|
||||
loginButtonText: '로그인',
|
||||
tips: {
|
||||
reset1: '비밀번호를 재설정하려면 NanoKVM의 BOOT 버튼을 10초 동안 누르고 계세요.',
|
||||
reset1:
|
||||
'비밀번호를 재설정하려면 NanoKVM의 BOOT 버튼을 10초 동안 누르고 계세요.',
|
||||
reset2: '자세한 절차는 이 문서를 참조하세요:',
|
||||
reset3: '웹 기본 계정:',
|
||||
reset4: 'SSH 기본 계정:',
|
||||
@@ -89,9 +89,11 @@ const ko = {
|
||||
title: '이미지',
|
||||
loading: '불러오는 중...',
|
||||
empty: '아무것도 없습니다.',
|
||||
cdrom: 'CD-ROM 모드로 이미지 마운트',
|
||||
mountFailed: '이미지 마운트 실패',
|
||||
mountDesc:
|
||||
'일부 시스템에서는 이미지를 마운트하기 전에 원격 호스트에서 가상 디스크를 제거해야 합니다.',
|
||||
refresh: '이미지 목록 새로고침',
|
||||
tips: {
|
||||
title: '업로드 방법',
|
||||
usb1: 'USB를 통해 NanoKVM을 컴퓨터에 연결하세요.',
|
||||
@@ -99,7 +101,7 @@ const ko = {
|
||||
usb3: '컴퓨터에서 가상 디스크를 열고 이미지 파일을 가상 디스크의 루트 디렉토리로 복사하세요.',
|
||||
scp1: 'NanoKVM과 컴퓨터가 동일한 로컬 네트워크에 있는지 확인하세요.',
|
||||
scp2: '컴퓨터에서 터미널을 열고 SCP 명령을 사용하여 이미지 파일을 NanoKVM의 /data 디렉터리에 업로드하세요.',
|
||||
scp3: '예: scp [이미지 파일 경로] root@[NanoKVM IP 주소]:/data',
|
||||
scp3: '예시: scp [이미지 파일 경로] root@[NanoKVM IP 주소]:/data',
|
||||
tfCard: 'TF 카드',
|
||||
tf1: '이 방법은 Linux 시스템에서 지원됩니다',
|
||||
tf2: 'NanoKVM에서 TF 카드를 가져옵니다(전체 버전의 경우 먼저 케이스를 분해하세요).',
|
||||
@@ -116,7 +118,7 @@ const ko = {
|
||||
runFailed: '실행 실패',
|
||||
attention: '주의',
|
||||
delDesc: '이 파일을 정말로 삭제합니까?',
|
||||
confirm: '예',
|
||||
confirm: '네',
|
||||
cancel: '아니오',
|
||||
delete: '삭제',
|
||||
close: '닫기'
|
||||
@@ -222,7 +224,8 @@ const ko = {
|
||||
upTailscale: 'tailscale을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/bin/',
|
||||
upTailscaled: 'tailscaled을 NanoKVM 의 다음 경로에 업로드 했습니다. : /usr/sbin/',
|
||||
refresh: '현재 페이지 새로고침',
|
||||
notLogin: '이 기기는 현재 연동 되지 않았습니다. 로그인해서 계정에 이 장치를 연동하세요.',
|
||||
notLogin:
|
||||
'이 기기는 현재 연동 되지 않았습니다. 로그인해서 계정에 이 장치를 연동하세요.',
|
||||
urlPeriod: '이 주소는 10분간 유효합니다.',
|
||||
login: '로그인',
|
||||
loginSuccess: '로그인 성공',
|
||||
@@ -233,7 +236,7 @@ const ko = {
|
||||
logout: '로그아웃',
|
||||
logout2: '정말로 로그아웃 합니까?',
|
||||
uninstall: 'Tailscale 제거',
|
||||
okBtn: '예',
|
||||
okBtn: '네',
|
||||
cancelBtn: '아니오'
|
||||
},
|
||||
update: {
|
||||
@@ -253,6 +256,10 @@ const ko = {
|
||||
updateBtn: '업데이트',
|
||||
logoutBtn: '로그아웃'
|
||||
}
|
||||
},
|
||||
error: {
|
||||
title: '문제가 발생했습니다.',
|
||||
refresh: '새로고침'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,9 +16,10 @@ import { client } from '@/lib/websocket.ts';
|
||||
type ImagesProps = {
|
||||
cdrom: boolean;
|
||||
setIsMounted: (isMounted: boolean) => void;
|
||||
onRefresh?: (callback: () => void) => void;
|
||||
};
|
||||
|
||||
export const Images = ({ cdrom, setIsMounted }: ImagesProps) => {
|
||||
export const Images = ({ cdrom, setIsMounted, onRefresh }: ImagesProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [notify, contextHolder] = notification.useNotification();
|
||||
|
||||
@@ -31,6 +32,12 @@ export const Images = ({ cdrom, setIsMounted }: ImagesProps) => {
|
||||
getImages();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (onRefresh) {
|
||||
onRefresh(getImages);
|
||||
}
|
||||
}, [onRefresh]);
|
||||
|
||||
function getImages() {
|
||||
if (isLoading) return;
|
||||
setIsLoading(true);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Divider, Popover, Switch, Tooltip } from 'antd';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Button, Divider, Popover, Switch, Tooltip } from 'antd';
|
||||
import clsx from 'clsx';
|
||||
import { DiscIcon } from 'lucide-react';
|
||||
import { DiscIcon, RefreshCwIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMediaQuery } from 'react-responsive';
|
||||
|
||||
@@ -18,9 +18,13 @@ export const Image = () => {
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const [cdrom, setCdrom] = useState(false);
|
||||
|
||||
|
||||
const getImagesRef = useRef<() => void>(() => {});
|
||||
|
||||
const tooltip = t('image.title');
|
||||
const [tooltipValue, setTooltipValue] = useState(tooltip);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
getMountedImage().then((rsp) => {
|
||||
if (rsp.code !== 0) return;
|
||||
@@ -33,6 +37,12 @@ export const Image = () => {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const refreshImages = () => {
|
||||
if (getImagesRef.current) {
|
||||
getImagesRef.current();
|
||||
}
|
||||
};
|
||||
|
||||
const content = (
|
||||
<div className="min-w-[300px]">
|
||||
<div className="flex items-center justify-between px-1">
|
||||
@@ -41,18 +51,26 @@ export const Image = () => {
|
||||
<Tips />
|
||||
</div>
|
||||
|
||||
<Tooltip title={t('image.cdrom')} placement="bottom">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-xs text-neutral-400">CD-ROM</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Tooltip title={t('image.cdrom')} placement="bottom">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-xs text-neutral-400">CD-ROM</span>
|
||||
|
||||
<Switch size="small" checked={cdrom} onChange={(checked) => setCdrom(checked)}></Switch>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Switch size="small" checked={cdrom} onChange={(checked) => setCdrom(checked)}></Switch>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title={t('image.refresh')} placement="bottom">
|
||||
<Button type="default" icon={<RefreshCwIcon size={16} />} onClick={refreshImages} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider style={{ margin: '10px 0 15px 0' }} />
|
||||
|
||||
{isPopoverOpen && <Images cdrom={cdrom} setIsMounted={setIsMounted} />}
|
||||
{isPopoverOpen && (
|
||||
<Images cdrom={cdrom} setIsMounted={setIsMounted} onRefresh={(callback) => (getImagesRef.current = callback)} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user