diff --git a/backend/internal/handler/instance_detail.go b/backend/internal/handler/instance_detail.go index a5d3953..ea42bcf 100644 --- a/backend/internal/handler/instance_detail.go +++ b/backend/internal/handler/instance_detail.go @@ -915,14 +915,35 @@ func executeRedisQuery(instance *model.Instance, dbSelect string, command string prettyJSON = strings.TrimRight(buf.String(), "\n") } } - return gin.H{ + // ARDM-style: auto-fetch key type and TTL for KeyHeader + var keyType, keyTTL string + if keyName != "" && cmd != "TYPE" && cmd != "TTL" && cmd != "PTTL" && cmd != "EXISTS" && cmd != "DBSIZE" && cmd != "INFO" { + if t, err := client.Type(ctx, keyName).Result(); err == nil { + keyType = t + } + if ttl, err := client.TTL(ctx, keyName).Result(); err == nil { + if ttl < 0 { + keyTTL = "永不过期" + } else { + keyTTL = fmt.Sprintf("%vs", int(ttl.Seconds())) + } + } + } + result := gin.H{ "columns": []string{}, "rows": []interface{}{}, "total": 0, "raw_value": prettyJSON != "", "raw_key": keyName, "raw_text": rawValue, - }, nil + } + if keyType != "" { + result["raw_type"] = keyType + } + if keyTTL != "" { + result["raw_ttl"] = keyTTL + } + return result, nil } } diff --git a/frontend/src/pages/QueriesPage.tsx b/frontend/src/pages/QueriesPage.tsx index da8e2bc..0d9dd95 100644 --- a/frontend/src/pages/QueriesPage.tsx +++ b/frontend/src/pages/QueriesPage.tsx @@ -31,6 +31,8 @@ interface QueryResult { raw_value?: boolean; raw_key?: string; raw_text?: string; + raw_type?: string; + raw_ttl?: string; error?: string; masked_columns?: string[]; } @@ -132,6 +134,9 @@ export default function QueriesPage() { const [rowHeight] = useState(36); const [visibleRange, setVisibleRange] = useState({ start: 0, end: 50 }); const resultContainerRef = useRef(null); + // -- Sidebar 拖拽宽度 -- + const [sideWidth, setSideWidth] = useState(() => Number(localStorage.getItem('query-side-width')) || 280); + const sidebarDragRef = useRef(null); // -- 历史 state -- const [history, setHistory] = useState(() => { @@ -316,6 +321,28 @@ export default function QueriesPage() { setHistory([]); setClearConfirm(false); }, [clearConfirm]); + // -- Sidebar 拖拽 -- + const handleDragStart = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + const startX = e.clientX; + const startWidth = sideWidth; + const onMove = (ev: MouseEvent) => { + const delta = ev.clientX - startX; + const next = Math.min(600, Math.max(200, startWidth + delta)); + setSideWidth(next); + }; + const onUp = () => { + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + setSideWidth(w => { + localStorage.setItem('query-side-width', String(w)); + return w; + }); + }; + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + }, [sideWidth]); + const filteredHistory = useMemo(() => { let r = history; if (favFilter) r = r.filter(h => h.favorite); @@ -435,16 +462,25 @@ export default function QueriesPage() {
{/* Schema 侧边栏(左侧,独立) */} {showSidebar && instanceId && ( -
- { - const sep = dbType === 'redis' ? (sql.trim() ? '\\n' : '') : (sql.trim() ? ' ' : ''); - setSql(prev => prev + sep + text + (dbType === 'redis' ? '' : ' ')); - }} - onDatabaseChange={setDatabase} - singleDb={!!database} /> -
+ <> +
+ { + const sep = dbType === 'redis' ? (sql.trim() ? '\\n' : '') : (sql.trim() ? ' ' : ''); + setSql(prev => prev + sep + text + (dbType === 'redis' ? '' : ' ')); + }} + onDatabaseChange={setDatabase} + singleDb={!!database} /> +
+
+
+
+ )} {/* 编辑器 + 查询结果(右侧主体,垂直排列) */} @@ -520,13 +556,35 @@ export default function QueriesPage() { {showResult && (
{result.raw_text !== undefined ? ( - /* Raw value display (Redis GET/HGET/TTL etc.) */ -
+ /* Raw value display — ARDM-style FormatViewer */ +
+ {/* Key info bar (ARDM KeyHeader style) */} {result.raw_key && ( -
{result.raw_key}
+
+ + {result.raw_key} + {result.raw_type && ( + {result.raw_type} + )} + {result.raw_ttl && ( + {result.raw_ttl} + )} +
+ {result.raw_value ? 'JSON' : 'String'} + +
)} + {/* Formatted value */}
-
               ) : result.columns && result.columns.length > 0 ? (
                 
+ {/* ARDM-style: Hash field/value compact list for Redis HGETALL */} + {dbType === 'redis' && result.columns.length === 2 && result.columns[0] === 'field' && result.columns[1] === 'value' ? ( +
+ {(sortedResult?.rows || []).map((row, i) => { + const field = String(row.field ?? ''); + const value = String(row.value ?? ''); + const isJson = (() => { try { const p = JSON.parse(value); return typeof p === 'object'; } catch { return false; } })(); + const formatted = isJson ? JSON.stringify(JSON.parse(value), null, 2) : value; + return ( +
+
+ {field} +
+
+ {isJson ? ( +
{formatted}
+ ) : ( + {value} + )} +
+
+ ); + })} +
+ ) : ( + /* Standard table for other results */ @@ -588,6 +672,7 @@ export default function QueriesPage() {
+ )} {/* Pagination */} {result.total > pageSize && (