Skip to content

Enhance SSH dialog user experience #4260

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 15 additions & 72 deletions packages/playground/src/components/node_details.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,8 @@
>
<v-toolbar color="primary">
<div class="d-flex justify-center">
<v-btn
icon
dark
@click="() => $emit('close-dialog', false)"
>
<v-icon color="anchor">
mdi-close
</v-icon>
<v-btn icon dark @click="() => $emit('close-dialog', false)">
<v-icon color="anchor"> mdi-close </v-icon>
</v-btn>
</div>
</v-toolbar>
Expand All @@ -33,87 +27,37 @@
<template v-else-if="isError">
<v-card class="d-flex justify-center align-center h-screen">
<div class="text-center w-100 pa-3">
<v-icon
variant="tonal"
color="error"
style="font-size: 50px"
icon="mdi-close-circle-outline"
/>
<v-icon variant="tonal" color="error" style="font-size: 50px" icon="mdi-close-circle-outline" />
<p class="mt-4 mb-4 font-weight-bold text-error">
{{ errorMessage }}
</p>
<v-btn
class="mr-4"
text="Try Again"
@click="requestNode"
/>
<v-btn
color="error"
text="Cancel"
@click="(val: boolean) => closeDialog(val)"
/>
<v-btn class="mr-4" text="Try Again" @click="requestNode" />
<v-btn color="error" text="Cancel" @click="(val: boolean) => closeDialog(val)" />
</div>
</v-card>
</template>

<template v-else>
<v-card>
<node-resources-charts
:node="node"
:is-live-stats="isLiveStats"
:hint-message="errorLoadingStatsMessage"
/>
<v-row
class="pa-8 mt-5"
justify-md="start"
justify-sm="center"
>
<v-col
cols="12"
md="6"
sm="12"
>
<node-resources-charts :node="node" :is-live-stats="isLiveStats" :hint-message="errorLoadingStatsMessage" />
<v-row class="pa-8 mt-5" justify-md="start" justify-sm="center">
<v-col cols="12" md="6" sm="12">
<node-details-card :node="node" />
<farm-details-card
class="mt-5"
:node="node"
/>
<interfaces-details-card
class="mt-5"
:node="node"
/>
<farm-details-card class="mt-5" :node="node" />
<interfaces-details-card class="mt-5" :node="node" />
<public-config-details-card
v-if="node.publicConfig && node.publicConfig.domain"
class="mt-5"
:node="node"
/>

