Sql Query Manager -> Move to Host Data

This commit is contained in:
Sedat Öztürk 2026-05-27 15:36:46 +03:00
parent 0f30c4ad7c
commit 84b9f65107
7 changed files with 449 additions and 47 deletions

View file

@ -44,5 +44,10 @@ public interface ISqlObjectManagerAppService : IApplicationService
/// <summary> /// <summary>
/// Lists .sql files currently available under DbMigrator Seeds/SqlData. /// Lists .sql files currently available under DbMigrator Seeds/SqlData.
/// </summary> /// </summary>
Task<List<SqlDataFileDto>> GetSqlDataFilesAsync(); Task<List<SqlDataFileDto>> GetSqlDataFilesAsync(string dataDirectoryName = "SqlData", string relativePath = "");
/// <summary>
/// Moves a SQL seed file between the selected data directory root and HostData.
/// </summary>
Task MoveSqlDataFileAsync(MoveSqlDataFileDto input);
} }

View file

@ -5,5 +5,8 @@ namespace Sozsoft.SqlQueryManager.Application.Contracts;
public class SqlDataFileDto public class SqlDataFileDto
{ {
public string FileName { get; set; } = string.Empty; public string FileName { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string RelativePath { get; set; } = string.Empty;
public bool IsDirectory { get; set; }
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
} }

View file

@ -46,3 +46,10 @@ public class DeleteSqlDataFilesDto
/// </summary> /// </summary>
public List<string> FileNames { get; set; } = new(); public List<string> FileNames { get; set; } = new();
} }
public class MoveSqlDataFileDto
{
public string DataDirectoryName { get; set; } = string.Empty;
public string SourceRelativePath { get; set; } = string.Empty;
public string TargetRelativePath { get; set; } = string.Empty;
}

View file

