🐛 fix(sql-log): 统一 V2 SQL 日志入口

- V2 左侧入口改为打开当前工作区内嵌 SQL 日志,legacy 继续使用底部全局面板
- 表数据页新增 sqlLog 视图并复用嵌入式 LogPanel,避免无 SQL 编辑器时无法查看日志
- 移除 V2 侧栏底部重复 SQL 日志按钮,保留慢查询入口并补充回归测试
This commit is contained in:
Syngnat
2026-06-25 17:39:57 +08:00
parent 9ab31a7614
commit aebe9bab54
17 changed files with 187 additions and 69 deletions

View File

@@ -117,22 +117,37 @@ describe('tool center menu entries', () => {
expect(appSource).toContain('const handleOpenToolsModal = useCallback(');
expect(appSource).toContain('const handleOpenSettingsModal = useCallback(');
expect(appSource).toContain('const handleToggleLogPanel = useCallback(');
expect(appSource).toContain('new CustomEvent');
expect(appSource).toContain("'gonavi:show-sql-execution-log'");
expect(appSource).toContain("detail: { mode: 'open' }");
expect(appSource).toContain('toggleAppLogPanel();');
expect(appSource).toContain('const handleFocusSidebarSearch = useCallback(');
expect(appSource).toContain('const antdTheme = useMemo(() => ({');
expect(appSource).toContain('theme={antdTheme}');
expect(appSource).toContain('const sqlLogCount = useStore(state => state.sqlLogs.length);');
expect(appSource).toContain('onOpenTools={handleOpenToolsModal}');
expect(appSource).toContain('onOpenSettings={handleOpenSettingsModal}');
expect(appSource).toContain('onToggleLogPanel={handleToggleLogPanel}');
expect(appSource).toContain('onFocusCommandSearch={handleFocusSidebarSearch}');
expect(appSource).toContain('sqlLogCount={sqlLogCount}');
expect(appSource).not.toContain('onOpenTools={() => setIsToolsModalOpen(true)}');
expect(appSource).not.toContain('onOpenSettings={() => setIsSettingsModalOpen(true)}');
expect(appSource).not.toContain('onToggleLogPanel={() => setIsLogPanelOpen((prev) => !prev)}');
expect(appSource).not.toContain('sqlLogCount={sqlLogCount}');
expect(appSource).not.toContain('theme={{');
expect(appSource).not.toContain('const sqlLogs = useStore(state => state.sqlLogs);');
});
it('renders the shared SQL log panel only for legacy layouts', () => {
const logPanelIndex = appSource.indexOf('<LogPanel', appSource.indexOf('<Content'));
const logPanelGuardIndex = appSource.lastIndexOf('{isLogPanelOpen && (', logPanelIndex);
const legacyOnlyGuardIndex = appSource.lastIndexOf('{!isV2Ui && isLogPanelOpen && (', logPanelIndex);
expect(logPanelIndex).toBeGreaterThan(-1);
expect(logPanelGuardIndex).toBe(-1);
expect(legacyOnlyGuardIndex).toBeGreaterThan(-1);
expect(appSource).toContain('onClose={handleCloseLogPanel}');
expect(appSource).toContain('onResizeStart={handleLogResizeStart}');
});
it('lets the v2 Sidebar own the entire left layout instead of stacking legacy controls above it', () => {
const siderIndex = appSource.indexOf("className={isV2Ui ? 'gn-v2-app-sider' : undefined}");
const legacyGuardIndex = appSource.indexOf('{!isV2Ui && (', siderIndex);

View File

@@ -391,7 +391,6 @@ function App() {
const aiPanelVisible = useStore(state => state.aiPanelVisible);
const toggleAIPanel = useStore(state => state.toggleAIPanel);
const setAIPanelVisible = useStore(state => state.setAIPanelVisible);
const sqlLogCount = useStore(state => state.sqlLogs.length);
const globalProxyInvalidHintShownRef = React.useRef(false);
const windowDiagSequenceRef = React.useRef(0);
const windowDiagLastSignatureRef = React.useRef('');
@@ -2139,23 +2138,23 @@ function App() {
const {
handleCloseLogPanel: handleCloseLegacyLogPanel,
handleCloseLogPanel: handleCloseAppLogPanel,
handleLogResizeStart,
handleToggleLogPanel: toggleLegacyLogPanel,
handleToggleLogPanel: toggleAppLogPanel,
isLogPanelOpen,
logGhostRef,
logPanelHeight,
} = useAppLogPanelResize();
const handleToggleLogPanel = useCallback(() => {
if (isV2Ui) {
window.dispatchEvent(new CustomEvent('gonavi:show-sql-execution-log'));
window.dispatchEvent(new CustomEvent('gonavi:show-sql-execution-log', { detail: { mode: 'open' } }));
return;
}
toggleLegacyLogPanel();
}, [isV2Ui, toggleLegacyLogPanel]);
toggleAppLogPanel();
}, [isV2Ui, toggleAppLogPanel]);
const handleCloseLogPanel = useCallback(() => {
handleCloseLegacyLogPanel();
}, [handleCloseLegacyLogPanel]);
handleCloseAppLogPanel();
}, [handleCloseAppLogPanel]);
const handleCreateConnection = useCallback(() => {
setSecurityUpdateRepairSource(null);
@@ -2944,7 +2943,6 @@ function App() {
onOpenSettings={handleOpenSettingsModal}
onToggleAI={toggleAIPanel}
onToggleLogPanel={handleToggleLogPanel}
sqlLogCount={sqlLogCount}
uiVersion={appearance.uiVersion}
onFocusCommandSearch={handleFocusSidebarSearch}
/>

View File

@@ -34,6 +34,12 @@ const readDataGridSource = () => [
'./DataGridCore.tsx',
'./DataGridShell.tsx',
].map((file) => readFileSync(new URL(file, import.meta.url), 'utf8')).join('\n');
const readDataViewerSource = (): string =>
readFileSync(new URL('./DataViewer.tsx', import.meta.url), 'utf8');
const readDataGridSecondaryActionsSource = (): string =>
readFileSync(new URL('./DataGridSecondaryActions.tsx', import.meta.url), 'utf8');
const readDataGridShellSource = (): string =>
readFileSync(new URL('./DataGridShell.tsx', import.meta.url), 'utf8');
const mockStoreState = vi.hoisted(() => ({
languagePreference: 'system' as LanguagePreference,
@@ -172,6 +178,7 @@ describe('DataGrid layout', () => {
expect(markup).toContain('data-grid-column-quick-find-action="true"');
expect(markup).toContain('字段显示');
expect(markup).toContain('跳列');
expect(markup).toContain('日志');
expect(markup).toContain(zhObjectDesignLabel);
expect(markup).toContain('data-grid-page-find="true"');
expect(markup).toContain('data-grid-page-find-prev="true"');
@@ -190,6 +197,27 @@ describe('DataGrid layout', () => {
expect(markup).toContain('当前页查找...');
});
it('opens the embedded SQL log view from the shared V2 SQL log event in table data tabs', () => {
const source = readDataGridSource();
const dataViewerSource = readDataViewerSource();
const secondaryActionsSource = readDataGridSecondaryActionsSource();
const shellSource = readDataGridShellSource();
expect(dataViewerSource).toContain('isActive={isActive}');
expect(dataViewerSource).toContain('enableSqlLogEvent');
expect(source).toContain("isActive = true");
expect(source).toContain("enableSqlLogEvent = false");
expect(source).toContain("'gonavi:show-sql-execution-log'");
expect(source).toContain("if (!enableSqlLogEvent || !isV2Ui || !isActive) return;");
expect(source).toContain("handleViewModeChange('sqlLog');");
expect(source).toContain("'sqlLog'");
expect(shellSource).toContain('import LogPanel from');
expect(shellSource).toContain("viewMode === 'sqlLog'");
expect(shellSource).toContain('<LogPanel variant="embedded" />');
expect(secondaryActionsSource).toContain("key: 'sqlLog'");
expect(secondaryActionsSource).toContain("translate('log_panel.short_title')");
});
it('localizes DataGrid error boundary, column drag affordances, and legacy row context menu labels through i18n keys', () => {
const source = readDataGridSource();
const expectedKeys = [

View File

@@ -286,7 +286,7 @@ const DataGrid: React.FC<DataGridProps> = ({
resultExportAllSql,
onReload, onSort, onPageChange, pagination, onRequestTotalCount, onCancelTotalCount, sortInfoExternal, showFilter, onToggleFilter, exportSqlWithFilter, onApplyFilter, appliedFilterConditions, quickWhereCondition,
onApplyQuickWhereCondition,
scrollSnapshot, onScrollSnapshotChange, toolbarExtraActions, showRowNumberColumn = false
scrollSnapshot, onScrollSnapshotChange, toolbarExtraActions, showRowNumberColumn = false, isActive = true, enableSqlLogEvent = false
}) => {
const connections = useStore(state => state.connections);
const addTab = useStore(state => state.addTab);
@@ -1351,13 +1351,23 @@ const DataGrid: React.FC<DataGridProps> = ({
if (String(detail.tableName || '') !== String(tableName || '')) return;
const nextMode = String(detail.viewMode || '').trim();
if (!nextMode) return;
if (!['table', 'json', 'text', 'fields', 'ddl', 'er'].includes(nextMode)) return;
if (!['table', 'json', 'text', 'fields', 'ddl', 'er', 'sqlLog'].includes(nextMode)) return;
handleViewModeChange(nextMode as GridViewMode);
};
window.addEventListener('gonavi:data-grid:set-view-mode', handleExternalViewModeChange as EventListener);
return () => window.removeEventListener('gonavi:data-grid:set-view-mode', handleExternalViewModeChange as EventListener);
}, [canOpenObjectDesigner, connectionId, dbName, handleViewModeChange, tableName]);
}, [connectionId, dbName, handleViewModeChange, tableName]);
useEffect(() => {
if (!enableSqlLogEvent || !isV2Ui || !isActive) return;
const handleOpenSqlExecutionLog = () => {
handleViewModeChange('sqlLog');
};
window.addEventListener('gonavi:show-sql-execution-log', handleOpenSqlExecutionLog as EventListener);
return () => window.removeEventListener('gonavi:show-sql-execution-log', handleOpenSqlExecutionLog as EventListener);
}, [enableSqlLogEvent, handleViewModeChange, isActive, isV2Ui]);
useEffect(() => {
if (!isTableSurfaceActive || !isV2Ui || !cellContextMenu.visible) return;

View File

@@ -1334,6 +1334,8 @@ interface DataGridProps {
scrollSnapshot?: { top: number; left: number };
onScrollSnapshotChange?: (snapshot: { top: number; left: number }) => void;
toolbarExtraActions?: React.ReactNode;
isActive?: boolean;
enableSqlLogEvent?: boolean;
}
type GridFilterCondition = FilterCondition & {
@@ -1344,7 +1346,7 @@ type GridFilterCondition = FilterCondition & {
value2?: string;
};
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er';
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er' | 'sqlLog';
type DdlViewLayoutMode = 'bottom' | 'side';
type DataGridExportScope = 'selected' | 'page' | 'all' | 'filteredAll';
type VirtualEditingCellState = {

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { Segmented } from 'antd';
import { t as defaultTranslate, type I18nParams } from '../i18n';
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er';
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er' | 'sqlLog';
export type DataGridResultViewTranslate = (key: string, params?: I18nParams) => string;

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { Button, Popover } from 'antd';
import {
AimOutlined,
BugOutlined,
ConsoleSqlOutlined,
EditOutlined,
FileTextOutlined,
@@ -10,7 +11,7 @@ import {
} from '@ant-design/icons';
import { t as defaultTranslate, type I18nParams } from '../i18n';
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er';
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er' | 'sqlLog';
export type DataGridSecondaryActionsTranslate = (key: string, params?: I18nParams) => string;
@@ -69,6 +70,7 @@ const DataGridSecondaryActions: React.FC<DataGridSecondaryActionsProps> = ({
{ key: 'fields', label: fieldsActionLabel, icon: fieldsActionIcon },
{ key: 'ddl', label: translate('data_grid.secondary.view_ddl'), icon: <ConsoleSqlOutlined />, disabled: !canViewDdl },
{ key: 'er', label: translate('data_grid.secondary.er_diagram'), icon: <LinkOutlined /> },
{ key: 'sqlLog', label: translate('log_panel.short_title'), icon: <BugOutlined /> },
];
return (

View File

@@ -9,6 +9,7 @@ import DataGridModals from './DataGridModals';
import DataGridPreviewPanel from './DataGridPreviewPanel';
import DataGridSecondaryActions from './DataGridSecondaryActions';
import DataGridToolbarFrame from './DataGridToolbarFrame';
import LogPanel from './LogPanel';
import { DataGridJsonView, DataGridTextView } from './DataGridRecordViews';
import { DataGridV2DdlSideWorkspace, DataGridV2DdlView } from './DataGridV2DdlWorkspace';
import { DataGridV2ErView, DataGridV2FieldsView } from './DataGridV2MetadataViews';
@@ -789,6 +790,8 @@ const renderDataTableView = () => (
onOpenTable={onOpenErTable}
translate={translateDataGrid}
/>
) : isV2Ui && viewMode === 'sqlLog' ? (
<LogPanel variant="embedded" />
) : viewMode === 'json' ? (
<DataGridJsonView
darkMode={darkMode}

View File

@@ -1229,6 +1229,8 @@ const DataViewer: React.FC<{ tab: TabData; isActive?: boolean }> = React.memo(({
exportSqlWithFilter={exportSqlWithFilter || undefined}
scrollSnapshot={scrollSnapshotRef.current}
onScrollSnapshotChange={handleTableScrollSnapshotChange}
isActive={isActive}
enableSqlLogEvent
/>
</div>
);

View File

@@ -989,7 +989,7 @@ describe('QueryEditor external SQL save', () => {
renderer.unmount();
});
it('opens the embedded sql execution log tab from the shared log toggle event in v2', async () => {
it('opens the embedded sql execution log tab from the shared log event in v2', async () => {
storeState.appearance.uiVersion = 'v2';
storeState.sqlLogs = [{
id: 'log-1',
@@ -1043,6 +1043,54 @@ describe('QueryEditor external SQL save', () => {
renderer.unmount();
});
it('keeps the embedded sql execution log tab open for explicit open events in v2', async () => {
storeState.appearance.uiVersion = 'v2';
storeState.sqlLogs = [{
id: 'log-1',
timestamp: Date.now(),
sql: 'select 1',
status: 'success',
duration: 12,
}];
const windowListeners: Record<string, ((event?: any) => void)[]> = {};
vi.stubGlobal('window', {
addEventListener: vi.fn((type: string, listener: (event?: any) => void) => {
windowListeners[type] ||= [];
windowListeners[type].push(listener);
}),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
requestAnimationFrame: vi.fn((callback: FrameRequestCallback) => {
callback(0);
return 1;
}),
cancelAnimationFrame: vi.fn(),
innerHeight: 900,
});
let renderer!: ReactTestRenderer;
await act(async () => {
renderer = create(<QueryEditor tab={createTab()} />);
});
const openEvent = new CustomEvent('gonavi:show-sql-execution-log', { detail: { mode: 'open' } });
await act(async () => {
windowListeners['gonavi:show-sql-execution-log']?.forEach((listener) => listener(openEvent));
});
expect(textContent(renderer.toJSON())).toContain('SQL 执行日志');
await act(async () => {
windowListeners['gonavi:show-sql-execution-log']?.forEach((listener) => listener(openEvent));
});
expect(textContent(renderer.toJSON())).toContain('SQL 执行日志');
expect(storeState.updateQueryTabDraft).toHaveBeenLastCalledWith('tab-1', {
resultPanelVisible: true,
});
renderer.unmount();
});
it('shows execution failures inside the embedded sql log tab in v2', async () => {
storeState.appearance.uiVersion = 'v2';
backendApp.DBQueryMulti.mockResolvedValueOnce({

View File

@@ -15,6 +15,7 @@ import QueryEditor, {
resolveQueryEditorNavigationDecorations,
resolveQueryEditorNavigationTarget,
} from './QueryEditor';
import QueryEditorResultsPanel from './QueryEditorResultsPanel';
const storeState = vi.hoisted(() => ({
connections: [
@@ -2645,20 +2646,59 @@ describe('QueryEditor external SQL save', () => {
expect(source).toContain("if (isEditableElement(event.target) && !inEditorPane) {");
});
it('embeds the sql execution log as a result tab instead of a standalone workspace panel in v2', () => {
it('keeps the embedded sql execution log limited to v2 query editor result tabs', () => {
const panelSource = readFileSync(new URL('./QueryEditorResultsPanel.tsx', import.meta.url), 'utf8');
const editorSource = readFileSync(new URL('./QueryEditor.tsx', import.meta.url), 'utf8');
expect(panelSource).toContain('QUERY_EDITOR_SQL_LOG_TAB_KEY');
expect(panelSource).toContain('const shouldShowSqlLogTab = isV2Ui && (sqlLogCount > 0 || activeResultKey === QUERY_EDITOR_SQL_LOG_TAB_KEY);');
expect(panelSource).toContain('<LogPanel');
expect(panelSource).toContain('variant="embedded"');
expect(panelSource).toContain('executionError={executionError}');
expect(panelSource).toContain("t('log_panel.short_title')");
expect(panelSource).toContain('[logTabItem, ...resultTabItems]');
expect(editorSource).toContain("window.addEventListener('gonavi:show-sql-execution-log'");
expect(editorSource).toContain("event instanceof CustomEvent && event.detail?.mode === 'open'");
expect(editorSource).toContain('setActiveResultKey(QUERY_EDITOR_SQL_LOG_TAB_KEY)');
});
it('does not render the embedded sql execution log tab in legacy UI', () => {
const renderResultsPanel = (isV2Ui: boolean) => create(
<QueryEditorResultsPanel
resultSets={[]}
activeResultKey=""
loading={false}
executionError=""
sqlLogCount={1}
darkMode={false}
isV2Ui={isV2Ui}
currentDb="main"
currentConnectionId="conn-1"
toggleShortcutLabel=""
onActiveResultKeyChange={vi.fn()}
onHide={vi.fn()}
onCloseResult={vi.fn()}
onCloseOtherResultTabs={vi.fn()}
onCloseResultTabsToLeft={vi.fn()}
onCloseResultTabsToRight={vi.fn()}
onCloseAllResultTabs={vi.fn()}
onReloadResult={vi.fn()}
onResultPageChange={vi.fn()}
onDiagnoseExecutionError={vi.fn()}
/>,
);
const legacyRenderer = renderResultsPanel(false);
expect(legacyRenderer.root.findAll((node) => node.props?.['data-log-panel'] === 'true')).toHaveLength(0);
expect(legacyRenderer.root.findAll((node) => node.props?.['data-tab-key'] === '__gonavi_sql_execution_log__')).toHaveLength(0);
legacyRenderer.unmount();
const v2Renderer = renderResultsPanel(true);
expect(v2Renderer.root.findAll((node) => node.props?.['data-log-panel'] === 'true')).toHaveLength(1);
expect(v2Renderer.root.findAll((node) => node.props?.['data-tab-key'] === '__gonavi_sql_execution_log__')).toHaveLength(1);
v2Renderer.unmount();
});
it('keeps the v2 query editor toolbar grouped and compact', () => {
const source = readFileSync(new URL('./QueryEditor.tsx', import.meta.url), 'utf8');
const toolbarSource = readFileSync(new URL('./QueryEditorToolbar.tsx', import.meta.url), 'utf8');

View File

@@ -385,11 +385,11 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
return nextVisible;
});
}, [tab.id, updateQueryTabDraft]);
const handleShowSqlExecutionLog = useCallback(() => {
const handleShowSqlExecutionLog = useCallback((mode: 'open' | 'toggle' = 'toggle') => {
if (!isActive) {
return;
}
if (isResultPanelVisible && activeResultKey === QUERY_EDITOR_SQL_LOG_TAB_KEY) {
if (mode !== 'open' && isResultPanelVisible && activeResultKey === QUERY_EDITOR_SQL_LOG_TAB_KEY) {
updateResultPanelVisibility(false);
return;
}
@@ -4467,8 +4467,9 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
}, [isActive, handleQuickSave]);
useEffect(() => {
const handleOpenSqlExecutionLog = () => {
handleShowSqlExecutionLog();
const handleOpenSqlExecutionLog = (event: Event) => {
const mode = event instanceof CustomEvent && event.detail?.mode === 'open' ? 'open' : 'toggle';
handleShowSqlExecutionLog(mode);
};
window.addEventListener('gonavi:show-sql-execution-log', handleOpenSqlExecutionLog as EventListener);

View File

@@ -81,7 +81,7 @@ const QueryEditorResultsPanel: React.FC<QueryEditorResultsPanelProps> = ({
}) => {
const i18n = useOptionalI18n();
const t = i18n?.t ?? defaultTranslate;
const shouldShowSqlLogTab = sqlLogCount > 0 || activeResultKey === QUERY_EDITOR_SQL_LOG_TAB_KEY;
const shouldShowSqlLogTab = isV2Ui && (sqlLogCount > 0 || activeResultKey === QUERY_EDITOR_SQL_LOG_TAB_KEY);
const logTabCountLabel = sqlLogCount > 999 ? '999+' : String(sqlLogCount);
const resolvedResultSetKey = activeResultKey && resultSets.some((rs) => rs.key === activeResultKey)
? activeResultKey

View File

@@ -697,8 +697,8 @@ describe('Sidebar locate toolbar', () => {
expect(source).not.toContain("justifyContent: 'space-between', borderTop: `1px solid ${darkMode ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)'}`, borderBottom: `1px solid ${darkMode ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)'}`, background: darkMode ? 'rgba(0,0,0,0.2)' : 'rgba(0,0,0,0.015)' }}>");
});
it('renders the v2 sidebar rail, command search hint, filter tabs and log footer', () => {
const markup = renderSidebarMarkup({ uiVersion: 'v2', sqlLogCount: 2341, onCreateConnection: mocks.noop });
it('renders the v2 sidebar rail, command search hint, filter tabs and slow-query footer', () => {
const markup = renderSidebarMarkup({ uiVersion: 'v2', onCreateConnection: mocks.noop });
const source = readSidebarSource();
expect(markup).toContain('gn-v2-sidebar-redesign');
@@ -734,8 +734,10 @@ describe('Sidebar locate toolbar', () => {
expect(source).toContain('onClick={() => setV2ExplorerFilter(item.key)}');
expect(source).toContain('treeData={isV2Ui ? v2VisibleTreeData : displayTreeData}');
expect(markup).toContain('gn-v2-sidebar-log-footer');
expect(markup).toContain('SQL 执行日志');
expect(markup).toContain('2,341');
expect(markup).toContain('gn-v2-sidebar-slow-query-button');
expect(markup).not.toContain('gn-v2-sidebar-log-button');
expect(markup).not.toContain('SQL 执行日志');
expect(markup).not.toContain('2,341');
expect(markup).not.toContain('gn-v2-rail-action-group');
expect(source).toContain('className="gn-v2-rail-primary-actions"');
expect(markup).toContain('data-sidebar-create-group-action="true"');

View File

@@ -99,8 +99,7 @@ import { Tree, message, Dropdown, MenuProps, Input, Button, Form, Popover, Toolt
AimOutlined,
MoreOutlined,
ToolOutlined,
SettingOutlined,
BarsOutlined
SettingOutlined
} from '@ant-design/icons';
import {
buildSidebarRootConnectionToken,
@@ -369,7 +368,6 @@ const Sidebar: React.FC<{
onOpenSettings?: () => void;
onToggleAI?: () => void;
onToggleLogPanel?: () => void;
sqlLogCount?: number;
uiVersion?: 'legacy' | 'v2';
onFocusCommandSearch?: () => void;
}> = React.memo(({
@@ -379,7 +377,6 @@ const Sidebar: React.FC<{
onOpenSettings,
onToggleAI,
onToggleLogPanel,
sqlLogCount = 0,
uiVersion,
onFocusCommandSearch,
}) => {
@@ -2854,11 +2851,6 @@ const Sidebar: React.FC<{
{isV2Ui && (
<div className="gn-v2-sidebar-log-footer">
<button type="button" className="gn-v2-sidebar-log-button" onClick={onToggleLogPanel}>
<BarsOutlined />
<span>{t('app.sidebar.sql_execution_log')}</span>
<small>{sqlLogCount.toLocaleString()}</small>
</button>
<SlowQueryRailButton
className="gn-v2-sidebar-slow-query-button"
tooltipPlacement="top"

View File

@@ -4,7 +4,7 @@ import { t as catalogTranslate } from '../i18n/catalog';
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
import { formatDdlForDisplay } from '../utils/ddlFormat';
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er';
type GridViewMode = 'table' | 'json' | 'text' | 'fields' | 'ddl' | 'er' | 'sqlLog';
type DdlViewLayoutMode = 'bottom' | 'side';
type TranslateParams = Record<string, string | number | boolean | null | undefined>;
@@ -118,15 +118,19 @@ export const useDataGridDdlView = ({
}, [canViewDdl, currentConnConfig, dbName, dbType, isV2Ui, messageApi, tableName, translateMessage]);
React.useEffect(() => {
if (isV2Ui || (viewMode !== 'fields' && viewMode !== 'ddl' && viewMode !== 'er')) return;
if (isV2Ui || (viewMode !== 'fields' && viewMode !== 'ddl' && viewMode !== 'er' && viewMode !== 'sqlLog')) return;
setViewMode('table');
}, [isV2Ui, viewMode]);
const handleViewModeChange = React.useCallback((nextMode: GridViewMode) => {
if ((nextMode === 'fields' || nextMode === 'ddl' || nextMode === 'er') && !isV2Ui) {
if ((nextMode === 'fields' || nextMode === 'ddl' || nextMode === 'er' || nextMode === 'sqlLog') && !isV2Ui) {
setViewMode('table');
return;
}
if (nextMode === 'sqlLog') {
setViewMode('sqlLog');
return;
}
if (nextMode === 'ddl') {
void handleOpenTableDdl({ asView: true });
setViewMode('ddl');

View File

@@ -2950,35 +2950,6 @@ body[data-ui-version="v2"] .gn-v2-sidebar-log-footer {
gap: 8px;
}
body[data-ui-version="v2"] .gn-v2-sidebar-log-button {
width: auto;
min-width: 0;
flex: 1 1 auto;
height: 28px;
border: 0;
border-radius: 8px;
background: transparent;
color: var(--gn-fg-2);
display: inline-flex;
align-items: center;
gap: 6px;
padding: 0 10px;
font-size: 12.5px;
font-weight: 400;
}
body[data-ui-version="v2"] .gn-v2-sidebar-log-button:hover {
background: var(--gn-bg-hover);
}
body[data-ui-version="v2"] .gn-v2-sidebar-log-button small {
margin-left: auto;
color: var(--gn-fg-4);
font-family: var(--gn-font-mono);
font-size: 10.5px;
font-weight: 400;
}
body[data-ui-version="v2"] .gn-v2-sidebar-slow-query-button {
width: 28px;
height: 28px;