Sql Query Manager -> Move to Host Data
This commit is contained in:
parent
0f30c4ad7c
commit
84b9f65107
7 changed files with 449 additions and 47 deletions
|
|
@ -44,5 +44,10 @@ public interface ISqlObjectManagerAppService : IApplicationService
|
|||
/// <summary>
|
||||
/// Lists .sql files currently available under DbMigrator Seeds/SqlData.
|
||||
/// </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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,5 +5,8 @@ namespace Sozsoft.SqlQueryManager.Application.Contracts;
|
|||
public class SqlDataFileDto
|
||||
{
|
||||
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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,3 +46,10 @@ public class DeleteSqlDataFilesDto
|
|||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -955,27 +955,48 @@ FROM (
|
|||
}
|
||||
|
||||
[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();
|
||||
|
||||
try
|
||||
{
|
||||
var outputPath = ResolveSqlDataOutputPath();
|
||||
var rootPath = ResolveSqlDataOutputPath(dataDirectoryName);
|
||||
var outputPath = ResolveSqlDataChildPath(rootPath, relativePath);
|
||||
if (!Directory.Exists(outputPath))
|
||||
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)
|
||||
.Select(f => new SqlDataFileDto
|
||||
{
|
||||
FileName = Path.GetFileName(f)!,
|
||||
Name = Path.GetFileName(f)!,
|
||||
RelativePath = BuildSqlDataRelativePath(relativePath, Path.GetFileName(f)!),
|
||||
IsDirectory = false,
|
||||
CreatedAt = File.GetCreationTime(f)
|
||||
})
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.FileName))
|
||||
.OrderBy(x => x.FileName, StringComparer.OrdinalIgnoreCase)
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.Name));
|
||||
|
||||
var entries = directories
|
||||
.Concat(files)
|
||||
.OrderByDescending(x => x.IsDirectory)
|
||||
.ThenBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult(files);
|
||||
return Task.FromResult(entries);
|
||||
}
|
||||
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()
|
||||
{
|
||||
return ResolveSqlDataOutputPath("SqlData");
|
||||
}
|
||||
|
||||
private string ResolveSqlDataOutputPath(string dataDirectoryName)
|
||||
{
|
||||
const string dbMigratorName = "Sozsoft.Platform.DbMigrator";
|
||||
var safeDirectoryName = NormalizeSqlDataDirectoryName(dataDirectoryName);
|
||||
var dir = new DirectoryInfo(_hostEnvironment.ContentRootPath);
|
||||
|
||||
while (dir != null)
|
||||
{
|
||||
var candidate = Path.Combine(dir.FullName, "src", dbMigratorName, "Seeds");
|
||||
if (Directory.Exists(candidate))
|
||||
return Path.Combine(candidate, "SqlData");
|
||||
return Path.Combine(candidate, safeDirectoryName);
|
||||
|
||||
candidate = Path.Combine(dir.FullName, dbMigratorName, "Seeds");
|
||||
if (Directory.Exists(candidate))
|
||||
return Path.Combine(candidate, "SqlData");
|
||||
return Path.Combine(candidate, safeDirectoryName);
|
||||
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17782,15 +17782,45 @@
|
|||
},
|
||||
{
|
||||
"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",
|
||||
"en": "No indexes defined",
|
||||
"tr": "Dizin tanımlanmamış"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.SqlQueryManager.IndexKey",
|
||||
"en": "Index Key",
|
||||
"tr": "Dizin Anahtarı"
|
||||
"key": "App.Platform.OperationCompleted",
|
||||
"en": "Operation completed successfully",
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -79,11 +79,28 @@ export class SqlObjectManagerService {
|
|||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
getSqlDataFiles = (config?: Partial<Config>) =>
|
||||
apiService.fetchData<{ fileName: string; createdAt: string }[], void>(
|
||||
getSqlDataFiles = (dataDirectoryName = 'SqlData', relativePath = '', config?: Partial<Config>) =>
|
||||
apiService.fetchData<
|
||||
{ fileName: string; name: string; relativePath: string; isDirectory: boolean; createdAt: string }[],
|
||||
void
|
||||
>(
|
||||
{
|
||||
method: 'GET',
|
||||
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 },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
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 ConfirmDialog from '@/components/shared/ConfirmDialog'
|
||||
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 type { SqlQueryExecutionResultDto } from '@/proxy/sql-query-manager/models'
|
||||
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 { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import SqlObjectExplorer, { type SqlExplorerSelectedObject } from './SqlObjectExplorer'
|
||||
|
|
@ -20,7 +29,7 @@ import { useStoreState } from '@/store/store'
|
|||
import { APP_NAME } from '@/constants/app.constant'
|
||||
import { UiEvalService } from '@/services/UiEvalService'
|
||||
import { FcAcceptDatabase } from 'react-icons/fc'
|
||||
import { FaFolderOpen } from "react-icons/fa"
|
||||
import { FaFolderOpen } from 'react-icons/fa'
|
||||
|
||||
interface SqlManagerState {
|
||||
dataSources: DataSourceDto[]
|
||||
|
|
@ -42,6 +51,14 @@ interface SqlCopyResultItem {
|
|||
message: string
|
||||
}
|
||||
|
||||
interface SqlDataExplorerEntry {
|
||||
fileName: string
|
||||
name: string
|
||||
relativePath: string
|
||||
isDirectory: boolean
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
const SqlQueryManager = () => {
|
||||
const { translate } = useLocalization()
|
||||
const editorRef = useRef<SqlEditorRef>(null)
|
||||
|
|
@ -82,7 +99,11 @@ const SqlQueryManager = () => {
|
|||
const [sqlScriptForCopy, setSqlScriptForCopy] = useState('')
|
||||
const [showSqlDataFilesDialog, setShowSqlDataFilesDialog] = 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(() => {
|
||||
loadDataSources()
|
||||
|
|
@ -150,6 +171,7 @@ const SqlQueryManager = () => {
|
|||
(item) => item.code === state.selectedDataSource,
|
||||
)?.dataSourceType
|
||||
const isPostgreSql = selectedDataSourceType === DataSourceTypeEnum.Postgresql
|
||||
const sqlDataDirectoryName = isPostgreSql ? 'PostgresData' : 'SqlData'
|
||||
|
||||
const buildTableScriptQuery = (schemaName: string, tableName: string) => {
|
||||
const fullName = getSafeFullName(schemaName, tableName)
|
||||
|
|
@ -990,15 +1012,24 @@ GO`,
|
|||
const copyErrorCount = copyResults.filter((x) => x.status === 'error').length
|
||||
const copySkippedCount = copyResults.filter((x) => x.status === 'skipped').length
|
||||
|
||||
const handleOpenSqlDataFilesDialog = async () => {
|
||||
setShowSqlDataFilesDialog(true)
|
||||
const loadSqlDataFiles = async () => {
|
||||
setIsLoadingSqlDataFiles(true)
|
||||
|
||||
try {
|
||||
const response = await sqlObjectManagerService.getSqlDataFiles()
|
||||
setSqlDataFiles(response.data || [])
|
||||
const [rootResponse, hostResponse] = await Promise.all([
|
||||
sqlObjectManagerService.getSqlDataFiles(sqlDataDirectoryName, ''),
|
||||
sqlObjectManagerService.getSqlDataFiles(sqlDataDirectoryName, 'HostData'),
|
||||
])
|
||||
|
||||
setSqlDataRootFiles(normalizeSqlDataEntries(rootResponse.data || [], ''))
|
||||
setSqlDataHostFiles(normalizeSqlDataEntries(hostResponse.data || [], 'HostData'))
|
||||
setSelectedRootSqlDataFiles([])
|
||||
setSelectedHostSqlDataFiles([])
|
||||
} catch (error: any) {
|
||||
setSqlDataFiles([])
|
||||
setSqlDataRootFiles([])
|
||||
setSqlDataHostFiles([])
|
||||
setSelectedRootSqlDataFiles([])
|
||||
setSelectedHostSqlDataFiles([])
|
||||
toast.push(
|
||||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{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 (
|
||||
<Container className="flex flex-col overflow-hidden" style={{ height: 'calc(100vh - 130px)' }}>
|
||||
<Helmet
|
||||
|
|
@ -1056,9 +1229,9 @@ GO`,
|
|||
icon={<FaFolderOpen />}
|
||||
onClick={handleOpenSqlDataFilesDialog}
|
||||
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>
|
||||
</div>
|
||||
|
||||
|
|
@ -1219,7 +1392,8 @@ GO`,
|
|||
}}
|
||||
>
|
||||
<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>
|
||||
</ConfirmDialog>
|
||||
|
||||
|
|
@ -1227,36 +1401,79 @@ GO`,
|
|||
isOpen={showSqlDataFilesDialog}
|
||||
onClose={() => setShowSqlDataFilesDialog(false)}
|
||||
onRequestClose={() => setShowSqlDataFilesDialog(false)}
|
||||
width={1050}
|
||||
contentClassName="max-h-[90vh] overflow-hidden"
|
||||
>
|
||||
<div className="flex max-h-[72vh] min-h-[320px] flex-col">
|
||||
<h5 className="mb-4 shrink-0">
|
||||
{translate('::App.SqlQueryManager.SqlDataFiles') || 'SqlData Files'}
|
||||
</h5>
|
||||
<Dialog.Body className="flex max-h-[90vh] min-h-[420px] flex-col gap-2">
|
||||
<div className="mb-4 shrink-0">
|
||||
<div>
|
||||
<h5>{translate('::App.SqlQueryManager.MoveFiles')}</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isLoadingSqlDataFiles ? (
|
||||
<p className="mb-4 text-gray-600 dark:text-gray-400">
|
||||
{translate('::App.Platform.Loading') || 'Loading...'}
|
||||
</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">
|
||||
<ul className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
{sqlDataFiles.map((file) => (
|
||||
<li key={file.fileName} className="flex items-center justify-between px-3 py-2 text-sm text-gray-700 dark:text-gray-200">
|
||||
<span>{file.fileName}</span>
|
||||
<span className="ml-4 shrink-0 text-xs text-gray-400 dark:text-gray-500">
|
||||
{new Date(file.createdAt).toLocaleString()}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex min-h-0 flex-1 flex-col gap-3 lg:flex-row">
|
||||
{renderSqlDataPane(
|
||||
sqlDataDirectoryName,
|
||||
sqlDataRootFiles,
|
||||
selectedRootSqlDataFiles,
|
||||
setSelectedRootSqlDataFiles,
|
||||
)}
|
||||
|
||||
<div className="flex shrink-0 flex-row items-center justify-center gap-2 lg:w-24 lg:flex-col">
|
||||
<Button
|
||||
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>
|
||||
</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>
|
||||
|
||||
{/* Template Confirmation Dialog */}
|
||||
|
|
@ -1273,7 +1490,12 @@ GO`,
|
|||
<Button variant="plain" onClick={handleCancelTemplateReplace}>
|
||||
{translate('::Cancel')}
|
||||
</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')}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue