diff --git a/backend/internal/handler/instance_detail.go b/backend/internal/handler/instance_detail.go index dce6ac4..b102a90 100644 --- a/backend/internal/handler/instance_detail.go +++ b/backend/internal/handler/instance_detail.go @@ -702,11 +702,25 @@ func executeRedisQuery(instance *model.Instance, dbSelect string, command string } columns = []string{"field", "value"} } else { - rows = append(rows, map[string]interface{}{ - "command": command, - "result": fmt.Sprintf("%v", v), - }) - columns = []string{"command", "result"} + // Single value result (GET, HGET, TYPE, TTL, etc.) + // Try to extract key name from command + keyName := "" + if len(parts) > 1 { + keyName = parts[1] + } + if keyName != "" { + rows = append(rows, map[string]interface{}{ + "key": keyName, + "value": fmt.Sprintf("%v", v), + }) + columns = []string{"key", "value"} + } else { + rows = append(rows, map[string]interface{}{ + "command": command, + "result": fmt.Sprintf("%v", v), + }) + columns = []string{"command", "result"} + } } } diff --git a/frontend/src/pages/QueriesPage.tsx b/frontend/src/pages/QueriesPage.tsx index 261d8ee..5e72cef 100644 --- a/frontend/src/pages/QueriesPage.tsx +++ b/frontend/src/pages/QueriesPage.tsx @@ -5,7 +5,7 @@ import { StarOff, Download, Search, ArrowUpDown, Database, Table2, ArrowUp, ArrowDown, Eye, Hash, PanelLeftClose, Columns3, KeyRound, FolderOpen, Server, Zap, ClipboardPaste, - History, PanelRightClose, PanelRightOpen, + History, PanelRightClose, PanelRightOpen, Copy, Check, } from 'lucide-react'; // Card components replaced by design system utility classes import { useActionPermission } from '@/hooks/useAuth'; @@ -264,6 +264,46 @@ export default function QueriesPage() { downloadCSV(`query_result_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.csv`, exportToCSV(result.columns, result.rows)); }, [result]); + /* ---- Copy Result ---- */ + const [copied, setCopied] = useState(false); + const handleCopyResult = useCallback(() => { + if (!result) return; + let text = ''; + if (result.rows?.length && result.columns?.length) { + // Format as readable text + const lines = result.rows.map((row: Record) => + result.columns.map((col: string) => { + const val = String(row[col] ?? ''); + // Try to pretty-format JSON values + try { + const parsed = JSON.parse(val); + if (typeof parsed === 'object') return JSON.stringify(parsed, null, 2); + } catch { /* not JSON */ } + return val; + }).join('\t') + ); + text = [result.columns.join('\t'), ...lines].join('\n'); + } else { + text = JSON.stringify(result, null, 2); + } + navigator.clipboard.writeText(text).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + }, [result]); + + /* ---- Smart cell value formatter (detect JSON) ---- */ + const formatCellValue = useCallback((val: unknown) => { + const str = String(val ?? ''); + try { + const parsed = JSON.parse(str); + if (typeof parsed === 'object' && parsed !== null) { + return { text: JSON.stringify(parsed, null, 2), isJson: true }; + } + } catch { /* not JSON */ } + return { text: str, isJson: false }; + }, []); + /* ---- History ---- */ const toggleFavorite = useCallback((id: number) => { setHistory(prev => { @@ -439,21 +479,27 @@ export default function QueriesPage() { 查询结果
- {result.execution_time_ms}ms + {result.execution_time_ms || result.cost_ms || 0}ms {result.rows?.length || 0} 行 {result.columns?.length || 0} 列 {result.rows && result.rows.length > 0 && ( - + <> + + + )}
{result.columns && result.columns.length > 0 ? ( -
+
@@ -481,9 +527,12 @@ export default function QueriesPage() { {result.columns.map(col => { const isSensitive = sensitiveKeywords.some(k => col.toLowerCase().includes(k)); const cellValue = row[col] ?? ''; + const { text: displayText, isJson } = formatCellValue(cellValue); return ( - ); })}
- {isSensitive ? {maskData(String(cellValue))} : String(cellValue)} + + {isSensitive ? {maskData(String(cellValue))} : + isJson ? {displayText} : + String(cellValue)}