feat(frontend): 支持批量编辑和管理已保存登录凭证
为连接表单补充已保存登录凭证的校验与提交流程, 允许新增、批量新增、测试连接时优先使用凭证 在连接列表中新增登录凭证管理入口,并支持批量编辑 时按连接类型筛选和应用已保存凭证 补充中英日文案,并修复 SSH 密钥选择器的绑定兼容性
This commit is contained in:
@@ -7,6 +7,7 @@ import { useUiNotificationsStore } from '../stores/uiNotifications.store';
|
||||
import { useProxiesStore } from '../stores/proxies.store';
|
||||
import { useTagsStore, type TagInfo } from '../stores/tags.store';
|
||||
import { useSshKeysStore, type SshKeyBasicInfo } from '../stores/sshKeys.store';
|
||||
import { useLoginCredentialsStore, type LoginCredentialBasicInfo } from '../stores/loginCredentials.store';
|
||||
import TagInput from './TagInput.vue';
|
||||
|
||||
interface BatchUpdateData {
|
||||
@@ -14,6 +15,7 @@ interface BatchUpdateData {
|
||||
username?: string | null;
|
||||
password?: string | null;
|
||||
ssh_key_id?: number | null;
|
||||
login_credential_id?: number | null;
|
||||
proxy_id?: number | null;
|
||||
tag_ids?: number[];
|
||||
notes?: string | null;
|
||||
@@ -38,6 +40,7 @@ const uiNotificationsStore = useUiNotificationsStore();
|
||||
const proxiesStore = useProxiesStore();
|
||||
const tagsStore = useTagsStore();
|
||||
const sshKeysStore = useSshKeysStore();
|
||||
const loginCredentialsStore = useLoginCredentialsStore();
|
||||
|
||||
const internalVisible = ref(props.visible);
|
||||
const isLoading = ref(false);
|
||||
@@ -51,6 +54,14 @@ const enableAdvancedEdit = ref(false);
|
||||
const availableTags = computed(() => tagsStore.tags as TagInfo[]);
|
||||
const availableProxies = computed(() => proxiesStore.proxies);
|
||||
const availableSshKeys = computed(() => sshKeysStore.sshKeys as SshKeyBasicInfo[]);
|
||||
const selectedConnections = computed(() => connectionsStore.connections.filter((conn) => props.connectionIds.includes(conn.id)));
|
||||
const selectedConnectionTypes = computed(() => Array.from(new Set(selectedConnections.value.map((conn) => conn.type))));
|
||||
const availableLoginCredentials = computed(() => {
|
||||
if (selectedConnectionTypes.value.length !== 1) {
|
||||
return [] as LoginCredentialBasicInfo[];
|
||||
}
|
||||
return loginCredentialsStore.loginCredentials.filter((credential) => credential.type === selectedConnectionTypes.value[0]);
|
||||
});
|
||||
|
||||
watch(() => props.visible, (newVal) => {
|
||||
internalVisible.value = newVal;
|
||||
@@ -60,6 +71,7 @@ watch(() => props.visible, (newVal) => {
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
ssh_key_id: undefined,
|
||||
login_credential_id: undefined,
|
||||
proxy_id: undefined,
|
||||
tag_ids: undefined,
|
||||
notes: undefined, // Keep notes initialization
|
||||
@@ -78,6 +90,9 @@ watch(() => props.visible, (newVal) => {
|
||||
if (availableSshKeys.value.length === 0 && !sshKeysStore.isLoading) {
|
||||
sshKeysStore.fetchSshKeys();
|
||||
}
|
||||
if (loginCredentialsStore.loginCredentials.length === 0 && !loginCredentialsStore.isLoading) {
|
||||
loginCredentialsStore.fetchLoginCredentials();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -118,6 +133,14 @@ const handleSave = async () => {
|
||||
if (formData.value.ssh_key_id !== undefined) {
|
||||
updatesToApply.ssh_key_id = formData.value.ssh_key_id;
|
||||
}
|
||||
if (formData.value.login_credential_id !== undefined) {
|
||||
if (selectedConnectionTypes.value.length !== 1 && formData.value.login_credential_id !== null) {
|
||||
uiNotificationsStore.addNotification({ message: t('connections.batchEdit.savedCredentialMixedType', '批量应用已保存凭证前,请先只选择同一种连接类型。'), type: 'warning' });
|
||||
isLoading.value = false;
|
||||
return;
|
||||
}
|
||||
updatesToApply.login_credential_id = formData.value.login_credential_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (enableAdvancedEdit.value) {
|
||||
@@ -199,6 +222,9 @@ onMounted(() => {
|
||||
if (availableSshKeys.value.length === 0 && !sshKeysStore.isLoading) {
|
||||
sshKeysStore.fetchSshKeys();
|
||||
}
|
||||
if (loginCredentialsStore.loginCredentials.length === 0 && !loginCredentialsStore.isLoading) {
|
||||
loginCredentialsStore.fetchLoginCredentials();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -264,6 +290,22 @@ onMounted(() => {
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="batch-login-credential" class="block text-sm font-medium text-text-secondary">{{ t('connections.form.savedLoginCredential', '已保存凭证') }}</label>
|
||||
<select
|
||||
id="batch-login-credential"
|
||||
v-model="formData.login_credential_id"
|
||||
class="mt-1 block w-full px-3 py-2 border border-border rounded-md shadow-sm bg-background text-foreground focus:outline-none focus:ring-1 focus:ring-primary sm:text-sm"
|
||||
:disabled="loginCredentialsStore.isLoading"
|
||||
>
|
||||
<option :value="undefined">{{ t('connections.batchEdit.noChange', '-- 不更改 --') }}</option>
|
||||
<option :value="null">{{ t('connections.form.clearSavedCredential', '取消已保存凭证') }}</option>
|
||||
<option v-if="selectedConnectionTypes.length !== 1" disabled>{{ t('connections.batchEdit.savedCredentialTypeHint', '仅支持同类型连接批量应用') }}</option>
|
||||
<option v-for="credential in availableLoginCredentials" :key="credential.id" :value="credential.id">
|
||||
{{ credential.name }} ({{ credential.username }})
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -333,4 +375,4 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -304,7 +304,10 @@ const cancelForm = () => {
|
||||
</div>
|
||||
<div v-else>
|
||||
<label class="block text-sm font-medium text-text-secondary mb-1">{{ t('connections.form.sshKey', 'SSH 密钥') }}</label>
|
||||
<SshKeySelector v-model="formData.ssh_key_id" />
|
||||
<SshKeySelector
|
||||
:model-value="formData.ssh_key_id ?? null"
|
||||
@update:model-value="value => formData.ssh_key_id = value"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user