diff --git a/ui/src/components/PrivateKeys/PrivateKeySelectWithAdd.vue b/ui/src/components/PrivateKeys/PrivateKeySelectWithAdd.vue
new file mode 100644
index 00000000000..322ce621fb8
--- /dev/null
+++ b/ui/src/components/PrivateKeys/PrivateKeySelectWithAdd.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+ Add New Private Key
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/components/Terminal/TerminalLoginForm.vue b/ui/src/components/Terminal/TerminalLoginForm.vue
index 86ee6ec54e9..a3e22cd7926 100644
--- a/ui/src/components/Terminal/TerminalLoginForm.vue
+++ b/ui/src/components/Terminal/TerminalLoginForm.vue
@@ -40,17 +40,10 @@
@update:model-value="togglePassphraseField"
/>
-
item.name);
const showPassphraseField = ref(false);
const showTerminalHelper = ref(false);
diff --git a/ui/tests/components/PrivateKeys/PrivateKeySelectWithAdd.spec.ts b/ui/tests/components/PrivateKeys/PrivateKeySelectWithAdd.spec.ts
new file mode 100644
index 00000000000..ac2773cd74c
--- /dev/null
+++ b/ui/tests/components/PrivateKeys/PrivateKeySelectWithAdd.spec.ts
@@ -0,0 +1,83 @@
+import { setActivePinia, createPinia } from "pinia";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { createVuetify } from "vuetify";
+import { flushPromises, mount, VueWrapper } from "@vue/test-utils";
+import PrivateKeySelectWithAdd from "@/components/PrivateKeys/PrivateKeySelectWithAdd.vue";
+import { SnackbarPlugin } from "@/plugins/snackbar";
+import usePrivateKeysStore from "@/store/modules/private_keys";
+import { nextTick } from "vue";
+
+type PrivateKeySelectWithAddWrapper = VueWrapper>;
+
+const mockPrivateKeys = [
+ { id: 1, name: "test-key-1", data: "private-key-data-1", hasPassphrase: true, fingerprint: "fingerprint-1" },
+ { id: 2, name: "test-key-2", data: "private-key-data-2", hasPassphrase: false, fingerprint: "fingerprint-2" },
+ { id: 3, name: "test-key-3", data: "private-key-data-3", hasPassphrase: false, fingerprint: "fingerprint-3" },
+];
+
+describe("Private Key Select With Add", () => {
+ let wrapper: PrivateKeySelectWithAddWrapper;
+ setActivePinia(createPinia());
+ const privateKeysStore = usePrivateKeysStore();
+ const vuetify = createVuetify();
+
+ beforeEach(() => {
+ privateKeysStore.privateKeys = mockPrivateKeys;
+
+ wrapper = mount(PrivateKeySelectWithAdd, {
+ global: {
+ plugins: [vuetify, SnackbarPlugin],
+ stubs: {
+ "v-file-upload": true,
+ "v-file-upload-item": true,
+ },
+ },
+ props: { modelValue: "test-key-1" },
+ });
+ });
+
+ it("Renders the private key select", () => {
+ const select = wrapper.find('[data-test="private-keys-select"]');
+ expect(select.exists()).toBe(true);
+ });
+
+ it("Displays all private keys in the select", () => {
+ const select = wrapper.findComponent({ name: "VSelect" });
+ expect(select.props("items")).toEqual(["test-key-1", "test-key-2", "test-key-3"]);
+ });
+
+ it("Auto-selects newly added key and emits key-added event", async () => {
+ const newKey = { id: 4, name: "new-test-key", data: "new-key-data", hasPassphrase: false, fingerprint: "new-fingerprint" };
+
+ const getPrivateKeyListSpy = vi.spyOn(privateKeysStore, "getPrivateKeyList").mockImplementation(() => {
+ privateKeysStore.privateKeys = [...mockPrivateKeys, newKey];
+ });
+
+ const privateKeyAdd = wrapper.findComponent({ name: "PrivateKeyAdd" });
+ await privateKeyAdd.vm.$emit("update");
+ await nextTick();
+ await flushPromises();
+
+ expect(getPrivateKeyListSpy).toHaveBeenCalled();
+ expect(wrapper.emitted("key-added")).toBeTruthy();
+ expect(wrapper.vm.selectedPrivateKeyName).toBe("new-test-key");
+ });
+
+ it("Handles empty private keys list", async () => {
+ privateKeysStore.privateKeys = [];
+
+ await nextTick();
+
+ const select = wrapper.findComponent({ name: "VSelect" });
+ expect(select.props("items")).toEqual([]);
+ });
+
+ it("Updates model value when selecting a key", async () => {
+ const select = wrapper.findComponent({ name: "VSelect" });
+ await select.setValue("test-key-2");
+ await flushPromises();
+
+ expect(wrapper.emitted("update:modelValue")).toBeTruthy();
+ expect(wrapper.emitted("update:modelValue")?.[0]).toEqual(["test-key-2"]);
+ });
+});
diff --git a/ui/tests/components/Terminal/TerminalLoginForm.spec.ts b/ui/tests/components/Terminal/TerminalLoginForm.spec.ts
index 5a9063d7703..6c448558f60 100644
--- a/ui/tests/components/Terminal/TerminalLoginForm.spec.ts
+++ b/ui/tests/components/Terminal/TerminalLoginForm.spec.ts
@@ -7,6 +7,7 @@ import TerminalLoginForm from "@/components/Terminal/TerminalLoginForm.vue";
import { IPrivateKey } from "@/interfaces/IPrivateKey";
import { TerminalAuthMethods } from "@/interfaces/ITerminal";
import usePrivateKeysStore from "@/store/modules/private_keys";
+import { SnackbarPlugin } from "@/plugins/snackbar";
const mockPrivateKeys: Array = [
{ id: 1, name: "test-key-1", data: "private-key-data-1", hasPassphrase: true, fingerprint: "fingerprint-1" },
@@ -25,7 +26,7 @@ describe("Terminal Login Form", () => {
wrapper = mount(TerminalLoginForm, {
global: {
- plugins: [vuetify],
+ plugins: [vuetify, SnackbarPlugin],
},
props: {
modelValue: true,