| 1 | import { useState } from "react"; |
| 2 | |
| 3 | import { isLocalEnvironmentDisabled } from "../../localEnvironment"; |
| 4 | import { |
| 5 | AlertDialog, |
| 6 | AlertDialogClose, |
| 7 | AlertDialogDescription, |
| 8 | AlertDialogFooter, |
| 9 | AlertDialogHeader, |
| 10 | AlertDialogPopup, |
| 11 | AlertDialogTitle, |
| 12 | } from "../ui/alert-dialog"; |
| 13 | import { Button } from "../ui/button"; |
| 14 | import { Spinner } from "../ui/spinner"; |
| 15 | import { Switch } from "../ui/switch"; |
| 16 | import { SettingsRow } from "./settingsLayout"; |
| 17 | import { searchableSetting } from "./settingsSearch"; |
| 18 | |
| 19 | // Toggling relaunches the desktop app, so the switch only reflects the value |
| 20 | // this process started with; there is no live state to keep in sync. |
| 21 | export function LocalEnvironmentSetting() { |
| 22 | const setEnabled = window.desktopBridge?.setLocalEnvironmentEnabled; |
| 23 | const [enabled] = useState(() => !isLocalEnvironmentDisabled()); |
| 24 | const [confirmOpen, setConfirmOpen] = useState(false); |
| 25 | const [isUpdating, setIsUpdating] = useState(false); |
| 26 | const [error, setError] = useState<string | null>(null); |
| 27 | if (!setEnabled) return null; |
| 28 | |
| 29 | const applyChange = async () => { |
| 30 | setIsUpdating(true); |
| 31 | setError(null); |
| 32 | try { |
| 33 | await setEnabled(!enabled); |
| 34 | } catch (cause) { |
| 35 | setError(cause instanceof Error ? cause.message : "Couldn't change this setting."); |
| 36 | setIsUpdating(false); |
| 37 | } |
| 38 | }; |
| 39 | |
| 40 | return ( |
| 41 | <> |
| 42 | <SettingsRow |
| 43 | {...searchableSetting("local-environment")} |
| 44 | description={ |
| 45 | enabled |
| 46 | ? "Run agents on this computer. Turn off to use The Knife only with remote environments." |
| 47 | : "Turned off. Agents only run in remote environments." |
| 48 | } |
| 49 | control={ |
| 50 | <Switch |
| 51 | checked={enabled} |
| 52 | disabled={isUpdating} |
| 53 | onCheckedChange={() => setConfirmOpen(true)} |
| 54 | aria-label="Local environment" |
| 55 | /> |
| 56 | } |
| 57 | /> |
| 58 | <AlertDialog |
| 59 | open={confirmOpen} |
| 60 | onOpenChange={(open) => { |
| 61 | if (isUpdating) return; |
| 62 | setConfirmOpen(open); |
| 63 | if (!open) setError(null); |
| 64 | }} |
| 65 | > |
| 66 | <AlertDialogPopup> |
| 67 | <AlertDialogHeader> |
| 68 | <AlertDialogTitle> |
| 69 | {enabled ? "Turn off local environment?" : "Turn on local environment?"} |
| 70 | </AlertDialogTitle> |
| 71 | <AlertDialogDescription> |
| 72 | {enabled |
| 73 | ? "The Knife will restart without running a server on this computer. Any agents and terminals running here will stop, and other devices will no longer be able to connect to this computer. Your projects, history, and remote environments are unaffected." |
| 74 | : "The Knife will restart and start running a server on this computer again."} |
| 75 | </AlertDialogDescription> |
| 76 | </AlertDialogHeader> |
| 77 | {error ? <p className="px-6 pb-4 text-sm text-destructive">{error}</p> : null} |
| 78 | <AlertDialogFooter> |
| 79 | <AlertDialogClose disabled={isUpdating} render={<Button variant="outline" />}> |
| 80 | Cancel |
| 81 | </AlertDialogClose> |
| 82 | <Button |
| 83 | variant={enabled ? "destructive" : "default"} |
| 84 | disabled={isUpdating} |
| 85 | onClick={() => void applyChange()} |
| 86 | > |
| 87 | {isUpdating ? ( |
| 88 | <> |
| 89 | <Spinner size="sm" /> |
| 90 | Restarting… |
| 91 | </> |
| 92 | ) : enabled ? ( |
| 93 | "Restart and turn off" |
| 94 | ) : ( |
| 95 | "Restart and turn on" |
| 96 | )} |
| 97 | </Button> |
| 98 | </AlertDialogFooter> |
| 99 | </AlertDialogPopup> |
| 100 | </AlertDialog> |
| 101 | </> |
| 102 | ); |
| 103 | } |