chore: ssh key selection

This commit is contained in:
jialin
2026-05-25 22:42:02 +08:00
committed by jialin
parent 921a2a0d81
commit b5ed64de51
13 changed files with 107 additions and 28 deletions
+4
View File
@@ -4,3 +4,7 @@ import { atom } from 'jotai';
export const currentClusterAtom = atom< export const currentClusterAtom = atom<
(Partial<ClusterListItem> & { label?: string; value?: number }) | null (Partial<ClusterListItem> & { label?: string; value?: number }) | null
>(null); >(null);
export const addSSHKeyPageAtom = atom<{ create: boolean }>({
create: false
});
+3
View File
@@ -40,6 +40,9 @@ export default {
'gpuservice.template.card.resources': 'Resources', 'gpuservice.template.card.resources': 'Resources',
'gpuservice.template.card.ports': 'Ports', 'gpuservice.template.card.ports': 'Ports',
'gpuservice.publicKey.label': 'SSH Public Key', 'gpuservice.publicKey.label': 'SSH Public Key',
'gpuservice.instance.ssh.enable': 'Enable SSH Access',
'gpuservice.instance.ssh.assignKey': 'Assign SSH Public Key',
'gpuservice.instance.ssh.addKey': 'Add SSH Public Key',
'gpuservice.publicKey.placeholder': 'gpuservice.publicKey.placeholder':
'Begin with ssh-rsa or ssh-ed25519. One Public Key per line.\n\nView Public Key:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', 'Begin with ssh-rsa or ssh-ed25519. One Public Key per line.\n\nView Public Key:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub',
'gpuservice.instance': 'GPU Instance', 'gpuservice.instance': 'GPU Instance',
+5 -1
View File
@@ -64,5 +64,9 @@ export default {
'noresult.gpuservice.instance.nofound': 'No matching GPU instances found.', 'noresult.gpuservice.instance.nofound': 'No matching GPU instances found.',
'noresult.gpuservice.storage.title': 'No Storage', 'noresult.gpuservice.storage.title': 'No Storage',
'noresult.gpuservice.storage.subTitle': 'No storage has been added yet.', 'noresult.gpuservice.storage.subTitle': 'No storage has been added yet.',
'noresult.gpuservice.storage.nofound': 'No matching storage found.' 'noresult.gpuservice.storage.nofound': 'No matching storage found.',
'noresult.gpuservice.sshkey.title': 'No SSH Public Keys',
'noresult.gpuservice.sshkey.subTitle':
'No SSH public keys have been added yet.',
'noresult.gpuservice.sshkey.nofound': 'No matching SSH public keys found.'
}; };
+3
View File
@@ -38,6 +38,9 @@ export default {
'gpuservice.template.card.resources': 'リソース', 'gpuservice.template.card.resources': 'リソース',
'gpuservice.template.card.ports': 'ポート', 'gpuservice.template.card.ports': 'ポート',
'gpuservice.publicKey.label': 'SSH 公開鍵', 'gpuservice.publicKey.label': 'SSH 公開鍵',
'gpuservice.instance.ssh.enable': 'SSH アクセスを有効化',
'gpuservice.instance.ssh.assignKey': 'SSH 公開鍵を割り当て',
'gpuservice.instance.ssh.addKey': 'SSH 公開鍵を追加',
'gpuservice.publicKey.placeholder': 'gpuservice.publicKey.placeholder':
'ssh-rsa または ssh-ed25519 で始まり、各公開鍵は1行ずつ記述します\n\n公開鍵を確認:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', 'ssh-rsa または ssh-ed25519 で始まり、各公開鍵は1行ずつ記述します\n\n公開鍵を確認:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub',
'gpuservice.instance': 'GPU インスタンス', 'gpuservice.instance': 'GPU インスタンス',
+4 -1
View File
@@ -66,5 +66,8 @@ export default {
'noresult.gpuservice.storage.title': 'ストレージなし', 'noresult.gpuservice.storage.title': 'ストレージなし',
'noresult.gpuservice.storage.subTitle': 'noresult.gpuservice.storage.subTitle':
'ストレージはまだ追加されていません。', 'ストレージはまだ追加されていません。',
'noresult.gpuservice.storage.nofound': '一致するストレージが見つかりません。' 'noresult.gpuservice.storage.nofound': '一致するストレージが見つかりません。',
'noresult.gpuservice.sshkey.title': 'SSH 公開鍵なし',
'noresult.gpuservice.sshkey.subTitle': 'SSH 公開鍵はまだ追加されていません。',
'noresult.gpuservice.sshkey.nofound': '一致する SSH 公開鍵が見つかりません。'
}; };
+3
View File
@@ -41,6 +41,9 @@ export default {
'gpuservice.template.card.resources': 'Ресурсы', 'gpuservice.template.card.resources': 'Ресурсы',
'gpuservice.template.card.ports': 'Порты', 'gpuservice.template.card.ports': 'Порты',
'gpuservice.publicKey.label': 'Открытый ключ SSH', 'gpuservice.publicKey.label': 'Открытый ключ SSH',
'gpuservice.instance.ssh.enable': 'Включить SSH-доступ',
'gpuservice.instance.ssh.assignKey': 'Назначить открытый ключ SSH',
'gpuservice.instance.ssh.addKey': 'Добавить открытый ключ SSH',
'gpuservice.publicKey.placeholder': 'gpuservice.publicKey.placeholder':
'Начинается с ssh-rsa или ssh-ed25519, по одному открытому ключу на строку\n\nПросмотр открытого ключа:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', 'Начинается с ssh-rsa или ssh-ed25519, по одному открытому ключу на строку\n\nПросмотр открытого ключа:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub',
'gpuservice.instance': 'Экземпляр GPU', 'gpuservice.instance': 'Экземпляр GPU',
+5 -1
View File
@@ -65,7 +65,11 @@ export default {
'Подходящие экземпляры GPU не найдены.', 'Подходящие экземпляры GPU не найдены.',
'noresult.gpuservice.storage.title': 'Нет хранилищ', 'noresult.gpuservice.storage.title': 'Нет хранилищ',
'noresult.gpuservice.storage.subTitle': 'Хранилища ещё не добавлены.', 'noresult.gpuservice.storage.subTitle': 'Хранилища ещё не добавлены.',
'noresult.gpuservice.storage.nofound': 'Подходящие хранилища не найдены.' 'noresult.gpuservice.storage.nofound': 'Подходящие хранилища не найдены.',
'noresult.gpuservice.sshkey.title': 'Нет открытых ключей SSH',
'noresult.gpuservice.sshkey.subTitle': 'Открытые ключи SSH ещё не добавлены.',
'noresult.gpuservice.sshkey.nofound':
'Подходящие открытые ключи SSH не найдены.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
+3
View File
@@ -40,6 +40,9 @@ export default {
'gpuservice.template.card.resources': 'Kaynaklar', 'gpuservice.template.card.resources': 'Kaynaklar',
'gpuservice.template.card.ports': 'Bağlantı Noktaları', 'gpuservice.template.card.ports': 'Bağlantı Noktaları',
'gpuservice.publicKey.label': 'SSH Açık Anahtarı', 'gpuservice.publicKey.label': 'SSH Açık Anahtarı',
'gpuservice.instance.ssh.enable': 'SSH Erişimini Etkinleştir',
'gpuservice.instance.ssh.assignKey': 'SSH Açık Anahtarı Ata',
'gpuservice.instance.ssh.addKey': 'SSH Açık Anahtarı Ekle',
'gpuservice.publicKey.placeholder': 'gpuservice.publicKey.placeholder':
'ssh-rsa veya ssh-ed25519 ile başlar, her açık anahtar ayrı bir satırda\n\nAçık anahtarı görüntüle:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', 'ssh-rsa veya ssh-ed25519 ile başlar, her açık anahtar ayrı bir satırda\n\nAçık anahtarı görüntüle:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub',
'gpuservice.instance': 'GPU Örneği', 'gpuservice.instance': 'GPU Örneği',
+4 -1
View File
@@ -63,7 +63,10 @@ export default {
'noresult.gpuservice.instance.nofound': 'Eşleşen GPU örneği bulunamadı.', 'noresult.gpuservice.instance.nofound': 'Eşleşen GPU örneği bulunamadı.',
'noresult.gpuservice.storage.title': 'Depolama Yok', 'noresult.gpuservice.storage.title': 'Depolama Yok',
'noresult.gpuservice.storage.subTitle': 'Henüz depolama eklenmedi.', 'noresult.gpuservice.storage.subTitle': 'Henüz depolama eklenmedi.',
'noresult.gpuservice.storage.nofound': 'Eşleşen depolama bulunamadı.' 'noresult.gpuservice.storage.nofound': 'Eşleşen depolama bulunamadı.',
'noresult.gpuservice.sshkey.title': 'SSH Açık Anahtarı Yok',
'noresult.gpuservice.sshkey.subTitle': 'Henüz SSH açık anahtarı eklenmedi.',
'noresult.gpuservice.sshkey.nofound': 'Eşleşen SSH açık anahtarı bulunamadı.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
+3
View File
@@ -35,6 +35,9 @@ export default {
'gpuservice.template.card.resources': '资源', 'gpuservice.template.card.resources': '资源',
'gpuservice.template.card.ports': '端口', 'gpuservice.template.card.ports': '端口',
'gpuservice.publicKey.label': 'SSH 公钥', 'gpuservice.publicKey.label': 'SSH 公钥',
'gpuservice.instance.ssh.enable': '启用 SSH 访问',
'gpuservice.instance.ssh.assignKey': '分配 SSH 公钥',
'gpuservice.instance.ssh.addKey': '添加 SSH 公钥',
'gpuservice.publicKey.placeholder': 'gpuservice.publicKey.placeholder':
'以 ssh-rsa 或 ssh-ed25519 开头,每个公钥单独一行\n\n查看公钥:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', '以 ssh-rsa 或 ssh-ed25519 开头,每个公钥单独一行\n\n查看公钥:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub',
'gpuservice.instance': 'GPU 实例', 'gpuservice.instance': 'GPU 实例',
+4 -1
View File
@@ -59,5 +59,8 @@ export default {
'noresult.gpuservice.instance.nofound': '未找到匹配的 GPU 实例', 'noresult.gpuservice.instance.nofound': '未找到匹配的 GPU 实例',
'noresult.gpuservice.storage.title': '暂无存储', 'noresult.gpuservice.storage.title': '暂无存储',
'noresult.gpuservice.storage.subTitle': '尚未添加任何存储。', 'noresult.gpuservice.storage.subTitle': '尚未添加任何存储。',
'noresult.gpuservice.storage.nofound': '未找到匹配的存储' 'noresult.gpuservice.storage.nofound': '未找到匹配的存储',
'noresult.gpuservice.sshkey.title': '暂无 SSH 公钥',
'noresult.gpuservice.sshkey.subTitle': '尚未添加任何 SSH 公钥。',
'noresult.gpuservice.sshkey.nofound': '未找到匹配的 SSH 公钥'
}; };
+65 -22
View File
@@ -1,17 +1,21 @@
import { addSSHKeyPageAtom } from '@/atoms/gpuservice';
import { PageAction } from '@/config'; import { PageAction } from '@/config';
import { PageActionType } from '@/config/types'; import { PageActionType } from '@/config/types';
import { PlusOutlined } from '@ant-design/icons';
import { import {
CheckboxField, CheckboxField,
CollapsePanel, CollapsePanel,
IconFont, IconFont,
MultipleSelect, MultipleSelect,
ScrollSpyTabs, ScrollSpyTabs,
useAppUtils,
useFinishFailed, useFinishFailed,
useScrollActiveChange, useScrollActiveChange,
useWrapperContext useWrapperContext
} from '@gpustack/core-ui'; } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max'; import { useIntl, useNavigate } from '@umijs/max';
import { Form } from 'antd'; import { Button, Empty, Form } from 'antd';
import { useSetAtom } from 'jotai';
import { import {
forwardRef, forwardRef,
useEffect, useEffect,
@@ -96,6 +100,9 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
onFinish onFinish
} = props; } = props;
const intl = useIntl(); const intl = useIntl();
const navigate = useNavigate();
const setAddSSHKeyPage = useSetAtom(addSSHKeyPageAtom);
const { getRuleMessage } = useAppUtils();
const [form] = Form.useForm<InstanceFormValues>(); const [form] = Form.useForm<InstanceFormValues>();
const scrollTabsRef = useRef<any>(null); const scrollTabsRef = useRef<any>(null);
const formAction = const formAction =
@@ -278,6 +285,12 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
getFieldsValue: () => form.getFieldsValue() getFieldsValue: () => form.getFieldsValue()
})); }));
const handleAddSSHKey = () => {
setAddSSHKeyPage({ create: true });
// to go ssh key page
navigate('/gpu-service/public-keys');
};
return ( return (
<ScrollSpyTabs <ScrollSpyTabs
ref={scrollTabsRef} ref={scrollTabsRef}
@@ -308,9 +321,6 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
resources: { resources: {
accelerator: '1' accelerator: '1'
}, },
sshPublicKey: {
name: 'gpustack-ssh-public-key'
},
volume: { volume: {
ephemeral: { ephemeral: {
capacity: '20Gi' capacity: '20Gi'
@@ -379,37 +389,70 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
> >
<CheckboxField <CheckboxField
disabled={disabled} disabled={disabled}
label={'启用 SSH 访问'} label={intl.formatMessage({
id: 'gpuservice.instance.ssh.enable'
})}
onChange={handleSSHEnableChange} onChange={handleSSHEnableChange}
></CheckboxField> ></CheckboxField>
</Form.Item> </Form.Item>
{sshEnabled && ( {sshEnabled && (
<div className={instanceStyles.sshkeySelection}> <div className={instanceStyles.sshkeySelection}>
<Form.Item<FormData> name={['spec', 'sshPublicKey', 'name']}> <Form.Item<FormData>
name={['spec', 'sshPublicKey', 'name']}
style={{
marginBottom: 12
}}
rules={[
{
required: true,
message: getRuleMessage(
'select',
'gpuservice.instance.ssh.assignKey'
)
}
]}
>
<MultipleSelect <MultipleSelect
required
disabled={disabled} disabled={disabled}
mode="multiple" mode="multiple"
maxTagCount={1} maxTagCount={1}
label={'SSH 公钥'} label={intl.formatMessage({
id: 'gpuservice.instance.ssh.assignKey'
})}
styles={{ styles={{
wrapper: { wrapper: {
width: '100%' width: '100%'
} }
}} }}
options={[ options={[]}
{ notFoundContent={
label: 'SSH-key-1', <Empty
value: 'gpustack-ssh-public-key' image={Empty.PRESENTED_IMAGE_SIMPLE}
}, description={intl.formatMessage({
{ id: 'noresult.gpuservice.sshkey.title'
label: 'SSH-key-2', })}
value: 'gpustack-ssh-public-key-2' styles={{
}, image: {
{ height: 32
label: 'SSH-key-3', },
value: 'gpustack-ssh-public-key-3' root: {
} margin: 0
]} }
}}
>
<Button
size="small"
type="link"
icon={<PlusOutlined />}
onClick={handleAddSSHKey}
>
{intl.formatMessage({
id: 'gpuservice.instance.ssh.addKey'
})}
</Button>
</Empty>
}
></MultipleSelect> ></MultipleSelect>
</Form.Item> </Form.Item>
</div> </div>
@@ -118,7 +118,7 @@ const InstanceTypeFormItem: React.FC<InstanceTypeFormItemProps> = ({
<InstanceTypePicker dataList={dataList} value={typeName} /> <InstanceTypePicker dataList={dataList} value={typeName} />
</Form.Item> </Form.Item>
</FieldBlock> </FieldBlock>
{!selectedInstanceType?.spec?.acceleratable && ( {selectedInstanceType?.spec?.acceleratable && (
<Form.Item <Form.Item
name={['spec', 'resources', 'accelerator']} name={['spec', 'resources', 'accelerator']}
rules={[ rules={[