This commit is contained in:
Baobhan Sith
2025-05-16 13:51:10 +08:00
parent 78314b1f0b
commit 10d7921edb
5 changed files with 52 additions and 25 deletions
@@ -226,17 +226,15 @@ onMounted(() => {
<!-- Port Section --> <!-- Port Section -->
<div class="p-4 border border-border rounded-md bg-card"> <div class="p-4 border border-border rounded-md bg-card">
<div class="flex justify-between items-center mb-2"> <div class="flex justify-between items-center mb-2">
<h4 class="text-base font-semibold">{{ t('connections.form.sectionBasic.port', '端口') }}</h4> <h4 class="text-base font-semibold">{{ t('connections.table.port', '端口') }}</h4>
<input type="checkbox" v-model="enablePortEdit" class="form-checkbox h-5 w-5 text-primary rounded border-gray-300 focus:ring-primary" /> <input type="checkbox" v-model="enablePortEdit" class="form-checkbox h-5 w-5 text-primary rounded border-gray-300 focus:ring-primary" />
</div> </div>
<div v-if="enablePortEdit"> <div v-if="enablePortEdit">
<label for="batch-port" class="block text-sm font-medium text-text-secondary">{{ t('connections.form.sectionBasic.portPlaceholder', '例如: 22') }}</label>
<input <input
type="text" type="text"
id="batch-port" id="batch-port"
v-model="formData.port" v-model="formData.port"
class="mt-1 block w-full px-3 py-2 border border-border rounded-md shadow-sm bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary sm:text-sm" class="mt-1 block w-full px-3 py-2 border border-border rounded-md shadow-sm bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary sm:text-sm"
:placeholder="t('connections.batchEdit.portPlaceholder', '输入新端口号')"
/> />
</div> </div>
</div> </div>
@@ -244,21 +242,20 @@ onMounted(() => {
<!-- Auth Section --> <!-- Auth Section -->
<div class="p-4 border border-border rounded-md bg-card"> <div class="p-4 border border-border rounded-md bg-card">
<div class="flex justify-between items-center mb-2"> <div class="flex justify-between items-center mb-2">
<h4 class="text-base font-semibold">{{ t('connections.form.sectionAuth.title', '认证信息') }}</h4> <h4 class="text-base font-semibold">{{ t('connections.form.sectionAuth', '认证信息') }}</h4>
<input type="checkbox" v-model="enableAuthEdit" class="form-checkbox h-5 w-5 text-primary rounded border-gray-300 focus:ring-primary" /> <input type="checkbox" v-model="enableAuthEdit" class="form-checkbox h-5 w-5 text-primary rounded border-gray-300 focus:ring-primary" />
</div> </div>
<div v-if="enableAuthEdit" class="space-y-3"> <div v-if="enableAuthEdit" class="space-y-3">
<div> <div>
<label for="batch-username" class="block text-sm font-medium text-text-secondary">{{ t('connections.form.sectionAuth.username', '用户名') }}</label> <label for="batch-username" class="block text-sm font-medium text-text-secondary">{{ t('connections.form.username', '用户名') }}</label>
<input type="text" id="batch-username" v-model="formData.username" class="mt-1 block w-full px-3 py-2 border border-border rounded-md shadow-sm bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary sm:text-sm" :placeholder="t('connections.batchEdit.authPlaceholder', '留空表示不更改此字段')" /> <input type="text" id="batch-username" v-model="formData.username" class="mt-1 block w-full px-3 py-2 border border-border rounded-md shadow-sm bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary sm:text-sm" />
</div> </div>
<div> <div>
<label for="batch-password" class="block text-sm font-medium text-text-secondary">{{ t('connections.form.sectionAuth.password', '密码') }}</label> <label for="batch-password" class="block text-sm font-medium text-text-secondary">{{ t('connections.form.authMethodPassword', '密码') }}</label>
<input type="password" id="batch-password" v-model="formData.password" class="mt-1 block w-full px-3 py-2 border border-border rounded-md shadow-sm bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary sm:text-sm" :placeholder="t('connections.batchEdit.passwordInfoBatch', '输入新密码,或留空不更改')" /> <input type="password" id="batch-password" v-model="formData.password" class="mt-1 block w-full px-3 py-2 border border-border rounded-md shadow-sm bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary sm:text-sm" />
<p class="mt-1 text-xs text-text-alt">{{ t('connections.batchEdit.passwordWarning', '注意:某些连接类型可能不支持空密码或有特定密码策略。') }}</p>
</div> </div>
<div> <div>
<label for="batch-ssh-key" class="block text-sm font-medium text-text-secondary">{{ t('connections.form.sectionAuth.sshKey', 'SSH 密钥') }}</label> <label for="batch-ssh-key" class="block text-sm font-medium text-text-secondary">{{ t('connections.form.authMethodKey', 'SSH 密钥') }}</label>
<select <select
id="batch-ssh-key" id="batch-ssh-key"
v-model="formData.ssh_key_id" v-model="formData.ssh_key_id"
@@ -266,13 +263,12 @@ onMounted(() => {
:disabled="sshKeysStore.isLoading" :disabled="sshKeysStore.isLoading"
> >
<option :value="undefined">{{ t('connections.batchEdit.noChange', '-- 不更改 --') }}</option> <option :value="undefined">{{ t('connections.batchEdit.noChange', '-- 不更改 --') }}</option>
<option :value="null">{{ t('connections.form.sectionAuth.noSshKey', '无密钥') }}</option> <option :value="null">{{ t('connections.form.noSshKey', '无密钥') }}</option>
<option v-if="sshKeysStore.isLoading" disabled>{{ t('common.loading', '加载中...') }}</option> <option v-if="sshKeysStore.isLoading" disabled>{{ t('common.loading', '加载中...') }}</option>
<option v-for="key in availableSshKeys" :key="key.id" :value="key.id"> <option v-for="key in availableSshKeys" :key="key.id" :value="key.id">
{{ key.name }} {{ key.name }}
</option> </option>
</select> </select>
<p class="mt-1 text-xs text-text-alt">{{ t('connections.batchEdit.sshKeyInfo', '选择密钥将覆盖密码认证(如果连接类型支持)。') }}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -280,34 +276,32 @@ onMounted(() => {
<!-- Advanced Section (Now includes Notes) --> <!-- Advanced Section (Now includes Notes) -->
<div class="p-4 border border-border rounded-md bg-card"> <div class="p-4 border border-border rounded-md bg-card">
<div class="flex justify-between items-center mb-2"> <div class="flex justify-between items-center mb-2">
<h4 class="text-base font-semibold">{{ t('connections.form.sectionAdvanced.title', '高级选项') }}</h4> <h4 class="text-base font-semibold">{{ t('connections.form.sectionAdvanced', '高级选项') }}</h4>
<input type="checkbox" v-model="enableAdvancedEdit" class="form-checkbox h-5 w-5 text-primary rounded border-gray-300 focus:ring-primary" /> <input type="checkbox" v-model="enableAdvancedEdit" class="form-checkbox h-5 w-5 text-primary rounded border-gray-300 focus:ring-primary" />
</div> </div>
<div v-if="enableAdvancedEdit" class="space-y-3"> <div v-if="enableAdvancedEdit" class="space-y-3">
<div> <div>
<label for="batch-proxy" class="block text-sm font-medium text-text-secondary">{{ t('connections.form.sectionAdvanced.proxy.label', '代理') }}</label> <label for="batch-proxy" class="block text-sm font-medium text-text-secondary">{{ t('connections.form.proxy', '代理') }}</label>
<select id="batch-proxy" v-model="formData.proxy_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"> <select id="batch-proxy" v-model="formData.proxy_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">
<option :value="undefined">{{ t('connections.batchEdit.noChange', '-- 不更改 --') }}</option> <option :value="undefined">{{ t('connections.batchEdit.noChange', '-- 不更改 --') }}</option>
<option :value="null">{{ t('connections.form.sectionAdvanced.proxy.none', '无代理') }}</option> <option :value="null">{{ t('connections.form.noProxy', '无代理') }}</option>
<option v-for="proxy in availableProxies" :key="proxy.id" :value="proxy.id"> <option v-for="proxy in availableProxies" :key="proxy.id" :value="proxy.id">
{{ proxy.name }} ({{ proxy.type }}) {{ proxy.name }} ({{ proxy.type }})
</option> </option>
</select> </select>
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-text-secondary">{{ t('connections.form.sectionAdvanced.tags.label', '标签') }}</label> <label class="block text-sm font-medium text-text-secondary">{{ t('connections.table.tags', '标签') }}</label>
<TagInput <TagInput
:modelValue="formData.tag_ids || []" :modelValue="formData.tag_ids || []"
@update:modelValue="val => formData.tag_ids = val" @update:modelValue="val => formData.tag_ids = val"
:availableTags="availableTags" :availableTags="availableTags"
@create-tag="handleCreateTag" @create-tag="handleCreateTag"
@delete-tag="handleDeleteTag" @delete-tag="handleDeleteTag"
:placeholder="t('connections.batchEdit.tagsPlaceholder', '选择或创建标签 (将替换现有标签)')"
:allow-create="true" :allow-create="true"
:allow-delete="true" :allow-delete="true"
class="mt-1" class="mt-1"
/> />
<p class="mt-1 text-xs text-text-alt">{{ t('connections.batchEdit.tagsInfoReplace', '选择的标签将替换所有选中连接的现有标签。') }}</p>
</div> </div>
<!-- Notes section moved here, no separate enable checkbox for notes itself --> <!-- Notes section moved here, no separate enable checkbox for notes itself -->
<div class="pt-2"> <div class="pt-2">
@@ -317,7 +311,6 @@ onMounted(() => {
v-model="formData.notes" v-model="formData.notes"
rows="3" rows="3"
class="mt-1 block w-full px-3 py-2 border border-border rounded-md shadow-sm bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary sm:text-sm" class="mt-1 block w-full px-3 py-2 border border-border rounded-md shadow-sm bg-input text-foreground focus:outline-none focus:ring-1 focus:ring-primary sm:text-sm"
:placeholder="t('connections.batchEdit.notesPlaceholder', '输入新备注,或留空不更改')"
></textarea> ></textarea>
</div> </div>
</div> </div>
@@ -340,7 +333,7 @@ onMounted(() => {
:disabled="isLoading || (!enablePortEdit && !enableAuthEdit && !enableAdvancedEdit)" :disabled="isLoading || (!enablePortEdit && !enableAuthEdit && !enableAdvancedEdit)"
> <!-- Removed enableNotesEdit from disabled condition --> > <!-- Removed enableNotesEdit from disabled condition -->
<i v-if="isLoading" class="fas fa-spinner fa-spin mr-2"></i> <i v-if="isLoading" class="fas fa-spinner fa-spin mr-2"></i>
{{ t('common.saveChanges', '保存更改') }} {{ t('common.save', '保存') }}
</button> </button>
</div> </div>
</div> </div>
+12
View File
@@ -127,7 +127,18 @@
"lastConnected": "Last Connected", "lastConnected": "Last Connected",
"actions": "Actions" "actions": "Actions"
}, },
"batchEdit":{
"toggleLabel":"Batch Edit",
"selectAll": "Select All",
"deselectAll": "Deselect All",
"invertSelection": "Invert Selection",
"title": "Batch Edit Connections",
"editSelected": "Edit Selected",
"noChange":"No change",
"selectedItems":"{count} items selected"
},
"actions": { "actions": {
"testAllFiltered":"Test All",
"connect": "Connect", "connect": "Connect",
"edit": "Edit", "edit": "Edit",
"delete": "Delete", "delete": "Delete",
@@ -152,6 +163,7 @@
"confirm": "Confirm Add", "confirm": "Confirm Add",
"adding": "Adding...", "adding": "Adding...",
"cancel": "Cancel", "cancel": "Cancel",
"noSshKey":"No SSH Key",
"errorRequiredFields": "Please fill in all required fields.", "errorRequiredFields": "Please fill in all required fields.",
"errorPasswordRequired": "Password is required for password authentication.", "errorPasswordRequired": "Password is required for password authentication.",
"errorPrivateKeyRequired": "Private key is required for key authentication.", "errorPrivateKeyRequired": "Private key is required for key authentication.",
+12
View File
@@ -109,7 +109,18 @@
"copied": "クリップボードにコピーしました" "copied": "クリップボードにコピーしました"
}, },
"connections": { "connections": {
"batchEdit": {
"toggleLabel": "一括編集",
"selectAll": "すべて選択",
"deselectAll": "すべて選択解除",
"invertSelection": "選択を反転",
"title": "接続の一括編集",
"editSelected": "選択した項目を編集",
"noChange": "変更なし",
"selectedItems": "{count} 件選択済み"
},
"actions": { "actions": {
"testAllFiltered":"すべてテスト",
"connect": "接続", "connect": "接続",
"delete": "削除", "delete": "削除",
"edit": "編集", "edit": "編集",
@@ -150,6 +161,7 @@
"password": "パスワード:", "password": "パスワード:",
"port": "ポート:", "port": "ポート:",
"privateKey": "秘密鍵:", "privateKey": "秘密鍵:",
"noSshKey":"SSHキーなし",
"proxy": "プロキシ:", "proxy": "プロキシ:",
"saving": "保存中...", "saving": "保存中...",
"sectionAdvanced": "詳細設定", "sectionAdvanced": "詳細設定",
+13
View File
@@ -126,7 +126,19 @@
"lastConnected": "上次连接", "lastConnected": "上次连接",
"actions": "操作" "actions": "操作"
}, },
"batchEdit":{
"toggleLabel":"批量修改",
"selectAll": "全选",
"deselectAll": "取消全选",
"invertSelection": "反向选择",
"editSelected": "编辑所选",
"title": "批量编辑连接",
"selectedItems": "已选项目",
"noChange": "保持不变",
"tagsPlaceholder": "输入标签 (替换现有)"
},
"actions": { "actions": {
"testAllFiltered":"测试全部",
"connect": "连接", "connect": "连接",
"edit": "编辑", "edit": "编辑",
"delete": "删除", "delete": "删除",
@@ -181,6 +193,7 @@
"testConnection": "测试连接", "testConnection": "测试连接",
"testing": "测试中...", "testing": "测试中...",
"sshKey": "SSH 密钥", "sshKey": "SSH 密钥",
"noSshKey":"无 SSH 密钥",
"privateKeyDirect": "私钥内容", "privateKeyDirect": "私钥内容",
"keyUpdateNoteDirect": "编辑时将私钥和密码短语留空以保留现有密钥。", "keyUpdateNoteDirect": "编辑时将私钥和密码短语留空以保留现有密钥。",
"keyUpdateNoteSelected": "编辑时选择其他密钥或使用直接输入来更改密钥。", "keyUpdateNoteSelected": "编辑时选择其他密钥或使用直接输入来更改密钥。",
@@ -485,7 +485,7 @@ const getTruncatedNotes = (notes: string | null | undefined): string => {
<i :class="['fas', isAscending ? 'fa-arrow-up-a-z' : 'fa-arrow-down-z-a', 'w-4 h-4']"></i> <i :class="['fas', isAscending ? 'fa-arrow-up-a-z' : 'fa-arrow-down-z-a', 'w-4 h-4']"></i>
</button> </button>
</div> </div>
<button @click="openAddConnectionForm" title="Add Connection" class="h-8 w-8 bg-button rounded-md shadow-sm hover:bg-button-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition duration-150 ease-in-out flex items-center justify-center flex-shrink-0 ml-2 sm:ml-0"> <button @click="openAddConnectionForm" :title="t('connections.addConnection', 'Add Connection')" class="h-8 w-8 bg-button rounded-md shadow-sm hover:bg-button-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition duration-150 ease-in-out flex items-center justify-center flex-shrink-0 ml-2 sm:ml-0">
<i class="fas fa-plus" style="color: white;"></i> <i class="fas fa-plus" style="color: white;"></i>
</button> </button>
<!-- Test All Filtered Connections Button --> <!-- Test All Filtered Connections Button -->
@@ -498,7 +498,6 @@ const getTruncatedNotes = (notes: string | null | undefined): string => {
<i v-if="isTestingAll" class="fas fa-spinner fa-spin mr-1 sm:mr-2"></i> <i v-if="isTestingAll" class="fas fa-spinner fa-spin mr-1 sm:mr-2"></i>
<i v-else class="fas fa-check-double mr-1 sm:mr-2"></i> <i v-else class="fas fa-check-double mr-1 sm:mr-2"></i>
<span class="hidden sm:inline">{{ t('connections.actions.testAllFiltered') }}</span> <span class="hidden sm:inline">{{ t('connections.actions.testAllFiltered') }}</span>
<span class="sm:hidden">{{ t('connections.actions.testAllShort', '测试全部') }}</span>
</button> </button>
</div> </div>
</div> </div>
@@ -560,7 +559,7 @@ const getTruncatedNotes = (notes: string | null | undefined): string => {
</span> </span>
<!-- 备注信息移到这里 --> <!-- 备注信息移到这里 -->
<div v-if="conn.notes && conn.notes.trim() !== ''" class="text-xs text-text-secondary mt-1"> <div v-if="conn.notes && conn.notes.trim() !== ''" class="text-xs text-text-secondary mt-1">
<span class="font-medium text-text-alt">{{ t('connections.notes.label', '备注:') }}</span> <span class="font-medium text-text-alt">{{ t('connections.form.notes', '备注:') }}</span>
<span class="break-words leading-snug ml-1" :title="conn.notes"> <span class="break-words leading-snug ml-1" :title="conn.notes">
{{ getTruncatedNotes(conn.notes) }} {{ getTruncatedNotes(conn.notes) }}
</span> </span>
@@ -619,7 +618,6 @@ const getTruncatedNotes = (notes: string | null | undefined): string => {
class="px-3 py-1.5 bg-transparent text-foreground border border-border rounded-md shadow-sm hover:bg-border focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition duration-150 ease-in-out text-sm font-medium h-9 flex items-center justify-center" class="px-3 py-1.5 bg-transparent text-foreground border border-border rounded-md shadow-sm hover:bg-border focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition duration-150 ease-in-out text-sm font-medium h-9 flex items-center justify-center"
:disabled="isBatchEditMode" :disabled="isBatchEditMode"
:class="{ 'opacity-50 cursor-not-allowed': isBatchEditMode }" :class="{ 'opacity-50 cursor-not-allowed': isBatchEditMode }"
:title="isBatchEditMode ? t('connections.batchEdit.disabledInBatchMode', '批量模式下禁用') : t('connections.actions.edit', '编辑')"
> >
<i class="fas fa-pencil-alt mr-1"></i>{{ t('connections.actions.edit') }} <i class="fas fa-pencil-alt mr-1"></i>{{ t('connections.actions.edit') }}
</button> </button>
@@ -628,7 +626,6 @@ const getTruncatedNotes = (notes: string | null | undefined): string => {
class="px-4 py-2 bg-button text-button-text rounded-md shadow-sm hover:bg-button-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition duration-150 ease-in-out text-sm font-medium h-9 flex items-center justify-center" class="px-4 py-2 bg-button text-button-text rounded-md shadow-sm hover:bg-button-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition duration-150 ease-in-out text-sm font-medium h-9 flex items-center justify-center"
:disabled="isBatchEditMode" :disabled="isBatchEditMode"
:class="{ 'opacity-50 cursor-not-allowed': isBatchEditMode }" :class="{ 'opacity-50 cursor-not-allowed': isBatchEditMode }"
:title="isBatchEditMode ? t('connections.batchEdit.disabledInBatchMode', '批量模式下禁用') : t('connections.actions.connect', '连接')"
> >
{{ t('connections.actions.connect') }} {{ t('connections.actions.connect') }}
</button> </button>