diff --git a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlObjectManagerAppService.cs b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlObjectManagerAppService.cs index aaecbd75..ae6f5cf2 100644 --- a/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlObjectManagerAppService.cs +++ b/api/modules/Erp.SqlQueryManager/Erp.SqlQueryManager.Application/SqlObjectManagerAppService.cs @@ -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 _functionRepository; private readonly ISqlExecutorService _sqlExecutorService; private readonly ISqlTemplateProvider _templateProvider; + private readonly ICurrentTenant _currentTenant; + private readonly IHttpContextAccessor _httpContextAccessor; public SqlObjectManagerAppService( IRepository queryRepository, @@ -32,7 +36,10 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA IRepository viewRepository, IRepository 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 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 CreateQueryAsync(CreateSqlQueryDto input) { + ValidateTenantAccess(); + var query = ObjectMapper.Map(input); query.Status = SqlQueryStatus.Draft; @@ -195,6 +229,7 @@ public class SqlObjectManagerAppService : ApplicationService, ISqlObjectManagerA public async Task 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 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 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 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 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 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 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 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 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> 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 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) diff --git a/ui/src/proxy/data-source/models.ts b/ui/src/proxy/data-source/models.ts index 5837ad5a..c2675688 100644 --- a/ui/src/proxy/data-source/models.ts +++ b/ui/src/proxy/data-source/models.ts @@ -1,5 +1,5 @@ import { FullAuditedEntityDto } from '../abp' -import { DataSourceTypeEnum } from '../form' +import { DataSourceTypeEnum } from '../form/models' export interface DataSourceDto extends FullAuditedEntityDto { code?: string diff --git a/ui/src/views/admin/auditLog/AuditLogDetail.tsx b/ui/src/views/admin/auditLog/AuditLogDetail.tsx index 2bfa1ebf..eb8a10bc 100644 --- a/ui/src/views/admin/auditLog/AuditLogDetail.tsx +++ b/ui/src/views/admin/auditLog/AuditLogDetail.tsx @@ -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({ ) : !selectedLog ? (
- +

No audit log found

) : ( - + Overview - + Actions {selectedLog.actions?.length > 0 && ( - + Entity Changes {selectedLog.entityChanges?.length > 0 && (
- + Request Information
@@ -189,12 +189,12 @@ function AuditLogs({ {/* HTTP Details */}
- + HTTP Details
@@ -240,7 +240,7 @@ function AuditLogs({ {selectedLog.exceptions && (
- + Exceptions
@@ -253,7 +253,7 @@ function AuditLogs({
               {selectedLog.comments && (
                 
                   
- + Comments

{selectedLog.comments}

@@ -266,7 +266,7 @@ function AuditLogs({ {!selectedLog.actions || selectedLog.actions.length === 0 ? (
- +

No actions recorded

) : ( @@ -320,7 +320,7 @@ function AuditLogs({ {!selectedLog.entityChanges || selectedLog.entityChanges.length === 0 ? (
- +

No entity changes recorded

) : ( diff --git a/ui/src/views/admin/files/components/Breadcrumb.tsx b/ui/src/views/admin/files/components/Breadcrumb.tsx index e9ef057c..ee1b6e84 100644 --- a/ui/src/views/admin/files/components/Breadcrumb.tsx +++ b/ui/src/views/admin/files/components/Breadcrumb.tsx @@ -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((props, ref) => {
{items.map((item, index) => (
- {index > 0 && } + {index > 0 && } diff --git a/ui/src/views/admin/files/components/FileItem.tsx b/ui/src/views/admin/files/components/FileItem.tsx index bcbff437..e7cccb3c 100644 --- a/ui/src/views/admin/files/components/FileItem.tsx +++ b/ui/src/views/admin/files/components/FileItem.tsx @@ -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 + return } else { - return + return } } @@ -49,22 +49,22 @@ const getFileIcon = (item: FileItemType, large: boolean = false) => { const mimeType = item.mimeType?.toLowerCase() if (mimeType?.startsWith('image/')) { - return + return } if (mimeType?.startsWith('video/')) { - return + return } if (mimeType?.startsWith('audio/')) { - return + return } if (['zip', 'rar', '7z', 'tar', 'gz'].includes(extension || '')) { - return + return } - return + return } const formatFileSize = (bytes?: number): string => { @@ -236,17 +236,17 @@ const FileItem = forwardRef((props, ref) => { }} > {menuItem.icon === 'HiFolderPlus' && ( - + )} - {menuItem.icon === 'HiEye' && } + {menuItem.icon === 'HiEye' && } {menuItem.icon === 'HiArrowDownTray' && ( - + )} - {menuItem.icon === 'HiPencil' && } + {menuItem.icon === 'HiPencil' && } {menuItem.icon === 'HiArrowRightOnRectangle' && ( - + )} - {menuItem.icon === 'HiTrash' && } + {menuItem.icon === 'HiTrash' && } {menuItem.label}
))} diff --git a/ui/src/views/admin/files/components/FileModals.tsx b/ui/src/views/admin/files/components/FileModals.tsx index 41d2f6f7..2242ed23 100644 --- a/ui/src/views/admin/files/components/FileModals.tsx +++ b/ui/src/views/admin/files/components/FileModals.tsx @@ -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(

Rename {item.type === 'folder' ? 'Folder' : 'File'}

-
diff --git a/ui/src/views/admin/files/components/FileUploadModal.tsx b/ui/src/views/admin/files/components/FileUploadModal.tsx index 8bbd6858..470de2fb 100644 --- a/ui/src/views/admin/files/components/FileUploadModal.tsx +++ b/ui/src/views/admin/files/components/FileUploadModal.tsx @@ -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((props, disabled={uploading} /> - +

Choose files or drag and drop here

@@ -317,7 +317,7 @@ const FileUploadModal = forwardRef((props,