feat: TicketCreatePage tab化 + SchemaSidebar + Redis快速搜索 + Dashboard导航修复
TicketCreatePage 重构: - SQL编辑器右侧新增数据库浏览器面板(tab化) - 表结构Tab: 表列表→字段展开(PK/索引标记)→点击插入SQL - 索引Tab: 选中表的索引详情(索引名/类型/字段/基数) - Redis Keys Tab: DB选择器+Key模式搜索+Key列表+类型/TTL/值查看 SchemaSidebar 新组件(365行): - MySQL/PostgreSQL: 左侧数据库表树→字段→点击插入SQL - Redis: Key树(DB选择→模式搜索→Key值查看, 类似Another Redis Desktop Manager) - 用于 QueriesPage 查询页面 Dashboard: - 快捷按钮改用 useNavigate + onClick 替代 <a href> API增强: - redis.ts: 新增 getKeys/createKey/deleteKey 方法 - instanceDetail.ts: 导出 TableMeta/TableDetail/ColumnInfo/IndexInfo 类型接口
This commit is contained in:
1 parent
8471f368eb
commit
0fb947dcb5
6 files changed
+1020
-476
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
ClipboardList, Clock, CheckCircle, AlertTriangle,
|
||||
Database, ChevronRight, X, AlertCircle, Info, Bell,
|
||||
@@ -125,6 +126,7 @@ export default function Dashboard() {
|
||||
const chartInstanceRef = useRef<echarts.ECharts | null>(null);
|
||||
const { theme } = useTheme();
|
||||
const { user } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const [dismissedIds, setDismissedIds] = useState<Set<string>>(new Set());
|
||||
const [readIds, setReadIds] = useState<Set<string>>(new Set());
|
||||
const [expandedId, setExpandedId] = useState<string | null>(null);
|
||||
@@ -338,9 +340,9 @@ export default function Dashboard() {
|
||||
}, [stats]);
|
||||
|
||||
const quickActions = [
|
||||
{ label: '新建工单', icon: FileText, href: '/tickets', color: 'text-blue-400', bg: 'bg-blue-400/10', border: 'border-blue-400/20' },
|
||||
{ label: '新建查询', icon: Search, href: '/queries', color: 'text-green-400', bg: 'bg-green-400/10', border: 'border-green-400/20' },
|
||||
{ label: '添加实例', icon: Database, href: '/instances', color: 'text-purple-400', bg: 'bg-purple-400/10', border: 'border-purple-400/20' },
|
||||
{ label: '新建工单', icon: FileText, onClick: () => navigate('/tickets/create'), color: 'text-blue-400', bg: 'bg-blue-400/10', border: 'border-blue-400/20' },
|
||||
{ label: '新建查询', icon: Search, onClick: () => navigate('/queries'), color: 'text-green-400', bg: 'bg-green-400/10', border: 'border-green-400/20' },
|
||||
{ label: '添加实例', icon: Database, onClick: () => navigate('/instances'), color: 'text-purple-400', bg: 'bg-purple-400/10', border: 'border-purple-400/20' },
|
||||
];
|
||||
|
||||
const TrendIcon = ({ trend }: { trend: 'up' | 'down' | 'neutral' }) => {
|
||||
@@ -371,15 +373,15 @@ export default function Dashboard() {
|
||||
{/* ---- Quick Actions ---- */}
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{quickActions.map(action => (
|
||||
<a
|
||||
<button
|
||||
key={action.label}
|
||||
href={action.href}
|
||||
onClick={action.onClick}
|
||||
className={`flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium border transition-all ${action.bg} ${action.color} ${action.border} hover:opacity-80`}
|
||||
>
|
||||
<action.icon className="w-4 h-4" />
|
||||
{action.label}
|
||||
<ArrowUpRight className="w-3 h-3 opacity-50" />
|
||||
</a>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||
import {
|
||||
Plus, X, AlertTriangle, CheckCircle, Play, Loader2,
|
||||
ChevronDown, Clock, FileText, Sparkles, Star,
|
||||
StarOff, Download, Search, ArrowUpDown,
|
||||
ArrowUp, ArrowDown, Eye, Hash, PanelLeftClose,
|
||||
ChevronDown, ChevronRight, Clock, FileText, Sparkles, Star,
|
||||
StarOff, Download, Search, ArrowUpDown, Database, Table2,
|
||||
ArrowUp, ArrowDown, Eye, Hash, PanelLeftClose, Columns3,
|
||||
KeyRound, FolderOpen, Server, Zap,
|
||||
} from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { useActionPermission } from '@/hooks/useAuth';
|
||||
@@ -12,7 +13,10 @@ import EmptyState from '@/components/EmptyState';
|
||||
import { queryApi } from '@/api/query';
|
||||
import { instanceApi } from '@/api/instance';
|
||||
import SqlEditor from '@/components/SqlEditor';
|
||||
import SchemaSidebar from '@/components/SchemaSidebar';
|
||||
import { getTemplateSQL, getTemplatesForType } from '@/lib/queryTemplates';
|
||||
import { instanceDetailApi, type TableMeta, type ColumnInfo } from '@/api/instanceDetail';
|
||||
import { redisApi } from '@/api/redis';
|
||||
import type { DBType, QueryTemplate } from '@/lib/queryTemplates';
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -299,6 +303,7 @@ function NewQueryPanel({
|
||||
const [rowHeight] = useState(36);
|
||||
const [visibleRange, setVisibleRange] = useState({ start: 0, end: 50 });
|
||||
const resultContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [showSidebar, setShowSidebar] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
instanceApi.list().then(res => {
|
||||
@@ -564,6 +569,20 @@ function NewQueryPanel({
|
||||
<FileText className="w-4 h-4 text-[var(--dg-text-secondary)]" />
|
||||
查询语句 <span className="text-red-400">*</span>
|
||||
</label>
|
||||
<div className="flex items-center gap-2">
|
||||
{instanceId && (
|
||||
<button
|
||||
onClick={() => setShowSidebar(!showSidebar)}
|
||||
className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border transition-all ${
|
||||
showSidebar
|
||||
? 'bg-blue-500/15 text-blue-400 border-blue-500/30'
|
||||
: 'text-[var(--dg-text-secondary)] bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] hover:bg-[var(--dg-bg-hover)]'
|
||||
}`}
|
||||
>
|
||||
<Database className="w-3.5 h-3.5" />
|
||||
{showSidebar ? '隐藏导航' : '显示导航'}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setShowTemplates(!showTemplates)}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium bg-[var(--dg-bg-tertiary)] hover:bg-[var(--dg-bg-hover)] border border-[var(--dg-border)] hover:border-[var(--dg-text-muted)] rounded-lg transition-all"
|
||||
@@ -572,6 +591,7 @@ function NewQueryPanel({
|
||||
快捷模板 ({templates.length})
|
||||
<ChevronDown className={`w-3.5 h-3.5 transition-transform ${showTemplates ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showTemplates && (
|
||||
@@ -626,9 +646,23 @@ function NewQueryPanel({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* CodeMirror Editor */}
|
||||
<div className="dg-card overflow-hidden">
|
||||
<SqlEditor
|
||||
{/* CodeMirror Editor + Schema Sidebar */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
|
||||
{/* Schema Sidebar */}
|
||||
{showSidebar && instanceId && (
|
||||
<div className="lg:col-span-1 h-[280px]">
|
||||
<SchemaSidebar
|
||||
instanceId={instanceId}
|
||||
database={database}
|
||||
dbType={dbType}
|
||||
onInsert={(text) => setSql(prev => prev + (prev.trim() ? ' ' : '') + text + ' ')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* Editor */}
|
||||
<div className={showSidebar && instanceId ? 'lg:col-span-3' : 'lg:col-span-4'}>
|
||||
<div className="dg-card overflow-hidden">
|
||||
<SqlEditor
|
||||
value={sql}
|
||||
onChange={setSql}
|
||||
placeholder={placeholderMap[dbType]}
|
||||
@@ -655,6 +689,8 @@ function NewQueryPanel({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Security notice */}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user