Sql Query Manager Tenant kontrolü

This commit is contained in:
Sedat Öztürk 2025-12-06 13:10:39 +03:00
parent 9938acb94c
commit 7bd6216515
11 changed files with 146 additions and 128 deletions

View file

@ -7,8 +7,10 @@ using Erp.SqlQueryManager.Domain.Entities;
using Erp.SqlQueryManager.Domain.Services;
using Erp.SqlQueryManager.Domain.Shared;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.MultiTenancy;
namespace Erp.SqlQueryManager.Application;
@ -25,6 +27,8 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
private readonly IRepository<SqlFunction, Guid> _functionRepository;
private readonly ISqlExecutorService _sqlExecutorService;
private readonly ISqlTemplateProvider _templateProvider;
private readonly ICurrentTenant _currentTenant;
private readonly IHttpContextAccessor _httpContextAccessor;
public SqlObjectManagerAppService(
IRepository<SqlQuery, Guid> queryRepository,
@ -32,7 +36,10 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
IRepository<SqlView, Guid> viewRepository,
IRepository<SqlFunction, Guid> functionRepository,
ISqlExecutorService sqlExecutorService,
ISqlTemplateProvider templateProvider)
ISqlTemplateProvider templateProvider,
ICurrentTenant currentTenant,
IHttpContextAccessor httpContextAccessor
)
{
_queryRepository = queryRepository;
_procedureRepository = procedureRepository;
@ -40,10 +47,35 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
_functionRepository = functionRepository;
_sqlExecutorService = sqlExecutorService;
_templateProvider = templateProvider;
_currentTenant = currentTenant;
_httpContextAccessor = httpContextAccessor;
}
private string GetTenantFromHeader()
{
return _httpContextAccessor.HttpContext?
.Request?
.Headers["__tenant"]
.FirstOrDefault();
}
private void ValidateTenantAccess()
{
var headerTenant = GetTenantFromHeader();
var currentTenantName = _currentTenant.Name;
if (_currentTenant.IsAvailable)
{
if (headerTenant != currentTenantName)
{
throw new Volo.Abp.UserFriendlyException($"Tenant mismatch. Header tenant '{headerTenant}' does not match current tenant '{currentTenantName}'.");
}
}
}
public async Task<SqlObjectExplorerDto> GetAllObjectsAsync(string dataSourceCode)
{
ValidateTenantAccess();
var result = new SqlObjectExplorerDto();
// Get all queries for this data source
@ -186,6 +218,8 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlQueryDto> CreateQueryAsync(CreateSqlQueryDto input)
{
ValidateTenantAccess();
var query = ObjectMapper.Map<CreateSqlQueryDto, SqlQuery>(input);
query.Status = SqlQueryStatus.Draft;
@ -195,6 +229,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlQueryDto> UpdateQueryAsync(Guid id, UpdateSqlQueryDto input)
{
ValidateTenantAccess();
var query = await _queryRepository.GetAsync(id);
query.Name = input.Name;
@ -209,21 +244,23 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task DeleteQueryAsync(Guid id)
{
ValidateTenantAccess();
await _queryRepository.DeleteAsync(id);
}
public async Task<SqlQueryExecutionResultDto> ExecuteQueryAsync(ExecuteSqlQueryDto input)
{
ValidateTenantAccess();
var sqlText = input.QueryText.Trim();
var sqlUpper = sqlText.ToUpperInvariant();
// Check if this is a DDL command (CREATE/ALTER/DROP for VIEW/PROCEDURE/FUNCTION)
bool isDDLCommand =
bool isDDLCommand =
sqlUpper.Contains("CREATE VIEW") || sqlUpper.Contains("ALTER VIEW") ||
sqlUpper.Contains("CREATE PROCEDURE") || sqlUpper.Contains("CREATE PROC") ||
sqlUpper.Contains("ALTER PROCEDURE") || sqlUpper.Contains("ALTER PROC") ||
sqlUpper.Contains("CREATE FUNCTION") || sqlUpper.Contains("ALTER FUNCTION") ||
sqlUpper.Contains("DROP VIEW") || sqlUpper.Contains("DROP PROCEDURE") ||
sqlUpper.Contains("DROP VIEW") || sqlUpper.Contains("DROP PROCEDURE") ||
sqlUpper.Contains("DROP PROC") || sqlUpper.Contains("DROP FUNCTION");
if (isDDLCommand)
@ -234,7 +271,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
// Try to parse/validate the SQL using SET PARSEONLY
var validationSql = $"SET PARSEONLY ON;\n{sqlText}\nSET PARSEONLY OFF;";
await _sqlExecutorService.ExecuteNonQueryAsync(validationSql, input.DataSourceCode);
return new SqlQueryExecutionResultDto
{
Success = true,
@ -264,6 +301,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlQueryExecutionResultDto> ExecuteSavedQueryAsync(Guid id)
{
ValidateTenantAccess();
var query = await _queryRepository.GetAsync(id);
var result = await _sqlExecutorService.ExecuteQueryAsync(query.QueryText, query.DataSourceCode);
@ -281,6 +319,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlStoredProcedureDto> UpdateStoredProcedureAsync(Guid id, UpdateSqlStoredProcedureDto input)
{
ValidateTenantAccess();
var procedure = await _procedureRepository.GetAsync(id);
procedure.DisplayName = input.DisplayName;
@ -296,6 +335,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task DeleteStoredProcedureAsync(Guid id)
{
ValidateTenantAccess();
var procedure = await _procedureRepository.GetAsync(id);
// Drop stored procedure from SQL Server (always try, regardless of IsDeployed flag)
@ -314,14 +354,15 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlQueryExecutionResultDto> DeployStoredProcedureAsync(DeployStoredProcedureDto input)
{
ValidateTenantAccess();
var procedure = await _procedureRepository.GetAsync(input.Id);
try
{
// Önce DROP işlemi yap (varsa)
var dropSql = $"IF OBJECT_ID('[{procedure.SchemaName}].[{procedure.ProcedureName}]', 'P') IS NOT NULL DROP PROCEDURE [{procedure.SchemaName}].[{procedure.ProcedureName}]";
await _sqlExecutorService.ExecuteNonQueryAsync(dropSql, procedure.DataSourceCode);
// Sonra CREATE işlemi yap
var result = await _sqlExecutorService.DeployStoredProcedureAsync(
procedure.ProcedureBody,
@ -356,6 +397,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlViewDto> UpdateViewAsync(Guid id, UpdateSqlViewDto input)
{
ValidateTenantAccess();
var view = await _viewRepository.GetAsync(id);
view.DisplayName = input.DisplayName;
@ -371,6 +413,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task DeleteViewAsync(Guid id)
{
ValidateTenantAccess();
var view = await _viewRepository.GetAsync(id);
// Drop view from SQL Server (always try, regardless of IsDeployed flag)
@ -389,14 +432,15 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlQueryExecutionResultDto> DeployViewAsync(DeployViewDto input)
{
ValidateTenantAccess();
var view = await _viewRepository.GetAsync(input.Id);
try
{
// Önce DROP işlemi yap (varsa)
var dropSql = $"IF OBJECT_ID('[{view.SchemaName}].[{view.ViewName}]', 'V') IS NOT NULL DROP VIEW [{view.SchemaName}].[{view.ViewName}]";
await _sqlExecutorService.ExecuteNonQueryAsync(dropSql, view.DataSourceCode);
// Sonra CREATE işlemi yap
var result = await _sqlExecutorService.DeployViewAsync(
view.ViewDefinition,
@ -431,6 +475,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlFunctionDto> UpdateFunctionAsync(Guid id, UpdateSqlFunctionDto input)
{
ValidateTenantAccess();
var function = await _functionRepository.GetAsync(id);
function.DisplayName = input.DisplayName;
@ -446,6 +491,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task DeleteFunctionAsync(Guid id)
{
ValidateTenantAccess();
var function = await _functionRepository.GetAsync(id);
// Drop function from SQL Server (always try, regardless of IsDeployed flag)
@ -464,14 +510,15 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlQueryExecutionResultDto> DeployFunctionAsync(DeployFunctionDto input)
{
ValidateTenantAccess();
var function = await _functionRepository.GetAsync(input.Id);
try
{
// Önce DROP işlemi yap (varsa)
var dropSql = $"IF OBJECT_ID('[{function.SchemaName}].[{function.FunctionName}]', 'FN') IS NOT NULL DROP FUNCTION [{function.SchemaName}].[{function.FunctionName}]";
await _sqlExecutorService.ExecuteNonQueryAsync(dropSql, function.DataSourceCode);
// Sonra CREATE işlemi yap
var result = await _sqlExecutorService.DeployFunctionAsync(
function.FunctionBody,
@ -506,6 +553,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<List<DatabaseColumnDto>> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName)
{
ValidateTenantAccess();
var query = $@"
SELECT
c.name AS ColumnName,
@ -550,6 +598,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SmartSaveResultDto> SmartSaveAsync(SmartSaveInputDto input)
{
ValidateTenantAccess();
var result = new SmartSaveResultDto();
var sqlText = input.SqlText.Trim();
var sqlUpper = sqlText.ToUpperInvariant();
@ -773,39 +822,6 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
return result;
}
private string ExtractObjectName(string sql, params string[] keywords)
{
var sqlUpper = sql.ToUpperInvariant();
foreach (var keyword in keywords)
{
var createIndex = sqlUpper.IndexOf($"CREATE {keyword}", StringComparison.OrdinalIgnoreCase);
var alterIndex = sqlUpper.IndexOf($"ALTER {keyword}", StringComparison.OrdinalIgnoreCase);
var startIndex = Math.Max(createIndex, alterIndex);
if (startIndex >= 0)
{
startIndex = sqlUpper.IndexOf(keyword, startIndex, StringComparison.OrdinalIgnoreCase) + keyword.Length;
var endIndex = sql.IndexOfAny(new[] { ' ', '\r', '\n', '\t', '(', '[' }, startIndex);
if (endIndex > startIndex)
{
var name = sql.Substring(startIndex, endIndex - startIndex).Trim();
// Remove schema prefix if exists
if (name.Contains("."))
{
name = name.Substring(name.LastIndexOf('.') + 1);
}
// Remove square brackets
name = name.Replace("[", "").Replace("]", "");
return name;
}
}
}
return "UnnamedObject";
}
#region Helper Methods
private SqlQueryExecutionResultDto MapExecutionResult(SqlExecutionResult result)

View file

@ -1,5 +1,5 @@
import { FullAuditedEntityDto } from '../abp'
import { DataSourceTypeEnum } from '../form'
import { DataSourceTypeEnum } from '../form/models'
export interface DataSourceDto extends FullAuditedEntityDto<string> {
code?: string

View file

@ -8,14 +8,14 @@ import { AdaptableCard } from '@/components/shared'
import { AuditLogDto } from '@/proxy/auditLog/audit-log'
import { getAuditLogs } from '@/services/identity.service'
import {
HiOutlineClock,
HiOutlineGlobe,
HiOutlineUser,
HiOutlineCode,
HiOutlineDocumentText,
HiOutlineExclamationCircle,
HiOutlineCheckCircle,
} from 'react-icons/hi'
FaRegClock,
FaGlobe,
FaUser,
FaCode,
FaRegFileAlt,
FaExclamationCircle,
FaRegCheckCircle,
} from 'react-icons/fa'
function AuditLogs({
open,
@ -108,18 +108,18 @@ function AuditLogs({
</div>
) : !selectedLog ? (
<div className="text-center py-16">
<HiOutlineExclamationCircle className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<FaExclamationCircle className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<p className="text-gray-500">No audit log found</p>
</div>
) : (
<Tabs defaultValue="log" variant="pill">
<TabList className="mb-6 bg-gray-50 p-1 rounded-lg">
<TabNav value="log">
<HiOutlineDocumentText className="w-4 h-4 mr-2" />
<FaRegFileAlt className="w-4 h-4 mr-2" />
Overview
</TabNav>
<TabNav value="actions">
<HiOutlineCode className="w-4 h-4 mr-2" />
<FaCode className="w-4 h-4 mr-2" />
Actions
{selectedLog.actions?.length > 0 && (
<Badge
@ -129,7 +129,7 @@ function AuditLogs({
)}
</TabNav>
<TabNav value="changes">
<HiOutlineCheckCircle className="w-4 h-4 mr-2" />
<FaRegCheckCircle className="w-4 h-4 mr-2" />
Entity Changes
{selectedLog.entityChanges?.length > 0 && (
<Badge
@ -146,18 +146,18 @@ function AuditLogs({
{/* Request Information */}
<AdaptableCard className="shadow-sm">
<h6 className="font-semibold text-gray-700 mb-4 flex items-center gap-2">
<HiOutlineGlobe className="w-5 h-5 text-blue-500" />
<FaGlobe className="w-5 h-5 text-blue-500" />
Request Information
</h6>
<div className="space-y-1">
<InfoRow
icon={HiOutlineUser}
icon={FaUser}
label="User"
value={selectedLog.userName || 'Anonymous'}
valueClassName="font-medium text-gray-800"
/>
<InfoRow
icon={HiOutlineClock}
icon={FaRegClock}
label="Execution Time"
value={new Date(selectedLog.executionTime).toLocaleString('tr-TR', {
day: '2-digit',
@ -169,7 +169,7 @@ function AuditLogs({
})}
/>
<InfoRow
icon={HiOutlineClock}
icon={FaRegClock}
label="Duration"
value={formatDuration(selectedLog.executionDuration)}
valueClassName={
@ -179,7 +179,7 @@ function AuditLogs({
}
/>
<InfoRow
icon={HiOutlineGlobe}
icon={FaGlobe}
label="Client IP"
value={selectedLog.clientIpAddress || 'Unknown'}
/>
@ -189,12 +189,12 @@ function AuditLogs({
{/* HTTP Details */}
<AdaptableCard className="shadow-sm">
<h6 className="font-semibold text-gray-700 mb-4 flex items-center gap-2">
<HiOutlineCode className="w-5 h-5 text-green-500" />
<FaCode className="w-5 h-5 text-green-500" />
HTTP Details
</h6>
<div className="space-y-1">
<InfoRow
icon={HiOutlineCode}
icon={FaCode}
label="Method"
value={
<Badge
@ -214,18 +214,18 @@ function AuditLogs({
}
/>
<InfoRow
icon={HiOutlineCheckCircle}
icon={FaRegCheckCircle}
label="Status Code"
value={getStatusBadge(selectedLog.httpStatusCode)}
/>
<InfoRow
icon={HiOutlineGlobe}
icon={FaGlobe}
label="URL"
value={selectedLog.url}
valueClassName="text-blue-600 font-mono text-xs"
/>
<InfoRow
icon={HiOutlineDocumentText}
icon={FaRegFileAlt}
label="Browser"
value={
<span className="text-xs text-gray-600 line-clamp-2">
@ -240,7 +240,7 @@ function AuditLogs({
{selectedLog.exceptions && (
<AdaptableCard className="lg:col-span-2 shadow-sm border-l-4 border-red-500">
<h6 className="font-semibold text-red-600 mb-3 flex items-center gap-2">
<HiOutlineExclamationCircle className="w-5 h-5" />
<FaExclamationCircle className="w-5 h-5" />
Exceptions
</h6>
<pre className="bg-red-50 text-red-700 p-4 rounded-lg text-xs whitespace-pre-wrap leading-relaxed font-mono overflow-x-auto">
@ -253,7 +253,7 @@ function AuditLogs({
{selectedLog.comments && (
<AdaptableCard className="lg:col-span-2 shadow-sm bg-blue-50">
<h6 className="font-semibold text-blue-700 mb-2 flex items-center gap-2">
<HiOutlineDocumentText className="w-5 h-5" />
<FaRegFileAlt className="w-5 h-5" />
Comments
</h6>
<p className="text-sm text-blue-800">{selectedLog.comments}</p>
@ -266,7 +266,7 @@ function AuditLogs({
<TabContent value="actions">
{!selectedLog.actions || selectedLog.actions.length === 0 ? (
<div className="text-center py-16">
<HiOutlineCode className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<FaCode className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<p className="text-gray-500">No actions recorded</p>
</div>
) : (
@ -320,7 +320,7 @@ function AuditLogs({
<TabContent value="changes">
{!selectedLog.entityChanges || selectedLog.entityChanges.length === 0 ? (
<div className="text-center py-16">
<HiOutlineCheckCircle className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<FaRegCheckCircle className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<p className="text-gray-500">No entity changes recorded</p>
</div>
) : (

View file

@ -1,6 +1,6 @@
import { forwardRef } from 'react'
import classNames from 'classnames'
import { HiChevronRight, HiFolder, HiHome } from 'react-icons/hi2'
import { FaChevronRight, FaFolder, FaHome } from 'react-icons/fa'
import type { BreadcrumbItem } from '@/types/fileManagement'
export interface BreadcrumbProps {
@ -16,7 +16,7 @@ const Breadcrumb = forwardRef<HTMLDivElement, BreadcrumbProps>((props, ref) => {
<div ref={ref} className={classNames('flex items-center space-x-1 text-sm', className)}>
{items.map((item, index) => (
<div key={item.path} className="flex items-center">
{index > 0 && <HiChevronRight className="mx-2 h-4 w-4 text-gray-400" />}
{index > 0 && <FaChevronRight className="mx-2 h-4 w-4 text-gray-400" />}
<button
onClick={() => onNavigate(item)}
className={classNames(
@ -28,9 +28,9 @@ const Breadcrumb = forwardRef<HTMLDivElement, BreadcrumbProps>((props, ref) => {
disabled={index === items.length - 1}
>
{index === 0 ? (
<HiHome className="h-4 w-4 mr-1" />
<FaHome className="h-4 w-4 mr-1" />
) : (
<HiFolder className="h-4 w-4 mr-1" />
<FaFolder className="h-4 w-4 mr-1" />
)}
<span className="truncate max-w-32">{item.name}</span>
</button>

View file

@ -1,20 +1,20 @@
import { forwardRef, useState, useEffect } from 'react'
import classNames from 'classnames'
import {
HiFolder,
HiFolderPlus,
HiDocument,
HiPhoto,
HiFilm,
HiMusicalNote,
HiArchiveBox,
HiEllipsisVertical,
HiPencil,
HiArrowRightOnRectangle,
HiTrash,
HiArrowDownTray,
HiEye,
} from 'react-icons/hi2'
FaFolder,
FaFolderPlus,
FaRegFileAlt,
FaRegImage,
FaFilm,
FaMusic,
FaArchive,
FaEllipsisV,
FaPencilAlt,
FaSignOutAlt,
FaTrash,
FaDownload,
FaEye,
} from 'react-icons/fa'
// import { Dropdown } from '@/components/ui' // Artık kullanmıyoruz
import type { FileItem as FileItemType, FileActionMenuItem } from '@/types/fileManagement'
@ -39,9 +39,9 @@ const getFileIcon = (item: FileItemType, large: boolean = false) => {
if (item.type === 'folder') {
if (item.isReadOnly) {
return <HiFolder className={`${iconSize} text-gray-500`} />
return <FaFolder className={`${iconSize} text-gray-500`} />
} else {
return <HiFolder className={`${iconSize} text-blue-500`} />
return <FaFolder className={`${iconSize} text-blue-500`} />
}
}
@ -49,22 +49,22 @@ const getFileIcon = (item: FileItemType, large: boolean = false) => {
const mimeType = item.mimeType?.toLowerCase()
if (mimeType?.startsWith('image/')) {
return <HiPhoto className={`${iconSize} text-green-500`} />
return <FaRegImage className={`${iconSize} text-green-500`} />
}
if (mimeType?.startsWith('video/')) {
return <HiFilm className={`${iconSize} text-purple-500`} />
return <FaFilm className={`${iconSize} text-purple-500`} />
}
if (mimeType?.startsWith('audio/')) {
return <HiMusicalNote className={`${iconSize} text-pink-500`} />
return <FaMusic className={`${iconSize} text-pink-500`} />
}
if (['zip', 'rar', '7z', 'tar', 'gz'].includes(extension || '')) {
return <HiArchiveBox className={`${iconSize} text-orange-500`} />
return <FaArchive className={`${iconSize} text-orange-500`} />
}
return <HiDocument className={`${iconSize} text-gray-500`} />
return <FaRegFileAlt className={`${iconSize} text-gray-500`} />
}
const formatFileSize = (bytes?: number): string => {
@ -236,17 +236,17 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
}}
>
{menuItem.icon === 'HiFolderPlus' && (
<HiFolderPlus className="h-4 w-4 mr-3 text-blue-500" />
<FaFolderPlus className="h-4 w-4 mr-3 text-blue-500" />
)}
{menuItem.icon === 'HiEye' && <HiEye className="h-4 w-4 mr-3 text-gray-500" />}
{menuItem.icon === 'HiEye' && <FaEye className="h-4 w-4 mr-3 text-gray-500" />}
{menuItem.icon === 'HiArrowDownTray' && (
<HiArrowDownTray className="h-4 w-4 mr-3 text-green-500" />
<FaDownload className="h-4 w-4 mr-3 text-green-500" />
)}
{menuItem.icon === 'HiPencil' && <HiPencil className="h-4 w-4 mr-3 text-orange-500" />}
{menuItem.icon === 'HiPencil' && <FaPencilAlt className="h-4 w-4 mr-3 text-orange-500" />}
{menuItem.icon === 'HiArrowRightOnRectangle' && (
<HiArrowRightOnRectangle className="h-4 w-4 mr-3 text-purple-500" />
<FaSignOutAlt className="h-4 w-4 mr-3 text-purple-500" />
)}
{menuItem.icon === 'HiTrash' && <HiTrash className="h-4 w-4 mr-3" />}
{menuItem.icon === 'HiTrash' && <FaTrash className="h-4 w-4 mr-3" />}
<span className="flex-1">{menuItem.label}</span>
</div>
))}

View file

@ -1,6 +1,6 @@
import { forwardRef, useState, useEffect } from 'react'
import { Dialog, Button, Input, FormItem } from '@/components/ui'
import { HiXMark } from 'react-icons/hi2'
import { FaTimes } from 'react-icons/fa'
import type { FileItem } from '@/types/fileManagement'
// Create Folder Modal
@ -136,7 +136,7 @@ export const RenameItemModal = forwardRef<HTMLDivElement, RenameItemModalProps>(
<h3 className="text-lg font-semibold">
Rename {item.type === 'folder' ? 'Folder' : 'File'}
</h3>
<Button variant="plain" size="sm" icon={<HiXMark />} onClick={handleClose} />
<Button variant="plain" size="sm" icon={<FaTimes />} onClick={handleClose} />
</div>
<form onSubmit={handleSubmit} className="py-6">

View file

@ -1,6 +1,6 @@
import { forwardRef, useState, useCallback, useRef } from 'react'
import { Dialog, Button, Progress } from '@/components/ui'
import { HiCloudArrowUp, HiXMark } from 'react-icons/hi2'
import { FaCloudUploadAlt, FaTimes } from 'react-icons/fa'
import classNames from 'classnames'
export interface FileUploadModalProps {
@ -258,7 +258,7 @@ const FileUploadModal = forwardRef<HTMLDivElement, FileUploadModalProps>((props,
disabled={uploading}
/>
<HiCloudArrowUp className="mx-auto h-12 w-12 text-gray-400 mb-4" />
<FaCloudUploadAlt className="mx-auto h-12 w-12 text-gray-400 mb-4" />
<p className="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">
Choose files or drag and drop here
</p>
@ -317,7 +317,7 @@ const FileUploadModal = forwardRef<HTMLDivElement, FileUploadModalProps>((props,
<Button
variant="plain"
size="xs"
icon={<HiXMark />}
icon={<FaTimes />}
onClick={() => removeFile(file.id)}
disabled={uploading}
className="ml-2 flex-shrink-0"

View file

@ -14,7 +14,7 @@ import type {
import { SqlObjectType } from '@/proxy/sql-query-manager/models'
import { sqlObjectManagerService } from '@/services/sql-query-manager.service'
import { FaDatabase, FaPlay, FaSave, FaSyncAlt, FaCloudUploadAlt } from 'react-icons/fa'
import { HiOutlineCheckCircle } from 'react-icons/hi'
import { FaCheckCircle } from 'react-icons/fa'
import { useLocalization } from '@/utils/hooks/useLocalization'
import SqlObjectExplorer from './components/SqlObjectExplorer'
import SqlEditor, { SqlEditorRef } from './components/SqlEditor'
@ -22,12 +22,13 @@ import SqlResultsGrid from './components/SqlResultsGrid'
import SqlObjectProperties from './components/SqlObjectProperties'
import { Splitter } from '@/components/codeLayout/Splitter'
import { Helmet } from 'react-helmet'
import { useStoreState } from '@/store/store'
export type SqlObject = SqlFunctionDto | SqlQueryDto | SqlStoredProcedureDto | SqlViewDto
interface SqlManagerState {
dataSources: DataSourceDto[]
selectedDataSource: DataSourceDto | null
selectedDataSource: string | null
selectedObject: SqlObject | null
selectedObjectType: SqlObjectType | null
editorContent: string
@ -43,10 +44,11 @@ interface SqlManagerState {
const SqlQueryManager = () => {
const { translate } = useLocalization()
const editorRef = useRef<SqlEditorRef>(null)
const tenantName = useStoreState((state) => state.locale.currentTenantName)
const [state, setState] = useState<SqlManagerState>({
dataSources: [],
selectedDataSource: null,
selectedDataSource: tenantName ?? null,
selectedObject: null,
selectedObjectType: null,
editorContent: '',
@ -84,7 +86,7 @@ const SqlQueryManager = () => {
setState((prev) => ({
...prev,
dataSources: items,
selectedDataSource: items[0],
selectedDataSource: items[0].code ?? null,
}))
}
} catch (error) {
@ -100,7 +102,7 @@ const SqlQueryManager = () => {
const handleDataSourceChange = useCallback((dataSource: DataSourceDto) => {
setState((prev) => ({
...prev,
selectedDataSource: dataSource,
selectedDataSource: dataSource.code ?? null,
selectedObject: null,
editorContent: '',
executionResult: null,
@ -404,7 +406,7 @@ GO`,
try {
const result = await sqlObjectManagerService.executeQuery({
queryText: queryToExecute,
dataSourceCode: state.selectedDataSource.code || '',
dataSourceCode: state.selectedDataSource || '',
})
setState((prev) => ({
@ -510,7 +512,7 @@ GO`,
// Smart save ile kaydet
const result = await sqlObjectManagerService.smartSave({
sqlText: state.editorContent,
dataSourceCode: state.selectedDataSource.code || '',
dataSourceCode: state.selectedDataSource || '',
name: saveDialogData.name,
description: saveDialogData.description,
})
@ -638,7 +640,7 @@ GO`,
try {
const response = await sqlObjectManagerService.getTableColumns(
state.selectedDataSource.code || '',
state.selectedDataSource || '',
schemaName,
tableName,
)
@ -704,7 +706,8 @@ GO`,
</span>
<select
className="border border-gray-300 rounded px-1 py-1"
value={state.selectedDataSource?.code || ''}
disabled={state.selectedDataSource?.length === 0}
value={state.selectedDataSource || ''}
onChange={(e) => {
const ds = state.dataSources.find((d) => d.code === e.target.value)
if (ds) handleDataSourceChange(ds)
@ -817,7 +820,7 @@ GO`,
<div className="border rounded-lg shadow-sm bg-white dark:bg-gray-800 flex flex-col h-full">
<div className="border-b px-4 py-2 bg-gray-50 dark:bg-gray-800 flex-shrink-0 flex items-center justify-between">
<div className="flex items-center gap-2">
<HiOutlineCheckCircle className="text-green-500" />
<FaCheckCircle className="text-green-500" />
<span className="text-sm text-green-700 dark:text-green-400">
{state.executionResult?.message ||
state.tableColumns?.message ||
@ -911,7 +914,7 @@ GO`,
{saveDialogData.detectedType && (
<div className="p-3 bg-blue-50 dark:bg-blue-900/20 rounded-md border border-blue-200 dark:border-blue-800">
<div className="flex items-center gap-2">
<HiOutlineCheckCircle className="text-blue-600 dark:text-blue-400 text-xl" />
<FaCheckCircle className="text-blue-600 dark:text-blue-400 text-xl" />
<div>
<div className="text-sm font-semibold text-blue-900 dark:text-blue-100">
{translate('::App.Platform.DetectedObjectType')}

View file

@ -12,7 +12,6 @@ import {
FaTrash,
FaTable,
} from 'react-icons/fa'
import type { DataSourceDto } from '@/proxy/data-source'
import type {
SqlFunctionDto,
SqlQueryDto,
@ -38,7 +37,7 @@ interface TreeNode {
}
interface SqlObjectExplorerProps {
dataSource: DataSourceDto | null
dataSource: string | null
onObjectSelect: (object: SqlObject | null, objectType: SqlObjectType | null) => void
selectedObject: SqlObject | null
onTemplateSelect?: (template: string, templateType: string) => void
@ -87,13 +86,13 @@ const SqlObjectExplorer = ({
setLoading(true)
try {
// Single API call to get all objects
const response = await sqlObjectManagerService.getAllObjects(dataSource.code || '')
const response = await sqlObjectManagerService.getAllObjects(dataSource || '')
const allObjects = response.data
const tree: TreeNode[] = [
{
id: 'root',
label: dataSource.code || 'Database',
label: dataSource || 'Database',
type: 'root',
expanded: true,
children: [
@ -261,7 +260,7 @@ const SqlObjectExplorer = ({
const loadTableColumns = async (schemaName: string, tableName: string): Promise<TreeNode[]> => {
try {
const response = await sqlObjectManagerService.getTableColumns(
dataSource?.code || '',
dataSource || '',
schemaName,
tableName,
)
@ -368,7 +367,7 @@ const SqlObjectExplorer = ({
// Check if it's a table
else if (node.id.startsWith('table-') && onTemplateSelect) {
const table = node.data as any
const selectQuery = `-- SELECT from ${table.fullName || table.tableName}\nSELECT TOP 100 * \nFROM ${table.fullName || `[${table.schemaName}].[${table.tableName}]`};`
const selectQuery = `SELECT TOP 10 * \nFROM ${table.fullName || `[${table.schemaName}].[${table.tableName}]`};`
onTemplateSelect(selectQuery, 'table-select')
} else if (node.objectType) {
onObjectSelect(node.data, node.objectType)

View file

@ -3,7 +3,7 @@ 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'
import { FaTimesCircle } from 'react-icons/fa'
interface SqlResultsGridProps {
result: SqlQueryExecutionResultDto
@ -39,7 +39,7 @@ const SqlResultsGrid = ({ result }: SqlResultsGridProps) => {
return (
<div className="h-full flex flex-col">
<div className="flex items-center gap-2 mb-4 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded">
<HiOutlineXCircle className="text-red-500 text-xl" />
<FaTimesCircle className="text-red-500 text-xl" />
<div>
<div className="font-semibold text-red-700 dark:text-red-400">
{translate('::App.Platform.Error')}

View file

@ -1,7 +1,7 @@
import { useEffect, useState } from "react"
import AdaptableCard from "@/components/shared/AdaptableCard"
import Container from "@/components/shared/Container"
import { HiTag, HiCheckCircle } from "react-icons/hi"
import { FaTag, FaRegCheckCircle } from "react-icons/fa"
import { useLocalization } from "@/utils/hooks/useLocalization"
import { Helmet } from "react-helmet"
@ -19,7 +19,7 @@ const Log = ({ version, date, children }: { version: string; date: string; child
{date}
</time>
<div className="flex items-center text-xl font-bold text-gray-900">
<HiTag className="mr-2 text-indigo-500" />
<FaTag className="mr-2 text-indigo-500" />
v{version}
</div>
</div>
@ -59,7 +59,7 @@ const Changelog = () => {
<ul className="list-none mt-2 space-y-2">
{rel.changeLog.map((item, i) => (
<li key={i} className="flex items-start">
<HiCheckCircle className="w-4 h-4 text-emerald-500 mr-2 flex-shrink-0" />
<FaRegCheckCircle className="w-4 h-4 text-emerald-500 mr-2 flex-shrink-0" />
<span>{item}</span>
</li>
))}