mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
feat: add unified UI for managing allow/deny command lists
- Updated CommandPatternSelector to show both allow and deny options - Added mutual exclusivity between allow and deny states - Added visual indicators (green check for allow, red X for deny) - Added translation strings for the new UI - Integrated deny command handling in CommandExecution component
This commit is contained in:
parent
a0e8c24352
commit
18e3cd1878
3 changed files with 124 additions and 22 deletions
|
|
@ -26,7 +26,7 @@ interface CommandExecutionProps {
|
|||
|
||||
export const CommandExecution = ({ executionId, text, icon, title }: CommandExecutionProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
const { terminalShellIntegrationDisabled = false, allowedCommands = [] } = useExtensionState()
|
||||
const { terminalShellIntegrationDisabled = false, allowedCommands = [], deniedCommands = [] } = useExtensionState()
|
||||
|
||||
const { command, output: parsedOutput, suggestions } = useMemo(() => parseCommandAndOutput(text), [text])
|
||||
|
||||
|
|
@ -266,6 +266,30 @@ export const CommandExecution = ({ executionId, text, icon, title }: CommandExec
|
|||
[allowedCommands],
|
||||
)
|
||||
|
||||
const handleDenyPatternChange = useCallback(
|
||||
(pattern: string) => {
|
||||
if (!pattern) return
|
||||
|
||||
const isDenied = deniedCommands.includes(pattern)
|
||||
let updatedDeniedCommands: string[]
|
||||
|
||||
if (isDenied) {
|
||||
// Remove from deny list
|
||||
updatedDeniedCommands = deniedCommands.filter((p) => p !== pattern)
|
||||
} else {
|
||||
// Add to deny list
|
||||
updatedDeniedCommands = [...deniedCommands, pattern]
|
||||
}
|
||||
|
||||
// Send message to update denied commands
|
||||
vscode.postMessage({
|
||||
type: "deniedCommands",
|
||||
commands: updatedDeniedCommands,
|
||||
})
|
||||
},
|
||||
[deniedCommands],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
{/* Header section */}
|
||||
|
|
@ -337,12 +361,14 @@ export const CommandExecution = ({ executionId, text, icon, title }: CommandExec
|
|||
<CodeBlock source={command} language="shell" />
|
||||
</div>
|
||||
|
||||
{/* Whitelist section */}
|
||||
{/* Command management section */}
|
||||
{showSuggestions && (
|
||||
<CommandPatternSelector
|
||||
patterns={commandPatterns}
|
||||
allowedCommands={allowedCommands}
|
||||
onPatternChange={handleAllowPatternChange}
|
||||
deniedCommands={deniedCommands}
|
||||
onAllowPatternChange={handleAllowPatternChange}
|
||||
onDenyPatternChange={handleDenyPatternChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { useState } from "react"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
import { ChevronDown, Check, X } from "lucide-react"
|
||||
import { useAppTranslation } from "@src/i18n/TranslationContext"
|
||||
import { cn } from "@src/lib/utils"
|
||||
|
||||
|
|
@ -12,10 +11,18 @@ interface CommandPattern {
|
|||
interface CommandPatternSelectorProps {
|
||||
patterns: CommandPattern[]
|
||||
allowedCommands: string[]
|
||||
onPatternChange: (pattern: string) => void
|
||||
deniedCommands: string[]
|
||||
onAllowPatternChange: (pattern: string) => void
|
||||
onDenyPatternChange: (pattern: string) => void
|
||||
}
|
||||
|
||||
export const CommandPatternSelector = ({ patterns, allowedCommands, onPatternChange }: CommandPatternSelectorProps) => {
|
||||
export const CommandPatternSelector = ({
|
||||
patterns,
|
||||
allowedCommands,
|
||||
deniedCommands,
|
||||
onAllowPatternChange,
|
||||
onDenyPatternChange,
|
||||
}: CommandPatternSelectorProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
|
||||
|
|
@ -23,12 +30,38 @@ export const CommandPatternSelector = ({ patterns, allowedCommands, onPatternCha
|
|||
return null
|
||||
}
|
||||
|
||||
const getPatternStatus = (pattern: string): "allowed" | "denied" | "none" => {
|
||||
if (allowedCommands.includes(pattern)) return "allowed"
|
||||
if (deniedCommands.includes(pattern)) return "denied"
|
||||
return "none"
|
||||
}
|
||||
|
||||
const handleAllowClick = (pattern: string) => {
|
||||
const status = getPatternStatus(pattern)
|
||||
if (status === "denied") {
|
||||
// Remove from denied list first
|
||||
onDenyPatternChange(pattern)
|
||||
}
|
||||
// Toggle allow status
|
||||
onAllowPatternChange(pattern)
|
||||
}
|
||||
|
||||
const handleDenyClick = (pattern: string) => {
|
||||
const status = getPatternStatus(pattern)
|
||||
if (status === "allowed") {
|
||||
// Remove from allowed list first
|
||||
onAllowPatternChange(pattern)
|
||||
}
|
||||
// Toggle deny status
|
||||
onDenyPatternChange(pattern)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border-t border-vscode-panel-border bg-vscode-sideBar-background/30">
|
||||
<button
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
className="flex items-center gap-2 w-full px-3 py-2 text-xs text-vscode-descriptionForeground hover:text-vscode-foreground hover:bg-vscode-list-hoverBackground transition-all"
|
||||
aria-label={isExpanded ? "Collapse allowed commands section" : "Expand allowed commands section"}
|
||||
aria-label={isExpanded ? "Collapse command management section" : "Expand command management section"}
|
||||
aria-expanded={isExpanded}>
|
||||
<ChevronDown
|
||||
className={cn("size-3 transition-transform duration-200", {
|
||||
|
|
@ -36,21 +69,62 @@ export const CommandPatternSelector = ({ patterns, allowedCommands, onPatternCha
|
|||
"-rotate-90": !isExpanded,
|
||||
})}
|
||||
/>
|
||||
<span className="font-medium">{t("chat:commandExecution.addToAllowedCommands")}</span>
|
||||
<span className="font-medium">{t("chat:commandExecution.manageCommands")}</span>
|
||||
</button>
|
||||
{isExpanded && (
|
||||
<div className="px-3 pb-3 space-y-1.5">
|
||||
{patterns.map((item, index) => (
|
||||
<div key={`${item.pattern}-${index}`} className="ml-5">
|
||||
<VSCodeCheckbox
|
||||
checked={allowedCommands.includes(item.pattern)}
|
||||
onChange={() => onPatternChange(item.pattern)}
|
||||
className="text-xs"
|
||||
aria-label={`Allow command pattern: ${item.pattern}`}>
|
||||
<span className="font-mono text-vscode-foreground">{item.pattern}</span>
|
||||
</VSCodeCheckbox>
|
||||
</div>
|
||||
))}
|
||||
<div className="px-3 pb-3 space-y-2">
|
||||
<div className="text-xs text-vscode-descriptionForeground mb-2 ml-5">
|
||||
{t("chat:commandExecution.commandManagementDescription")}
|
||||
</div>
|
||||
{patterns.map((item, index) => {
|
||||
const status = getPatternStatus(item.pattern)
|
||||
return (
|
||||
<div key={`${item.pattern}-${index}`} className="ml-5 flex items-center gap-2">
|
||||
<div className="flex-1">
|
||||
<span className="font-mono text-xs text-vscode-foreground">{item.pattern}</span>
|
||||
{item.description && (
|
||||
<span className="text-xs text-vscode-descriptionForeground ml-2">
|
||||
- {item.description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => handleAllowClick(item.pattern)}
|
||||
className={cn(
|
||||
"p-1 rounded transition-all",
|
||||
status === "allowed"
|
||||
? "bg-green-500/20 text-green-500 hover:bg-green-500/30"
|
||||
: "text-vscode-descriptionForeground hover:text-green-500 hover:bg-green-500/10",
|
||||
)}
|
||||
aria-label={
|
||||
status === "allowed"
|
||||
? `Remove ${item.pattern} from allowed list`
|
||||
: `Add ${item.pattern} to allowed list`
|
||||
}
|
||||
title={status === "allowed" ? "Remove from allowed" : "Add to allowed"}>
|
||||
<Check className="size-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDenyClick(item.pattern)}
|
||||
className={cn(
|
||||
"p-1 rounded transition-all",
|
||||
status === "denied"
|
||||
? "bg-red-500/20 text-red-500 hover:bg-red-500/30"
|
||||
: "text-vscode-descriptionForeground hover:text-red-500 hover:bg-red-500/10",
|
||||
)}
|
||||
aria-label={
|
||||
status === "denied"
|
||||
? `Remove ${item.pattern} from denied list`
|
||||
: `Add ${item.pattern} to denied list`
|
||||
}
|
||||
title={status === "denied" ? "Remove from denied" : "Add to denied"}>
|
||||
<X className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -213,7 +213,9 @@
|
|||
"exited": "Exited ({{exitCode}})",
|
||||
"addToAllowedCommands": "Add to Allowed Auto-Execute Commands",
|
||||
"allowAllNpmRun": "Allow all npm run commands",
|
||||
"allowAllNpm": "Allow all npm commands"
|
||||
"allowAllNpm": "Allow all npm commands",
|
||||
"manageCommands": "Manage Command Permissions",
|
||||
"commandManagementDescription": "Click ✓ to allow auto-execution, ✗ to deny execution"
|
||||
},
|
||||
"commandOutput": "Command Output",
|
||||
"response": "Response",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue