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.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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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')}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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')}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue