|
12 | 12 | >
|
13 | 13 | Quick Connect
|
14 | 14 | </v-btn>
|
| 15 | + |
15 | 16 | <div>
|
16 |
| - <p |
17 |
| - class="text-caption text-md font-weight-bold text-grey-darken-1 ma-1" |
18 |
| - data-test="quick-connect-instructions" |
19 |
| - > |
| 17 | + <p class="text-caption text-md font-weight-bold text-grey-darken-1 ma-1" data-test="quick-connect-instructions"> |
20 | 18 | Press <v-chip density="compact" size="small" label>Ctrl+K</v-chip> to Quick Connect!
|
21 | 19 | </p>
|
22 | 20 | </div>
|
23 |
| - <v-dialog |
24 |
| - v-model="dialog" |
25 |
| - width="1000" |
26 |
| - transition="dialog-bottom-transition" |
27 |
| - > |
| 21 | + |
| 22 | + <v-dialog v-model="dialog" width="1000" transition="dialog-bottom-transition"> |
28 | 23 | <v-card class="bg-v-theme-surface content" min-height="700" max-height="700">
|
29 | 24 | <div class="pa-5">
|
30 | 25 | <v-row>
|
31 | 26 | <v-col>
|
32 | 27 | <v-text-field
|
33 | 28 | label="Search your online devices!"
|
34 |
| - variant="solo" |
| 29 | + variant="outlined" |
| 30 | + bg-color="bg-v-theme-surface" |
35 | 31 | color="primary"
|
36 | 32 | single-line
|
37 | 33 | hide-details
|
38 | 34 | v-model.trim="filter"
|
39 |
| - v-on:keyup="searchDevices" |
| 35 | + @keyup="searchDevices" |
40 | 36 | prepend-inner-icon="mdi-magnify"
|
41 | 37 | density="comfortable"
|
42 | 38 | data-test="search-text"
|
43 | 39 | autofocus
|
44 |
| - class="shrink mx-1" |
| 40 | + class="shrink mx-1 mt-2" |
45 | 41 | />
|
| 42 | + |
46 | 43 | </v-col>
|
47 | 44 | </v-row>
|
48 | 45 | </div>
|
| 46 | + |
49 | 47 | <v-card-text class="mt-4 mb-0 pb-1 flex">
|
50 | 48 | <v-row>
|
51 |
| - <v-col> |
52 |
| - <p class="text-body-2 mb-2 font-weight-bold text-center" data-test="hostname-header"> |
53 |
| - Hostname |
54 |
| - </p> |
55 |
| - </v-col> |
56 |
| - <v-col> |
57 |
| - <p class="text-body-2 mb-2 font-weight-bold text-center" data-test="os-header"> |
58 |
| - Operating System |
59 |
| - </p> |
60 |
| - </v-col> |
61 |
| - <v-col> |
62 |
| - <p class="text-body-2 mb-2 font-weight-bold text-center" data-test="sshid-header"> |
63 |
| - SSHID |
64 |
| - </p> |
65 |
| - </v-col> |
66 |
| - <v-col> |
67 |
| - <p class="text-body-2 mr-3 font-weight-bold text-center" data-test="tags-header"> |
68 |
| - Tags |
| 49 | + <v-col |
| 50 | + v-for="header in headers" |
| 51 | + :key="header.label" |
| 52 | + > |
| 53 | + <p |
| 54 | + class="text-body-2 mb-2 font-(weight-bold) text-center" |
| 55 | + :data-test="`${normalizeLabel(header.label)}-header`" |
| 56 | + > |
| 57 | + {{ header.label }} |
69 | 58 | </p>
|
70 | 59 | </v-col>
|
71 | 60 | </v-row>
|
72 |
| - <QuickConnectionList ref="list" /> |
| 61 | + |
| 62 | + <QuickConnectionList ref="listRef" /> |
73 | 63 | </v-card-text>
|
| 64 | + |
74 | 65 | <v-card-actions>
|
75 | 66 | <v-row class="ml-2">
|
76 | 67 | <v-col>
|
77 | 68 | <p class="text-body-2 mb-0 font-weight-bold text-grey-darken-1">
|
78 |
| - <v-icon color="#7284D0" data-test="connect-icon">mdi-arrow-u-left-bottom</v-icon> |
79 |
| - To connect |
| 69 | + <v-icon color="#7284D0" data-test="connect-icon">mdi-arrow-u-left-bottom</v-icon> To connect |
80 | 70 | </p>
|
81 | 71 | </v-col>
|
82 | 72 | <v-col>
|
83 | 73 | <p class="text-body-2 mb-0 font-weight-bold text-grey-darken-1">
|
84 | 74 | <v-icon color="#7284D0" data-test="navigate-up-icon">mdi-arrow-up</v-icon>
|
85 |
| - <v-icon color="#7284D0" data-test="navigate-down-icon">mdi-arrow-down</v-icon> |
86 |
| - To navigate |
| 75 | + <v-icon color="#7284D0" data-test="navigate-down-icon">mdi-arrow-down</v-icon> To navigate |
87 | 76 | </p>
|
88 | 77 | </v-col>
|
89 | 78 | <v-col>
|
90 |
| - <p |
91 |
| - class="text-body-2 font-weight-bold text-grey-darken-1" |
92 |
| - data-test="copy-sshid-instructions" |
93 |
| - > |
| 79 | + <p class="text-body-2 font-weight-bold text-grey-darken-1" data-test="copy-sshid-instructions"> |
94 | 80 | Press "Ctrl + C" to copy SSHID
|
95 | 81 | </p>
|
96 | 82 | </v-col>
|
97 | 83 | </v-row>
|
98 |
| - <v-btn variant="text" data-test="close-btn" @click="dialog = !dialog"> |
99 |
| - Close |
100 |
| - </v-btn> |
| 84 | + |
| 85 | + <v-btn variant="text" data-test="close-btn" @click="dialog = false">Close</v-btn> |
101 | 86 | </v-card-actions>
|
102 | 87 | </v-card>
|
103 | 88 | </v-dialog>
|
104 | 89 | </div>
|
105 | 90 | </template>
|
106 | 91 |
|
107 | 92 | <script setup lang="ts">
|
108 |
| -// eslint-disable-next-line import/no-extraneous-dependencies |
| 93 | +import { ref, watch, onUnmounted } from "vue"; |
109 | 94 | import { useMagicKeys } from "@vueuse/core";
|
110 |
| -import { watch, ref, onUnmounted } from "vue"; |
111 | 95 | import axios, { AxiosError } from "axios";
|
112 | 96 | import QuickConnectionList from "./QuickConnectionList.vue";
|
113 | 97 | import { useStore } from "@/store";
|
114 | 98 | import handleError from "@/utils/handleError";
|
115 | 99 |
|
116 |
| -const list = ref<InstanceType<typeof QuickConnectionList>>(); |
117 | 100 | const dialog = ref(false);
|
118 |
| -const store = useStore(); |
119 | 101 | const filter = ref("");
|
120 |
| -const show = ref(false); |
| 102 | +const listRef = ref<InstanceType<typeof QuickConnectionList> | null>(null); |
| 103 | +const store = useStore(); |
| 104 | +
|
| 105 | +const headers = [ |
| 106 | + { label: "Hostname" }, |
| 107 | + { label: "Operating System" }, |
| 108 | + { label: "SSHID" }, |
| 109 | + { label: "Tags" }, |
| 110 | +]; |
| 111 | +
|
| 112 | +const normalizeLabel = (label: string) => label.toLowerCase().replace(/\s+/g, "-"); |
| 113 | +
|
| 114 | +useMagicKeys({ |
| 115 | + passive: false, |
| 116 | + onEventFired(event) { |
| 117 | + if (event.ctrlKey && event.key.toLowerCase() === "k" && event.type === "keydown") { |
| 118 | + event.preventDefault(); |
| 119 | + dialog.value = !dialog.value; |
| 120 | + } else if ((event.key === "ArrowDown" || event.key === "ArrowUp") && event.type === "keydown") { |
| 121 | + event.preventDefault(); |
| 122 | + listRef.value?.rootEl?.focus?.(); |
| 123 | + } |
| 124 | + }, |
| 125 | +}); |
121 | 126 |
|
122 | 127 | const searchDevices = () => {
|
123 | 128 | let encodedFilter = "";
|
124 | 129 |
|
125 |
| - if (filter.value) { |
126 |
| - const filterToEncodeBase64 = [ |
| 130 | + if (filter.value.trim()) { |
| 131 | + const filterObj = [ |
127 | 132 | {
|
128 | 133 | type: "property",
|
129 | 134 | params: { name: "name", operator: "contains", value: filter.value },
|
130 | 135 | },
|
131 | 136 | ];
|
132 |
| - encodedFilter = btoa(JSON.stringify(filterToEncodeBase64)); |
| 137 | + encodedFilter = btoa(JSON.stringify(filterObj)); |
133 | 138 | }
|
134 | 139 |
|
135 |
| - if (dialog.value === false) { |
| 140 | + if (!dialog.value) { |
136 | 141 | encodedFilter = "";
|
137 | 142 | }
|
138 | 143 |
|
139 |
| - try { |
140 |
| - store.dispatch("devices/searchQuickConnection", { |
141 |
| - page: store.getters["devices/getPage"], |
142 |
| - perPage: store.getters["devices/getPerPage"], |
143 |
| - filter: encodedFilter, |
144 |
| - status: store.getters["devices/getStatus"], |
145 |
| - }); |
146 |
| - } catch { |
| 144 | + store.dispatch("devices/searchQuickConnection", { |
| 145 | + page: store.getters["devices/getPage"], |
| 146 | + perPage: store.getters["devices/getPerPage"], |
| 147 | + filter: encodedFilter, |
| 148 | + status: store.getters["devices/getStatus"], |
| 149 | + }).catch(() => { |
147 | 150 | store.dispatch("snackbar/showSnackbarErrorDefault");
|
148 |
| - } |
| 151 | + }); |
149 | 152 | };
|
150 | 153 |
|
151 |
| -watch(dialog, async (value) => { |
152 |
| - if (!value) return; |
| 154 | +watch(dialog, async (isOpen) => { |
| 155 | + if (!isOpen) return; |
153 | 156 |
|
154 | 157 | try {
|
155 | 158 | await store.dispatch("stats/get");
|
156 |
| - show.value = true; |
157 |
| - } catch (error: unknown) { |
158 |
| - const axiosError = error as AxiosError; |
159 |
| - switch (axios.isAxiosError(error)) { |
160 |
| - case axiosError.response?.status === 403: { |
161 |
| - store.dispatch("snackbar/showSnackbarErrorAssociation"); |
162 |
| - break; } |
163 |
| - default: store.dispatch("snackbar/showSnackbarErrorDefault"); |
| 159 | + } catch (err: unknown) { |
| 160 | + const error = err as AxiosError; |
| 161 | + if (axios.isAxiosError(error) && error.response?.status === 403) { |
| 162 | + store.dispatch("snackbar/showSnackbarErrorAssociation"); |
| 163 | + } else { |
| 164 | + store.dispatch("snackbar/showSnackbarErrorDefault"); |
164 | 165 | }
|
165 | 166 | handleError(error);
|
166 | 167 | }
|
167 | 168 | });
|
168 | 169 |
|
169 |
| -onUnmounted(async () => { |
170 |
| - await store.dispatch("devices/setFilter", ""); |
171 |
| -}); |
172 |
| -
|
173 |
| -// eslint-disable-next-line @typescript-eslint/no-unused-vars |
174 |
| -const keyboardMacros = useMagicKeys({ |
175 |
| - passive: false, |
176 |
| - onEventFired(e) { |
177 |
| - if (e.ctrlKey && e.key === "k" && e.type === "keydown") { |
178 |
| - e.preventDefault(); |
179 |
| - dialog.value = !dialog.value; |
180 |
| - } else if ((e.key === "ArrowDown" || e.key === "ArrowUp") && e.type === "keydown") { |
181 |
| - e.preventDefault(); |
182 |
| - list.value?.rootEl?.focus(); |
183 |
| - } |
184 |
| - }, |
| 170 | +onUnmounted(() => { |
| 171 | + store.dispatch("devices/setFilter", ""); |
185 | 172 | });
|
186 | 173 | </script>
|
187 | 174 |
|
188 |
| -<style lang="scss" scoped> |
| 175 | +<style scoped lang="scss"> |
189 | 176 | .code {
|
190 | 177 | font-family: monospace;
|
191 | 178 | font-size: 85%;
|
|
0 commit comments