|
1 | 1 | <template> |
2 | | - <div v-if="!hasStatus"> |
3 | | - <!-- Namespace Info Card --> |
4 | | - <v-row> |
5 | | - <v-col cols="12"> |
6 | | - <v-card |
7 | | - class="bg-transparent mb-6" |
8 | | - elevation="0" |
9 | | - rounded="0" |
| 2 | + <div v-if="!hasError"> |
| 3 | + <v-card |
| 4 | + class="bg-transparent mb-6" |
| 5 | + elevation="0" |
| 6 | + rounded="0" |
| 7 | + > |
| 8 | + <v-row> |
| 9 | + <v-col |
| 10 | + cols="12" |
| 11 | + md="6" |
10 | 12 | > |
11 | | - <v-row> |
12 | | - <v-col |
13 | | - cols="12" |
14 | | - md="6" |
| 13 | + <div class="d-flex align-start"> |
| 14 | + <v-avatar |
| 15 | + color="primary" |
| 16 | + size="48" |
| 17 | + class="mr-4" |
15 | 18 | > |
16 | | - <div class="d-flex align-start"> |
17 | | - <v-avatar |
| 19 | + <v-icon |
| 20 | + size="32" |
| 21 | + icon="mdi-home" |
| 22 | + /> |
| 23 | + </v-avatar> |
| 24 | + <div> |
| 25 | + <div class="text-overline text-medium-emphasis mb-1">Home</div> |
| 26 | + <div class="text-h5 font-weight-bold mb-2"> |
| 27 | + {{ hasNamespace ? namespace.name : "No Active Namespace" }} |
| 28 | + </div> |
| 29 | + <div class="text-body-2 text-medium-emphasis">{{ activeNamespaceDescription }}</div> |
| 30 | + </div> |
| 31 | + </div> |
| 32 | + </v-col> |
| 33 | + <v-col |
| 34 | + cols="12" |
| 35 | + md="6" |
| 36 | + > |
| 37 | + <v-card |
| 38 | + class="pa-4" |
| 39 | + variant="tonal" |
| 40 | + > |
| 41 | + <div v-if="hasNamespace"> |
| 42 | + <div class="text-overline text-medium-emphasis mb-2">TENANT ID</div> |
| 43 | + <div class="d-flex align-center justify-space-between"> |
| 44 | + <code |
| 45 | + class="text-primary" |
| 46 | + data-test="tenant-info-text" |
| 47 | + >{{ namespace.tenant_id }}</code> |
| 48 | + <CopyWarning copied-item="Tenant ID"> |
| 49 | + <template #default="{ copyText }"> |
| 50 | + <v-btn |
| 51 | + data-test="copy-tenant-btn" |
| 52 | + color="primary" |
| 53 | + variant="elevated" |
| 54 | + size="small" |
| 55 | + prepend-icon="mdi-content-copy" |
| 56 | + @click="copyText(namespace.tenant_id)" |
| 57 | + > |
| 58 | + Copy |
| 59 | + </v-btn> |
| 60 | + </template> |
| 61 | + </CopyWarning> |
| 62 | + </div> |
| 63 | + <div class="text-caption text-medium-emphasis mt-2">Use this ID to register new devices to this namespace</div> |
| 64 | + </div> |
| 65 | + <div v-else> |
| 66 | + <div class="text-overline text-medium-emphasis">Create your first namespace</div> |
| 67 | + <div class="my-2"> |
| 68 | + <v-btn |
18 | 69 | color="primary" |
19 | | - size="48" |
20 | | - class="mr-4" |
21 | | - > |
22 | | - <v-icon size="32"> |
23 | | - mdi-home |
24 | | - </v-icon> |
25 | | - </v-avatar> |
26 | | - <div> |
27 | | - <div class="text-overline text-medium-emphasis mb-1"> |
28 | | - Home |
29 | | - </div> |
30 | | - <div class="text-h5 font-weight-bold mb-2"> |
31 | | - {{ namespace.name }} |
32 | | - </div> |
33 | | - <div class="text-body-2 text-medium-emphasis"> |
34 | | - This is your active namespace. All devices, sessions and configurations are isolated within this namespace. |
35 | | - </div> |
36 | | - </div> |
| 70 | + variant="elevated" |
| 71 | + size="small" |
| 72 | + prepend-icon="mdi-plus" |
| 73 | + data-test="create-namespace-home-btn" |
| 74 | + text="Create Namespace" |
| 75 | + @click="showNamespaceAdd = true" |
| 76 | + /> |
37 | 77 | </div> |
38 | | - </v-col> |
39 | | - <v-col |
40 | | - cols="12" |
41 | | - md="6" |
42 | | - > |
43 | | - <v-card |
44 | | - class="pa-4" |
45 | | - variant="tonal" |
46 | | - > |
47 | | - <div class="text-overline text-medium-emphasis mb-2"> |
48 | | - TENANT ID |
49 | | - </div> |
50 | | - <div class="d-flex align-center justify-space-between"> |
51 | | - <code |
52 | | - class="text-primary" |
53 | | - data-test="tenant-info-text" |
54 | | - >{{ namespace.tenant_id }}</code> |
55 | | - <CopyWarning :copied-item="'Tenant ID'"> |
56 | | - <template #default="{ copyText }"> |
57 | | - <v-btn |
58 | | - data-test="copy-tenant-btn" |
59 | | - color="primary" |
60 | | - variant="elevated" |
61 | | - size="small" |
62 | | - prepend-icon="mdi-content-copy" |
63 | | - @click="copyText(namespace.tenant_id)" |
64 | | - > |
65 | | - Copy |
66 | | - </v-btn> |
67 | | - </template> |
68 | | - </CopyWarning> |
69 | | - </div> |
70 | | - <div class="text-caption text-medium-emphasis mt-2"> |
71 | | - Use this ID to register new devices to this namespace |
72 | | - </div> |
73 | | - </v-card> |
74 | | - </v-col> |
75 | | - </v-row> |
76 | | - </v-card> |
77 | | - </v-col> |
78 | | - </v-row> |
79 | | - |
80 | | - <!-- Devices Section --> |
81 | | - <v-row> |
82 | | - <v-col |
83 | | - cols="12" |
84 | | - class="d-flex align-center mb-2" |
85 | | - > |
86 | | - <v-icon class="mr-2"> |
87 | | - mdi-devices |
88 | | - </v-icon> |
89 | | - <h2 class="text-h6"> |
90 | | - Devices |
91 | | - </h2> |
92 | | - </v-col> |
93 | | - </v-row> |
94 | | - |
95 | | - <v-row> |
96 | | - <v-col |
97 | | - cols="12" |
98 | | - md="3" |
99 | | - > |
100 | | - <StatCard |
101 | | - title="Accepted Devices" |
102 | | - :stat="stats.registered_devices || 0" |
103 | | - icon="mdi-check" |
104 | | - button-label="View all devices" |
105 | | - path="/devices" |
106 | | - /> |
107 | | - </v-col> |
108 | | - |
109 | | - <v-col |
110 | | - cols="12" |
111 | | - md="3" |
112 | | - > |
113 | | - <StatCard |
114 | | - title="Online Devices" |
115 | | - :stat="stats.online_devices || 0" |
116 | | - icon="mdi-lan-connect" |
117 | | - button-label="View Online Devices" |
118 | | - path="/devices" |
119 | | - /> |
120 | | - </v-col> |
121 | | - |
122 | | - <v-col |
123 | | - cols="12" |
124 | | - md="3" |
125 | | - > |
126 | | - <StatCard |
127 | | - title="Pending Devices" |
128 | | - :stat="stats.pending_devices || 0" |
129 | | - icon="mdi-clock-outline" |
130 | | - button-label="Approve Devices" |
131 | | - path="/devices/pending" |
132 | | - /> |
133 | | - </v-col> |
| 78 | + <div class="text-caption text-medium-emphasis"> |
| 79 | + You need to create or join a namespace to start managing your devices and remote connections. |
| 80 | + </div> |
| 81 | + </div> |
| 82 | + </v-card> |
| 83 | + </v-col> |
| 84 | + </v-row> |
| 85 | + </v-card> |
134 | 86 |
|
135 | | - <v-col |
136 | | - cols="12" |
137 | | - md="3" |
138 | | - > |
139 | | - <v-card class="pa-6 bg-transparent text-center h-100 border border-dashed"> |
140 | | - <v-avatar |
141 | | - color="surface-variant" |
142 | | - size="64" |
143 | | - class="mb-4" |
144 | | - theme="dark" |
145 | | - > |
146 | | - <v-icon |
147 | | - size="40" |
148 | | - color="primary" |
149 | | - icon="mdi-developer-board" |
150 | | - /> |
151 | | - </v-avatar> |
152 | | - <v-card-title class="text-h6 font-weight-bold mb-2"> |
153 | | - Add a new device |
154 | | - </v-card-title> |
155 | | - <v-card-subtitle class="text-body-2 text-medium-emphasis mb-4 text-wrap"> |
156 | | - Register new devices to this namespace and start managing remote connections |
157 | | - </v-card-subtitle> |
158 | | - <DeviceAdd /> |
159 | | - </v-card> |
160 | | - </v-col> |
161 | | - </v-row> |
| 87 | + <div v-if="hasNamespace"> |
| 88 | + <v-row> |
| 89 | + <v-col |
| 90 | + cols="12" |
| 91 | + class="d-flex align-center mb-2" |
| 92 | + > |
| 93 | + <v-icon |
| 94 | + class="mr-2" |
| 95 | + icon="mdi-devices" |
| 96 | + /> |
| 97 | + <h2 class="text-h6">Devices</h2> |
| 98 | + </v-col> |
| 99 | + </v-row> |
| 100 | + <v-row> |
| 101 | + <v-col |
| 102 | + cols="12" |
| 103 | + md="3" |
| 104 | + > |
| 105 | + <StatCard |
| 106 | + title="Accepted Devices" |
| 107 | + :stat="stats.registered_devices || 0" |
| 108 | + icon="mdi-check" |
| 109 | + button-label="View all devices" |
| 110 | + path="/devices" |
| 111 | + /> |
| 112 | + </v-col> |
| 113 | + <v-col |
| 114 | + cols="12" |
| 115 | + md="3" |
| 116 | + > |
| 117 | + <StatCard |
| 118 | + title="Online Devices" |
| 119 | + :stat="stats.online_devices || 0" |
| 120 | + icon="mdi-lan-connect" |
| 121 | + button-label="View Online Devices" |
| 122 | + path="/devices" |
| 123 | + /> |
| 124 | + </v-col> |
| 125 | + <v-col |
| 126 | + cols="12" |
| 127 | + md="3" |
| 128 | + > |
| 129 | + <StatCard |
| 130 | + title="Pending Devices" |
| 131 | + :stat="stats.pending_devices || 0" |
| 132 | + icon="mdi-clock-outline" |
| 133 | + button-label="Approve Devices" |
| 134 | + path="/devices/pending" |
| 135 | + /> |
| 136 | + </v-col> |
| 137 | + <v-col |
| 138 | + cols="12" |
| 139 | + md="3" |
| 140 | + > |
| 141 | + <v-card class="pa-6 bg-transparent text-center h-100 border border-dashed"> |
| 142 | + <v-avatar |
| 143 | + color="surface-variant" |
| 144 | + size="64" |
| 145 | + class="mb-4" |
| 146 | + theme="dark" |
| 147 | + > |
| 148 | + <v-icon |
| 149 | + size="40" |
| 150 | + color="primary" |
| 151 | + icon="mdi-developer-board" |
| 152 | + /> |
| 153 | + </v-avatar> |
| 154 | + <v-card-title class="text-h6 font-weight-bold mb-2">Add a new device</v-card-title> |
| 155 | + <v-card-subtitle class="text-body-2 text-medium-emphasis mb-4 text-wrap"> |
| 156 | + Register new devices to this namespace and start managing remote connections |
| 157 | + </v-card-subtitle> |
| 158 | + <DeviceAdd /> |
| 159 | + </v-card> |
| 160 | + </v-col> |
| 161 | + </v-row> |
| 162 | + </div> |
162 | 163 | </div> |
163 | 164 | <v-card |
164 | 165 | v-else |
|
169 | 170 | Something is wrong, try again! |
170 | 171 | </p> |
171 | 172 | </v-card> |
| 173 | + |
| 174 | + <NamespaceAdd v-model="showNamespaceAdd" /> |
172 | 175 | </template> |
173 | 176 |
|
174 | 177 | <script setup lang="ts"> |
175 | 178 | import { computed, onMounted, ref, watch } from "vue"; |
176 | | -import axios, { AxiosError } from "axios"; |
177 | 179 | import handleError from "@/utils/handleError"; |
178 | 180 | import useSnackbar from "@/helpers/snackbar"; |
179 | 181 | import useNamespacesStore from "@/store/modules/namespaces"; |
180 | 182 | import useStatsStore from "@/store/modules/stats"; |
181 | 183 | import DeviceAdd from "@/components/Devices/DeviceAdd.vue"; |
182 | 184 | import CopyWarning from "@/components/User/CopyWarning.vue"; |
183 | 185 | import StatCard from "@/components/StatCard.vue"; |
| 186 | +import NamespaceAdd from "@/components/Namespace/NamespaceAdd.vue"; |
184 | 187 |
|
185 | 188 | const namespacesStore = useNamespacesStore(); |
186 | 189 | const statsStore = useStatsStore(); |
187 | 190 | const snackbar = useSnackbar(); |
188 | | -const hasStatus = ref(false); |
| 191 | +const hasError = ref(false); |
189 | 192 | const stats = computed(() => statsStore.stats); |
190 | 193 | const namespace = computed(() => namespacesStore.currentNamespace); |
191 | 194 | const hasNamespace = computed(() => namespacesStore.namespaceList.length !== 0); |
| 195 | +const showNamespaceAdd = ref(false); |
| 196 | +
|
| 197 | +const activeNamespaceDescription = computed(() => ( |
| 198 | + hasNamespace.value |
| 199 | + ? "This is your active namespace. All devices, sessions and configurations are isolated within this namespace." |
| 200 | + : `A namespace is a logical grouping that isolates your devices, sessions, and configurations from others. |
| 201 | + Each namespace has its own unique Tenant ID used to register devices. |
| 202 | + You can create multiple namespaces to organize different projects, teams, or environments.` |
| 203 | +)); |
192 | 204 |
|
193 | 205 | const fetchStats = async () => { |
194 | 206 | if (!hasNamespace.value) return; |
195 | 207 |
|
196 | 208 | try { |
197 | 209 | await statsStore.fetchStats(); |
198 | 210 | } catch (error: unknown) { |
199 | | - if (axios.isAxiosError(error)) { |
200 | | - const axiosError = error as AxiosError; |
201 | | - switch (true) { |
202 | | - case axiosError.response && axiosError.response?.status === 403: { |
203 | | - hasStatus.value = true; |
204 | | - break; |
205 | | - } |
206 | | - default: { |
207 | | - hasStatus.value = true; |
208 | | - snackbar.showError("Failed to load the home page."); |
209 | | - break; |
210 | | - } |
211 | | - } |
212 | | - } |
| 211 | + hasError.value = true; |
| 212 | + snackbar.showError("Failed to load the home page."); |
213 | 213 | handleError(error); |
214 | 214 | } |
215 | 215 | }; |
216 | 216 |
|
217 | | -onMounted(async () => { |
218 | | - await fetchStats(); |
219 | | -}); |
| 217 | +onMounted(async () => { await fetchStats(); }); |
220 | 218 |
|
221 | 219 | watch(hasNamespace, async (newValue) => { if (newValue) await fetchStats(); }); |
222 | 220 |
|
223 | | -defineExpose({ hasStatus }); |
| 221 | +defineExpose({ hasError }); |
224 | 222 | </script> |
0 commit comments