Refactor: Rename NanoKVM to BatchuKVM and update server URL

This commit is contained in:
2025-12-09 20:35:38 +09:00
commit 8cf674c9e5
396 changed files with 54380 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
import { GithubOutlined, XOutlined } from '@ant-design/icons';
import { BookOpenIcon, MessageCircleQuestionIcon, MessageSquareIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
export const Community = () => {
const { t } = useTranslation();
const communities = [
{ name: 'Document', icon: <BookOpenIcon size={24} />, url: 'https://wiki.sipeed.com/nanokvm' },
{
name: 'GitHub',
icon: <GithubOutlined style={{ fontSize: '20px' }} width={24} height={24} />,
url: 'https://github.com/sipeed/NanoKVM'
},
{
name: 'X',
icon: <XOutlined style={{ fontSize: '20px' }} width={24} height={24} />,
url: 'https://twitter.com/SipeedIO'
},
{
name: 'Discussion',
icon: <MessageSquareIcon size={24} />,
url: 'https://maixhub.com/discussion/nanokvm'
},
{
name: 'FAQ',
icon: <MessageCircleQuestionIcon size={24} />,
url: 'https://wiki.sipeed.com/hardware/en/kvm/NanoKVM/faq.html'
}
];
return (
<>
<div className="pb-5 text-neutral-400">{t('settings.about.community')}</div>
<div className="my-3 flex space-x-3">
{communities.map((community) => (
<a
key={community.name}
className="flex h-[64px] w-[80px] flex-col items-center justify-center space-y-2 rounded-lg text-neutral-300 outline outline-1 outline-neutral-800 hover:bg-neutral-800 hover:text-white focus:bg-neutral-800 md:h-[72px] md:w-[100px]"
href={community.url}
target="_blank"
>
{community.icon}
<span className="text-xs">{community.name}</span>
</a>
))}
</div>
</>
);
};

View File

@@ -0,0 +1,103 @@
import { useEffect, useState } from 'react';
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { Button, Input } from 'antd';
import { ClipboardPenIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import * as api from '@/api/vm.ts';
export const Hostname = () => {
const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false);
const [hostname, setHostname] = useState('');
const [editState, setEditState] = useState<'' | 'editing' | 'edited'>('');
const [input, setInput] = useState('');
useEffect(() => {
getHostname();
}, []);
function getHostname() {
setIsLoading(true);
api
.getHostname()
.then((rsp) => {
if (rsp.data?.hostname) {
setHostname(rsp.data?.hostname);
}
})
.finally(() => {
setIsLoading(false);
});
}
function showInput() {
setInput(hostname);
setEditState('editing');
}
function update() {
if (input === hostname) {
setEditState('');
return;
}
if (isLoading) return;
setIsLoading(true);
api
.setHostname(input)
.then((rsp) => {
if (rsp.code !== 0) {
console.log(rsp.msg);
return;
}
setHostname(input);
setEditState('edited');
})
.finally(() => {
setIsLoading(false);
});
}
return (
<>
<div className="flex w-full items-center justify-between pt-4">
<span>{t('settings.about.hostname')}</span>
{editState === 'editing' ? (
<div className="flex items-center space-x-1">
<Input
disabled={isLoading}
style={{ width: 150 }}
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<Button size="small" icon={<CheckOutlined />} onClick={update} />
<Button size="small" icon={<CloseOutlined />} onClick={() => setEditState('')} />
</div>
) : (
<div className="flex items-center space-x-2">
<span>{hostname}</span>
<div
className="size-[16px] cursor-pointer text-neutral-500 hover:text-blue-500"
onClick={showInput}
>
<ClipboardPenIcon size={16} />
</div>
</div>
)}
</div>
{editState === 'edited' && (
<div className="flex w-full justify-end pt-1 text-xs text-green-500">
{t('settings.about.hostnameUpdated')}
</div>
)}
</>
);
};

View File

@@ -0,0 +1,23 @@
import { Divider } from 'antd';
import { useTranslation } from 'react-i18next';
import { Community } from './community.tsx';
import { Hostname } from './hostname.tsx';
import { Information } from './information.tsx';
export const About = () => {
const { t } = useTranslation();
return (
<>
<div className="text-base font-bold">{t('settings.about.title')}</div>
<Divider />
<Information />
<Hostname />
<Divider />
<Community />
</>
);
};

View File

@@ -0,0 +1,109 @@
import { useEffect, useState } from 'react';
import { Tooltip } from 'antd';
import { CircleHelpIcon, EthernetPortIcon, WifiIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import * as api from '@/api/vm.ts';
type IP = {
name: string;
addr: string;
version: string;
type: string;
};
type Info = {
ips: IP[];
mdns: string;
image: string;
application: string;
deviceKey: string;
};
export const Information = () => {
const { t } = useTranslation();
const [information, setInformation] = useState<Info>();
useEffect(() => {
api.getInfo().then((rsp: any) => {
if (rsp.code !== 0) {
console.log(rsp.msg);
return;
}
setInformation(rsp.data);
});
}, []);
return (
<>
<div className="pb-5 text-neutral-400">{t('settings.about.information')}</div>
<div className="flex w-full flex-col space-y-4">
{/* IP list */}
<div className="flex w-full items-start justify-between">
<span>{t('settings.about.ip')}</span>
{information?.ips && information.ips.length > 0 ? (
<div className="flex flex-col space-y-1">
{information.ips.map((ip) => (
<div key={ip.addr} className="flex items-center justify-end space-x-2">
<span>{ip.addr}</span>
<div className="size-[16px] text-neutral-500">
{ip.type === 'Wireless' ? (
<WifiIcon size={16} />
) : (
<EthernetPortIcon size={16} />
)}
</div>
</div>
))}
</div>
) : (
<span>-</span>
)}
</div>
{/* mDNS */}
{!!information?.mdns && (
<div className="flex w-full items-center justify-between">
<span>{t('settings.about.mdns')}</span>
<span>{information.mdns}</span>
</div>
)}
{/* image version */}
<div className="flex w-full items-center justify-between">
<div className="flex items-center space-x-2">
<span>{t('settings.about.image')}</span>
<Tooltip
title={t('settings.about.imageTip')}
className="cursor-pointer text-neutral-500"
placement="right"
>
<CircleHelpIcon size={15} />
</Tooltip>
</div>
<span>{information ? information.image : '-'}</span>
</div>
{/* application version */}
<div className="flex w-full items-center justify-between">
<div className="flex items-center space-x-2">
<span>{t('settings.about.application')}</span>
<Tooltip
title={t('settings.about.applicationTip')}
className="cursor-pointer text-neutral-500"
placement="right"
>
<CircleHelpIcon size={15} />
</Tooltip>
</div>
<span>{information ? information.application : '-'}</span>
</div>
</div>
</>
);
};