Refactor: Rename NanoKVM to BatchuKVM and update server URL
This commit is contained in:
51
web/src/pages/desktop/menu/settings/about/community.tsx
Normal file
51
web/src/pages/desktop/menu/settings/about/community.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
103
web/src/pages/desktop/menu/settings/about/hostname.tsx
Normal file
103
web/src/pages/desktop/menu/settings/about/hostname.tsx
Normal 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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
23
web/src/pages/desktop/menu/settings/about/index.tsx
Normal file
23
web/src/pages/desktop/menu/settings/about/index.tsx
Normal 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 />
|
||||
</>
|
||||
);
|
||||
};
|
||||
109
web/src/pages/desktop/menu/settings/about/information.tsx
Normal file
109
web/src/pages/desktop/menu/settings/about/information.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user