Skip to content

Commit fe754f4

Browse files
committed
refactor(ssh): removing old api calls (#504)
1 parent c7fe851 commit fe754f4

File tree

2 files changed

+115
-162
lines changed

2 files changed

+115
-162
lines changed

Diff for: src/components/standalone/ssh/SshKeys.vue

+109-160
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,47 @@
44
-->
55

66
<script lang="ts" setup>
7-
import type { Ref } from 'vue'
87
import { onMounted, ref } from 'vue'
9-
import { ubusCall } from '@/lib/standalone/ubus'
10-
import { AxiosError } from 'axios'
8+
import { ubusCall, ValidationError } from '@/lib/standalone/ubus'
9+
import { AxiosError, type AxiosResponse } from 'axios'
1110
import {
12-
NeInlineNotification,
11+
focusElement,
12+
getAxiosErrorMessage,
1313
NeButton,
14+
NeInlineNotification,
15+
NeModal,
1416
NeSkeleton,
15-
NeTextInput,
16-
focusElement,
17-
getAxiosErrorMessage
17+
NeTextInput
1818
} from '@nethesis/vue-components'
19-
import { NeModal } from '@nethesis/vue-components'
2019
import { useI18n } from 'vue-i18n'
2120
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
2221
import FormLayout from '@/components/standalone/FormLayout.vue'
23-
import { MessageBag, ValidationError } from '@/lib/validation'
24-
import { SshKey } from '@/lib/standalone/sshKey'
22+
import { MessageBag } from '@/lib/validation'
23+
import { faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'
24+
import { useNotificationsStore } from '@/stores/notifications'
2525

26-
type SshKeyError = {
27-
code: number
28-
data: string
29-
message: string
26+
type SshKey = {
27+
type: string
28+
key: string
29+
comment: string
3030
}
3131

32-
type SshKeysResponse = {
33-
data: {
34-
keys: string
35-
}
36-
}
32+
type SshKeysResponse = AxiosResponse<{
33+
keys: SshKey[]
34+
}>
3735

36+
const notifications = useNotificationsStore()
3837
const { t } = useI18n()
3938
const uploadSshKey = ref('')
40-
const sshKeys = ref(new Array<SshKey>())
39+
const sshKeys = ref<SshKey[]>([])
4140

4241
const loading = ref(true)
4342
const submitting = ref(false)
4443
const deleting = ref(false)
4544
const validationErrors = ref(new MessageBag())
46-
const error: Ref<Error | undefined> = ref(undefined)
45+
const error = ref<Error>()
4746

48-
const keyToDelete: Ref<SshKey | undefined> = ref(undefined)
47+
const keyToDelete = ref<SshKey>()
4948

5049
onMounted(() => {
5150
load()
@@ -56,121 +55,63 @@ function load() {
5655
loading.value = true
5756
ubusCall('ns.ssh', 'list-keys')
5857
.then((response: SshKeysResponse) => {
59-
sshKeys.value = new Array<SshKey>()
60-
// the response will be a string with all the keys separated by a newline
61-
response.data.keys.split('\n').forEach((line) => {
62-
// final end line, skipping
63-
if (line.length == 0) {
64-
return
65-
}
66-
// If the key retrieved is invalid there's nothing we can do, so we just ignore it
67-
try {
68-
sshKeys.value.push(new SshKey(line))
69-
} catch (exception: any) {
70-
console.error("There's an issue with the SSH key: " + exception.message)
71-
}
72-
})
58+
sshKeys.value = response.data.keys
7359
})
74-
.catch((exception: AxiosError<SshKeyError>) => {
75-
// if file is empty or not present, the response is exit status 4
76-
if (exception.response?.data.data != 'exit status 4') {
77-
// otherwise it's a real axios error
78-
if (error.value instanceof AxiosError) {
79-
error.value = new Error(getAxiosErrorMessage(exception))
80-
} else {
81-
error.value = exception
82-
}
83-
}
60+
.catch((reason: AxiosError) => {
61+
error.value = reason
8462
})
8563
.finally(() => {
8664
loading.value = false
8765
})
8866
}
8967

9068
function addKey() {
91-
validate()
92-
if (validationErrors.value.size == 0) {
93-
const keys = sshKeys.value
94-
.map((key) => {
95-
let string = key.type + ' ' + key.key
96-
if (key.comment != undefined) {
97-
string = string + ' ' + key.comment
98-
}
99-
return string
100-
})
101-
.join('\n')
102-
.concat('\n')
103-
.concat(uploadSshKey.value)
104-
.concat('\n')
105-
submitting.value = true
106-
writeKeys(keys)
107-
.then(() => {
108-
uploadSshKey.value = ''
109-
})
110-
.finally(() => {
111-
submitting.value = false
112-
})
113-
} else {
114-
focusElement('uploadSshKeyInput')
115-
error.value = new ValidationError()
116-
}
117-
}
118-
119-
function validate() {
120-
validationErrors.value = new MessageBag()
121-
try {
122-
const parsedSshKey = new SshKey(uploadSshKey.value)
123-
if (sshKeys.value.some((key) => key.key == parsedSshKey.key)) {
124-
validationErrors.value.set('uploadSshKey', [
125-
t('standalone.ssh.ssh_keys.validation.duplicate')
126-
])
127-
}
128-
} catch (exception: any) {
129-
validationErrors.value.set('uploadSshKey', [t('standalone.ssh.ssh_keys.validation.invalid')])
130-
}
131-
}
132-
133-
/**
134-
* Method that sends the keys to the server
135-
* @param mappedKeys string containing all the keys separated by a newline
136-
*/
137-
function writeKeys(mappedKeys: string): Promise<any> {
138-
error.value = undefined
139-
return ubusCall('file', 'write', {
140-
path: '/etc/dropbear/authorized_keys',
141-
mode: 384, // 0600
142-
data: mappedKeys
69+
submitting.value = true
70+
ubusCall('ns.ssh', 'add-key', {
71+
key: uploadSshKey.value
14372
})
14473
.then(() => {
74+
uploadSshKey.value = ''
14575
load()
76+
notifications.addNotification({
77+
kind: 'success',
78+
id: 'added-ssh-key',
79+
title: t('standalone.ssh.ssh_keys.key_added_notification')
80+
})
81+
})
82+
.catch((reason: Error) => {
83+
if (reason instanceof ValidationError) {
84+
validationErrors.value = reason.errorBag
85+
focusElement('uploadSshKeyInput')
86+
} else {
87+
error.value = reason
88+
}
14689
})
147-
.catch((exception: AxiosError) => {
148-
error.value = new Error(getAxiosErrorMessage(exception))
90+
.finally(() => {
91+
submitting.value = false
14992
})
15093
}
15194

15295
function deleteKey() {
153-
if (keyToDelete.value == undefined) {
154-
return
155-
}
156-
// mapping the keys to a string, excluding the selected one
157-
const mappedKeys = sshKeys.value
158-
.filter((key) => key.key != keyToDelete.value?.key)
159-
.map((key) => {
160-
let string = key.type + ' ' + key.key
161-
if (key.comment != undefined) {
162-
string = string + ' ' + key.comment
163-
}
164-
return string
165-
})
166-
.join('\n')
167-
.concat('\n')
168-
16996
deleting.value = true
170-
writeKeys(mappedKeys).finally(() => {
171-
keyToDelete.value = undefined
172-
deleting.value = false
97+
ubusCall('ns.ssh', 'delete-key', {
98+
key: keyToDelete.value?.key
17399
})
100+
.then(() => {
101+
keyToDelete.value = undefined
102+
load()
103+
notifications.addNotification({
104+
kind: 'success',
105+
id: 'deleted-ssh-key',
106+
title: t('standalone.ssh.ssh_keys.key_deleted_notification')
107+
})
108+
})
109+
.catch((reason) => {
110+
error.value = reason
111+
})
112+
.finally(() => {
113+
deleting.value = false
114+
})
174115
}
175116
</script>
176117

@@ -192,56 +133,64 @@ function deleteKey() {
192133
</code>
193134
</NeModal>
194135
<NeInlineNotification
195-
v-if="error != undefined && !(error instanceof ValidationError)"
196-
:title="t(error.message)"
136+
v-if="error != undefined"
137+
:description="t(getAxiosErrorMessage(error))"
197138
kind="error"
139+
:title="t('error.generic_error')"
198140
/>
199141
<NeSkeleton v-if="loading" :lines="10" />
200142
<FormLayout
201143
v-else
202144
:description="t('standalone.ssh.ssh_keys.description')"
203145
:title="t('standalone.ssh.ssh_keys.title')"
204146
>
205-
<div class="mb-4">
206-
<!-- Key Element -->
207-
<div v-for="key in sshKeys" :key="key.key" class="mb-2 flex gap-x-2 last:mb-0">
208-
<div class="w-10/12 rounded border border-gray-200 p-3 text-xs dark:border-gray-700">
209-
<p class="mb-1 font-bold">
210-
{{ key.comment ?? t('standalone.ssh.ssh_keys.unnamed_key') }}
211-
</p>
212-
<p class="mb-1">{{ key.type }}</p>
213-
<code class="truncate">
214-
{{ key.key }}
215-
</code>
216-
</div>
217-
<div class="grid w-2/12 place-content-center">
218-
<NeButton kind="tertiary" size="lg" @click.prevent="keyToDelete = key">
219-
<font-awesome-icon :icon="['fas', 'trash']" aria-hidden="true" class="h-4 w-4" />
147+
<div class="space-y-4">
148+
<ul class="space-y-2">
149+
<li v-for="key in sshKeys" :key="key.key" class="flex items-center gap-2">
150+
<div
151+
class="min-w-0 flex-grow rounded border border-gray-200 p-3 text-xs dark:border-gray-700"
152+
>
153+
<p class="mb-1 font-bold">
154+
<template v-if="key.comment != ''">
155+
{{ key.comment }}
156+
</template>
157+
<template v-else>
158+
{{ t('standalone.ssh.ssh_keys.unnamed_key') }}
159+
</template>
160+
</p>
161+
<p class="mb-1">{{ key.type }}</p>
162+
<code class="truncate">
163+
{{ key.key }}
164+
</code>
165+
</div>
166+
<NeButton kind="tertiary" size="lg" @click="keyToDelete = key">
167+
<FontAwesomeIcon :icon="faTrash" class="h-4 w-4" />
220168
</NeButton>
221-
</div>
222-
</div>
169+
</li>
170+
</ul>
171+
<!-- Add Key form -->
172+
<form class="flex flex-col gap-y-4" @submit.prevent="addKey()">
173+
<NeTextInput
174+
ref="uploadSshKeyInput"
175+
v-model="uploadSshKey"
176+
:disabled="submitting"
177+
:invalid-message="t(validationErrors.getFirstI18nKeyFor('key'))"
178+
:label="t('standalone.ssh.ssh_keys.add_new_ssh_key.label')"
179+
:placeholder="t('standalone.ssh.ssh_keys.add_new_ssh_key.placeholder')"
180+
/>
181+
<NeButton
182+
:disabled="submitting"
183+
:loading="submitting"
184+
class="self-start"
185+
size="lg"
186+
type="submit"
187+
>
188+
<template #prefix>
189+
<FontAwesomeIcon :icon="faPlus" aria-hidden="true" />
190+
</template>
191+
{{ t('standalone.ssh.ssh_keys.add_key_button') }}
192+
</NeButton>
193+
</form>
223194
</div>
224-
<!-- Add Key form -->
225-
<form class="flex flex-col gap-y-4">
226-
<NeTextInput
227-
ref="uploadSshKeyInput"
228-
v-model="uploadSshKey"
229-
:invalid-message="validationErrors.get('uploadSshKey')?.[0]"
230-
:label="t('standalone.ssh.ssh_keys.add_new_ssh_key.label')"
231-
:placeholder="t('standalone.ssh.ssh_keys.add_new_ssh_key.placeholder')"
232-
></NeTextInput>
233-
<NeButton
234-
:disabled="submitting"
235-
:loading="submitting"
236-
size="lg"
237-
class="self-start"
238-
@click.prevent="addKey()"
239-
>
240-
<template #prefix>
241-
<FontAwesomeIcon :icon="['fas', 'plus']" aria-hidden="true" />
242-
</template>
243-
{{ t('standalone.ssh.ssh_keys.add_key_button') }}
244-
</NeButton>
245-
</form>
246195
</FormLayout>
247196
</template>

Diff for: src/i18n/en/translation.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,9 @@
398398
"cannot_save_blocked_domain": "Cannot save blocked domain",
399399
"invalid_domain_name": "Invalid domain name",
400400
"duplicate_rule": "Rule already exists",
401-
"duplicate_suppression": "Suppression already exists"
401+
"duplicate_suppression": "Suppression already exists",
402+
"key_already_exists": "Key already exists",
403+
"key_invalid_format": "Invalid key format"
402404
},
403405
"ne_text_input": {
404406
"show_password": "Show password",
@@ -610,7 +612,9 @@
610612
"required": "Required",
611613
"invalid": "Invalid key",
612614
"duplicate": "Key already exists"
613-
}
615+
},
616+
"key_deleted_notification": "Key deleted successfully",
617+
"key_added_notification": "Key added successfully"
614618
}
615619
},
616620
"backup_and_restore": {

0 commit comments

Comments
 (0)