Sql Query Manager Tenant kontrolü
This commit is contained in:
parent
9938acb94c
commit
7bd6216515
11 changed files with 146 additions and 128 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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')}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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')}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
|||
Loading…
Reference in a new issue