feat: 后端 & 前端: 实现连接与标签的关联管理
This commit is contained in:
@@ -23,8 +23,8 @@ interface ConnectionInfoBase {
|
||||
* 创建新连接 (POST /api/v1/connections)
|
||||
*/
|
||||
export const createConnection = async (req: Request, res: Response): Promise<void> => {
|
||||
// 新增 proxy_id
|
||||
const { name, host, port = 22, username, auth_method, password, private_key, passphrase, proxy_id } = req.body;
|
||||
// 新增 proxy_id 和 tag_ids
|
||||
const { name, host, port = 22, username, auth_method, password, private_key, passphrase, proxy_id, tag_ids } = req.body;
|
||||
const userId = req.session.userId; // 从会话获取用户 ID
|
||||
|
||||
// 输入验证 (基础)
|
||||
@@ -88,14 +88,42 @@ export const createConnection = async (req: Request, res: Response): Promise<voi
|
||||
stmt.finalize(); // 完成语句执行
|
||||
});
|
||||
|
||||
// 返回成功响应 (不包含敏感信息)
|
||||
// 返回成功响应 (包含 proxy_id)
|
||||
const newConnectionId = result.lastID;
|
||||
|
||||
// 处理标签关联
|
||||
if (Array.isArray(tag_ids) && tag_ids.length > 0) {
|
||||
const insertTagStmt = db.prepare(`INSERT INTO connection_tags (connection_id, tag_id) VALUES (?, ?)`);
|
||||
// 使用事务确保原子性
|
||||
db.serialize(() => {
|
||||
db.run('BEGIN TRANSACTION');
|
||||
try {
|
||||
tag_ids.forEach((tagId: any) => {
|
||||
if (typeof tagId === 'number' && tagId > 0) {
|
||||
insertTagStmt.run(newConnectionId, tagId);
|
||||
} else {
|
||||
console.warn(`创建连接 ${newConnectionId} 时,提供的 tag_id 无效: ${tagId}`);
|
||||
}
|
||||
});
|
||||
db.run('COMMIT');
|
||||
} catch (tagError: any) {
|
||||
console.error(`为连接 ${newConnectionId} 添加标签时出错:`, tagError);
|
||||
db.run('ROLLBACK'); // 出错时回滚
|
||||
// 可以选择抛出错误或仅记录警告
|
||||
// throw new Error('处理标签关联失败');
|
||||
} finally {
|
||||
insertTagStmt.finalize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 返回成功响应 (包含 proxy_id 和 tag_ids)
|
||||
res.status(201).json({
|
||||
message: '连接创建成功。',
|
||||
connection: {
|
||||
id: result.lastID,
|
||||
id: newConnectionId,
|
||||
name, host, port, username, auth_method,
|
||||
proxy_id: proxy_id ?? null, // 返回 proxy_id
|
||||
proxy_id: proxy_id ?? null,
|
||||
tag_ids: Array.isArray(tag_ids) ? tag_ids.filter(id => typeof id === 'number' && id > 0) : [], // 返回有效的 tag_ids
|
||||
created_at: now, updated_at: now, last_connected_at: null
|
||||
}
|
||||
});
|
||||
@@ -113,20 +141,28 @@ export const getConnections = async (req: Request, res: Response): Promise<void>
|
||||
const userId = req.session.userId; // 虽然 MVP 只有一个用户,但保留以备将来使用
|
||||
|
||||
try {
|
||||
// 查询数据库,排除敏感字段 encrypted_password, encrypted_private_key, encrypted_passphrase
|
||||
// 注意:如果未来支持多用户,需要添加 WHERE user_id = ? 条件
|
||||
// 新增:包含 proxy_id
|
||||
const connections = await new Promise<(ConnectionInfoBase & { proxy_id: number | null })[]>((resolve, reject) => {
|
||||
// 更新查询以包含关联的标签 ID (使用 GROUP_CONCAT)
|
||||
const connections = await new Promise<(ConnectionInfoBase & { proxy_id: number | null, tag_ids: number[] })[]>((resolve, reject) => {
|
||||
db.all(
|
||||
`SELECT id, name, host, port, username, auth_method, proxy_id, created_at, updated_at, last_connected_at
|
||||
FROM connections
|
||||
ORDER BY name ASC`,
|
||||
(err, rows: (ConnectionInfoBase & { proxy_id: number | null })[]) => {
|
||||
`SELECT
|
||||
c.id, c.name, c.host, c.port, c.username, c.auth_method, c.proxy_id,
|
||||
c.created_at, c.updated_at, c.last_connected_at,
|
||||
GROUP_CONCAT(ct.tag_id) as tag_ids_str
|
||||
FROM connections c
|
||||
LEFT JOIN connection_tags ct ON c.id = ct.connection_id
|
||||
GROUP BY c.id
|
||||
ORDER BY c.name ASC`,
|
||||
(err, rows: any[]) => { // 使用 any[] 因为 tag_ids_str 是字符串
|
||||
if (err) {
|
||||
console.error('查询连接列表时出错:', err.message);
|
||||
return reject(new Error('获取连接列表失败'));
|
||||
}
|
||||
resolve(rows);
|
||||
// 处理 tag_ids_str,将其转换为数字数组
|
||||
const processedRows = rows.map(row => ({
|
||||
...row,
|
||||
tag_ids: row.tag_ids_str ? row.tag_ids_str.split(',').map(Number) : []
|
||||
}));
|
||||
resolve(processedRows);
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -152,21 +188,29 @@ export const getConnectionById = async (req: Request, res: Response): Promise<vo
|
||||
}
|
||||
|
||||
try {
|
||||
// 查询数据库,排除敏感字段
|
||||
// 注意:如果未来支持多用户,需要添加 AND user_id = ? 条件
|
||||
// 新增:包含 proxy_id
|
||||
const connection = await new Promise<(ConnectionInfoBase & { proxy_id: number | null }) | null>((resolve, reject) => {
|
||||
// 更新查询以包含关联的标签 ID (使用 GROUP_CONCAT)
|
||||
const connection = await new Promise<(ConnectionInfoBase & { proxy_id: number | null, tag_ids: number[] }) | null>((resolve, reject) => {
|
||||
db.get(
|
||||
`SELECT id, name, host, port, username, auth_method, proxy_id, created_at, updated_at, last_connected_at
|
||||
FROM connections
|
||||
WHERE id = ?`,
|
||||
`SELECT
|
||||
c.id, c.name, c.host, c.port, c.username, c.auth_method, c.proxy_id,
|
||||
c.created_at, c.updated_at, c.last_connected_at,
|
||||
GROUP_CONCAT(ct.tag_id) as tag_ids_str
|
||||
FROM connections c
|
||||
LEFT JOIN connection_tags ct ON c.id = ct.connection_id
|
||||
WHERE c.id = ?
|
||||
GROUP BY c.id`, // GROUP BY 仍然需要,即使只有一行
|
||||
[connectionId],
|
||||
(err, row: (ConnectionInfoBase & { proxy_id: number | null })) => {
|
||||
(err, row: any) => { // 使用 any[] 因为 tag_ids_str 是字符串
|
||||
if (err) {
|
||||
console.error(`查询连接 ${connectionId} 时出错:`, err.message);
|
||||
return reject(new Error('获取连接信息失败'));
|
||||
}
|
||||
resolve(row || null); // 如果找不到则返回 null
|
||||
if (row) {
|
||||
// 处理 tag_ids_str
|
||||
row.tag_ids = row.tag_ids_str ? row.tag_ids_str.split(',').map(Number) : [];
|
||||
delete row.tag_ids_str; // 移除临时字段
|
||||
}
|
||||
resolve(row || null);
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -188,8 +232,8 @@ export const getConnectionById = async (req: Request, res: Response): Promise<vo
|
||||
*/
|
||||
export const updateConnection = async (req: Request, res: Response): Promise<void> => {
|
||||
const connectionId = parseInt(req.params.id, 10);
|
||||
// 新增 proxy_id
|
||||
const { name, host, port, username, auth_method, password, private_key, passphrase, proxy_id } = req.body;
|
||||
// 新增 proxy_id 和 tag_ids
|
||||
const { name, host, port, username, auth_method, password, private_key, passphrase, proxy_id, tag_ids } = req.body;
|
||||
const userId = req.session.userId;
|
||||
|
||||
if (isNaN(connectionId)) {
|
||||
@@ -357,6 +401,61 @@ export const updateConnection = async (req: Request, res: Response): Promise<voi
|
||||
(err, row: ConnectionInfoBase & { proxy_id: number | null }) => err ? reject(err) : resolve(row || null)
|
||||
);
|
||||
});
|
||||
|
||||
// 处理标签关联更新
|
||||
if (tag_ids !== undefined && Array.isArray(tag_ids)) { // 仅当提供了 tag_ids 时才处理
|
||||
const deleteStmt = db.prepare(`DELETE FROM connection_tags WHERE connection_id = ?`);
|
||||
const insertStmt = db.prepare(`INSERT INTO connection_tags (connection_id, tag_id) VALUES (?, ?)`);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
db.run('BEGIN TRANSACTION');
|
||||
try {
|
||||
// 1. 删除旧关联
|
||||
deleteStmt.run(connectionId, (err: Error | null) => { // 添加 err 类型
|
||||
if (err) throw err; // 抛出错误以触发 rollback
|
||||
});
|
||||
deleteStmt.finalize(); // finalize delete statement
|
||||
|
||||
// 2. 插入新关联 (如果 tag_ids 不为空)
|
||||
if (tag_ids.length > 0) {
|
||||
tag_ids.forEach((tagId: any) => {
|
||||
if (typeof tagId === 'number' && tagId > 0) {
|
||||
insertStmt.run(connectionId, tagId, (err: Error | null) => { // 添加 err 类型
|
||||
if (err) throw err; // 抛出错误以触发 rollback
|
||||
});
|
||||
} else {
|
||||
console.warn(`更新连接 ${connectionId} 时,提供的 tag_id 无效: ${tagId}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
insertStmt.finalize(); // finalize insert statement
|
||||
db.run('COMMIT', (commitErr: Error | null) => { // 添加 commitErr 类型
|
||||
if (commitErr) throw commitErr;
|
||||
resolve(); // 事务成功
|
||||
});
|
||||
} catch (tagError: any) {
|
||||
console.error(`更新连接 ${connectionId} 的标签关联时出错:`, tagError);
|
||||
db.run('ROLLBACK');
|
||||
// 将标签处理错误附加到主错误或单独处理
|
||||
reject(new Error('处理标签关联失败'));
|
||||
}
|
||||
});
|
||||
});
|
||||
} // 结束标签处理
|
||||
|
||||
// 在返回的 updatedConnection 中添加 tag_ids
|
||||
if (updatedConnection) {
|
||||
// 查询最新的 tag_ids
|
||||
const currentTagIds = await new Promise<number[]>((resolve, reject) => {
|
||||
db.all('SELECT tag_id FROM connection_tags WHERE connection_id = ?', [connectionId], (err: Error | null, rows: { tag_id: number }[]) => { // 添加 err 类型
|
||||
if (err) return reject(err);
|
||||
resolve(rows.map(r => r.tag_id));
|
||||
});
|
||||
});
|
||||
(updatedConnection as any).tag_ids = currentTagIds; // 添加 tag_ids 字段
|
||||
}
|
||||
|
||||
res.status(200).json({ message: '连接更新成功。', connection: updatedConnection });
|
||||
}
|
||||
|
||||
|
||||
@@ -57,8 +57,18 @@ CREATE TABLE IF NOT EXISTS tags (
|
||||
);
|
||||
`;
|
||||
|
||||
// 新增:创建 connection_tags 关联表的 SQL
|
||||
const createConnectionTagsTableSQL = `
|
||||
CREATE TABLE IF NOT EXISTS connection_tags (
|
||||
connection_id INTEGER NOT NULL,
|
||||
tag_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (connection_id, tag_id),
|
||||
FOREIGN KEY (connection_id) REFERENCES connections(id) ON DELETE CASCADE, -- 删除连接时,自动删除关联
|
||||
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE -- 删除标签时,自动删除关联
|
||||
);
|
||||
`;
|
||||
|
||||
// 未来可能需要的其他表 (根据项目文档)
|
||||
// const createConnectionTagsTableSQL = \`...\`; // 连接与标签的关联表
|
||||
// const createSettingsTableSQL = \`...\`; // 设置表
|
||||
// const createAuditLogsTableSQL = \`...\`; // 审计日志表
|
||||
// const createApiKeysTableSQL = \`...\`; // API 密钥表
|
||||
@@ -170,6 +180,15 @@ export const runMigrations = async (db: Database): Promise<void> => {
|
||||
});
|
||||
});
|
||||
|
||||
// 新增:创建 connection_tags 表 (如果不存在)
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
db.run(createConnectionTagsTableSQL, (err) => {
|
||||
if (err) return reject(new Error(`创建 connection_tags 表时出错: ${err.message}`));
|
||||
console.log('Connection_Tags 表已检查/创建。');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Add other tables or columns here in the future
|
||||
|
||||
console.log('数据库迁移检查完成。');
|
||||
|
||||
Binary file not shown.
@@ -4,6 +4,7 @@ import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useConnectionsStore, ConnectionInfo } from '../stores/connections.store';
|
||||
import { useProxiesStore } from '../stores/proxies.store'; // 引入代理 Store
|
||||
import { useTagsStore } from '../stores/tags.store'; // 引入标签 Store
|
||||
|
||||
// 定义组件发出的事件
|
||||
const emit = defineEmits(['close', 'connection-added', 'connection-updated']);
|
||||
@@ -16,8 +17,10 @@ const props = defineProps<{
|
||||
const { t } = useI18n();
|
||||
const connectionsStore = useConnectionsStore();
|
||||
const proxiesStore = useProxiesStore(); // 获取代理 store 实例
|
||||
const tagsStore = useTagsStore(); // 获取标签 store 实例
|
||||
const { isLoading: isConnLoading, error: connStoreError } = storeToRefs(connectionsStore);
|
||||
const { proxies, isLoading: isProxyLoading, error: proxyStoreError } = storeToRefs(proxiesStore); // 获取代理列表和状态
|
||||
const { tags, isLoading: isTagLoading, error: tagStoreError } = storeToRefs(tagsStore); // 获取标签列表和状态
|
||||
|
||||
// 表单数据模型
|
||||
const initialFormData = {
|
||||
@@ -29,13 +32,15 @@ const initialFormData = {
|
||||
password: '',
|
||||
private_key: '',
|
||||
passphrase: '',
|
||||
proxy_id: null as number | null, // 新增 proxy_id 字段
|
||||
proxy_id: null as number | null,
|
||||
tag_ids: [] as number[], // 新增 tag_ids 字段
|
||||
};
|
||||
const formData = reactive({ ...initialFormData });
|
||||
|
||||
const formError = ref<string | null>(null); // 表单级别的错误信息
|
||||
const isLoading = computed(() => isConnLoading.value || isProxyLoading.value); // 合并加载状态
|
||||
const storeError = computed(() => connStoreError.value || proxyStoreError.value); // 合并错误状态
|
||||
// 合并所有 store 的加载和错误状态
|
||||
const isLoading = computed(() => isConnLoading.value || isProxyLoading.value || isTagLoading.value);
|
||||
const storeError = computed(() => connStoreError.value || proxyStoreError.value || tagStoreError.value);
|
||||
|
||||
// 计算属性判断是否为编辑模式
|
||||
const isEditMode = computed(() => !!props.connectionToEdit);
|
||||
@@ -64,7 +69,8 @@ watch(() => props.connectionToEdit, (newVal) => {
|
||||
formData.port = newVal.port;
|
||||
formData.username = newVal.username;
|
||||
formData.auth_method = newVal.auth_method;
|
||||
formData.proxy_id = newVal.proxy_id ?? null; // 填充 proxy_id
|
||||
formData.proxy_id = newVal.proxy_id ?? null;
|
||||
formData.tag_ids = newVal.tag_ids ? [...newVal.tag_ids] : []; // 填充 tag_ids (深拷贝)
|
||||
// 清空敏感字段
|
||||
formData.password = '';
|
||||
formData.private_key = '';
|
||||
@@ -75,9 +81,10 @@ watch(() => props.connectionToEdit, (newVal) => {
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 组件挂载时获取代理列表
|
||||
// 组件挂载时获取代理和标签列表
|
||||
onMounted(() => {
|
||||
proxiesStore.fetchProxies();
|
||||
tagsStore.fetchTags(); // 获取标签列表
|
||||
});
|
||||
|
||||
// 处理表单提交
|
||||
@@ -137,6 +144,7 @@ const handleSubmit = async () => {
|
||||
username: formData.username,
|
||||
auth_method: formData.auth_method,
|
||||
proxy_id: formData.proxy_id || null,
|
||||
tag_ids: formData.tag_ids || [], // 发送 tag_ids
|
||||
};
|
||||
|
||||
// 处理敏感字段
|
||||
@@ -248,6 +256,20 @@ const handleSubmit = async () => {
|
||||
<div v-if="proxyStoreError" class="error-small">{{ t('proxies.error', { error: proxyStoreError }) }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增:标签选择 (多选框) -->
|
||||
<div class="form-group">
|
||||
<label>{{ t('connections.form.tags') }} ({{ t('connections.form.optional') }})</label>
|
||||
<div class="tag-checkbox-group">
|
||||
<div v-if="isTagLoading" class="loading-small">{{ t('tags.loading') }}</div>
|
||||
<div v-else-if="tagStoreError" class="error-small">{{ t('tags.error', { error: tagStoreError }) }}</div>
|
||||
<div v-else-if="tags.length === 0" class="info-small">{{ t('tags.noTags') }}</div>
|
||||
<label v-for="tag in tags" :key="tag.id" class="tag-checkbox-label">
|
||||
<input type="checkbox" :value="tag.id" v-model="formData.tag_ids">
|
||||
{{ tag.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 显示 storeError 或 formError -->
|
||||
<div v-if="formError || storeError" class="error-message">
|
||||
{{ formError || storeError }} <!-- 使用合并后的 storeError -->
|
||||
@@ -315,6 +337,38 @@ textarea {
|
||||
box-sizing: border-box; /* Include padding and border in element's total width and height */
|
||||
}
|
||||
|
||||
/* 标签选择样式 */
|
||||
.tag-checkbox-group {
|
||||
max-height: 150px; /* 限制高度,出现滚动条 */
|
||||
overflow-y: auto;
|
||||
border: 1px solid #ccc;
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.tag-checkbox-label {
|
||||
display: block; /* 每个标签占一行 */
|
||||
margin-bottom: 0.3rem;
|
||||
font-weight: normal; /* 普通字体 */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tag-checkbox-label input[type="checkbox"] {
|
||||
margin-right: 0.5rem;
|
||||
width: auto; /* 恢复复选框默认宽度 */
|
||||
}
|
||||
|
||||
.loading-small, .error-small, .info-small {
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
.error-small {
|
||||
color: red;
|
||||
}
|
||||
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@@ -1,24 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { onMounted, computed } from 'vue'; // 引入 computed
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useRouter } from 'vue-router'; // 引入 useRouter
|
||||
import { useI18n } from 'vue-i18n'; // 引入 useI18n
|
||||
import { useConnectionsStore, ConnectionInfo } from '../stores/connections.store'; // 引入 ConnectionInfo 类型
|
||||
import { useTagsStore } from '../stores/tags.store'; // 引入 Tags Store
|
||||
|
||||
const { t } = useI18n(); // 获取 t 函数
|
||||
const router = useRouter(); // 获取 router 实例
|
||||
const connectionsStore = useConnectionsStore();
|
||||
const tagsStore = useTagsStore(); // 获取 Tags Store 实例
|
||||
// 使用 storeToRefs 来保持 state 属性的响应性
|
||||
const { connections, isLoading, error } = storeToRefs(connectionsStore);
|
||||
const { tags: allTags } = storeToRefs(tagsStore); // 获取所有标签
|
||||
|
||||
// 定义组件发出的事件 (添加 edit-connection)
|
||||
const emit = defineEmits(['edit-connection']);
|
||||
|
||||
// 组件挂载时获取连接列表
|
||||
// 组件挂载时获取连接和标签列表
|
||||
onMounted(() => {
|
||||
connectionsStore.fetchConnections();
|
||||
tagsStore.fetchTags(); // 获取标签列表
|
||||
});
|
||||
|
||||
// 创建标签 ID 到名称的映射
|
||||
const tagMap = computed(() => {
|
||||
const map = new Map<number, string>();
|
||||
allTags.value.forEach(tag => {
|
||||
map.set(tag.id, tag.name);
|
||||
});
|
||||
return map;
|
||||
});
|
||||
|
||||
// 获取连接的标签名称数组
|
||||
const getConnectionTagNames = (conn: ConnectionInfo): string[] => {
|
||||
if (!conn.tag_ids || conn.tag_ids.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return conn.tag_ids
|
||||
.map(tagId => tagMap.value.get(tagId)) // 使用映射获取名称
|
||||
.filter((name): name is string => !!name); // 过滤掉未找到的标签并确保类型为 string
|
||||
};
|
||||
|
||||
// 辅助函数:格式化时间戳
|
||||
const formatTimestamp = (timestamp: number | null): string => {
|
||||
if (!timestamp) return t('connections.status.never'); // 使用 i18n
|
||||
@@ -59,6 +82,7 @@ const handleDelete = async (conn: ConnectionInfo) => {
|
||||
<th>{{ t('connections.table.port') }}</th>
|
||||
<th>{{ t('connections.table.user') }}</th>
|
||||
<th>{{ t('connections.table.authMethod') }}</th>
|
||||
<th>{{ t('connections.table.tags') }}</th> <!-- 新增标签列 -->
|
||||
<th>{{ t('connections.table.lastConnected') }}</th>
|
||||
<th>{{ t('connections.table.actions') }}</th>
|
||||
</tr>
|
||||
@@ -70,6 +94,14 @@ const handleDelete = async (conn: ConnectionInfo) => {
|
||||
<td>{{ conn.port }}</td>
|
||||
<td>{{ conn.username }}</td>
|
||||
<td>{{ conn.auth_method }}</td>
|
||||
<td> <!-- 显示标签 -->
|
||||
<span v-if="getConnectionTagNames(conn).length > 0" class="tag-list">
|
||||
<span v-for="tagName in getConnectionTagNames(conn)" :key="tagName" class="tag-item">
|
||||
{{ tagName }}
|
||||
</span>
|
||||
</span>
|
||||
<span v-else class="no-tags">-</span>
|
||||
</td>
|
||||
<td>{{ formatTimestamp(conn.last_connected_at) }}</td>
|
||||
<td>
|
||||
<button @click="connectToServer(conn.id)">{{ t('connections.actions.connect') }}</button>
|
||||
@@ -137,4 +169,23 @@ button {
|
||||
padding: 0.2rem 0.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 标签样式 */
|
||||
.tag-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
.tag-item {
|
||||
background-color: #e0e0e0;
|
||||
color: #333;
|
||||
padding: 0.1rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.85em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.no-tags {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"port": "Port",
|
||||
"user": "User",
|
||||
"authMethod": "Auth Method",
|
||||
"tags": "Tags",
|
||||
"lastConnected": "Last Connected",
|
||||
"actions": "Actions"
|
||||
},
|
||||
@@ -65,7 +66,8 @@
|
||||
"errorUpdate": "Failed to update connection: {error}",
|
||||
"keyUpdateNote": "Leave private key and passphrase blank to keep the existing key.",
|
||||
"proxy": "Proxy:",
|
||||
"noProxy": "No Proxy"
|
||||
"noProxy": "No Proxy",
|
||||
"tags": "Tags:"
|
||||
},
|
||||
"prompts": {
|
||||
"confirmDelete": "Are you sure you want to delete the connection \"{name}\"? This cannot be undone."
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"port": "端口",
|
||||
"user": "用户名",
|
||||
"authMethod": "认证方式",
|
||||
"tags": "标签",
|
||||
"lastConnected": "上次连接",
|
||||
"actions": "操作"
|
||||
},
|
||||
@@ -65,7 +66,8 @@
|
||||
"errorUpdate": "更新连接失败: {error}",
|
||||
"keyUpdateNote": "将私钥和密码短语留空以保留现有密钥。",
|
||||
"proxy": "代理:",
|
||||
"noProxy": "无代理"
|
||||
"noProxy": "无代理",
|
||||
"tags": "标签:"
|
||||
},
|
||||
"prompts": {
|
||||
"confirmDelete": "确定要删除连接 \"{name}\" 吗?此操作不可撤销。"
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface ConnectionInfo {
|
||||
username: string;
|
||||
auth_method: 'password' | 'key';
|
||||
proxy_id?: number | null; // 新增:关联的代理 ID (可选)
|
||||
tag_ids?: number[]; // 新增:关联的标签 ID 数组 (可选)
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
last_connected_at: number | null;
|
||||
@@ -62,7 +63,8 @@ export const useConnectionsStore = defineStore('connections', {
|
||||
password?: string;
|
||||
private_key?: string;
|
||||
passphrase?: string;
|
||||
proxy_id?: number | null; // 新增:允许传入 proxy_id
|
||||
proxy_id?: number | null;
|
||||
tag_ids?: number[]; // 新增:允许传入 tag_ids
|
||||
}) {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
@@ -84,8 +86,8 @@ export const useConnectionsStore = defineStore('connections', {
|
||||
},
|
||||
|
||||
// 更新连接 Action
|
||||
// 更新参数类型以包含 proxy_id
|
||||
async updateConnection(connectionId: number, updatedData: Partial<Omit<ConnectionInfo, 'id' | 'created_at' | 'updated_at' | 'last_connected_at'> & { password?: string; private_key?: string; passphrase?: string; proxy_id?: number | null }>) {
|
||||
// 更新参数类型以包含 proxy_id 和 tag_ids
|
||||
async updateConnection(connectionId: number, updatedData: Partial<Omit<ConnectionInfo, 'id' | 'created_at' | 'updated_at' | 'last_connected_at'> & { password?: string; private_key?: string; passphrase?: string; proxy_id?: number | null; tag_ids?: number[] }>) {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user