@@ -6,7 +6,6 @@ import { CLOUD_APP, DEVICE_API } from "../ui.config";
6
6
import api from "../api" ;
7
7
import { LocalDevice } from "./devices.$id" ;
8
8
import { useDeviceUiNavigation } from "../hooks/useAppNavigation" ;
9
- import { isOnDevice } from "../main" ;
10
9
import { GridCard } from "../components/Card" ;
11
10
import { ShieldCheckIcon } from "@heroicons/react/24/outline" ;
12
11
import notifications from "../notifications" ;
@@ -15,16 +14,21 @@ import { useJsonRpc } from "../hooks/useJsonRpc";
15
14
import { InputFieldWithLabel } from "../components/InputField" ;
16
15
import { SelectMenuBasic } from "../components/SelectMenuBasic" ;
17
16
import { SettingsSectionHeader } from "../components/SettingsSectionHeader" ;
17
+ import { isOnDevice } from "../main" ;
18
18
19
19
export const loader = async ( ) => {
20
- const status = await api
21
- . GET ( `${ DEVICE_API } /device` )
22
- . then ( res => res . json ( ) as Promise < LocalDevice > ) ;
23
- return status ;
20
+ if ( isOnDevice ) {
21
+ const status = await api
22
+ . GET ( `${ DEVICE_API } /device` )
23
+ . then ( res => res . json ( ) as Promise < LocalDevice > ) ;
24
+ return status ;
25
+ }
26
+ return null ;
24
27
} ;
25
28
26
29
export default function SettingsAccessIndexRoute ( ) {
27
- const { authMode } = useLoaderData ( ) as LocalDevice ;
30
+ const loaderData = useLoaderData ( ) as LocalDevice | null ;
31
+
28
32
const { navigateTo } = useDeviceUiNavigation ( ) ;
29
33
30
34
const [ send ] = useJsonRpc ( ) ;
@@ -137,194 +141,193 @@ export default function SettingsAccessIndexRoute() {
137
141
syncCloudUrl ( ) ;
138
142
} , [ cloudProviders , syncCloudUrl ] ) ;
139
143
140
- console . log ( "is adopted:" , isAdopted ) ;
141
-
142
144
return (
143
145
< div className = "space-y-4" >
144
146
< SettingsPageHeader
145
147
title = "Access"
146
148
description = "Manage the Access Control of the device"
147
149
/>
148
150
149
- < div className = "space-y-4" >
150
- < SettingsSectionHeader
151
- title = "Local"
152
- description = "Manage the mode of local access to the device"
153
- />
154
- < SettingsItem
155
- title = "Authentication Mode"
156
- description = { `Current mode: ${ authMode === "password" ? "Password protected" : "No password" } ` }
157
- >
158
- { authMode === "password" ? (
159
- < Button
160
- size = "SM"
161
- theme = "light"
162
- text = "Disable Protection"
163
- onClick = { ( ) => {
164
- navigateTo ( "./local-auth" , { state : { init : "deletePassword" } } ) ;
165
- } }
166
- />
167
- ) : (
168
- < Button
169
- size = "SM"
170
- theme = "light"
171
- text = "Enable Password"
172
- onClick = { ( ) => {
173
- navigateTo ( "./local-auth" , { state : { init : "createPassword" } } ) ;
174
- } }
151
+ { loaderData ?. authMode && (
152
+ < >
153
+ < div className = "space-y-4" >
154
+ < SettingsSectionHeader
155
+ title = "Local"
156
+ description = "Manage the mode of local access to the device"
175
157
/>
176
- ) }
177
- </ SettingsItem >
158
+ < SettingsItem
159
+ title = "Authentication Mode"
160
+ description = { `Current mode: ${ loaderData . authMode === "password" ? "Password protected" : "No password" } ` }
161
+ >
162
+ { loaderData . authMode === "password" ? (
163
+ < Button
164
+ size = "SM"
165
+ theme = "light"
166
+ text = "Disable Protection"
167
+ onClick = { ( ) => {
168
+ navigateTo ( "./local-auth" , { state : { init : "deletePassword" } } ) ;
169
+ } }
170
+ />
171
+ ) : (
172
+ < Button
173
+ size = "SM"
174
+ theme = "light"
175
+ text = "Enable Password"
176
+ onClick = { ( ) => {
177
+ navigateTo ( "./local-auth" , { state : { init : "createPassword" } } ) ;
178
+ } }
179
+ />
180
+ ) }
181
+ </ SettingsItem >
182
+
183
+ { loaderData . authMode === "password" && (
184
+ < SettingsItem
185
+ title = "Change Password"
186
+ description = "Update your device access password"
187
+ >
188
+ < Button
189
+ size = "SM"
190
+ theme = "light"
191
+ text = "Change Password"
192
+ onClick = { ( ) => {
193
+ navigateTo ( "./local-auth" , { state : { init : "updatePassword" } } ) ;
194
+ } }
195
+ />
196
+ </ SettingsItem >
197
+ ) }
198
+ </ div >
199
+ < div className = "h-px w-full bg-slate-800/10 dark:bg-slate-300/20" />
200
+ </ >
201
+ ) }
178
202
179
- { authMode === "password" && (
180
- < SettingsItem
181
- title = "Change Password"
182
- description = "Update your device access password"
183
- >
184
- < Button
185
- size = "SM"
186
- theme = "light"
187
- text = "Change Password"
188
- onClick = { ( ) => {
189
- navigateTo ( "./local-auth" , { state : { init : "updatePassword" } } ) ;
190
- } }
191
- />
192
- </ SettingsItem >
193
- ) }
194
- </ div >
195
- < div className = "h-px w-full bg-slate-800/10 dark:bg-slate-300/20" />
196
203
< div className = "space-y-4" >
197
204
< SettingsSectionHeader
198
205
title = "Remote"
199
206
description = "Manage the mode of Remote access to the device"
200
207
/>
201
208
202
- { isOnDevice && (
203
- < >
204
- < div className = "space-y-4" >
205
- { ! isAdopted && (
206
- < >
207
- < SettingsItem
208
- title = "Cloud Provider"
209
- description = "Select the cloud provider for your device"
210
- >
211
- < SelectMenuBasic
212
- size = "SM"
213
- value = { selectedUrlOption }
214
- onChange = { e => {
215
- const value = e . target . value ;
216
- setSelectedUrlOption ( value ) ;
217
- } }
218
- options = { cloudProviders ?? [ ] }
219
- />
220
- </ SettingsItem >
209
+ < div className = "space-y-4" >
210
+ { ! isAdopted && (
211
+ < >
212
+ < SettingsItem
213
+ title = "Cloud Provider"
214
+ description = "Select the cloud provider for your device"
215
+ >
216
+ < SelectMenuBasic
217
+ size = "SM"
218
+ value = { selectedUrlOption }
219
+ onChange = { e => {
220
+ const value = e . target . value ;
221
+ setSelectedUrlOption ( value ) ;
222
+ } }
223
+ options = { cloudProviders ?? [ ] }
224
+ />
225
+ </ SettingsItem >
221
226
222
- { selectedUrlOption === "custom" && (
223
- < div className = "mt-4 flex items-end gap-x-2 space-y-4" >
224
- < InputFieldWithLabel
225
- size = "SM"
226
- label = "Custom Cloud URL"
227
- value = { cloudUrl }
228
- onChange = { e => setCloudUrl ( e . target . value ) }
229
- placeholder = "https://api.example.com"
230
- />
231
- </ div >
232
- ) }
233
- </ >
227
+ { selectedUrlOption === "custom" && (
228
+ < div className = "mt-4 flex items-end gap-x-2 space-y-4" >
229
+ < InputFieldWithLabel
230
+ size = "SM"
231
+ label = "Custom Cloud URL"
232
+ value = { cloudUrl }
233
+ onChange = { e => setCloudUrl ( e . target . value ) }
234
+ placeholder = "https://api.example.com"
235
+ />
236
+ </ div >
234
237
) }
238
+ </ >
239
+ ) }
235
240
236
- { /*
241
+ { /*
237
242
We do the harcoding here to avoid flickering when the default Cloud URL being fetched.
238
243
I've tried to avoid harcoding api.jetkvm.com, but it's the only reasonable way I could think of to avoid flickering for now.
239
244
*/ }
240
- { selectedUrlOption === ( defaultCloudUrl || "https://api.jetkvm.com" ) && (
241
- < GridCard >
242
- < div className = "flex items-start gap-x-4 p-4" >
243
- < ShieldCheckIcon className = "mt-1 h-8 w-8 shrink-0 text-blue-600 dark:text-blue-500" />
244
- < div className = "space-y-3" >
245
- < div className = "space-y-2" >
246
- < h3 className = "text-base font-bold text-slate-900 dark:text-white" >
247
- Cloud Security
248
- </ h3 >
249
- < div >
250
- < ul className = "list-disc space-y-1 pl-5 text-xs text-slate-700 dark:text-slate-300" >
251
- < li > End-to-end encryption using WebRTC (DTLS and SRTP)</ li >
252
- < li > Zero Trust security model</ li >
253
- < li > OIDC (OpenID Connect) authentication</ li >
254
- < li > All streams encrypted in transit</ li >
255
- </ ul >
256
- </ div >
257
-
258
- < div className = "text-xs text-slate-700 dark:text-slate-300" >
259
- All cloud components are open-source and available on{ " " }
260
- < a
261
- href = "https://github.com/jetkvm"
262
- target = "_blank"
263
- rel = "noopener noreferrer"
264
- className = "font-medium text-blue-600 hover:text-blue-800 dark:text-blue-500 dark:hover:text-blue-400"
265
- >
266
- GitHub
267
- </ a >
268
- .
269
- </ div >
270
- </ div >
271
- < hr className = "block w-full dark:border-slate-600" />
245
+ { selectedUrlOption === ( defaultCloudUrl || "https://api.jetkvm.com" ) && (
246
+ < GridCard >
247
+ < div className = "flex items-start gap-x-4 p-4" >
248
+ < ShieldCheckIcon className = "mt-1 h-8 w-8 shrink-0 text-blue-600 dark:text-blue-500" />
249
+ < div className = "space-y-3" >
250
+ < div className = "space-y-2" >
251
+ < h3 className = "text-base font-bold text-slate-900 dark:text-white" >
252
+ Cloud Security
253
+ </ h3 >
254
+ < div >
255
+ < ul className = "list-disc space-y-1 pl-5 text-xs text-slate-700 dark:text-slate-300" >
256
+ < li > End-to-end encryption using WebRTC (DTLS and SRTP)</ li >
257
+ < li > Zero Trust security model</ li >
258
+ < li > OIDC (OpenID Connect) authentication</ li >
259
+ < li > All streams encrypted in transit</ li >
260
+ </ ul >
261
+ </ div >
272
262
273
- < div >
274
- < LinkButton
275
- to = "https://jetkvm.com/docs/networking/remote-access"
276
- size = "SM"
277
- theme = "light"
278
- text = "Learn about our cloud security"
279
- />
280
- </ div >
263
+ < div className = "text-xs text-slate-700 dark:text-slate-300" >
264
+ All cloud components are open-source and available on{ " " }
265
+ < a
266
+ href = "https://github.com/jetkvm"
267
+ target = "_blank"
268
+ rel = "noopener noreferrer"
269
+ className = "font-medium text-blue-600 hover:text-blue-800 dark:text-blue-500 dark:hover:text-blue-400"
270
+ >
271
+ GitHub
272
+ </ a >
273
+ .
281
274
</ div >
282
275
</ div >
283
- </ GridCard >
284
- ) }
276
+ < hr className = "block w-full dark:border-slate-600" />
285
277
286
- { ! isAdopted ? (
287
- < div className = "flex items-end gap-x-2" >
278
+ < div >
279
+ < LinkButton
280
+ to = "https://jetkvm.com/docs/networking/remote-access"
281
+ size = "SM"
282
+ theme = "light"
283
+ text = "Learn about our cloud security"
284
+ />
285
+ </ div >
286
+ </ div >
287
+ </ div >
288
+ </ GridCard >
289
+ ) }
290
+
291
+ { ! isAdopted ? (
292
+ < div className = "flex items-end gap-x-2" >
293
+ < Button
294
+ onClick = { ( ) => onCloudAdoptClick ( cloudUrl ) }
295
+ size = "SM"
296
+ theme = "primary"
297
+ text = "Adopt KVM to Cloud"
298
+ />
299
+ </ div >
300
+ ) : (
301
+ < div >
302
+ < div className = "space-y-2" >
303
+ < p className = "text-sm text-slate-600 dark:text-slate-300" >
304
+ Your device is adopted to JetKVM Cloud
305
+ </ p >
306
+ < div >
288
307
< Button
289
- onClick = { ( ) => onCloudAdoptClick ( cloudUrl ) }
290
308
size = "SM"
291
- theme = "primary"
292
- text = "Adopt KVM to Cloud"
309
+ theme = "light"
310
+ text = "De-register from Cloud"
311
+ className = "text-red-600"
312
+ onClick = { ( ) => {
313
+ if ( deviceId ) {
314
+ if (
315
+ window . confirm (
316
+ "Are you sure you want to de-register this device?" ,
317
+ )
318
+ ) {
319
+ deregisterDevice ( ) ;
320
+ }
321
+ } else {
322
+ notifications . error ( "No device ID available" ) ;
323
+ }
324
+ } }
293
325
/>
294
326
</ div >
295
- ) : (
296
- < div >
297
- < div className = "space-y-2" >
298
- < p className = "text-sm text-slate-600 dark:text-slate-300" >
299
- Your device is adopted to JetKVM Cloud
300
- </ p >
301
- < div >
302
- < Button
303
- size = "SM"
304
- theme = "light"
305
- text = "De-register from Cloud"
306
- className = "text-red-600"
307
- onClick = { ( ) => {
308
- if ( deviceId ) {
309
- if (
310
- window . confirm (
311
- "Are you sure you want to de-register this device?" ,
312
- )
313
- ) {
314
- deregisterDevice ( ) ;
315
- }
316
- } else {
317
- notifications . error ( "No device ID available" ) ;
318
- }
319
- } }
320
- />
321
- </ div >
322
- </ div >
323
- </ div >
324
- ) }
327
+ </ div >
325
328
</ div >
326
- </ >
327
- ) }
329
+ ) }
330
+ </ div >
328
331
</ div >
329
332
</ div >
330
333
) ;
0 commit comments