@ -955,27 +955,48 @@ FROM (
} }
[HttpGet("api/app/sql-object-manager/sql-data-files")] [HttpGet("api/app/sql-object-manager/sql-data-files")]
public Task<List<SqlDataFileDto>> GetSqlDataFilesAsync() public Task<List<SqlDataFileDto>> GetSqlDataFilesAsync(
[FromQuery] string dataDirectoryName = "SqlData",
[FromQuery] string relativePath = "")
{ {
ValidateTenantAccess(); ValidateTenantAccess();
try try
{ {
var outputPath = ResolveSqlDataOutputPath(); var rootPath = ResolveSqlDataOutputPath(dataDirectoryName);
var outputPath = ResolveSqlDataChildPath(rootPath, relativePath);
if (!Directory.Exists(outputPath)) if (!Directory.Exists(outputPath))
return Task.FromResult(new List<SqlDataFileDto>()); return Task.FromResult(new List<SqlDataFileDto>());
var directories = Directory.GetDirectories(outputPath, "*", SearchOption.TopDirectoryOnly)
.Where(d => string.Equals(Path.GetFileName(d), "HostData", StringComparison.OrdinalIgnoreCase))
.Select(d => new SqlDataFileDto
{
FileName = Path.GetFileName(d)!,
Name = Path.GetFileName(d)!,
RelativePath = BuildSqlDataRelativePath(relativePath, Path.GetFileName(d)!),
IsDirectory = true,
CreatedAt = Directory.GetCreationTime(d)
});
var files = Directory.GetFiles(outputPath, "*.sql", SearchOption.TopDirectoryOnly) var files = Directory.GetFiles(outputPath, "*.sql", SearchOption.TopDirectoryOnly)
.Select(f => new SqlDataFileDto .Select(f => new SqlDataFileDto
{ {
FileName = Path.GetFileName(f)!, FileName = Path.GetFileName(f)!,
Name = Path.GetFileName(f)!,
RelativePath = BuildSqlDataRelativePath(relativePath, Path.GetFileName(f)!),
IsDirectory = false,
CreatedAt = File.GetCreationTime(f) CreatedAt = File.GetCreationTime(f)
}) })
.Where(x => !string.IsNullOrWhiteSpace(x.FileName)) .Where(x => !string.IsNullOrWhiteSpace(x.Name));
.OrderBy(x => x.FileName, StringComparer.OrdinalIgnoreCase)
var entries = directories
.Concat(files)
.OrderByDescending(x => x.IsDirectory)
.ThenBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
.ToList(); .ToList();
return Task.FromResult(files); return Task.FromResult(entries);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -984,24 +1005,121 @@ FROM (
} }
} }
[HttpPost("api/app/sql-object-manager/move-sql-data-file")]
public Task MoveSqlDataFileAsync(MoveSqlDataFileDto input)
{
ValidateTenantAccess();
if (input == null)
throw new Volo.Abp.UserFriendlyException("Invalid move request.");
var rootPath = ResolveSqlDataOutputPath(input.DataDirectoryName);
var sourcePath = ResolveSqlDataChildPath(rootPath, input.SourceRelativePath);
var targetPath = ResolveSqlDataChildPath(rootPath, input.TargetRelativePath);
if (!File.Exists(sourcePath))
throw new Volo.Abp.UserFriendlyException("Source file was not found.");
if (!string.Equals(Path.GetExtension(sourcePath), ".sql", StringComparison.OrdinalIgnoreCase) ||
!string.Equals(Path.GetExtension(targetPath), ".sql", StringComparison.OrdinalIgnoreCase))
{
throw new Volo.Abp.UserFriendlyException("Only .sql files can be moved.");
}
Directory.CreateDirectory(Path.GetDirectoryName(targetPath)!);
if (File.Exists(targetPath))
throw new Volo.Abp.UserFriendlyException("A file with the same name already exists in the target folder.");
File.Move(sourcePath, targetPath);
_logger.LogInformation("SQL seed file moved from {SourcePath} to {TargetPath}", sourcePath, targetPath);
return Task.CompletedTask;
}
private string ResolveSqlDataOutputPath() private string ResolveSqlDataOutputPath()
{
return ResolveSqlDataOutputPath("SqlData");
}
private string ResolveSqlDataOutputPath(string dataDirectoryName)
{ {
const string dbMigratorName = "Sozsoft.Platform.DbMigrator"; const string dbMigratorName = "Sozsoft.Platform.DbMigrator";
var safeDirectoryName = NormalizeSqlDataDirectoryName(dataDirectoryName);
var dir = new DirectoryInfo(_hostEnvironment.ContentRootPath); var dir = new DirectoryInfo(_hostEnvironment.ContentRootPath);
while (dir != null) while (dir != null)
{ {
var candidate = Path.Combine(dir.FullName, "src", dbMigratorName, "Seeds"); var candidate = Path.Combine(dir.FullName, "src", dbMigratorName, "Seeds");
if (Directory.Exists(candidate)) if (Directory.Exists(candidate))
return Path.Combine(candidate, "SqlData"); return Path.Combine(candidate, safeDirectoryName);
candidate = Path.Combine(dir.FullName, dbMigratorName, "Seeds"); candidate = Path.Combine(dir.FullName, dbMigratorName, "Seeds");
if (Directory.Exists(candidate)) if (Directory.Exists(candidate))
return Path.Combine(candidate, "SqlData"); return Path.Combine(candidate, safeDirectoryName);
dir = dir.Parent; dir = dir.Parent;
} }
return Path.Combine(_hostEnvironment.ContentRootPath, "Seeds", "SqlData"); return Path.Combine(_hostEnvironment.ContentRootPath, "Seeds", safeDirectoryName);
}
private static string NormalizeSqlDataDirectoryName(string dataDirectoryName)
{
return string.Equals(dataDirectoryName, "PostgresData", StringComparison.OrdinalIgnoreCase)
? "PostgresData"
: "SqlData";
}
private static string ResolveSqlDataChildPath(string rootPath, string relativePath)
{
var normalized = NormalizeSqlDataRelativePath(relativePath);
var fullPath = Path.GetFullPath(Path.Combine(rootPath, normalized));
var fullRoot = Path.GetFullPath(rootPath);
var fullRootWithSeparator = fullRoot.EndsWith(Path.DirectorySeparatorChar)
? fullRoot
: fullRoot + Path.DirectorySeparatorChar;
if (!string.Equals(fullPath, fullRoot, StringComparison.OrdinalIgnoreCase) &&
!fullPath.StartsWith(fullRootWithSeparator, StringComparison.OrdinalIgnoreCase))
{
throw new Volo.Abp.UserFriendlyException("Invalid path.");
}
return fullPath;
}
private static string NormalizeSqlDataRelativePath(string relativePath)
{
if (string.IsNullOrWhiteSpace(relativePath))
return string.Empty;
var normalized = relativePath.Replace('\\', '/').Trim('/');
var parts = normalized.Split('/', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 0)
return string.Empty;
if (parts.Any(p => p == "." || p == ".." || p.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0))
throw new Volo.Abp.UserFriendlyException("Invalid path.");
var isRootFile = parts.Length == 1 && parts[0].EndsWith(".sql", StringComparison.OrdinalIgnoreCase);
var isHostDataFolder = parts.Length == 1 && string.Equals(parts[0], "HostData", StringComparison.OrdinalIgnoreCase);
var isHostDataFile = parts.Length == 2 &&
string.Equals(parts[0], "HostData", StringComparison.OrdinalIgnoreCase) &&
parts[1].EndsWith(".sql", StringComparison.OrdinalIgnoreCase);
if (!isRootFile && !isHostDataFolder && !isHostDataFile)
throw new Volo.Abp.UserFriendlyException("Invalid path.");
return Path.Combine(parts);
}
private static string BuildSqlDataRelativePath(string parentRelativePath, string name)
{
if (string.IsNullOrWhiteSpace(parentRelativePath))
return name;
return $"{parentRelativePath.Trim('/', '\\')}/{name}";
} }
} }

