Skip to content

Commit d962348

Browse files
committed
feat: SSH key management - auto-generate, file upload, remove button
1 parent 6deb40c commit d962348

3 files changed

Lines changed: 45 additions & 11 deletions

File tree

src/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@
151151
"sshPrivateKeySet": "(set)",
152152
"sshPrivateKeyHint": "Paste your private key (OpenSSH or PEM format). Leave empty to keep current.",
153153
"sshPrivateKeyEncrypted": "Private key is stored encrypted",
154-
"sshPrivateKeyClear": "Clear key",
154+
"sshPrivateKeyClear": "Remove key",
155+
"sshPrivateKeyUpload": "Upload file",
155156
"generateSshKey": "Generate & Install SSH Key",
156157
"generateSshKeyHint": "Generates an ed25519 keypair and installs the public key on the server via SSH (requires password or existing key).",
157158
"generateSshKeyWarning": "Generate and install a new SSH key on this node?\n\nThis will:\n• Generate a new ed25519 keypair\n• Add the public key to ~/.ssh/authorized_keys\n• Save the private key to the panel (encrypted)\n\nThe existing password will remain unchanged.",

src/locales/ru.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
"sshPrivateKeyHint": "Вставьте приватный ключ (формат OpenSSH или PEM). Оставьте пустым чтобы не менять.",
153153
"sshPrivateKeyEncrypted": "Приватный ключ хранится в зашифрованном виде",
154154
"sshPrivateKeyClear": "Удалить ключ",
155+
"sshPrivateKeyUpload": "Загрузить файл",
155156
"generateSshKey": "Сгенерировать и установить SSH ключ",
156157
"generateSshKeyHint": "Генерирует пару ed25519 ключей и устанавливает публичный ключ на сервер через SSH (требуется пароль или уже установленный ключ).",
157158
"generateSshKeyWarning": "Сгенерировать и установить новый SSH ключ на эту ноду?\n\nЭто:\n• Сгенерирует новую пару ed25519 ключей\n• Добавит публичный ключ в ~/.ssh/authorized_keys\n• Сохранит приватный ключ в панели (зашифрованно)\n\nСуществующий пароль останется без изменений.",

views/node-form.ejs

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -342,21 +342,27 @@
342342
<label for="sshPrivateKey">
343343
<%= t('nodes.sshPrivateKey') %>
344344
<% if (node?.ssh?.privateKey) { %>
345-
<span class="badge badge-success"><%= t('nodes.sshPrivateKeySet') %></span>
345+
<span class="badge badge-success" id="sshPrivateKeyBadge"><%= t('nodes.sshPrivateKeySet') %></span>
346+
<% } else { %>
347+
<span class="badge badge-success" id="sshPrivateKeyBadge" style="display:none"><%= t('nodes.sshPrivateKeySet') %></span>
346348
<% } %>
347349
</label>
348350
<textarea id="sshPrivateKey" name="ssh.privateKey" rows="5"
349351
placeholder="<%= t('nodes.sshPrivateKeyHint') %>"
350352
style="font-family: monospace; font-size: 12px; resize: vertical;"></textarea>
351-
<small class="hint"><%= t('nodes.sshPrivateKeyEncrypted') %></small>
352-
<% if (node?.ssh?.privateKey) { %>
353-
<div style="margin-top: 6px;">
354-
<label style="display: inline-flex; align-items: center; gap: 6px; font-size: 13px; cursor: pointer;">
355-
<input type="checkbox" id="clearPrivateKey" name="ssh.clearPrivateKey" value="1">
356-
<%= t('nodes.sshPrivateKeyClear') %>
357-
</label>
358-
</div>
359-
<% } %>
353+
<input type="hidden" name="ssh.clearPrivateKey" id="clearPrivateKeyInput" value="0">
354+
<div style="display: flex; align-items: center; gap: 8px; margin-top: 6px; flex-wrap: wrap;">
355+
<label class="btn btn-sm btn-secondary" for="sshPrivateKeyFile" style="cursor:pointer; margin:0;">
356+
<%= t('nodes.sshPrivateKeyUpload') %>
357+
<input type="file" id="sshPrivateKeyFile" accept=".pem,.key,*" style="display:none">
358+
</label>
359+
<% if (node?.ssh?.privateKey) { %>
360+
<button type="button" id="clearPrivateKeyBtn" class="btn btn-sm btn-danger">
361+
<%= t('nodes.sshPrivateKeyClear') %>
362+
</button>
363+
<% } %>
364+
<small class="hint" style="margin:0"><%= t('nodes.sshPrivateKeyEncrypted') %></small>
365+
</div>
360366
</div>
361367
<% if (node?._id) { %>
362368
<div class="form-group" id="generateSshKeyGroup">
@@ -1039,6 +1045,32 @@ async function generateXrayKeys() {
10391045
}
10401046
})();
10411047
1048+
// SSH private key file upload
1049+
document.getElementById('sshPrivateKeyFile')?.addEventListener('change', function() {
1050+
const file = this.files[0];
1051+
if (!file) return;
1052+
const reader = new FileReader();
1053+
reader.onload = (e) => {
1054+
document.getElementById('sshPrivateKey').value = e.target.result;
1055+
document.getElementById('sshPrivateKeyBadge').style.display = '';
1056+
};
1057+
reader.readAsText(file);
1058+
this.value = '';
1059+
});
1060+
1061+
// Clear private key button
1062+
document.getElementById('clearPrivateKeyBtn')?.addEventListener('click', function() {
1063+
document.getElementById('sshPrivateKey').value = '';
1064+
document.getElementById('clearPrivateKeyInput').value = '1';
1065+
document.getElementById('sshPrivateKeyBadge').style.display = 'none';
1066+
this.style.display = 'none';
1067+
});
1068+
1069+
// Show badge when user types in textarea
1070+
document.getElementById('sshPrivateKey')?.addEventListener('input', function() {
1071+
document.getElementById('sshPrivateKeyBadge').style.display = this.value.trim() ? '' : 'none';
1072+
});
1073+
10421074
<% if (node && node._id) { %>
10431075
// Generate & Install SSH Key
10441076
document.getElementById('generateSshKeyBtn')?.addEventListener('click', async function() {

0 commit comments

Comments
 (0)