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.Services;
using Erp.SqlQueryManager.Domain.Shared; using Erp.SqlQueryManager.Domain.Shared;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.MultiTenancy;
namespace Erp.SqlQueryManager.Application; namespace Erp.SqlQueryManager.Application;
@ -25,6 +27,8 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
private readonly IRepository<SqlFunction, Guid> _functionRepository; private readonly IRepository<SqlFunction, Guid> _functionRepository;
private readonly ISqlExecutorService _sqlExecutorService; private readonly ISqlExecutorService _sqlExecutorService;
private readonly ISqlTemplateProvider _templateProvider; private readonly ISqlTemplateProvider _templateProvider;
private readonly ICurrentTenant _currentTenant;
private readonly IHttpContextAccessor _httpContextAccessor;
public SqlObjectManagerAppService( public SqlObjectManagerAppService(
IRepository<SqlQuery, Guid> queryRepository, IRepository<SqlQuery, Guid> queryRepository,
@ -32,7 +36,10 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
IRepository<SqlView, Guid> viewRepository, IRepository<SqlView, Guid> viewRepository,
IRepository<SqlFunction, Guid> functionRepository, IRepository<SqlFunction, Guid> functionRepository,
ISqlExecutorService sqlExecutorService, ISqlExecutorService sqlExecutorService,
ISqlTemplateProvider templateProvider) ISqlTemplateProvider templateProvider,
ICurrentTenant currentTenant,
IHttpContextAccessor httpContextAccessor
)
{ {
_queryRepository = queryRepository; _queryRepository = queryRepository;
_procedureRepository = procedureRepository; _procedureRepository = procedureRepository;
@ -40,10 +47,35 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
_functionRepository = functionRepository; _functionRepository = functionRepository;
_sqlExecutorService = sqlExecutorService; _sqlExecutorService = sqlExecutorService;
_templateProvider = templateProvider; _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) public async Task<SqlObjectExplorerDto> GetAllObjectsAsync(string dataSourceCode)
{ {
ValidateTenantAccess();
var result = new SqlObjectExplorerDto(); var result = new SqlObjectExplorerDto();
// Get all queries for this data source // Get all queries for this data source
@ -186,6 +218,8 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlQueryDto> CreateQueryAsync(CreateSqlQueryDto input) public async Task<SqlQueryDto> CreateQueryAsync(CreateSqlQueryDto input)
{ {
ValidateTenantAccess();
var query = ObjectMapper.Map<CreateSqlQueryDto, SqlQuery>(input); var query = ObjectMapper.Map<CreateSqlQueryDto, SqlQuery>(input);
query.Status = SqlQueryStatus.Draft; query.Status = SqlQueryStatus.Draft;
@ -195,6 +229,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlQueryDto> UpdateQueryAsync(Guid id, UpdateSqlQueryDto input) public async Task<SqlQueryDto> UpdateQueryAsync(Guid id, UpdateSqlQueryDto input)
{ {
ValidateTenantAccess();
var query = await _queryRepository.GetAsync(id); var query = await _queryRepository.GetAsync(id);
query.Name = input.Name; query.Name = input.Name;
@ -209,11 +244,13 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task DeleteQueryAsync(Guid id) public async Task DeleteQueryAsync(Guid id)
{ {
ValidateTenantAccess();
await _queryRepository.DeleteAsync(id); await _queryRepository.DeleteAsync(id);
} }
public async Task<SqlQueryExecutionResultDto> ExecuteQueryAsync(ExecuteSqlQueryDto input) public async Task<SqlQueryExecutionResultDto> ExecuteQueryAsync(ExecuteSqlQueryDto input)
{ {
ValidateTenantAccess();
var sqlText = input.QueryText.Trim(); var sqlText = input.QueryText.Trim();
var sqlUpper = sqlText.ToUpperInvariant(); var sqlUpper = sqlText.ToUpperInvariant();
@ -264,6 +301,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlQueryExecutionResultDto> ExecuteSavedQueryAsync(Guid id) public async Task<SqlQueryExecutionResultDto> ExecuteSavedQueryAsync(Guid id)
{ {
ValidateTenantAccess();
var query = await _queryRepository.GetAsync(id); var query = await _queryRepository.GetAsync(id);
var result = await _sqlExecutorService.ExecuteQueryAsync(query.QueryText, query.DataSourceCode); 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) public async Task<SqlStoredProcedureDto> UpdateStoredProcedureAsync(Guid id, UpdateSqlStoredProcedureDto input)
{ {
ValidateTenantAccess();
var procedure = await _procedureRepository.GetAsync(id); var procedure = await _procedureRepository.GetAsync(id);
procedure.DisplayName = input.DisplayName; procedure.DisplayName = input.DisplayName;
@ -296,6 +335,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task DeleteStoredProcedureAsync(Guid id) public async Task DeleteStoredProcedureAsync(Guid id)
{ {
ValidateTenantAccess();
var procedure = await _procedureRepository.GetAsync(id); var procedure = await _procedureRepository.GetAsync(id);
// Drop stored procedure from SQL Server (always try, regardless of IsDeployed flag) // Drop stored procedure from SQL Server (always try, regardless of IsDeployed flag)
@ -314,6 +354,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlQueryExecutionResultDto> DeployStoredProcedureAsync(DeployStoredProcedureDto input) public async Task<SqlQueryExecutionResultDto> DeployStoredProcedureAsync(DeployStoredProcedureDto input)
{ {
ValidateTenantAccess();
var procedure = await _procedureRepository.GetAsync(input.Id); var procedure = await _procedureRepository.GetAsync(input.Id);
try try
@ -356,6 +397,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlViewDto> UpdateViewAsync(Guid id, UpdateSqlViewDto input) public async Task<SqlViewDto> UpdateViewAsync(Guid id, UpdateSqlViewDto input)
{ {
ValidateTenantAccess();
var view = await _viewRepository.GetAsync(id); var view = await _viewRepository.GetAsync(id);
view.DisplayName = input.DisplayName; view.DisplayName = input.DisplayName;
@ -371,6 +413,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task DeleteViewAsync(Guid id) public async Task DeleteViewAsync(Guid id)
{ {
ValidateTenantAccess();
var view = await _viewRepository.GetAsync(id); var view = await _viewRepository.GetAsync(id);
// Drop view from SQL Server (always try, regardless of IsDeployed flag) // Drop view from SQL Server (always try, regardless of IsDeployed flag)
@ -389,6 +432,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlQueryExecutionResultDto> DeployViewAsync(DeployViewDto input) public async Task<SqlQueryExecutionResultDto> DeployViewAsync(DeployViewDto input)
{ {
ValidateTenantAccess();
var view = await _viewRepository.GetAsync(input.Id); var view = await _viewRepository.GetAsync(input.Id);
try try
@ -431,6 +475,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlFunctionDto> UpdateFunctionAsync(Guid id, UpdateSqlFunctionDto input) public async Task<SqlFunctionDto> UpdateFunctionAsync(Guid id, UpdateSqlFunctionDto input)
{ {
ValidateTenantAccess();
var function = await _functionRepository.GetAsync(id); var function = await _functionRepository.GetAsync(id);
function.DisplayName = input.DisplayName; function.DisplayName = input.DisplayName;
@ -446,6 +491,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task DeleteFunctionAsync(Guid id) public async Task DeleteFunctionAsync(Guid id)
{ {
ValidateTenantAccess();
var function = await _functionRepository.GetAsync(id); var function = await _functionRepository.GetAsync(id);
// Drop function from SQL Server (always try, regardless of IsDeployed flag) // Drop function from SQL Server (always try, regardless of IsDeployed flag)
@ -464,6 +510,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SqlQueryExecutionResultDto> DeployFunctionAsync(DeployFunctionDto input) public async Task<SqlQueryExecutionResultDto> DeployFunctionAsync(DeployFunctionDto input)
{ {
ValidateTenantAccess();
var function = await _functionRepository.GetAsync(input.Id); var function = await _functionRepository.GetAsync(input.Id);
try try
@ -506,6 +553,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<List<DatabaseColumnDto>> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName) public async Task<List<DatabaseColumnDto>> GetTableColumnsAsync(string dataSourceCode, string schemaName, string tableName)
{ {
ValidateTenantAccess();
var query = $@" var query = $@"
SELECT SELECT
c.name AS ColumnName, c.name AS ColumnName,
@ -550,6 +598,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
public async Task<SmartSaveResultDto> SmartSaveAsync(SmartSaveInputDto input) public async Task<SmartSaveResultDto> SmartSaveAsync(SmartSaveInputDto input)
{ {
ValidateTenantAccess();
var result = new SmartSaveResultDto(); var result = new SmartSaveResultDto();
var sqlText = input.SqlText.Trim(); var sqlText = input.SqlText.Trim();
var sqlUpper = sqlText.ToUpperInvariant(); var sqlUpper = sqlText.ToUpperInvariant();
@ -773,39 +822,6 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA
return result; 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 #region Helper Methods
private SqlQueryExecutionResultDto MapExecutionResult(SqlExecutionResult result) private SqlQueryExecutionResultDto MapExecutionResult(SqlExecutionResult result)

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import { forwardRef } from 'react' import { forwardRef } from 'react'
import classNames from 'classnames' 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' import type { BreadcrumbItem } from '@/types/fileManagement'
export interface BreadcrumbProps { 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)}> <div ref={ref} className={classNames('flex items-center space-x-1 text-sm', className)}>
{items.map((item, index) => ( {items.map((item, index) => (
<div key={item.path} className="flex items-center"> <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 <button
onClick={() => onNavigate(item)} onClick={() => onNavigate(item)}
className={classNames( className={classNames(
@ -28,9 +28,9 @@ const Breadcrumb = forwardRef<HTMLDivElement, BreadcrumbProps>((props, ref) => {
disabled={index === items.length - 1} disabled={index === items.length - 1}
> >
{index === 0 ? ( {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> <span className="truncate max-w-32">{item.name}</span>
</button> </button>

View file

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

View file

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

View file

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

View file

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

View file

@ -12,7 +12,6 @@ import {
FaTrash, FaTrash,
FaTable, FaTable,
} from 'react-icons/fa' } from 'react-icons/fa'
import type { DataSourceDto } from '@/proxy/data-source'
import type { import type {
SqlFunctionDto, SqlFunctionDto,
SqlQueryDto, SqlQueryDto,
@ -38,7 +37,7 @@ interface TreeNode {
} }
interface SqlObjectExplorerProps { interface SqlObjectExplorerProps {
dataSource: DataSourceDto | null dataSource: string | null
onObjectSelect: (object: SqlObject | null, objectType: SqlObjectType | null) => void onObjectSelect: (object: SqlObject | null, objectType: SqlObjectType | null) => void
selectedObject: SqlObject | null selectedObject: SqlObject | null
onTemplateSelect?: (template: string, templateType: string) => void onTemplateSelect?: (template: string, templateType: string) => void
@ -87,13 +86,13 @@ const SqlObjectExplorer = ({
setLoading(true) setLoading(true)
try { try {
// Single API call to get all objects // 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 allObjects = response.data
const tree: TreeNode[] = [ const tree: TreeNode[] = [
{ {
id: 'root', id: 'root',
label: dataSource.code || 'Database', label: dataSource || 'Database',
type: 'root', type: 'root',
expanded: true, expanded: true,
children: [ children: [
@ -261,7 +260,7 @@ const SqlObjectExplorer = ({
const loadTableColumns = async (schemaName: string, tableName: string): Promise<TreeNode[]> => { const loadTableColumns = async (schemaName: string, tableName: string): Promise<TreeNode[]> => {
try { try {
const response = await sqlObjectManagerService.getTableColumns( const response = await sqlObjectManagerService.getTableColumns(
dataSource?.code || '', dataSource || '',
schemaName, schemaName,
tableName, tableName,
) )
@ -368,7 +367,7 @@ const SqlObjectExplorer = ({
// Check if it's a table // Check if it's a table
else if (node.id.startsWith('table-') && onTemplateSelect) { else if (node.id.startsWith('table-') && onTemplateSelect) {
const table = node.data as any 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') onTemplateSelect(selectQuery, 'table-select')
} else if (node.objectType) { } else if (node.objectType) {
onObjectSelect(node.data, 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 { Column, Paging, Scrolling, SearchPanel, Export, Selection } from 'devextreme-react/data-grid'
import type { SqlQueryExecutionResultDto } from '@/proxy/sql-query-manager/models' import type { SqlQueryExecutionResultDto } from '@/proxy/sql-query-manager/models'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import { HiOutlineCheckCircle, HiOutlineXCircle } from 'react-icons/hi' import { FaTimesCircle } from 'react-icons/fa'
interface SqlResultsGridProps { interface SqlResultsGridProps {
result: SqlQueryExecutionResultDto result: SqlQueryExecutionResultDto
@ -39,7 +39,7 @@ const SqlResultsGrid = ({ result }: SqlResultsGridProps) => {
return ( return (
<div className="h-full flex flex-col"> <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"> <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>
<div className="font-semibold text-red-700 dark:text-red-400"> <div className="font-semibold text-red-700 dark:text-red-400">
{translate('::App.Platform.Error')} {translate('::App.Platform.Error')}

View file

@ -1,7 +1,7 @@
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import AdaptableCard from "@/components/shared/AdaptableCard" import AdaptableCard from "@/components/shared/AdaptableCard"
import Container from "@/components/shared/Container" 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 { useLocalization } from "@/utils/hooks/useLocalization"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet"
@ -19,7 +19,7 @@ const Log = ({ version, date, children }: { version: string; date: string; child
{date} {date}
</time> </time>
<div className="flex items-center text-xl font-bold text-gray-900"> <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} v{version}
</div> </div>
</div> </div>
@ -59,7 +59,7 @@ const Changelog = () => {
<ul className="list-none mt-2 space-y-2"> <ul className="list-none mt-2 space-y-2">
{rel.changeLog.map((item, i) => ( {rel.changeLog.map((item, i) => (
<li key={i} className="flex items-start"> <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> <span>{item}</span>
</li> </li>
))} ))}