<cpu-benchmark-card
v-if="hasActiveProfile && node.healthy"
class="mt-5"
:node="node"
/>
<cpu-benchmark-card v-if="hasActiveProfile && node.healthy" class="mt-5" :node="node" />
</v-col>
<v-col
cols="12"
md="6"
sm="12"
>
<v-col cols="12" md="6" sm="12">
<country-details-card :node="node" />
<twin-details-card
class="mt-3"
:node="node"
/>
<gpu-details-card
v-if="node.gpus?.length"
class="mt-3"
:node="node"
/>
<i-perf-card
v-if="hasActiveProfile && node.healthy"
class="mt-3"
:node="node"
/>
<twin-details-card class="mt-3" :node="node" />
<gpu-details-card v-if="node.gpus?.length" class="mt-3" :node="node" />
<i-perf-card v-if="hasActiveProfile && node.healthy" class="mt-3" :node="node" />
</v-col>
</v-row>
</v-card>
Expand Down Expand Up @@ -143,7 +87,6 @@ import { getNode, getNodeStatusColor } from "@/utils/get_nodes";
import IPerfCard from "./node_details_cards/iperf_details_card.vue";
import NodeResourcesCharts from "./node_resources_charts.vue";
export default {

components: {
NodeResourcesCharts,
NodeDetailsCard,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,12 +336,12 @@ async function storeAndLogin() {
const grid = await getGrid({ mnemonic: mnemonic.value, keypairType: keypairType.value });
storeEmail(grid!, email.value);
setCredentials(md5(password.value), mnemonicHash, keypairTypeHash, md5(email.value));
await handlePostLogin(grid!, password.value, email.value);
const profile = await loadProfile(grid!);
if (email.value && profile.email !== email.value) {
profile.email = email.value;
}
profileManager.set({ ...profile, mnemonic: mnemonic.value });
await handlePostLogin(grid!, password.value, email.value);
} catch (e) {
if (e instanceof TwinNotExistError) {
isNonActiveMnemonic.value = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ async function login() {
: KeypairType.sr25519;

const grid = await getGrid({ mnemonic: mnemonic, keypairType: keypairType as KeypairType });
await handlePostLogin(grid!, password.value);
profileManager.set({ ...(await loadProfile(grid!)), mnemonic });
await handlePostLogin(grid!, password.value);
emit("closeDialog");
}
} else {
Expand Down
24 changes: 14 additions & 10 deletions packages/playground/src/components/ssh_keys/SshDataDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
<template #default>
<v-card>
<v-toolbar color="primary" class="custom-toolbar">
<p class="mb-5">
SSH-Key Details
</p>
<p class="mb-5">SSH-Key Details</p>
</v-toolbar>
<v-card-text>
<template v-for="[_key, value] of Object.entries(selectedKey).sort()" :key="_key">
Expand All @@ -22,7 +20,10 @@
v-model="currentKey[_key as keyof SSHKeyData]"
:label="_key"
:readonly="_key === 'fingerPrint'"
:rules="[(value: string) => !!value || `${_key} is required.`, _key === 'name' ? validateName(currentKey.name): true]"
:rules="[
(value: string) => !!value || `${_key} is required.`,
_key === 'name' ? validateName(currentKey.name) : true,
]"
/>
</CopyInputWrapper>
<CopyInputWrapper v-else :data="value" #="{ props: copyInputProps }">
Expand All @@ -41,12 +42,8 @@

<v-tooltip text="Key status">
<template #activator="{ props }">
<v-chip v-if="selectedKey.isActive" v-bind="props">
Active
</v-chip>
<v-chip v-else v-bind="props" color="anchor">
Inactive
</v-chip>
<v-chip v-if="selectedKey.isActive" v-bind="props"> Active </v-chip>
<v-chip v-else v-bind="props" color="anchor"> Inactive </v-chip>
</template>
</v-tooltip>

Expand Down Expand Up @@ -74,6 +71,7 @@ import { capitalize, defineComponent, type PropType, ref, watch } from "vue";

import type { SSHKeyData } from "@/types";
import SSHKeysManagement from "@/utils/ssh";
import { isAlphanumericWithSpace } from "@/utils/validators";

export default defineComponent({
name: "SSHDataDialog",
Expand Down Expand Up @@ -133,6 +131,12 @@ export default defineComponent({
if (name === props.selectedKey.name) {
return true;
}
if (isAlphanumericWithSpace("Invalid name")(name) !== true) {
return "Key name must only contain letters, numbers, and spaces within the name.";
}
if (name.length > 30) {
return "Please enter a key name with fewer than 30 characters.";
}
const found = props.allKeys.find(key => key.name === name);
return found ? "You have another key with the same name." : true;
}
Expand Down
26 changes: 18 additions & 8 deletions packages/playground/src/components/ssh_keys/SshFormDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@
v-model="$props.open"
max-width="800"
attach="#modals"
@click:outside="() => $emit('close')"
@keydown.esc="() => $emit('close')"
@click:outside="
() => {
if (!generating && !savingKey) $emit('close');
}
"
@keydown.esc="
() => {
if (!generating && !savingKey) $emit('close');
}
"
>
<template #default>
<v-form v-model="isValidForm">
Expand All @@ -23,7 +31,7 @@
>
<v-text-field
v-model="keyName"
hint="Leave this field empty to generate a name automatically, or enter a custom name to save it with your key."
hint="Enter a unique name (letters, numbers, and spaces only, less than 30 characters) to identify your SSH key."
class="mb-4"
hide-details="auto"
label="Name"
Expand Down Expand Up @@ -68,7 +76,7 @@
</v-card-text>

<v-card-actions class="justify-end mb-1 mr-2">
<v-btn color="anchor" text="Close" @click="$emit('close')" />
<v-btn color="anchor" :disabled="generating || savingKey" text="Close" @click="$emit('close')" />

<v-btn
v-if="$props.dialogType === SSHCreationMethod.Generate"
Expand Down Expand Up @@ -103,6 +111,7 @@ import { type Profile, useProfileManager } from "@/stores/profile_manager";
import { SSHCreationMethod, type SSHKeyData } from "@/types";
import { type Balance, loadBalance } from "@/utils/grid";
import SSHKeysManagement from "@/utils/ssh";
import { isAlphanumericWithSpace } from "@/utils/validators";

const props = defineProps({
open: {
Expand Down Expand Up @@ -156,7 +165,7 @@ function generateSSHKey() {
id: keyId,
publicKey: "",
createdAt: sshKeysManagement.formatDate(now),
name: keyName.value,
name: keyName.value.trimEnd(),
isActive: true,
};

Expand All @@ -171,7 +180,7 @@ function createNewSSHKey() {
id: keyId,
publicKey: sshKey.value,
createdAt: sshKeysManagement.formatDate(now),
name: keyName.value,
name: keyName.value.trimEnd(),
isActive: true,
};

Expand All @@ -185,7 +194,7 @@ function createNewSSHKey() {
}
}

createdKey.value.name = keyName.value;
createdKey.value.name = keyName.value.trimEnd();

emits("save", createdKey.value);
}
Expand Down Expand Up @@ -256,8 +265,9 @@ function sshRules(value: any) {

function sshNameRules(value: any) {
return [
(v: string) => !!v || "Key name is required.",
isAlphanumericWithSpace("Key name must only contain letters, numbers, and spaces within the name."),
(v: string) => v.length < 30 || "Please enter a key name with fewer than 30 characters.",
(v: string) => !v.includes(" ") || "Key names cannot include spaces. Please use a name without spaces.",
(v: string) => sshKeysManagement.availableName(v) || "You have another key with the same name.",
];
}
Expand Down
10 changes: 9 additions & 1 deletion packages/playground/src/utils/profile_manager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { GridClient } from "@threefold/grid_client";

import router from "@/router";
import type { SSHKeyData } from "@/types";

import { createCustomToast, ToastType } from "./custom_toast";
import { readEmail } from "./grid";
Expand Down Expand Up @@ -33,8 +34,15 @@ export async function handlePostLogin(grid: GridClient, password: string, email?

// Migrate the ssh-key
const sshKeysManagement = new SSHKeysManagement();
let newKeys: SSHKeyData[] = [];
if (!sshKeysManagement.migrated()) {
const newKeys = sshKeysManagement.migrate();
newKeys = sshKeysManagement.migrate();
}
if (sshKeysManagement.needsDefaultNameAssignment()) {
newKeys = sshKeysManagement.assignDefaultNames();
}
if (newKeys.length > 0) {
await sshKeysManagement.update(newKeys);
createCustomToast("SSH keys have been recovered successfully.", ToastType.success);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The keys will be renamed, this is to make the user aware of what happened to the keys

}
}
Loading