Resources tuneup
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { Server, Wrench, Loader2 } from 'lucide-react';
|
||||
import { Server, Wrench, Loader2, RefreshCw, Trash2 } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { useGlobal } from 'hooks/useGlobal';
|
||||
import { usePanelChannel } from 'hooks/usePanelChannel';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -14,7 +15,6 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { CommandBlock } from '@/components/CommandBlock';
|
||||
import { useResources, getResourceCategory, type Resource, type PingResult } from '@/state/useResources';
|
||||
import { RUN_COMMAND_CHANNEL, type RunCommandState } from './run-command-channel';
|
||||
|
||||
@@ -135,29 +135,133 @@ type LocalAvailabilitySectionProps = {
|
||||
onRun: (command: string) => void;
|
||||
};
|
||||
|
||||
const LocalAvailabilitySection = ({ resource, onRun }: LocalAvailabilitySectionProps) => (
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-duck-dark mb-3">Local Availability</h3>
|
||||
<div className="mb-3">
|
||||
type ResourceAction = 'install' | 'uninstall' | 'verify' | 'update' | 'manage';
|
||||
|
||||
const LocalAvailabilitySection = ({ resource, onRun }: LocalAvailabilitySectionProps) => {
|
||||
const { runCommand } = useResources();
|
||||
const [runningAction, setRunningAction] = useState<ResourceAction | null>(null);
|
||||
|
||||
const isSudo = (cmd: string) => cmd.trimStart().startsWith('sudo');
|
||||
|
||||
const handleAction = async (action: ResourceAction, command: string) => {
|
||||
if (isSudo(command)) {
|
||||
onRun(command);
|
||||
return;
|
||||
}
|
||||
setRunningAction(action);
|
||||
try {
|
||||
const result = await runCommand(resource.id, action);
|
||||
if (result.exitCode === 0) {
|
||||
toast.success('Command completed successfully');
|
||||
} else {
|
||||
toast.error(result.output || `Command failed (exit code ${result.exitCode})`, { duration: 8000 });
|
||||
}
|
||||
} catch {
|
||||
toast.error('Failed to run command');
|
||||
} finally {
|
||||
setRunningAction(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{resource.installed ? (
|
||||
<span className="text-xs bg-green-100 text-green-700 rounded-full px-2 py-0.5">
|
||||
Installed{resource.version ? ` (${resource.version})` : ''}
|
||||
</span>
|
||||
<>
|
||||
<div>
|
||||
<span className="text-xs bg-green-100 text-green-700 rounded-full px-2 py-0.5">Installed</span>
|
||||
</div>
|
||||
{resource.version && (
|
||||
<div className="flex items-center gap-2">
|
||||
{resource.updateCommand && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 text-xs cursor-pointer"
|
||||
onClick={() => handleAction('update', resource.updateCommand!)}
|
||||
disabled={!!runningAction}
|
||||
>
|
||||
<RefreshCw className={`h-3 w-3 mr-1 ${runningAction === 'update' ? 'animate-spin' : ''}`} />
|
||||
Update
|
||||
</Button>
|
||||
)}
|
||||
<span className="text-xs text-duck-dark/50 dark:text-foreground/50">{resource.version}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-1.5">
|
||||
{!resource.version && resource.updateCommand && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 text-xs cursor-pointer"
|
||||
onClick={() => handleAction('update', resource.updateCommand!)}
|
||||
disabled={!!runningAction}
|
||||
>
|
||||
<RefreshCw className={`h-3 w-3 mr-1 ${runningAction === 'update' ? 'animate-spin' : ''}`} />
|
||||
Update
|
||||
</Button>
|
||||
)}
|
||||
{resource.verifyCommand && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 text-xs cursor-pointer"
|
||||
onClick={() => handleAction('verify', resource.verifyCommand!)}
|
||||
disabled={!!runningAction}
|
||||
>
|
||||
{runningAction === 'verify' && <Loader2 className="h-3 w-3 mr-1 animate-spin" />}
|
||||
Verify
|
||||
</Button>
|
||||
)}
|
||||
{resource.manageCommand && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 text-xs cursor-pointer"
|
||||
onClick={() => handleAction('manage', resource.manageCommand!)}
|
||||
disabled={!!runningAction}
|
||||
>
|
||||
{runningAction === 'manage' && <Loader2 className="h-3 w-3 mr-1 animate-spin" />}
|
||||
Manage
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{resource.uninstallCommand && (
|
||||
<div className="flex items-center mt-1.5">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 text-xs cursor-pointer text-red-500 hover:text-red-600 border-red-500/30 hover:border-red-500/50 hover:bg-red-500/5"
|
||||
onClick={() => handleAction('uninstall', resource.uninstallCommand!)}
|
||||
disabled={!!runningAction}
|
||||
>
|
||||
{runningAction === 'uninstall' ? (
|
||||
<Loader2 className="h-3 w-3 mr-1 animate-spin" />
|
||||
) : (
|
||||
<Trash2 className="h-3 w-3 mr-1" />
|
||||
)}
|
||||
Uninstall
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<span className="text-xs bg-duck-dark/5 text-duck-dark/40 rounded-full px-2 py-0.5">Not installed</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs bg-duck-dark/5 text-duck-dark/40 rounded-full px-2 py-0.5">Not installed</span>
|
||||
{resource.installCommand && (
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-6 text-xs bg-duck-teal text-duck-yellow hover:bg-duck-teal/90 cursor-pointer"
|
||||
onClick={() => handleAction('install', resource.installCommand!)}
|
||||
disabled={runningAction === 'install'}
|
||||
>
|
||||
{runningAction === 'install' ? 'Installing...' : 'Install'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
{resource.installCommand && <CommandBlock label="Install" command={resource.installCommand} onRun={onRun} />}
|
||||
{resource.uninstallCommand && (
|
||||
<CommandBlock label="Uninstall" command={resource.uninstallCommand} onRun={onRun} />
|
||||
)}
|
||||
{resource.manageCommand && <CommandBlock label="Manage" command={resource.manageCommand} onRun={onRun} />}
|
||||
{resource.verifyCommand && <CommandBlock label="Verify" command={resource.verifyCommand} onRun={onRun} />}
|
||||
{resource.updateCommand && <CommandBlock label="Update" command={resource.updateCommand} onRun={onRun} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
type CatalogCardProps = {
|
||||
resource: Resource;
|
||||
@@ -306,18 +410,12 @@ const ResourceDetail = ({ resource }: { resource: Resource }) => {
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Run with elevated privileges</AlertDialogTitle>
|
||||
<AlertDialogDescription asChild>
|
||||
<div>
|
||||
You are about to run a command with elevated privileges (sudo)
|
||||
<br />
|
||||
<span className="text-red-500">
|
||||
in the host machine.
|
||||
<br />
|
||||
ARE YOU SURE?
|
||||
</span>
|
||||
</div>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<p className="text-sm text-duck-dark/70 dark:text-foreground/70">
|
||||
For this operation the script must be run with elevated privileges (sudo) on the host machine.
|
||||
<br />
|
||||
Not to worry, though, we wrote it and battle tested it ourselves.
|
||||
</p>
|
||||
<code className="text-sm font-mono bg-[#1a1a2e] text-[#e0e0e0] rounded-lg px-3 py-2 break-all">{confirmCommand}</code>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel className="cursor-pointer">Cancel</AlertDialogCancel>
|
||||
|
||||
@@ -58,5 +58,11 @@ export const useResources = () => {
|
||||
return client.post<PingResult>(`/server-settings/resources/${id}/ping`, { url });
|
||||
};
|
||||
|
||||
return { resources, isLoading, saveConnectionConfig, pingResource };
|
||||
const runCommand = async (id: string, action: string) => {
|
||||
const result = await client.post<{ exitCode: number; output: string }>(`/server-settings/resources/${id}/run`, { action });
|
||||
queryClient.invalidateQueries({ queryKey: RESOURCES_KEY });
|
||||
return result;
|
||||
};
|
||||
|
||||
return { resources, isLoading, saveConnectionConfig, pingResource, runCommand };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user