+ {loading &&
{translate('::App.Platform.Loading')}
}
+ {!loading && treeData.length === 0 && (
+
+ {translate('::App.Platform.NoDataSourceSelected')}
+
+ )}
+ {!loading && treeData.length > 0 && (
+
{treeData.map((node) => renderNode(node))}
+ )}
+
+ {contextMenu.show && (
+ <>
+
setContextMenu({ show: false, x: 0, y: 0, node: null })}
+ />
+
+ {contextMenu.node?.type === 'object' && (
+ <>
+
+
+
+ >
+ )}
+
+ {contextMenu.node?.type === 'folder' && (
+
+ )}
+
+ >
+ )}
+
+
+
+ )
+}
+
+export default SqlObjectExplorer
diff --git a/ui/src/views/sqlQueryManager/components/SqlObjectProperties.tsx b/ui/src/views/sqlQueryManager/components/SqlObjectProperties.tsx
new file mode 100644
index 00000000..80bcae58
--- /dev/null
+++ b/ui/src/views/sqlQueryManager/components/SqlObjectProperties.tsx
@@ -0,0 +1,214 @@
+import type {
+ SqlFunctionDto,
+ SqlQueryDto,
+ SqlStoredProcedureDto,
+ SqlViewDto,
+ SqlObjectType,
+} from '@/proxy/sql-query-manager/models'
+import { useLocalization } from '@/utils/hooks/useLocalization'
+import dayjs from 'dayjs'
+
+export type SqlObject = SqlFunctionDto | SqlQueryDto | SqlStoredProcedureDto | SqlViewDto
+
+interface SqlObjectPropertiesProps {
+ object: SqlObject | null
+ type: SqlObjectType | null
+}
+
+const SqlObjectProperties = ({ object, type }: SqlObjectPropertiesProps) => {
+ const { translate } = useLocalization()
+
+ if (!object || !type) {
+ return (
+
+ {translate('::App.Platform.SelectAnObjectToViewProperties')}
+
+ )
+ }
+
+ const PropertyRow = ({ label, value }: { label: string; value: any }) => (
+
+
{label}
+
{value || '-'}
+
+ )
+
+ const getObjectTypeName = () => {
+ switch (type) {
+ case 1:
+ return translate('::App.Platform.Query')
+ case 2:
+ return translate('::App.Platform.StoredProcedure')
+ case 3:
+ return translate('::App.Platform.View')
+ case 4:
+ return translate('::App.Platform.Function')
+ default:
+ return translate('::App.Platform.Object')
+ }
+ }
+
+ const getStatusBadge = (status: number) => {
+ switch (status) {
+ case 1:
+ return
{translate('::App.Platform.Draft')}
+ case 2:
+ return
{translate('::App.Platform.Active')}
+ case 3:
+ return
{translate('::App.Platform.Archived')}
+ default:
+ return
{translate('::App.Platform.Unknown')}
+ }
+ }
+
+ const renderCommonProperties = () => (
+ <>
+
+
+ {object.creationTime && (
+
+ )}
+ {object.lastModificationTime && (
+
+ )}
+ >
+ )
+
+ const renderQueryProperties = () => {
+ const query = object as SqlQueryDto
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ {query.lastExecutedAt && (
+
+ )}
+ {renderCommonProperties()}
+ >
+ )
+ }
+
+ const renderStoredProcedureProperties = () => {
+ const sp = object as SqlStoredProcedureDto
+ return (
+ <>
+
+
+
+
+
+
+
+
+ {sp.lastDeployedAt && (
+
+ )}
+ {renderCommonProperties()}
+ >
+ )
+ }
+
+ const renderViewProperties = () => {
+ const view = object as SqlViewDto
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ {view.lastDeployedAt && (
+
+ )}
+ {renderCommonProperties()}
+ >
+ )
+ }
+
+ const renderFunctionProperties = () => {
+ const func = object as SqlFunctionDto
+ const getFunctionType = (funcType: number) => {
+ switch (funcType) {
+ case 1:
+ return translate('::App.Platform.ScalarFunction')
+ case 2:
+ return translate('::App.Platform.TableValuedFunction')
+ case 3:
+ return translate('::App.Platform.InlineTableValuedFunction')
+ default:
+ return translate('::App.Platform.Unknown')
+ }
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {func.lastDeployedAt && (
+
+ )}
+ {renderCommonProperties()}
+ >
+ )
+ }
+
+ return (
+
+
+
{translate('::App.Platform.Properties')}
+
+
+
+ {type === 1 && renderQueryProperties()}
+ {type === 2 && renderStoredProcedureProperties()}
+ {type === 3 && renderViewProperties()}
+ {type === 4 && renderFunctionProperties()}
+
+
+ )
+}
+
+export default SqlObjectProperties
diff --git a/ui/src/views/sqlQueryManager/components/SqlResultsGrid.tsx b/ui/src/views/sqlQueryManager/components/SqlResultsGrid.tsx
new file mode 100644
index 00000000..738ad4dc
--- /dev/null
+++ b/ui/src/views/sqlQueryManager/components/SqlResultsGrid.tsx
@@ -0,0 +1,142 @@
+import { useMemo } from 'react'
+import { DataGrid } from 'devextreme-react'
+import { Column, Paging, Scrolling, SearchPanel, Export, Selection } from 'devextreme-react/data-grid'
+import type { SqlQueryExecutionResultDto } from '@/proxy/sql-query-manager/models'
+import { useLocalization } from '@/utils/hooks/useLocalization'
+import { HiOutlineCheckCircle, HiOutlineXCircle } from 'react-icons/hi'
+
+interface SqlResultsGridProps {
+ result: SqlQueryExecutionResultDto
+}
+
+const SqlResultsGrid = ({ result }: SqlResultsGridProps) => {
+ const { translate } = useLocalization()
+
+ const columns = useMemo(() => {
+ // Get columns from metadata if available
+ if (result.metadata?.columns && Array.isArray(result.metadata.columns)) {
+ return result.metadata.columns
+ }
+
+ // Otherwise infer from data
+ if (result.data && result.data.length > 0) {
+ const firstRow = result.data[0]
+ return Object.keys(firstRow).map((key) => ({
+ name: key,
+ dataType: typeof firstRow[key],
+ isNullable: true,
+ }))
+ }
+
+ return []
+ }, [result])
+
+ const dataSource = useMemo(() => {
+ return result.data || []
+ }, [result])
+
+ if (!result.success) {
+ return (
+
+
+
+
+
+ {translate('::App.Platform.Error')}
+
+
+ {result.error || result.message}
+
+
+
+
+ )
+ }
+
+ return (
+
+
+
+
+
+
+ {result.message || translate('::App.Platform.QueryExecutedSuccessfully')}
+
+
+
+
+ {translate('::App.Platform.Rows')}: {result.rowsAffected || dataSource.length}
+
+
+ {translate('::App.Platform.Time')}: {result.executionTimeMs}ms
+
+
+
+
+ {dataSource.length > 0 ? (
+
+
+
+
+
+
+
+
+
+ {columns.map((col, index) => (
+
+ ))}
+
+
+
+ ) : (
+
+
+
{translate('::App.Platform.NoResults')}
+
+ {result.rowsAffected > 0
+ ? translate('::App.Platform.RowCount', { count: result.rowsAffected })
+ : translate('::App.Platform.NoResultsReturned')}
+
+
+
+ )}
+
+ )
+}
+
+export default SqlResultsGrid