@@ -199,127 +199,123 @@ export function useAddConnectionForm(props: AddConnectionFormProps, emit: AddCon
|
|||||||
return ips;
|
return ips;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to parse a single script line
|
// Helper function to parse a single script line using minimist
|
||||||
const parseScriptLine = (line: string): { type: 'SSH' | 'RDP' | 'VNC' | null, userHostPort: string, name: string | null, password: string | null, keyName: string | null, proxyName: string | null, tags: string[], note: string | null, error?: string } => {
|
|
||||||
const firstSpaceIndex = line.indexOf(' ');
|
|
||||||
let userHostPortPart = '';
|
|
||||||
let remainingLine = line;
|
|
||||||
|
|
||||||
if (firstSpaceIndex !== -1) {
|
|
||||||
userHostPortPart = line.substring(0, firstSpaceIndex);
|
const parseScriptLine = (line: string): { type: 'SSH' | 'RDP' | 'VNC', userHostPort: string, name: string, password: string | null, keyName: string | null, proxyName: string | null, tags: string[], note: string | null, error?: string } => {
|
||||||
remainingLine = line.substring(firstSpaceIndex + 1).trim();
|
line = line.trim();
|
||||||
} else {
|
if (!line) {
|
||||||
userHostPortPart = line;
|
return { type: 'SSH', userHostPort: '', name: '', password: null, keyName: null, proxyName: null, tags: [], note: null, error: t('connections.form.scriptErrorEmptyLine', 'Input line cannot be empty') };
|
||||||
remainingLine = '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userHostPortPart) return { type: null, userHostPort: '', name: null, password: null, keyName: null, proxyName: null, tags: [], note: null, error: t('connections.form.scriptErrorMissingHost', '缺少 user@host:port 部分') };
|
// 1. Extract user@host:port
|
||||||
|
const firstSpaceIndex = line.indexOf(' ');
|
||||||
|
const userHostPortPart = firstSpaceIndex === -1 ? line : line.substring(0, firstSpaceIndex);
|
||||||
|
const optionsString = firstSpaceIndex === -1 ? '' : line.substring(firstSpaceIndex + 1).trim();
|
||||||
|
|
||||||
let type: 'SSH' | 'RDP' | 'VNC' | null = 'SSH';
|
// 2. Validate user@host:port (allow user@host without port)
|
||||||
let name: string | null = null;
|
const userHostPortRegex = /^([^@\s]+)@([^:\s]+)(?::([0-9]+))?$/;
|
||||||
|
const match = userHostPortPart.match(userHostPortRegex);
|
||||||
|
if (!match) {
|
||||||
|
return { type: 'SSH', userHostPort: userHostPortPart, name: '', password: null, keyName: null, proxyName: null, tags: [], note: null, error: t('connections.form.scriptErrorInvalidUserHostPortFormat', { part: userHostPortPart }) };
|
||||||
|
}
|
||||||
|
const [, user, host /*, portStr */] = match; // portStr not used for now
|
||||||
|
const defaultName = `${user}@${host}`; // Default name
|
||||||
|
|
||||||
|
// 3. Initialize results and defaults
|
||||||
|
let type: 'SSH' | 'RDP' | 'VNC' = 'SSH';
|
||||||
|
let name: string = defaultName;
|
||||||
let password: string | null = null;
|
let password: string | null = null;
|
||||||
let keyName: string | null = null;
|
let keyName: string | null = null;
|
||||||
let proxyName: string | null = null;
|
let proxyName: string | null = null;
|
||||||
const tags: string[] = [];
|
let tags: string[] = [];
|
||||||
let note: string | null = null;
|
let note: string | null = null;
|
||||||
let currentArg: string | null = null;
|
|
||||||
let noteParts: string[] = [];
|
|
||||||
|
|
||||||
const argRegex = /(-[^=\s]+)(?:\s+("(?:\\"|[^"])*"|[^-\s][^\s]*))?/g;
|
// 4. Parse optionsString
|
||||||
let match;
|
// Regex to split by space, respecting quotes
|
||||||
let lastIndex = 0;
|
const args = optionsString.match(/(?:[^\s"]+|"[^"]*")+/g) || [];
|
||||||
|
let i = 0;
|
||||||
|
while (i < args.length) {
|
||||||
|
const arg = args[i];
|
||||||
|
if (arg.startsWith('-')) {
|
||||||
|
const key = arg.substring(1).toLowerCase();
|
||||||
|
i++; // Move to the expected position of the value
|
||||||
|
|
||||||
while ((match = argRegex.exec(remainingLine)) !== null) {
|
if (key === 'tags') {
|
||||||
const arg = match[1];
|
// Handle -tags, which can be followed by zero or more tags
|
||||||
let value = match[2] || '';
|
tags = [];
|
||||||
|
while (i < args.length && !args[i].startsWith('-')) {
|
||||||
if (value.startsWith('"') && value.endsWith('"')) {
|
tags.push(args[i].replace(/^"|"$/g, '')); // Remove surrounding quotes
|
||||||
value = value.slice(1, -1).replace(/\\"/g, '"');
|
i++;
|
||||||
}
|
}
|
||||||
|
// No need to i++ here, the next loop iteration or outer loop handles it
|
||||||
if (noteParts.length > 0 && currentArg === '-note') {
|
} else if (key === 'note') {
|
||||||
note = noteParts.join(' ');
|
// Handle -note, which consumes the rest of the line
|
||||||
noteParts = [];
|
const noteParts = [];
|
||||||
|
while (i < args.length) {
|
||||||
|
noteParts.push(args[i]);
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
note = noteParts.join(' ').replace(/^"|"$/g, ''); // Join parts and remove quotes
|
||||||
currentArg = arg;
|
break; // Exit the outer loop as note consumes the rest
|
||||||
|
} else if (i >= args.length) {
|
||||||
if (arg === '-tags') {
|
// All other options require a value
|
||||||
// Tags handled later
|
return { type, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note, error: t('connections.form.scriptErrorMissingValueForKey', { key: arg }) };
|
||||||
} else if (arg === '-note') {
|
|
||||||
// Note handled later
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
switch (arg) {
|
|
||||||
case '-type':
|
|
||||||
const upperType = value.toUpperCase();
|
|
||||||
if (upperType === 'SSH' || upperType === 'RDP' || upperType === 'VNC') {
|
|
||||||
type = upperType as 'SSH' | 'RDP' | 'VNC';
|
|
||||||
} else {
|
} else {
|
||||||
return { type: null, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note, error: t('connections.form.scriptErrorInvalidType', { type: value }) };
|
// Handle options that require a single value
|
||||||
|
const value = args[i].replace(/^"|"$/g, ''); // Remove surrounding quotes
|
||||||
|
switch (key) {
|
||||||
|
case 'type':
|
||||||
|
const typeValue = value.toUpperCase();
|
||||||
|
if (typeValue === 'SSH' || typeValue === 'RDP' || typeValue === 'VNC') {
|
||||||
|
type = typeValue;
|
||||||
|
} else {
|
||||||
|
return { type, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note, error: t('connections.form.scriptErrorInvalidType', { value: args[i] }) };
|
||||||
}
|
}
|
||||||
currentArg = null;
|
|
||||||
break;
|
break;
|
||||||
case '-name':
|
case 'name':
|
||||||
name = value;
|
name = value;
|
||||||
currentArg = null;
|
|
||||||
break;
|
break;
|
||||||
case '-p':
|
case 'p': // password
|
||||||
password = value;
|
password = value;
|
||||||
currentArg = null;
|
|
||||||
break;
|
break;
|
||||||
case '-k':
|
case 'k': // key name
|
||||||
keyName = value;
|
keyName = value;
|
||||||
currentArg = null;
|
|
||||||
break;
|
break;
|
||||||
case '-proxy':
|
case 'proxy':
|
||||||
proxyName = value;
|
proxyName = value;
|
||||||
currentArg = null;
|
|
||||||
break;
|
|
||||||
case '-tags':
|
|
||||||
tags.push(value);
|
|
||||||
break;
|
|
||||||
case '-note':
|
|
||||||
noteParts.push(value);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (currentArg === '-note') {
|
return { type, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note, error: t('connections.form.scriptErrorUnknownOption', { option: arg }) };
|
||||||
noteParts.push(value);
|
}
|
||||||
|
i++; // Move past the value
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return { type, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note, error: t('connections.form.scriptErrorUnknownArg', { arg: currentArg }) };
|
// Arguments after user@host:port must start with '-'
|
||||||
}
|
return { type, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note, error: t('connections.form.scriptErrorUnexpectedArgument', { argument: arg }) };
|
||||||
}
|
|
||||||
}
|
|
||||||
lastIndex = argRegex.lastIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
const remainingPart = remainingLine.substring(lastIndex).trim();
|
|
||||||
if (remainingPart) {
|
|
||||||
if (currentArg === '-tags') {
|
|
||||||
const tagRegex = /("(?:\\"|[^"])*"|[^\s"]+)/g;
|
|
||||||
let tagMatch;
|
|
||||||
while ((tagMatch = tagRegex.exec(remainingPart)) !== null) {
|
|
||||||
let tag = tagMatch[1];
|
|
||||||
if (tag.startsWith('"') && tag.endsWith('"')) {
|
|
||||||
tag = tag.slice(1, -1).replace(/\\"/g, '"');
|
|
||||||
}
|
|
||||||
tags.push(tag);
|
|
||||||
}
|
|
||||||
} else if (currentArg === '-note') {
|
|
||||||
noteParts.push(remainingPart);
|
|
||||||
} else {
|
|
||||||
return { type, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note, error: t('connections.form.scriptErrorUnexpectedToken', { token: remainingPart }) };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noteParts.length > 0 && currentArg === '-note') {
|
// 5. Validation based on type
|
||||||
note = noteParts.join(' ');
|
if (type === 'SSH') {
|
||||||
|
if (!password && !keyName) {
|
||||||
|
return { type, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note, error: t('connections.form.scriptErrorMissingAuthForSsh') };
|
||||||
|
}
|
||||||
|
// Allow both password and key, handle precedence in handleScriptModeSubmit
|
||||||
|
} else if (type === 'RDP') {
|
||||||
|
if (!password) {
|
||||||
|
return { type, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note, error: t('connections.form.scriptErrorMissingPasswordForRdp') };
|
||||||
|
}
|
||||||
|
if (keyName) {
|
||||||
|
return { type, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note, error: t('connections.form.scriptErrorKeyNotApplicableForRdp') };
|
||||||
|
}
|
||||||
|
} else if (type === 'VNC') {
|
||||||
|
if (!password) {
|
||||||
|
return { type, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note, error: t('connections.form.scriptErrorMissingPasswordForVnc') };
|
||||||
|
}
|
||||||
|
if (keyName) {
|
||||||
|
return { type, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note, error: t('connections.form.scriptErrorKeyNotApplicableForVnc') };
|
||||||
}
|
}
|
||||||
|
|
||||||
const userHostPortRegex = /^[^@]+@[^:]+(:[0-9]+)?$/;
|
|
||||||
if (!userHostPortRegex.test(userHostPortPart)) {
|
|
||||||
return { type, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note, error: t('connections.form.scriptErrorInvalidUserHostPort', { part: userHostPortPart })};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { type, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note };
|
return { type, userHostPort: userHostPortPart, name, password, keyName, proxyName, tags, note };
|
||||||
@@ -421,15 +417,23 @@ export function useAddConnectionForm(props: AddConnectionFormProps, emit: AddCon
|
|||||||
if (connData.tag_names && connData.tag_names.length > 0) {
|
if (connData.tag_names && connData.tag_names.length > 0) {
|
||||||
const tagIds = [];
|
const tagIds = [];
|
||||||
for (const tagName of connData.tag_names) {
|
for (const tagName of connData.tag_names) {
|
||||||
const foundTag = tags.value.find(t_ => t_.name === tagName); // Renamed t to t_ to avoid conflict
|
let foundTag = tags.value.find(t_ => t_.name === tagName); // Renamed t to t_ to avoid conflict
|
||||||
if (foundTag) {
|
if (!foundTag) {
|
||||||
tagIds.push(foundTag.id);
|
// 自动创建不存在的标签
|
||||||
|
const newTag = await tagsStore.addTag(tagName);
|
||||||
|
if (newTag) {
|
||||||
|
foundTag = newTag;
|
||||||
|
uiNotificationsStore.showInfo(t('connections.form.scriptTagCreated', { tagName }));
|
||||||
|
// 确保标签列表已更新
|
||||||
|
await tagsStore.fetchTags();
|
||||||
} else {
|
} else {
|
||||||
uiNotificationsStore.showError(t('connections.form.scriptErrorTagNotFound', { tagName }));
|
uiNotificationsStore.showError(t('connections.form.scriptErrorTagCreationFailed', { tagName }));
|
||||||
resolutionErrorOccurred = true;
|
resolutionErrorOccurred = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tagIds.push(foundTag.id);
|
||||||
|
}
|
||||||
if (resolutionErrorOccurred) break;
|
if (resolutionErrorOccurred) break;
|
||||||
connData.tag_ids = tagIds;
|
connData.tag_ids = tagIds;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -220,6 +220,18 @@
|
|||||||
"scriptErrorTagNotFound": "Script processing error: Tag '{tagName}' not found.",
|
"scriptErrorTagNotFound": "Script processing error: Tag '{tagName}' not found.",
|
||||||
"scriptErrorSshKeyNotFound": "Script processing error: SSH Key '{keyName}' not found.",
|
"scriptErrorSshKeyNotFound": "Script processing error: SSH Key '{keyName}' not found.",
|
||||||
"scriptErrorNothingToProcess": "No valid connection data to process.",
|
"scriptErrorNothingToProcess": "No valid connection data to process.",
|
||||||
|
"scriptErrorMissingAuthForSsh": "SSH connection must provide password (-p) or key name (-k)",
|
||||||
|
"scriptErrorMissingPasswordForRdp": "RDP connection must provide password (-p)",
|
||||||
|
"scriptErrorKeyNotApplicableForRdp": "Key name (-k) is not applicable for RDP connection",
|
||||||
|
"scriptErrorMissingPasswordForVnc": "VNC connection must provide password (-p)",
|
||||||
|
"scriptErrorKeyNotApplicableForVnc": "Key name (-k) is not applicable for VNC connection",
|
||||||
|
"scriptErrorMissingValueForKey": "Missing value for parameter '{key}'",
|
||||||
|
"scriptErrorUnknownOption": "Unknown option '{option}'",
|
||||||
|
"scriptErrorUnexpectedArgument": "Unexpected argument '{argument}'",
|
||||||
|
"scriptErrorEmptyLine": "Input line cannot be empty",
|
||||||
|
"scriptErrorInvalidUserHostPortFormat": "Invalid format for '{part}', expected format is 'user@host' or 'user@host:port'",
|
||||||
|
"scriptTagCreated": "Tag '{tagName}' created",
|
||||||
|
"scriptErrorTagCreationFailed": "Failed to create tag '{tagName}'",
|
||||||
"scriptModeAddingConnections": "Adding {count} connections via script mode..."
|
"scriptModeAddingConnections": "Adding {count} connections via script mode..."
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
|
|||||||
@@ -205,6 +205,18 @@
|
|||||||
"scriptErrorTagNotFound": "スクリプト処理エラー: タグ '{tagName}' が見つかりません。",
|
"scriptErrorTagNotFound": "スクリプト処理エラー: タグ '{tagName}' が見つかりません。",
|
||||||
"scriptErrorSshKeyNotFound": "スクリプト処理エラー: SSH キー '{keyName}' が見つかりません。",
|
"scriptErrorSshKeyNotFound": "スクリプト処理エラー: SSH キー '{keyName}' が見つかりません。",
|
||||||
"scriptErrorNothingToProcess": "処理する有効な接続データがありません。",
|
"scriptErrorNothingToProcess": "処理する有効な接続データがありません。",
|
||||||
|
"scriptErrorMissingAuthForSsh": "SSH接続にはパスワード (-p) またはキー名 (-k) が必要です",
|
||||||
|
"scriptErrorMissingPasswordForRdp": "RDP接続にはパスワード (-p) が必要です",
|
||||||
|
"scriptErrorKeyNotApplicableForRdp": "キー名 (-k) はRDP接続には適用されません",
|
||||||
|
"scriptErrorMissingPasswordForVnc": "VNC接続にはパスワード (-p) が必要です",
|
||||||
|
"scriptErrorKeyNotApplicableForVnc": "キー名 (-k) はVNC接続には適用されません",
|
||||||
|
"scriptErrorMissingValueForKey": "パラメータ '{key}' に対応する値がありません",
|
||||||
|
"scriptErrorUnknownOption": "不明なオプション '{option}'",
|
||||||
|
"scriptErrorUnexpectedArgument": "予期しない引数 '{argument}'",
|
||||||
|
"scriptErrorEmptyLine": "入力行は空にできません",
|
||||||
|
"scriptErrorInvalidUserHostPortFormat": "'{part}' の形式が無効です、期待される形式は 'user@host' または 'user@host:port' です",
|
||||||
|
"scriptTagCreated": "タグ '{tagName}' が作成されました",
|
||||||
|
"scriptErrorTagCreationFailed": "タグ '{tagName}' の作成に失敗しました",
|
||||||
"scriptModeAddingConnections": "スクリプトモードで {count} 個の接続を追加しています..."
|
"scriptModeAddingConnections": "スクリプトモードで {count} 個の接続を追加しています..."
|
||||||
},
|
},
|
||||||
"noConnections": "接続がありません。'新しい接続を追加'をクリックして作成してください。",
|
"noConnections": "接続がありません。'新しい接続を追加'をクリックして作成してください。",
|
||||||
|
|||||||
@@ -219,6 +219,18 @@
|
|||||||
"scriptErrorTagNotFound": "脚本处理错误:未找到标签 '{tagName}'。",
|
"scriptErrorTagNotFound": "脚本处理错误:未找到标签 '{tagName}'。",
|
||||||
"scriptErrorSshKeyNotFound": "脚本处理错误:未找到 SSH 密钥 '{keyName}'。",
|
"scriptErrorSshKeyNotFound": "脚本处理错误:未找到 SSH 密钥 '{keyName}'。",
|
||||||
"scriptErrorNothingToProcess": "没有可处理的有效连接数据。",
|
"scriptErrorNothingToProcess": "没有可处理的有效连接数据。",
|
||||||
|
"scriptErrorMissingAuthForSsh": "SSH连接必须提供密码 (-p) 或密钥名称 (-k)",
|
||||||
|
"scriptErrorMissingPasswordForRdp": "RDP连接必须提供密码 (-p)",
|
||||||
|
"scriptErrorKeyNotApplicableForRdp": "密钥名称 (-k) 不适用于 RDP 连接",
|
||||||
|
"scriptErrorMissingPasswordForVnc": "VNC连接必须提供密码 (-p)",
|
||||||
|
"scriptErrorKeyNotApplicableForVnc": "密钥名称 (-k) 不适用于 VNC 连接",
|
||||||
|
"scriptErrorMissingValueForKey": "参数 '{key}' 缺少对应的值",
|
||||||
|
"scriptErrorUnknownOption": "未知选项 '{option}'",
|
||||||
|
"scriptErrorUnexpectedArgument": "意外参数 '{argument}'",
|
||||||
|
"scriptErrorEmptyLine": "输入行不能为空",
|
||||||
|
"scriptErrorInvalidUserHostPortFormat": "'{part}' 部分格式无效,期望格式为 'user@host' 或 'user@host:port'",
|
||||||
|
"scriptTagCreated": "标签 '{tagName}' 已创建",
|
||||||
|
"scriptErrorTagCreationFailed": "创建标签 '{tagName}' 失败",
|
||||||
"scriptModeAddingConnections": "正在通过脚本模式添加 {count} 个连接..."
|
"scriptModeAddingConnections": "正在通过脚本模式添加 {count} 个连接..."
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
|
|||||||
Reference in New Issue
Block a user