View file

@ -17782,15 +17782,45 @@
}, },
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.SqlQueryManager.MoveFiles",
"en": "Move to Host Folder",
"tr": "Host Klasörüne Taşı"
},
{
"resourceName": "Platform",
"key": "App.SqlQueryManager.MoveToHostData",
"en": "Move to Host Data",
"tr": "Host Klasörüne Taşı"
},
{
"resourceName": "Platform",
"key": "App.SqlQueryManager.MoveOut",
"en": "Move Out",
"tr": "Dışarı Taşı"
},
{
"resourceName": "Platform",
"key": "App.SqlQueryManager.NoIndexesDefined",
"en": "No indexes defined",
"tr": "Dizin tanımlanmamış"
},
{
"resourceName": "Platform",
"key": "App.SqlQueryManager.NoIndexesDefined", "key": "App.SqlQueryManager.NoIndexesDefined",
"en": "No indexes defined", "en": "No indexes defined",
"tr": "Dizin tanımlanmamış" "tr": "Dizin tanımlanmamış"
}, },
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.SqlQueryManager.IndexKey", "key": "App.Platform.OperationCompleted",
"en": "Index Key", "en": "Operation completed successfully",
"tr": "Dizin Anahtarı" "tr": "İşlem başarıyla tamamlandı"
},
{
"resourceName": "Platform",
"key": "App.SqlQueryManager.NoSqlDataFiles",
"en": "No SQL data files found",
"tr": "SQL veri dosyası bulunamadı"
}, },
{ {
"resourceName": "Platform", "resourceName": "Platform",

View file

@ -79,11 +79,28 @@ export class SqlObjectManagerService {
{ apiName: this.apiName, ...config }, { apiName: this.apiName, ...config },
) )
getSqlDataFiles = (config?: Partial<Config>) => getSqlDataFiles = (dataDirectoryName = 'SqlData', relativePath = '', config?: Partial<Config>) =>
apiService.fetchData<{ fileName: string; createdAt: string }[], void>( apiService.fetchData<
{ fileName: string; name: string; relativePath: string; isDirectory: boolean; createdAt: string }[],
void
>(
{ {
method: 'GET', method: 'GET',
url: '/api/app/sql-object-manager/sql-data-files', url: '/api/app/sql-object-manager/sql-data-files',
params: { dataDirectoryName, relativePath },
},
{ apiName: this.apiName, ...config },
)
moveSqlDataFile = (
input: { dataDirectoryName: string; sourceRelativePath: string; targetRelativePath: string },
config?: Partial<Config>,
) =>
apiService.fetchData<void, { dataDirectoryName: string; sourceRelativePath: string; targetRelativePath: string }>(
{
method: 'POST',
url: '/api/app/sql-object-manager/move-sql-data-file',
data: input,
}, },
{ apiName: this.apiName, ...config }, { apiName: this.apiName, ...config },
) )

View file

@ -1,5 +1,6 @@
import { useState, useCallback, useEffect, useRef } from 'react' import { useState, useCallback, useEffect, useRef } from 'react'
import { Button, Dialog, Notification, toast } from '@/components/ui' import type { Dispatch, SetStateAction } from 'react'
import { Button, Checkbox, Dialog, Notification, toast } from '@/components/ui'
import Container from '@/components/shared/Container' import Container from '@/components/shared/Container'
import ConfirmDialog from '@/components/shared/ConfirmDialog' import ConfirmDialog from '@/components/shared/ConfirmDialog'
import { getDataSources } from '@/services/data-source.service' import { getDataSources } from '@/services/data-source.service'
@ -7,7 +8,15 @@ import type { DataSourceDto } from '@/proxy/data-source'
import { DataSourceTypeEnum } from '@/proxy/form/models' import { DataSourceTypeEnum } from '@/proxy/form/models'
import type { SqlQueryExecutionResultDto } from '@/proxy/sql-query-manager/models' import type { SqlQueryExecutionResultDto } 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, FaFileAlt, FaCopy, FaExclamationTriangle } from 'react-icons/fa' import {
FaDatabase,
FaPlay,
FaFileAlt,
FaCopy,
FaExclamationTriangle,
FaArrowLeft,
FaArrowRight,
} from 'react-icons/fa'
import { FaCheckCircle } from 'react-icons/fa' import { FaCheckCircle } from 'react-icons/fa'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
import SqlObjectExplorer, { type SqlExplorerSelectedObject } from './SqlObjectExplorer' import SqlObjectExplorer, { type SqlExplorerSelectedObject } from './SqlObjectExplorer'
@ -20,7 +29,7 @@ import { useStoreState } from '@/store/store'
import { APP_NAME } from '@/constants/app.constant' import { APP_NAME } from '@/constants/app.constant'
import { UiEvalService } from '@/services/UiEvalService' import { UiEvalService } from '@/services/UiEvalService'
import { FcAcceptDatabase } from 'react-icons/fc' import { FcAcceptDatabase } from 'react-icons/fc'
import { FaFolderOpen } from "react-icons/fa" import { FaFolderOpen } from 'react-icons/fa'
interface SqlManagerState { interface SqlManagerState {
dataSources: DataSourceDto[] dataSources: DataSourceDto[]
@ -42,6 +51,14 @@ interface SqlCopyResultItem {
message: string message: string
} }
interface SqlDataExplorerEntry {
fileName: string
name: string
relativePath: string
isDirectory: boolean
createdAt: string
}
const SqlQueryManager = () => { const SqlQueryManager = () => {
const { translate } = useLocalization() const { translate } = useLocalization()
const editorRef = useRef<SqlEditorRef>(null) const editorRef = useRef<SqlEditorRef>(null)
@ -82,7 +99,11 @@ const SqlQueryManager = () => {
const [sqlScriptForCopy, setSqlScriptForCopy] = useState('') const [sqlScriptForCopy, setSqlScriptForCopy] = useState('')
const [showSqlDataFilesDialog, setShowSqlDataFilesDialog] = useState(false) const [showSqlDataFilesDialog, setShowSqlDataFilesDialog] = useState(false)
const [isLoadingSqlDataFiles, setIsLoadingSqlDataFiles] = useState(false) const [isLoadingSqlDataFiles, setIsLoadingSqlDataFiles] = useState(false)
const [sqlDataFiles, setSqlDataFiles] = useState<{ fileName: string; createdAt: string }[]>([]) const [isMovingSqlDataFile, setIsMovingSqlDataFile] = useState(false)
const [sqlDataRootFiles, setSqlDataRootFiles] = useState<SqlDataExplorerEntry[]>([])
const [sqlDataHostFiles, setSqlDataHostFiles] = useState<SqlDataExplorerEntry[]>([])
const [selectedRootSqlDataFiles, setSelectedRootSqlDataFiles] = useState<string[]>([])
const [selectedHostSqlDataFiles, setSelectedHostSqlDataFiles] = useState<string[]>([])
useEffect(() => { useEffect(() => {
loadDataSources() loadDataSources()
@ -150,6 +171,7 @@ const SqlQueryManager = () => {
(item) => item.code === state.selectedDataSource, (item) => item.code === state.selectedDataSource,
)?.dataSourceType )?.dataSourceType
const isPostgreSql = selectedDataSourceType === DataSourceTypeEnum.Postgresql const isPostgreSql = selectedDataSourceType === DataSourceTypeEnum.Postgresql
const sqlDataDirectoryName = isPostgreSql ? 'PostgresData' : 'SqlData'
const buildTableScriptQuery = (schemaName: string, tableName: string) => { const buildTableScriptQuery = (schemaName: string, tableName: string) => {
const fullName = getSafeFullName(schemaName, tableName) const fullName = getSafeFullName(schemaName, tableName)
@ -990,15 +1012,24 @@ GO`,
const copyErrorCount = copyResults.filter((x) => x.status === 'error').length const copyErrorCount = copyResults.filter((x) => x.status === 'error').length
const copySkippedCount = copyResults.filter((x) => x.status === 'skipped').length const copySkippedCount = copyResults.filter((x) => x.status === 'skipped').length
const handleOpenSqlDataFilesDialog = async () => { const loadSqlDataFiles = async () => {
setShowSqlDataFilesDialog(true)
setIsLoadingSqlDataFiles(true) setIsLoadingSqlDataFiles(true)
try { try {
const response = await sqlObjectManagerService.getSqlDataFiles() const [rootResponse, hostResponse] = await Promise.all([
setSqlDataFiles(response.data || []) sqlObjectManagerService.getSqlDataFiles(sqlDataDirectoryName, ''),
sqlObjectManagerService.getSqlDataFiles(sqlDataDirectoryName, 'HostData'),
])
setSqlDataRootFiles(normalizeSqlDataEntries(rootResponse.data || [], ''))
setSqlDataHostFiles(normalizeSqlDataEntries(hostResponse.data || [], 'HostData'))
setSelectedRootSqlDataFiles([])
setSelectedHostSqlDataFiles([])
} catch (error: any) { } catch (error: any) {
setSqlDataFiles([]) setSqlDataRootFiles([])
setSqlDataHostFiles([])
setSelectedRootSqlDataFiles([])
setSelectedHostSqlDataFiles([])
toast.push( toast.push(
<Notification type="danger" title={translate('::App.Platform.Error')}> <Notification type="danger" title={translate('::App.Platform.Error')}>
{error.response?.data?.error?.message || {error.response?.data?.error?.message ||
@ -1012,6 +1043,148 @@ GO`,
} }
} }
const handleOpenSqlDataFilesDialog = async () => {
setShowSqlDataFilesDialog(true)
await loadSqlDataFiles()
}
const normalizeSqlDataEntries = (
files: SqlDataExplorerEntry[],
parentRelativePath: '' | 'HostData',
) =>
files
.filter((file) => !file.isDirectory)
.map((file) => {
const fileName = file.name || file.fileName || file.relativePath?.split('/').pop() || ''
const relativePath =
file.relativePath || (parentRelativePath ? `${parentRelativePath}/${fileName}` : fileName)
return {
...file,
fileName,
name: fileName,
relativePath,
}
})
.filter((file) => Boolean(file.name && file.relativePath))
const toggleSqlDataFileSelection = (
setSelectedFiles: Dispatch<SetStateAction<string[]>>,
relativePath: string,
) => {
setSelectedFiles((current) =>
current.includes(relativePath)
? current.filter((item) => item !== relativePath)
: [...current, relativePath],
)
}
const handleMoveSqlDataFiles = async (direction: 'toHostData' | 'toRoot') => {
const selectedRelativePaths =
direction === 'toHostData' ? selectedRootSqlDataFiles : selectedHostSqlDataFiles
if (selectedRelativePaths.length === 0) {
return
}
setIsMovingSqlDataFile(true)
try {
for (const sourceRelativePath of selectedRelativePaths) {
const name = sourceRelativePath.split('/').pop() || sourceRelativePath
await sqlObjectManagerService.moveSqlDataFile({
dataDirectoryName: sqlDataDirectoryName,
sourceRelativePath,
targetRelativePath: direction === 'toHostData' ? `HostData/${name}` : name,
})
}
await loadSqlDataFiles()
toast.push(
<Notification type="success" title={translate('::App.Platform.Success')}>
{translate('::App.Platform.OperationCompleted') || 'Dosya tasindi.'}
</Notification>,
{ placement: 'top-center' },
)
} catch (error: any) {
toast.push(
<Notification type="danger" title={translate('::App.Platform.Error')}>
{error.response?.data?.error?.message || 'Dosya tasinamadi.'}
</Notification>,
{ placement: 'top-center' },
)
} finally {
setIsMovingSqlDataFile(false)
}
}
const renderSqlDataPane = (
title: string,
files: SqlDataExplorerEntry[],
selectedFiles: string[],
setSelectedFiles: Dispatch<SetStateAction<string[]>>,
) => (
<div className="flex min-h-[260px] flex-1 flex-col overflow-hidden rounded border border-gray-200 dark:border-gray-700">
<div className="flex shrink-0 items-center justify-between border-b border-gray-200 bg-gray-50 px-3 py-2 dark:border-gray-700 dark:bg-gray-800">
<div className="flex items-center gap-2">
<h6 className="text-sm font-semibold text-gray-800 dark:text-gray-100">{title}</h6>
<span className="text-xs text-gray-500 dark:text-gray-400">
{selectedFiles.length}/{files.length}
</span>
</div>
<Checkbox
checked={files.length > 0 && selectedFiles.length === files.length}
disabled={files.length === 0 || isMovingSqlDataFile}
onChange={(event) =>
setSelectedFiles(event ? files.map((file) => file.relativePath) : [])
}
>
{translate('::FileManager.SelectAll')}
</Checkbox>
</div>
<div className="flex-1 overflow-y-auto">
{files.length === 0 ? (
<p className="px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
{translate('::App.SqlQueryManager.NoSqlDataFiles') || 'Dosya bulunamadi.'}
</p>
) : (
<ul className="divide-y divide-gray-200 dark:divide-gray-700">
{files.map((file) => {
const selected = selectedFiles.includes(file.relativePath)
return (
<li key={file.relativePath || file.fileName}>
<label
className={`flex cursor-pointer items-center gap-3 px-3 py-2 text-xs ${
selected
? 'bg-blue-50 text-blue-700 dark:bg-blue-950/40 dark:text-blue-200'
: 'text-gray-700 hover:bg-gray-50 dark:text-gray-200 dark:hover:bg-gray-800'
}`}
>
<input
type="checkbox"
className="h-4 w-4 shrink-0"
checked={selected}
disabled={isMovingSqlDataFile}
onChange={() =>
toggleSqlDataFileSelection(setSelectedFiles, file.relativePath)
}
/>
<FaFileAlt className="shrink-0 text-gray-400" />
<span className="min-w-0 flex-1 truncate">{file.name || file.fileName}</span>
<span className="hidden shrink-0 text-xs text-gray-400 dark:text-gray-500 sm:inline">
{new Date(file.createdAt).toLocaleString()}
</span>
</label>
</li>
)
})}
</ul>
)}
</div>
</div>
)
return ( return (
<Container className="flex flex-col overflow-hidden" style={{ height: 'calc(100vh - 130px)' }}> <Container className="flex flex-col overflow-hidden" style={{ height: 'calc(100vh - 130px)' }}>
<Helmet <Helmet
@ -1056,9 +1229,9 @@ GO`,
icon={<FaFolderOpen />} icon={<FaFolderOpen />}
onClick={handleOpenSqlDataFilesDialog} onClick={handleOpenSqlDataFilesDialog}
className="shadow-sm px-2 py-1" className="shadow-sm px-2 py-1"
title={translate('::App.SqlQueryManager.SqlDataFiles') || 'Show SqlData files'} title={translate('::App.SqlQueryManager.MoveFiles')}
> >
{translate('::App.SqlQueryManager.SqlDataFiles') || 'SqlData Files'} {translate('::App.SqlQueryManager.MoveFiles')}
</Button> </Button>
</div> </div>
@ -1219,7 +1392,8 @@ GO`,
}} }}
> >
<p className="text-gray-600 dark:text-gray-400"> <p className="text-gray-600 dark:text-gray-400">
{translate('::App.DbMigrate.ConfirmMessage') || 'Are you sure you want to start the database migration process?'} {translate('::App.DbMigrate.ConfirmMessage') ||
'Are you sure you want to start the database migration process?'}
</p> </p>
</ConfirmDialog> </ConfirmDialog>
@ -1227,36 +1401,79 @@ GO`,
isOpen={showSqlDataFilesDialog} isOpen={showSqlDataFilesDialog}
onClose={() => setShowSqlDataFilesDialog(false)} onClose={() => setShowSqlDataFilesDialog(false)}
onRequestClose={() => setShowSqlDataFilesDialog(false)} onRequestClose={() => setShowSqlDataFilesDialog(false)}
width={1050}
contentClassName="max-h-[90vh] overflow-hidden" contentClassName="max-h-[90vh] overflow-hidden"
> >
<div className="flex max-h-[72vh] min-h-[320px] flex-col"> <Dialog.Body className="flex max-h-[90vh] min-h-[420px] flex-col gap-2">
<h5 className="mb-4 shrink-0"> <div className="mb-4 shrink-0">
{translate('::App.SqlQueryManager.SqlDataFiles') || 'SqlData Files'} <div>
</h5> <h5>{translate('::App.SqlQueryManager.MoveFiles')}</h5>
</div>
</div>
{isLoadingSqlDataFiles ? ( {isLoadingSqlDataFiles ? (
<p className="mb-4 text-gray-600 dark:text-gray-400"> <p className="mb-4 text-gray-600 dark:text-gray-400">
{translate('::App.Platform.Loading') || 'Loading...'} {translate('::App.Platform.Loading') || 'Loading...'}
</p> </p>
) : sqlDataFiles.length === 0 ? (
<p className="mb-4 text-gray-600 dark:text-gray-400">
{translate('::App.SqlQueryManager.NoSqlDataFiles') || 'SqlData klasorunde .sql dosyasi bulunamadi.'}
</p>
) : ( ) : (
<div className="mb-4 h-[70vh] min-h-[220px] overflow-y-auto rounded border border-gray-200 dark:border-gray-700"> <div className="flex min-h-0 flex-1 flex-col gap-3 lg:flex-row">
<ul className="divide-y divide-gray-200 dark:divide-gray-700"> {renderSqlDataPane(
{sqlDataFiles.map((file) => ( sqlDataDirectoryName,
<li key={file.fileName} className="flex items-center justify-between px-3 py-2 text-sm text-gray-700 dark:text-gray-200"> sqlDataRootFiles,
<span>{file.fileName}</span> selectedRootSqlDataFiles,
<span className="ml-4 shrink-0 text-xs text-gray-400 dark:text-gray-500"> setSelectedRootSqlDataFiles,
{new Date(file.createdAt).toLocaleString()} )}
</span>
</li> <div className="flex shrink-0 flex-row items-center justify-center gap-2 lg:w-24 lg:flex-col">
))} <Button
</ul> size="sm"
variant="default"
icon={<FaArrowRight />}
onClick={() => handleMoveSqlDataFiles('toHostData')}
loading={isMovingSqlDataFile}
disabled={isMovingSqlDataFile || selectedRootSqlDataFiles.length === 0}
title={
translate('::App.SqlQueryManager.MoveToHostData') || 'HostData klasorune tasi'
}
/>
<Button
size="sm"
variant="default"
icon={<FaArrowLeft />}
onClick={() => handleMoveSqlDataFiles('toRoot')}
loading={isMovingSqlDataFile}
disabled={isMovingSqlDataFile || selectedHostSqlDataFiles.length === 0}
title={translate('::App.SqlQueryManager.MoveOut') || 'Disari tasi'}
/>
</div>
{renderSqlDataPane(
'HostData',
sqlDataHostFiles,
selectedHostSqlDataFiles,
setSelectedHostSqlDataFiles,
)}
</div> </div>
)} )}
</div> </Dialog.Body>
<Dialog.Footer className="flex justify-end gap-2 pt-3 mt-1">
<Button
variant="plain"
onClick={loadSqlDataFiles}
loading={isLoadingSqlDataFiles}
disabled={isLoadingSqlDataFiles || isMovingSqlDataFile}
>
{translate('::App.Platform.Refresh') || 'Yenile'}
</Button>
<Button
variant="solid"
onClick={() => setShowSqlDataFilesDialog(false)}
disabled={isMovingSqlDataFile}
>
{translate('::Close') || translate('::Cancel') || 'Kapat'}
</Button>
</Dialog.Footer>
</Dialog> </Dialog>
{/* Template Confirmation Dialog */} {/* Template Confirmation Dialog */}
@ -1273,7 +1490,12 @@ GO`,
<Button variant="plain" onClick={handleCancelTemplateReplace}> <Button variant="plain" onClick={handleCancelTemplateReplace}>
{translate('::Cancel')} {translate('::Cancel')}
</Button> </Button>
<Button variant="solid" onClick={handleConfirmTemplateReplace} icon={<FaExclamationTriangle />} color="red-600"> <Button
variant="solid"
onClick={handleConfirmTemplateReplace}
icon={<FaExclamationTriangle />}
color="red-600"
>
{translate('::App.Platform.Replace')} {translate('::App.Platform.Replace')}
</Button> </Button>
</div> </div>