SQL Query Managerda RunSql Script

This commit is contained in:
Sedat Öztürk 2026-03-18 21:53:51 +03:00
parent ff58614f0d
commit 63695be34e
5 changed files with 335 additions and 107 deletions

View file

@ -10428,6 +10428,30 @@
"tr": "Seçili Nesneler", "tr": "Seçili Nesneler",
"en": "Selected Objects" "en": "Selected Objects"
}, },
{
"resourceName": "Platform",
"key": "App.Platform.CopyObjects",
"tr": "Nesneleri Kopyala",
"en": "Copy Objects"
},
{
"resourceName": "Platform",
"key": "App.Platform.DirectSqlScript",
"tr": "Doğrudan SQL Scripti",
"en": "Direct SQL Script"
},
{
"resourceName": "Platform",
"key": "App.Platform.SqlScriptPlaceholder",
"tr": "SQL scriptinizi buraya girin",
"en": "Enter your SQL script here"
},
{
"resourceName": "Platform",
"key": "App.Platform.NoObjectSelected",
"tr": "Nesne Seçilmedi",
"en": "No Object Selected"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.Platform.SourceDataSource", "key": "App.Platform.SourceDataSource",
@ -10650,12 +10674,6 @@
"tr": "Fonksiyon", "tr": "Fonksiyon",
"en": "Function" "en": "Function"
}, },
{
"resourceName": "Platform",
"key": "App.Platform.Object",
"tr": "Nesne",
"en": "Object"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.Platform.Draft", "key": "App.Platform.Draft",
@ -11600,7 +11618,7 @@
}, },
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.Platform.Intranet.AnnouncementDetailModal.Close", "key": "App.Platform.Close",
"tr": "Kapat", "tr": "Kapat",
"en": "Close" "en": "Close"
}, },
@ -17051,6 +17069,24 @@
"en": "Overwrite if exists", "en": "Overwrite if exists",
"tr": "Varsa üzerine yaz" "tr": "Varsa üzerine yaz"
}, },
{
"resourceName": "Platform",
"key": "App.Platform.ExecutionCompleted",
"en": "Execution completed",
"tr": "Yürütme tamamlandı"
},
{
"resourceName": "Platform",
"key": "App.Platform.CopyCompleted",
"en": "Copy completed",
"tr": "Kopyalama tamamlandı"
},
{
"resourceName": "Platform",
"key": "App.Platform.CreateScriptFailed",
"en": "Failed to create script",
"tr": "Kaynak objenin scripti oluşturulamadı"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.Platform.OverwriteIfExistsDesc", "key": "App.Platform.OverwriteIfExistsDesc",
@ -17136,12 +17172,6 @@
"en": "Table successfully created", "en": "Table successfully created",
"tr": "Tablo başarıyla oluşturuldu" "tr": "Tablo başarıyla oluşturuldu"
}, },
{
"resourceName": "Platform",
"key": "App.SqlQueryManager.Error",
"en": "Error",
"tr": "Hata"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "App.SqlQueryManager.TableCreationFailed", "key": "App.SqlQueryManager.TableCreationFailed",

View file

@ -4142,7 +4142,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
CaptionName = "App.Listform.ListformField.PermissionJson", CaptionName = "App.Listform.ListformField.PermissionJson",
Width = 700, Width = 700,
ListOrderNo = 16, ListOrderNo = 16,
Visible = true, Visible = false,
IsActive = true, IsActive = true,
IsDeleted = false, IsDeleted = false,
ColumnCustomizationJson = DefaultColumnCustomizationJson, ColumnCustomizationJson = DefaultColumnCustomizationJson,

View file

@ -21,7 +21,7 @@ public class ListFormField : FullAuditedEntity<Guid>
public int? SortIndex { get; set; } //bu sütuna göre sıralama yapılacaktır. Birden fazla sütuna göre sıralama varsa, sortindexe göre sıralama yapılır. public int? SortIndex { get; set; } //bu sütuna göre sıralama yapılacaktır. Birden fazla sütuna göre sıralama varsa, sortindexe göre sıralama yapılır.
public string SortDirection { get; set; } // Sortindex varsa alacagi degerler asc, desc public string SortDirection { get; set; } // Sortindex varsa alacagi degerler asc, desc
public bool? AllowSearch { get; set; } public bool? AllowSearch { get; set; } = true;
public bool? AllowEditing { get; set; } public bool? AllowEditing { get; set; }
public string BandName { get; set; } public string BandName { get; set; }

View file

@ -324,6 +324,7 @@ const Wizard = () => {
const col = selectCommandColumns.find((c) => c.columnName === item.dataField) const col = selectCommandColumns.find((c) => c.columnName === item.dataField)
return { return {
dataField: item.dataField, dataField: item.dataField,
captionName: `App.Listform.ListformField.${item.dataField}`,
editorType: item.editorType, editorType: item.editorType,
editorOptions: item.editorOptions ?? '', editorOptions: item.editorOptions ?? '',
editorScript: item.editorScript ?? '', editorScript: item.editorScript ?? '',
@ -340,11 +341,12 @@ const Wizard = () => {
</Notification>, </Notification>,
{ placement: 'top-end' }, { placement: 'top-end' },
) )
setTimeout(async () => {
getConfig(true) getConfig(true)
setTimeout(() => {
navigate(ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode)) navigate(ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode))
}, 1500) }, 6000)
} }
return ( return (
@ -383,13 +385,12 @@ const Wizard = () => {
{ placement: 'top-end' }, { placement: 'top-end' },
) )
setSubmitting(false) setSubmitting(false)
setTimeout(async () => {
getConfig(true) getConfig(true)
setTimeout(() => { navigate(ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode))
navigate( }, 6000)
ROUTES_ENUM.protected.admin.list.replace(':listFormCode', values.listFormCode),
)
}, 1500)
} catch (error: any) { } catch (error: any) {
toast.push(<Notification title={error.message} type="danger" />, { toast.push(<Notification title={error.message} type="danger" />, {
placement: 'top-end', placement: 'top-end',

View file

@ -72,6 +72,8 @@ const SqlQueryManager = () => {
const [isCopyingObjects, setIsCopyingObjects] = useState(false) const [isCopyingObjects, setIsCopyingObjects] = useState(false)
const [copyResults, setCopyResults] = useState<SqlCopyResultItem[]>([]) const [copyResults, setCopyResults] = useState<SqlCopyResultItem[]>([])
const [showCopyResultDialog, setShowCopyResultDialog] = useState(false) const [showCopyResultDialog, setShowCopyResultDialog] = useState(false)
const [copyDialogMode, setCopyDialogMode] = useState<'objects' | 'sql'>('objects')
const [sqlScriptForCopy, setSqlScriptForCopy] = useState('')
useEffect(() => { useEffect(() => {
loadDataSources() loadDataSources()
@ -614,19 +616,18 @@ GO`,
const handleOpenCopyDialog = () => { const handleOpenCopyDialog = () => {
if (!state.selectedDataSource) return if (!state.selectedDataSource) return
if (selectedExplorerObjects.length === 0) { setCopyDialogMode('objects')
toast.push(
<Notification type="warning" title={translate('::App.Platform.Warning')}>
{'Lutfen kopyalamak icin en az bir obje secin.'}
</Notification>,
{ placement: 'top-center' },
)
return
}
setCopyTargetDataSources([]) setCopyTargetDataSources([])
setOverwriteIfExists(false) setOverwriteIfExists(false)
setSqlScriptForCopy('')
setShowCopyDialog(true) setShowCopyDialog(true)
// Eğer seçili obje yoksa uyarı göster
if (selectedExplorerObjects.length === 0) {
// SQL mode'da obje seçimi zorunlu değil, object mode'da zorunlu
// Bu uyarı sadece object mode'da gerekirse
return
}
} }
const handleCopyObjects = async () => { const handleCopyObjects = async () => {
@ -665,7 +666,7 @@ GO`,
objectFullName: obj.fullName, objectFullName: obj.fullName,
objectType: obj.objectType, objectType: obj.objectType,
status: 'error', status: 'error',
message: 'Kaynak objenin scripti olusturulamadi.', message: translate('::App.Platform.CreateScriptFailed'),
}) })
continue continue
} }
@ -686,7 +687,7 @@ GO`,
objectFullName: obj.fullName, objectFullName: obj.fullName,
objectType: obj.objectType, objectType: obj.objectType,
status: 'skipped', status: 'skipped',
message: translate('::App.SqlQueryManager.SkippedDescription') , message: translate('::App.SqlQueryManager.SkippedDescription'),
}) })
continue continue
} }
@ -706,7 +707,7 @@ GO`,
objectFullName: obj.fullName, objectFullName: obj.fullName,
objectType: obj.objectType, objectType: obj.objectType,
status: 'success', status: 'success',
message: 'Basariyla kopyalandi.', message: translate('::App.Platform.CopyCompleted'),
}) })
} catch (error: any) { } catch (error: any) {
results.push({ results.push({
@ -734,7 +735,8 @@ GO`,
: translate('::App.Platform.Success') : translate('::App.Platform.Success')
} }
> >
{`Kopyalama tamamlandi. Basarili: ${successCount}, Atlanan: ${skippedCount}, Hata: ${errorCount}`} {translate('::App.Platform.CopyCompleted') ||
`translate('::App.Platform.Successful'): ${successCount}, ${translate('::App.Platform.Error')}: ${errorCount}, ${translate('::App.Platform.Skipped')}: ${skippedCount}`}
</Notification>, </Notification>,
{ placement: 'top-center' }, { placement: 'top-center' },
) )
@ -748,6 +750,82 @@ GO`,
} }
} }
const handleExecuteDirectSql = async () => {
if (!sqlScriptForCopy?.trim()) {
toast.push(
<Notification type="warning" title={translate('::App.Platform.Warning')}>
{translate('::App.Platform.PleaseEnterQuery')}
</Notification>,
{ placement: 'top-center' },
)
return
}
if (copyTargetDataSources.length === 0) {
toast.push(
<Notification type="warning" title={translate('::App.Platform.Warning')}>
{translate('::App.Platform.PleaseSelectAtLeastOneTarget')}
</Notification>,
{ placement: 'top-center' },
)
return
}
setIsCopyingObjects(true)
const results: SqlCopyResultItem[] = []
try {
for (const targetDataSource of copyTargetDataSources) {
try {
await sqlObjectManagerService.executeQuery({
queryText: sqlScriptForCopy,
dataSourceCode: targetDataSource,
})
results.push({
targetDataSource,
objectFullName: 'SQL Script',
objectType: 'script' as any,
status: 'success',
message: 'Basariyla calistirildi.',
})
} catch (error: any) {
results.push({
targetDataSource,
objectFullName: 'SQL Script',
objectType: 'script' as any,
status: 'error',
message: error.response?.data?.error?.message || 'Calistirma basarisiz.',
})
}
}
const successCount = results.filter((x) => x.status === 'success').length
const errorCount = results.filter((x) => x.status === 'error').length
const notificationType = errorCount > 0 ? 'warning' : 'success'
toast.push(
<Notification
type={notificationType}
title={
errorCount > 0
? translate('::App.Platform.Warning')
: translate('::App.Platform.Success')
}
>
{translate('::App.Platform.ExecutionCompleted') ||
`translate('::App.Platform.Successful'): ${successCount}, ${translate('::App.Platform.Error')}: ${errorCount}`}
</Notification>,
{ placement: 'top-center' },
)
setCopyResults(results)
setShowCopyResultDialog(true)
setShowCopyDialog(false)
} finally {
setIsCopyingObjects(false)
}
}
const availableTargetDataSourceCodes = state.dataSources const availableTargetDataSourceCodes = state.dataSources
.filter((d) => d.code && d.code !== state.selectedDataSource) .filter((d) => d.code && d.code !== state.selectedDataSource)
.map((d) => d.code || '') .map((d) => d.code || '')
@ -805,8 +883,12 @@ GO`,
size="sm" size="sm"
variant="default" variant="default"
onClick={handleOpenCopyDialog} onClick={handleOpenCopyDialog}
disabled={!state.selectedDataSource || selectedExplorerObjects.length === 0} disabled={!state.selectedDataSource}
className="shadow-sm" className="shadow-sm"
title={
translate('::App.Platform.CopyOrExecuteSql') ||
'Seçili nesneleri kopyala veya SQL script calistir'
}
> >
{translate('::App.Platform.CopySelectedObjects') || 'Copy Selected Objects'} {translate('::App.Platform.CopySelectedObjects') || 'Copy Selected Objects'}
</Button> </Button>
@ -978,7 +1060,37 @@ GO`,
> >
<div className="flex h-full max-h-[85vh] flex-col"> <div className="flex h-full max-h-[85vh] flex-col">
<h5 className="mb-3">{translate('::App.Platform.CopySelectedObjects')}</h5> <h5 className="mb-3">{translate('::App.Platform.CopySelectedObjects')}</h5>
{/* Mode Tabs */}
<div className="flex gap-2 mb-4 border-b">
<button
onClick={() => setCopyDialogMode('objects')}
className={`px-4 py-2 font-medium text-sm border-b-2 transition ${
copyDialogMode === 'objects'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-600 hover:text-gray-900'
}`}
disabled={isCopyingObjects}
>
{translate('::App.Platform.CopyObjects') || 'Nesneleri Kopyala'}
</button>
<button
onClick={() => setCopyDialogMode('sql')}
className={`px-4 py-2 font-medium text-sm border-b-2 transition ${
copyDialogMode === 'sql'
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-600 hover:text-gray-900'
}`}
disabled={isCopyingObjects}
>
{translate('::App.Platform.DirectSqlScript') || 'Direkt SQL Script'}
</button>
</div>
<div className="flex-1 overflow-y-auto pr-1"> <div className="flex-1 overflow-y-auto pr-1">
{copyDialogMode === 'objects' ? (
<>
{/* Objects Mode */}
<div className="mb-2 flex items-center justify-between gap-4"> <div className="mb-2 flex items-center justify-between gap-4">
<p className="text-sm text-gray-600 dark:text-gray-400 mb-0"> <p className="text-sm text-gray-600 dark:text-gray-400 mb-0">
{translate('::App.Platform.SourceDataSource')}:{' '} {translate('::App.Platform.SourceDataSource')}:{' '}
@ -996,11 +1108,18 @@ GO`,
</div> </div>
<div className="mb-4 max-h-36 overflow-auto border rounded p-2"> <div className="mb-4 max-h-36 overflow-auto border rounded p-2">
{selectedExplorerObjects.map((obj) => ( {selectedExplorerObjects.length === 0 ? (
<p className="text-sm text-gray-500">
{translate('::App.Platform.NoObjectSelected') ||
'Secili nesne yok. Lutfen Explorer alanından bir nesne secin.'}
</p>
) : (
selectedExplorerObjects.map((obj) => (
<div key={obj.id} className="text-sm py-0.5"> <div key={obj.id} className="text-sm py-0.5">
{obj.objectType.toUpperCase()} - {obj.fullName} {obj.objectType.toUpperCase()} - {obj.fullName}
</div> </div>
))} ))
)}
</div> </div>
<div className="mb-4"> <div className="mb-4">
@ -1019,7 +1138,10 @@ GO`,
{availableTargetDataSourceCodes.map((code) => { {availableTargetDataSourceCodes.map((code) => {
const checked = copyTargetDataSources.includes(code) const checked = copyTargetDataSources.includes(code)
return ( return (
<label key={code} className="flex items-center gap-2 text-sm cursor-pointer"> <label
key={code}
className="flex items-center gap-2 text-sm cursor-pointer"
>
<input <input
type="checkbox" type="checkbox"
checked={checked} checked={checked}
@ -1036,7 +1158,65 @@ GO`,
})} })}
</div> </div>
</div> </div>
</>
) : (
<>
{/* SQL Mode */}
<div className="mb-2">
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">
{translate('::App.Platform.SourceDataSource')}:{' '}
<strong>{state.selectedDataSource}</strong>
</p>
</div> </div>
<div className="mb-4 border rounded bg-white dark:bg-gray-800 h-64 overflow-hidden">
<SqlEditor
value={sqlScriptForCopy}
onChange={(value) => setSqlScriptForCopy(value || '')}
readOnly={isCopyingObjects}
/>
</div>
<div className="mb-4">
<div className="text-sm font-medium mb-2">
{translate('::App.Platform.TargetDataSources')}
</div>
<label className="flex items-center gap-2 text-sm cursor-pointer mb-2">
<input
type="checkbox"
checked={allTargetsSelected}
onChange={(e) => handleToggleSelectAllTargets(e.target.checked)}
/>
<span>{translate('::App.Platform.SelectAllTargets') || 'Tumunu sec'}</span>
</label>
<div className="max-h-44 overflow-auto border-t pt-2">
{availableTargetDataSourceCodes.map((code) => {
const checked = copyTargetDataSources.includes(code)
return (
<label
key={code}
className="flex items-center gap-2 text-sm cursor-pointer"
>
<input
type="checkbox"
checked={checked}
onChange={(e) => {
setCopyTargetDataSources((prev) => {
if (e.target.checked) return [...prev, code]
return prev.filter((x) => x !== code)
})
}}
/>
<span>{code}</span>
</label>
)
})}
</div>
</div>
</>
)}
</div>
<div className="mt-2 flex justify-end gap-2 border-t pt-3"> <div className="mt-2 flex justify-end gap-2 border-t pt-3">
<Button <Button
variant="plain" variant="plain"
@ -1047,11 +1227,18 @@ GO`,
</Button> </Button>
<Button <Button
variant="solid" variant="solid"
onClick={handleCopyObjects} onClick={copyDialogMode === 'objects' ? handleCopyObjects : handleExecuteDirectSql}
loading={isCopyingObjects} loading={isCopyingObjects}
disabled={copyTargetDataSources.length === 0 || selectedExplorerObjects.length === 0} disabled={
copyTargetDataSources.length === 0 ||
(copyDialogMode === 'objects'
? selectedExplorerObjects.length === 0
: !sqlScriptForCopy?.trim())
}
> >
{translate('::Copy')} {copyDialogMode === 'objects'
? translate('::Copy')
: translate('::App.Platform.Execute') || 'Calistir'}
</Button> </Button>
</div> </div>
</div> </div>
@ -1137,11 +1324,21 @@ GO`,
<table className="hidden md:table w-full table-fixed text-sm"> <table className="hidden md:table w-full table-fixed text-sm">
<thead className="bg-gray-50 dark:bg-gray-700 sticky top-0 z-10"> <thead className="bg-gray-50 dark:bg-gray-700 sticky top-0 z-10">
<tr> <tr>
<th className="w-[110px] text-left px-3 py-2 border-b">{translate('::App.Platform.Status')}</th> <th className="w-[110px] text-left px-3 py-2 border-b">
<th className="w-[270px] text-left px-3 py-2 border-b">{translate('::App.Platform.Object')}</th> {translate('::App.Platform.Status')}
<th className="w-[90px] text-left px-3 py-2 border-b">{translate('::App.Platform.Type')}</th> </th>
<th className="w-[140px] text-left px-3 py-2 border-b">{translate('::App.Platform.Target')}</th> <th className="w-[270px] text-left px-3 py-2 border-b">
<th className="text-left px-3 py-2 border-b">{translate('::App.Platform.Message')}</th> {translate('::App.Platform.Object')}
</th>
<th className="w-[90px] text-left px-3 py-2 border-b">
{translate('::App.Platform.Type')}
</th>
<th className="w-[140px] text-left px-3 py-2 border-b">
{translate('::App.Platform.Target')}
</th>
<th className="text-left px-3 py-2 border-b">
{translate('::App.Platform.Message')}
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -1209,7 +1406,7 @@ GO`,
<div className="flex justify-end gap-2 mt-4"> <div className="flex justify-end gap-2 mt-4">
<Button variant="solid" onClick={() => setShowCopyResultDialog(false)}> <Button variant="solid" onClick={() => setShowCopyResultDialog(false)}>
Kapat {translate('::App.Platform.Close')}
</Button> </Button>
</div> </div>
</Dialog> </Dialog>