sudo commands through ephemeral terminal
This commit is contained in:
+56
-5
@@ -1,10 +1,22 @@
|
||||
import { useState } from 'react';
|
||||
import { Copy, Check } from 'lucide-react';
|
||||
import { Copy, Check, Play } from 'lucide-react';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { usePanelChannel } from 'hooks/usePanelChannel';
|
||||
import { useServerSettings } from '@/state/useServerSettings';
|
||||
import { RUN_COMMAND_CHANNEL, type RunCommandState } from './run-command-channel';
|
||||
|
||||
type VersionInfo = { version: string | null; path: string | null; globalPath: string | null };
|
||||
type ClaudeAuthInfo = { authenticated: boolean; loggedIn?: boolean; subscriptionType?: string };
|
||||
@@ -106,15 +118,28 @@ export const AIHarnessesSection = () => {
|
||||
setTimeout(() => setCopied(null), 1500);
|
||||
};
|
||||
|
||||
const CopyCommand = ({ command }: { command: string }) => (
|
||||
const [, setRunCommand] = usePanelChannel<RunCommandState>(RUN_COMMAND_CHANNEL, null);
|
||||
|
||||
const [confirmCommand, setConfirmCommand] = useState<{ command: string; refetchKeys: string[] } | null>(null);
|
||||
|
||||
const CopyCommand = ({ command, refetchKeys }: { command: string; refetchKeys: string[] }) => (
|
||||
<div className="mt-2 text-xs text-amber-600">
|
||||
Not globally accessible. Run:
|
||||
<div className="flex items-center gap-1 mt-1">
|
||||
<code className="flex-1 bg-duck-dark/5 rounded px-2 py-1 text-duck-dark/70">{command}</code>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConfirmCommand({ command, refetchKeys })}
|
||||
className="shrink-0 p-1 rounded hover:bg-duck-teal/10 cursor-pointer transition-colors"
|
||||
title="Run in terminal"
|
||||
>
|
||||
<Play className="h-3.5 w-3.5 text-duck-teal" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copyToClipboard(command)}
|
||||
className="shrink-0 p-1 rounded hover:bg-duck-dark/10 cursor-pointer transition-colors"
|
||||
title="Copy command"
|
||||
>
|
||||
{copied === command ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-600" />
|
||||
@@ -127,6 +152,7 @@ export const AIHarnessesSection = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<label className="flex items-center gap-3 cursor-pointer">
|
||||
@@ -163,7 +189,7 @@ export const AIHarnessesSection = () => {
|
||||
</div>
|
||||
)}
|
||||
{!opencodeVersion.globalPath && opencodeVersion.path && (
|
||||
<CopyCommand command={`sudo ln -s ${opencodeVersion.path} /usr/local/bin/opencode`} />
|
||||
<CopyCommand command={`sudo ln -s ${opencodeVersion.path} /usr/local/bin/opencode`} refetchKeys={['OPENCODE_VERSION']} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
@@ -215,7 +241,7 @@ export const AIHarnessesSection = () => {
|
||||
</div>
|
||||
)}
|
||||
{!claudeVersion.globalPath && claudeVersion.path && (
|
||||
<CopyCommand command={`sudo ln -s ${claudeVersion.path} /usr/local/bin/claude`} />
|
||||
<CopyCommand command={`sudo ln -s ${claudeVersion.path} /usr/local/bin/claude`} refetchKeys={['CLAUDE_CODE_VERSION']} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
@@ -249,7 +275,7 @@ export const AIHarnessesSection = () => {
|
||||
<div>{piMonoVersion.version}</div>
|
||||
<div>{piMonoVersion.path}</div>
|
||||
{!piMonoVersion.globalPath && piMonoVersion.path && (
|
||||
<CopyCommand command={`sudo ln -s ${piMonoVersion.path} /usr/local/bin/pi`} />
|
||||
<CopyCommand command={`sudo ln -s ${piMonoVersion.path} /usr/local/bin/pi`} refetchKeys={['PI_MONO_VERSION']} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
@@ -266,5 +292,30 @@ export const AIHarnessesSection = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AlertDialog open={!!confirmCommand} onOpenChange={(open) => !open && setConfirmCommand(null)}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Run with elevated privileges</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
You are about to run a command with elevated privileges (sudo). Are you sure?
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<code className="text-xs bg-duck-dark/5 rounded px-3 py-2 text-duck-dark/70 break-all">{confirmCommand?.command}</code>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel className="cursor-pointer">Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className="cursor-pointer bg-duck-yellow hover:bg-duck-yellow/90 text-duck-teal font-bold"
|
||||
onClick={() => {
|
||||
if (confirmCommand) setRunCommand(confirmCommand);
|
||||
setConfirmCommand(null);
|
||||
}}
|
||||
>
|
||||
Run
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user