4
4
-->
5
5
6
6
<script lang="ts" setup>
7
- import type { Ref } from 'vue'
8
7
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'
11
10
import {
12
- NeInlineNotification,
11
+ focusElement,
12
+ getAxiosErrorMessage,
13
13
NeButton,
14
+ NeInlineNotification,
15
+ NeModal,
14
16
NeSkeleton,
15
- NeTextInput,
16
- focusElement,
17
- getAxiosErrorMessage
17
+ NeTextInput
18
18
} from '@nethesis/vue-components'
19
- import { NeModal } from '@nethesis/vue-components'
20
19
import { useI18n } from 'vue-i18n'
21
20
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
22
21
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'
25
25
26
- type SshKeyError = {
27
- code: number
28
- data : string
29
- message : string
26
+ type SshKey = {
27
+ type: string
28
+ key : string
29
+ comment : string
30
30
}
31
31
32
- type SshKeysResponse = {
33
- data: {
34
- keys: string
35
- }
36
- }
32
+ type SshKeysResponse = AxiosResponse<{
33
+ keys: SshKey[]
34
+ }>
37
35
36
+ const notifications = useNotificationsStore()
38
37
const { t } = useI18n()
39
38
const uploadSshKey = ref('')
40
- const sshKeys = ref(new Array <SshKey>() )
39
+ const sshKeys = ref<SshKey[]>([] )
41
40
42
41
const loading = ref(true)
43
42
const submitting = ref(false)
44
43
const deleting = ref(false)
45
44
const validationErrors = ref(new MessageBag())
46
- const error: Ref<Error | undefined> = ref(undefined )
45
+ const error = ref<Error>( )
47
46
48
- const keyToDelete: Ref<SshKey | undefined> = ref(undefined )
47
+ const keyToDelete = ref<SshKey>( )
49
48
50
49
onMounted(() => {
51
50
load()
@@ -56,121 +55,63 @@ function load() {
56
55
loading.value = true
57
56
ubusCall('ns.ssh', 'list-keys')
58
57
.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
73
59
})
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
84
62
})
85
63
.finally(() => {
86
64
loading.value = false
87
65
})
88
66
}
89
67
90
68
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
143
72
})
144
73
.then(() => {
74
+ uploadSshKey.value = ''
145
75
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
+ }
146
89
})
147
- .catch((exception: AxiosError ) => {
148
- error .value = new Error(getAxiosErrorMessage(exception))
90
+ .finally(( ) => {
91
+ submitting .value = false
149
92
})
150
93
}
151
94
152
95
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
-
169
96
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
173
99
})
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
+ })
174
115
}
175
116
</script>
176
117
@@ -192,56 +133,64 @@ function deleteKey() {
192
133
</code>
193
134
</NeModal>
194
135
<NeInlineNotification
195
- v-if="error != undefined && !(error instanceof ValidationError) "
196
- :title ="t(error.message )"
136
+ v-if="error != undefined"
137
+ :description ="t(getAxiosErrorMessage( error) )"
197
138
kind="error"
139
+ :title="t('error.generic_error')"
198
140
/>
199
141
<NeSkeleton v-if="loading" :lines="10" />
200
142
<FormLayout
201
143
v-else
202
144
:description="t('standalone.ssh.ssh_keys.description')"
203
145
:title="t('standalone.ssh.ssh_keys.title')"
204
146
>
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" />
220
168
</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>
223
194
</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>
246
195
</FormLayout>
247
196
</template>
0 commit comments