diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 9418bd75..a94b87d6 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -10,7 +10,8 @@ "deleteCredential": "Delete Credential", "updateCredential": "Update Credential", "credentialName": "Credential Name", - "credentialDescription": "Description", + "credentialDescription": "Description", + "username": "Username", "searchCredentials": "Search credentials...", "selectFolder": "Select Folder", "selectAuthType": "Select Auth Type", @@ -33,6 +34,32 @@ "failedToSaveCredential": "Failed to save credential", "failedToFetchCredentialDetails": "Failed to fetch credential details", "failedToFetchHostsUsing": "Failed to fetch hosts using this credential", + "loadingCredentials": "Loading credentials...", + "retry": "Retry", + "noCredentials": "No Credentials", + "noCredentialsMessage": "Start by creating your first SSH credential", + "sshCredentials": "SSH Credentials", + "credentialsCount": "{{count}} credentials", + "refresh": "Refresh", + "passwordRequired": "Password is required", + "sshKeyRequired": "SSH key is required", + "credentialAddedSuccessfully": "Credential \"{{name}}\" added successfully", + "general": "General", + "description": "Description", + "folder": "Folder", + "tags": "Tags", + "addTagsSpaceToAdd": "Add tags (press space to add)", + "password": "Password", + "key": "Key", + "sshPrivateKey": "SSH Private Key", + "upload": "Upload", + "updateKey": "Update Key", + "keyPassword": "Key Password (optional)", + "keyType": "Key Type", + "keyTypeRSA": "RSA", + "keyTypeECDSA": "ECDSA", + "keyTypeEd25519": "Ed25519", + "updateCredential": "Update Credential", "basicInfo": "Basic Info", "authentication": "Authentication", "organization": "Organization", @@ -236,6 +263,7 @@ }, "admin": { "title": "Admin Settings", + "oidc": "OIDC", "users": "Users", "userManagement": "User Management", "makeAdmin": "Make Admin", @@ -770,6 +798,9 @@ "folder": "folder", "password": "password", "keyPassword": "key password", + "credentialName": "My SSH Server", + "description": "SSH credential description", + "searchCredentials": "Search credentials by name, username, or tags...", "sshConfig": "endpoint ssh configuration", "homePath": "/home", "clientId": "your-client-id", @@ -780,6 +811,7 @@ "userIdField": "sub", "usernameField": "name", "scopes": "openid email profile", + "userinfoUrl": "https://your-provider.com/application/o/userinfo/", "enterUsername": "Enter username to make admin", "searchHosts": "Search hosts by name, username, IP, folder, tags...", "enterPassword": "Enter your password", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index ec5e0eea..678d383d 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -11,6 +11,7 @@ "updateCredential": "更新凭据", "credentialName": "凭据名称", "credentialDescription": "描述", + "username": "用户名", "searchCredentials": "搜索凭据...", "selectFolder": "选择文件夹", "selectAuthType": "选择认证类型", @@ -33,6 +34,32 @@ "failedToSaveCredential": "保存凭据失败", "failedToFetchCredentialDetails": "获取凭据详情失败", "failedToFetchHostsUsing": "获取使用此凭据的主机失败", + "loadingCredentials": "正在加载凭据...", + "retry": "重试", + "noCredentials": "暂无凭据", + "noCredentialsMessage": "开始创建您的第一个SSH凭据", + "sshCredentials": "SSH凭据", + "credentialsCount": "{{count}} 个凭据", + "refresh": "刷新", + "passwordRequired": "密码为必填项", + "sshKeyRequired": "SSH密钥为必填项", + "credentialAddedSuccessfully": "凭据「{{name}}」添加成功", + "general": "常规", + "description": "描述", + "folder": "文件夹", + "tags": "标签", + "addTagsSpaceToAdd": "添加标签(按空格键添加)", + "password": "密码", + "key": "密钥", + "sshPrivateKey": "SSH私钥", + "upload": "上传", + "updateKey": "更新密钥", + "keyPassword": "密钥密码(可选)", + "keyType": "密钥类型", + "keyTypeRSA": "RSA", + "keyTypeECDSA": "ECDSA", + "keyTypeEd25519": "Ed25519", + "updateCredential": "更新凭据", "basicInfo": "基本信息", "authentication": "认证方式", "organization": "组织管理", @@ -236,6 +263,7 @@ }, "admin": { "title": "管理员设置", + "oidc": "OIDC", "users": "用户", "userManagement": "用户管理", "makeAdmin": "设为管理员", @@ -807,6 +835,9 @@ "hostname": "主机名", "folder": "文件夹", "password": "密码", + "credentialName": "我的SSH服务器", + "description": "SSH凭据描述", + "searchCredentials": "按名称、用户名或标签搜索凭据...", "keyPassword": "密钥密码", "sshConfig": "端点 SSH 配置", "homePath": "/home", @@ -818,6 +849,7 @@ "userIdField": "sub", "usernameField": "name", "scopes": "openid email profile", + "userinfoUrl": "https://your-provider.com/application/o/userinfo/", "enterUsername": "输入用户名以设为管理员", "searchHosts": "按名称、用户名、IP、文件夹、标签搜索主机...", "enterPassword": "输入您的密码", diff --git a/src/backend/database/routes/ssh.ts b/src/backend/database/routes/ssh.ts index 49cf82c4..a49b9956 100644 --- a/src/backend/database/routes/ssh.ts +++ b/src/backend/database/routes/ssh.ts @@ -144,6 +144,8 @@ router.post('/db/host', authenticateJWT, upload.single('key'), async (req: Reque username, password, authMethod, + authType, + credentialId, key, keyPassword, keyType, @@ -160,6 +162,7 @@ router.post('/db/host', authenticateJWT, upload.single('key'), async (req: Reque return res.status(400).json({error: 'Invalid SSH data'}); } + const effectiveAuthType = authType || authMethod; const sshDataObj: any = { userId: userId, name, @@ -168,7 +171,8 @@ router.post('/db/host', authenticateJWT, upload.single('key'), async (req: Reque ip, port, username, - authType: authMethod, + authType: effectiveAuthType, + credentialId: credentialId || null, pin: !!pin ? 1 : 0, enableTerminal: !!enableTerminal ? 1 : 0, enableTunnel: !!enableTunnel ? 1 : 0, @@ -177,12 +181,12 @@ router.post('/db/host', authenticateJWT, upload.single('key'), async (req: Reque defaultPath: defaultPath || null, }; - if (authMethod === 'password') { + if (effectiveAuthType === 'password') { sshDataObj.password = password; sshDataObj.key = null; sshDataObj.keyPassword = null; sshDataObj.keyType = null; - } else if (authMethod === 'key') { + } else if (effectiveAuthType === 'key') { sshDataObj.key = key; sshDataObj.keyPassword = keyPassword; sshDataObj.keyType = keyType; @@ -232,6 +236,8 @@ router.put('/db/host/:id', authenticateJWT, upload.single('key'), async (req: Re username, password, authMethod, + authType, + credentialId, key, keyPassword, keyType, @@ -249,6 +255,7 @@ router.put('/db/host/:id', authenticateJWT, upload.single('key'), async (req: Re return res.status(400).json({error: 'Invalid SSH data'}); } + const effectiveAuthType = authType || authMethod; const sshDataObj: any = { name, folder, @@ -256,7 +263,8 @@ router.put('/db/host/:id', authenticateJWT, upload.single('key'), async (req: Re ip, port, username, - authType: authMethod, + authType: effectiveAuthType, + credentialId: credentialId || null, pin: !!pin ? 1 : 0, enableTerminal: !!enableTerminal ? 1 : 0, enableTunnel: !!enableTunnel ? 1 : 0, @@ -265,15 +273,23 @@ router.put('/db/host/:id', authenticateJWT, upload.single('key'), async (req: Re defaultPath: defaultPath || null, }; - if (authMethod === 'password') { - sshDataObj.password = password; + if (effectiveAuthType === 'password') { + if (password) { + sshDataObj.password = password; + } sshDataObj.key = null; sshDataObj.keyPassword = null; sshDataObj.keyType = null; - } else if (authMethod === 'key') { - sshDataObj.key = key; - sshDataObj.keyPassword = keyPassword; - sshDataObj.keyType = keyType; + } else if (effectiveAuthType === 'key') { + if (key) { + sshDataObj.key = key; + } + if (keyPassword !== undefined) { + sshDataObj.keyPassword = keyPassword; + } + if (keyType) { + sshDataObj.keyType = keyType; + } sshDataObj.password = null; } diff --git a/src/components/CredentialSelector.tsx b/src/components/CredentialSelector.tsx index 7eab693a..d41361fe 100644 --- a/src/components/CredentialSelector.tsx +++ b/src/components/CredentialSelector.tsx @@ -34,7 +34,9 @@ export function CredentialSelector({ value, onValueChange }: CredentialSelectorP try { setLoading(true); const data = await getCredentials(); - setCredentials(data.credentials || []); + // Handle both possible response formats: direct array or nested object + const credentialsArray = Array.isArray(data) ? data : (data.credentials || data.data || []); + setCredentials(credentialsArray); } catch (error) { console.error('Failed to fetch credentials:', error); setCredentials([]); @@ -102,7 +104,7 @@ export function CredentialSelector({ value, onValueChange }: CredentialSelectorP ref={buttonRef} type="button" variant="outline" - className="w-full justify-between text-left rounded-md px-3 py-2 bg-[#18181b] border border-input text-foreground" + className="w-full justify-between text-left rounded-lg px-3 py-2 bg-muted/50 focus:bg-background focus:ring-1 focus:ring-ring border border-border text-foreground transition-all duration-200" onClick={() => setDropdownOpen(!dropdownOpen)} > {loading ? ( @@ -127,9 +129,9 @@ export function CredentialSelector({ value, onValueChange }: CredentialSelectorP {dropdownOpen && (
Configure external identity provider for - OIDC/OAuth2 authentication.
+{t('admin.configureExternalProvider')}
{oidcError && (