sudo commands through ephemeral terminal
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { Terminal, Eye, Trash2, Bot, Server, Puzzle, Settings } from 'lucide-react';
|
||||
import { Terminal, Eye, Trash2, Bot, Server, Puzzle, Settings, X } from 'lucide-react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -10,8 +10,11 @@ import { Switch } from '@/components/ui/switch';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
||||
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components/ui/accordion';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import type { LayoutNode, PanelComponents } from '@/components/Workspace';
|
||||
import { WorkspaceLayout } from '@/components/Workspace';
|
||||
import { usePanelChannel } from 'hooks/usePanelChannel';
|
||||
import { TerminalView } from 'apps/Terminal';
|
||||
import { appRegistry } from '../Workspaces/app-registry';
|
||||
import { createSettingsPanelComponents, type SettingsSectionGroup } from './SettingsPanel';
|
||||
import { useSettings } from '@/state/useSettings';
|
||||
@@ -27,6 +30,7 @@ import {
|
||||
import type { UserSettings } from '@/state/types/user-settings';
|
||||
import { AIHarnessesSection } from './ServerSettings/AIHarnessesSection';
|
||||
import { PluginsSection } from './ServerSettings/PluginsSection';
|
||||
import { RUN_COMMAND_CHANNEL, type RunCommandState } from './ServerSettings/run-command-channel';
|
||||
|
||||
const groups: SettingsSectionGroup[] = [
|
||||
{
|
||||
@@ -54,7 +58,7 @@ const { Sidebar, Content } = createSettingsPanelComponents({
|
||||
groups,
|
||||
});
|
||||
|
||||
const layout: LayoutNode = {
|
||||
const baseLayout: LayoutNode = {
|
||||
type: 'group',
|
||||
id: 'system-root',
|
||||
direction: 'horizontal',
|
||||
@@ -64,11 +68,87 @@ const layout: LayoutNode = {
|
||||
],
|
||||
};
|
||||
|
||||
const splitLayout: LayoutNode = {
|
||||
type: 'group',
|
||||
id: 'system-root',
|
||||
direction: 'horizontal',
|
||||
children: [
|
||||
{ node: { type: 'panel', id: 'system-left', appType: null }, size: 20 },
|
||||
{
|
||||
node: {
|
||||
type: 'group',
|
||||
id: 'system-right-group',
|
||||
direction: 'vertical',
|
||||
children: [
|
||||
{ node: { type: 'panel', id: 'system-right', appType: null }, size: 50 },
|
||||
{ node: { type: 'panel', id: 'system-terminal', appType: null }, size: 50 },
|
||||
],
|
||||
},
|
||||
size: 80,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const SystemTerminalPanel = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const [state, setState] = usePanelChannel<RunCommandState>(RUN_COMMAND_CHANNEL, null);
|
||||
const [session, setSession] = useState<{ id: string; command: string } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (state && (!session || session.command !== state.command)) {
|
||||
setSession({ id: `run-cmd-${Date.now()}`, command: state.command });
|
||||
} else if (!state) {
|
||||
setSession(null);
|
||||
}
|
||||
}, [state]);
|
||||
|
||||
const close = () => setState(null);
|
||||
|
||||
const onCommandDone = (exitCode: number, output: string) => {
|
||||
if (state) {
|
||||
for (const key of state.refetchKeys) {
|
||||
queryClient.invalidateQueries({ queryKey: [key] });
|
||||
}
|
||||
}
|
||||
if (exitCode === 0) {
|
||||
toast.success('Command completed successfully');
|
||||
} else {
|
||||
toast.error(output || `Command failed with exit code ${exitCode}`, { duration: 8000 });
|
||||
}
|
||||
setState(null);
|
||||
};
|
||||
|
||||
if (!state || !session) return null;
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="shrink-0 px-4 py-1.5 border-b border-duck-dark/10 dark:border-foreground/10 bg-background/60 flex items-center gap-2">
|
||||
<span className="text-xs font-medium text-duck-dark/50 dark:text-foreground/50 flex-1">Run Command</span>
|
||||
<button onClick={close} className="p-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors">
|
||||
<X className="h-3.5 w-3.5 text-duck-dark/50 dark:text-foreground/50" />
|
||||
</button>
|
||||
</div>
|
||||
<TerminalView
|
||||
className="flex-1"
|
||||
sandboxed={false}
|
||||
command={session.command}
|
||||
sessionId={session.id}
|
||||
onCommandDone={onCommandDone}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SystemSettings = () => {
|
||||
const [runCommand] = usePanelChannel<RunCommandState>(RUN_COMMAND_CHANNEL, null);
|
||||
|
||||
const layout = useMemo(() => (runCommand ? splitLayout : baseLayout), [runCommand]);
|
||||
|
||||
const panelComponents: PanelComponents = useMemo(
|
||||
() => ({
|
||||
'system-left': Sidebar,
|
||||
'system-right': Content,
|
||||
'system-terminal': SystemTerminalPanel,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user