Sql Query Manager düzenlemesi
This commit is contained in:
parent
3d419a6e7c
commit
b9331e66b4
14 changed files with 3054 additions and 4 deletions
|
|
@ -619,6 +619,12 @@
|
|||
"en": "Forum Management",
|
||||
"tr": "Forum Yönetimi"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.SqlQueryManager",
|
||||
"en": "SQL Query Manager",
|
||||
"tr": "SQL Sorgu Yöneticisi"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.DeveloperKit",
|
||||
|
|
@ -10314,6 +10320,552 @@
|
|||
"key": "ListForms.SchedulerOptions.Agenda",
|
||||
"tr": "Ajanda",
|
||||
"en": "Agenda"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Error",
|
||||
"tr": "Hata",
|
||||
"en": "Error"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Edit",
|
||||
"tr": "Düzenle",
|
||||
"en": "Edit"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Delete",
|
||||
"tr": "Sil",
|
||||
"en": "Delete"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Warning",
|
||||
"tr": "Uyarı",
|
||||
"en": "Warning"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Success",
|
||||
"tr": "Başarılı",
|
||||
"en": "Success"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Execute",
|
||||
"tr": "Çalıştır",
|
||||
"en": "Execute"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Save",
|
||||
"tr": "Kaydet",
|
||||
"en": "Save"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Deploy",
|
||||
"tr": "Dağıt",
|
||||
"en": "Deploy"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Refresh",
|
||||
"tr": "Tazele",
|
||||
"en": "Refresh"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Replace",
|
||||
"tr": "Değiştir",
|
||||
"en": "Replace"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Cancel",
|
||||
"tr": "İptal",
|
||||
"en": "Cancel"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Loading",
|
||||
"tr": "Yükleniyor...",
|
||||
"en": "Loading..."
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.FailedtoloadDatasources",
|
||||
"tr": "Veri kaynakları yüklenemedi",
|
||||
"en": "Failed to load data sources"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.PleaseSelectDataSource",
|
||||
"tr": "Lütfen bir veri kaynağı seçin",
|
||||
"en": "Please select data source"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.UnsavedChangesConfirmation",
|
||||
"tr": "Kaydedilmemiş değişiklikleriniz var. Devam etmek istediğinizden emin misiniz?",
|
||||
"en": "You have unsaved changes. Are you sure you want to continue?"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.PleaseEnterQuery",
|
||||
"tr": "Lütfen sorgu girin",
|
||||
"en": "Please enter query"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.QueryExecutedSuccessfully",
|
||||
"tr": "Sorgu başarıyla çalıştırıldı",
|
||||
"en": "Query executed successfully"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.PleaseEnterContentToSave",
|
||||
"tr": "Lütfen kaydetmek için içerik girin",
|
||||
"en": "Please enter content to save"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ObjectUpdatedSuccessfully",
|
||||
"tr": "Nesne başarıyla güncellendi",
|
||||
"en": "Object updated successfully"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.FailedToUpdateObject",
|
||||
"tr": "Nesne güncellenemedi",
|
||||
"en": "Failed to update object"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.QuerySavedSuccessfully",
|
||||
"tr": "Sorgu başarıyla kaydedildi",
|
||||
"en": "Query saved successfully"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.FailedToSaveQuery",
|
||||
"tr": "Sorgu kaydedilemedi",
|
||||
"en": "Failed to save query"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.PleaseSelectAnObjectToDeploy",
|
||||
"tr": "Lütfen dağıtmak için bir nesne seçin",
|
||||
"en": "Please select an object to deploy"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ThisObjectTypeCannotBeDeployed",
|
||||
"tr": "Bu nesne türü dağıtılamaz",
|
||||
"en": "This object type cannot be deployed"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ObjectDeployedSuccessfully",
|
||||
"tr": "Bu nesne türü başarıyla dağıtıldı",
|
||||
"en": "Object deployed successfully"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.FailedToDeployObject",
|
||||
"tr": "Bu nesne türü dağıtılamadı",
|
||||
"en": "Failed to deploy object"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.SaveAsNewQuery",
|
||||
"tr": "Yeni Sorgu Olarak Kaydet",
|
||||
"en": "Save as New Query"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ObjectDeletedSuccessfully",
|
||||
"tr": "Nesne başarıyla silindi",
|
||||
"en": "Object deleted successfully"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.SelectAnObjectToViewProperties",
|
||||
"tr": "Özellikleri görüntülemek için bir nesne seçin",
|
||||
"en": "Select an object to view properties"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Query",
|
||||
"tr": "Sorgu",
|
||||
"en": "Query"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.StoredProcedure",
|
||||
"tr": "Saklı Yordam",
|
||||
"en": "Stored Procedure"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.View",
|
||||
"tr": "View",
|
||||
"en": "View"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Function",
|
||||
"tr": "Fonksiyon",
|
||||
"en": "Function"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Object",
|
||||
"tr": "Nesne",
|
||||
"en": "Object"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Draft",
|
||||
"tr": "Taslak",
|
||||
"en": "Draft"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Active",
|
||||
"tr": "Aktif",
|
||||
"en": "Active"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Archived",
|
||||
"tr": "Arşivlenmiş",
|
||||
"en": "Archived"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Unknown",
|
||||
"tr": "Bilinmiyor",
|
||||
"en": "Unknown"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ObjectType",
|
||||
"tr": "Nesne Tipi",
|
||||
"en": "Object Type"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ID",
|
||||
"tr": "ID",
|
||||
"en": "ID"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Created",
|
||||
"tr": "Oluşturulma",
|
||||
"en": "Created"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Modified",
|
||||
"tr": "Güncellenme",
|
||||
"en": "Modified"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Code",
|
||||
"tr": "Kod",
|
||||
"en": "Code"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Name",
|
||||
"tr": "Ad",
|
||||
"en": "Name"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Description",
|
||||
"tr": "Açıklama",
|
||||
"en": "Description"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.DataSource",
|
||||
"tr": "Veri Kaynağı",
|
||||
"en": "Data Source"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Status",
|
||||
"tr": "Durum",
|
||||
"en": "Status"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Category",
|
||||
"tr": "Kategori",
|
||||
"en": "Category"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Tags",
|
||||
"tr": "Etiketler",
|
||||
"en": "Tags"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ModifiesData",
|
||||
"tr": "Veri Değiştirir",
|
||||
"en": "Modifies Data"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Yes",
|
||||
"tr": "Evet",
|
||||
"en": "Yes"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.No",
|
||||
"tr": "Hayır",
|
||||
"en": "No"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ExecutionCount",
|
||||
"tr": "Çalıştırma Sayısı",
|
||||
"en": "Execution Count"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.LastExecuted",
|
||||
"tr": "Son Çalıştırma",
|
||||
"en": "Last Executed"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ProcedureName",
|
||||
"tr": "Prosedür Adı",
|
||||
"en": "Procedure Name"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Schema",
|
||||
"tr": "Şema",
|
||||
"en": "Schema"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.DisplayName",
|
||||
"tr": "Görünen Ad",
|
||||
"en": "Display Name"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Deployed",
|
||||
"tr": "Dağıtıldı",
|
||||
"en": "Deployed"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.LastDeployed",
|
||||
"tr": "Son Dağıtım",
|
||||
"en": "Last Deployed"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ViewName",
|
||||
"tr": "View Adı",
|
||||
"en": "View Name"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.SchemaBinding",
|
||||
"tr": "Schema Binding",
|
||||
"en": "Schema Binding"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.FunctionName",
|
||||
"tr": "Fonksiyon Adı",
|
||||
"en": "Function Name"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.FunctionType",
|
||||
"tr": "Fonksiyon Tipi",
|
||||
"en": "Function Type"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ReturnType",
|
||||
"tr": "Dönüş Tipi",
|
||||
"en": "Return Type"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ScalarFunction",
|
||||
"tr": "Scalar Fonksiyon",
|
||||
"en": "Scalar Function"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.TableValuedFunction",
|
||||
"tr": "Tablo Döndüren Fonksiyon",
|
||||
"en": "Table-Valued Function"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.InlineTableValuedFunction",
|
||||
"tr": "Inline Tablo Döndüren Fonksiyon",
|
||||
"en": "Inline Table-Valued Function"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Properties",
|
||||
"tr": "Özellikler",
|
||||
"en": "Properties"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Rows",
|
||||
"tr": "Satır",
|
||||
"en": "Rows"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Time",
|
||||
"tr": "Süre",
|
||||
"en": "Time"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Search",
|
||||
"tr": "Ara...",
|
||||
"en": "Search..."
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.NoResults",
|
||||
"tr": "Sonuç bulunamadı",
|
||||
"en": "No results"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.RowCount",
|
||||
"tr": "{{count}} satır etkilendi",
|
||||
"en": "{{count}} row(s) affected"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.NoResultsReturned",
|
||||
"tr": "Sorgu başarıyla çalıştı ancak sonuç döndürmedi.",
|
||||
"en": "The query executed successfully but returned no results."
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Queries",
|
||||
"tr": "Sorgular",
|
||||
"en": "Queries"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.StoredProcedures",
|
||||
"tr": "Saklı Yordamlar",
|
||||
"en": "Stored Procedures"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Views",
|
||||
"tr": "Görünümler",
|
||||
"en": "Views"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Functions",
|
||||
"tr": "Fonksiyonlar",
|
||||
"en": "Functions"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.FailedToLoadObjects",
|
||||
"tr": "Nesneler yüklenemedi",
|
||||
"en": "Failed to load objects"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ObjectExplorer",
|
||||
"tr": "Nesne Gezgini",
|
||||
"en": "Object Explorer"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.QueryEditor",
|
||||
"tr": "Sorgu Editörü",
|
||||
"en": "Query Editor"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Results",
|
||||
"tr": "Sonuçlar",
|
||||
"en": "Results"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.Templates",
|
||||
"tr": "Şablonlar",
|
||||
"en": "Templates"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.TemplateReplaceWarning",
|
||||
"tr": "Şablon içeriği mevcut sorgu içeriğini değiştirecektir. Devam etmek istediğinizden emin misiniz?",
|
||||
"en": "The template content will replace the current query content. Are you sure you want to continue?"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ConfirmTemplateReplace",
|
||||
"tr": "Şablon Değişikliğini Onayla",
|
||||
"en": "Confirm Template Replace"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.TemplateLoaded",
|
||||
"tr": "Şablon Yüklendi",
|
||||
"en": "Template Loaded"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.NoDataSourceSelected",
|
||||
"tr": "Veri kaynağı seçilmedi",
|
||||
"en": "No data source selected"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.FailedToDeleteObject",
|
||||
"tr": "Nesne silinemedi",
|
||||
"en": "Failed to delete object"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.ConfirmDelete",
|
||||
"tr": "Silme Onayı",
|
||||
"en": "Confirm Delete"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.DeleteConfirmationMessage",
|
||||
"tr": "Bu nesneyi silmek istediğinizden emin misiniz?",
|
||||
"en": "Are you sure you want to delete this object?"
|
||||
},
|
||||
{
|
||||
"resourceName": "Platform",
|
||||
"key": "App.Platform.DeleteAction",
|
||||
"tr": "Sil",
|
||||
"en": "Delete"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -296,6 +296,15 @@
|
|||
"routeType": "protected",
|
||||
"authority": []
|
||||
},
|
||||
{
|
||||
"key": "admin.sqlQueryManager",
|
||||
"path": "/admin/sqlQueryManager",
|
||||
"componentPath": "@/views/sqlQueryManager/SqlQueryManager",
|
||||
"routeType": "protected",
|
||||
"authority": [
|
||||
"App.SqlQueryManager"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "admin.developerkit",
|
||||
"path": "/admin/developerkit",
|
||||
|
|
@ -1698,6 +1707,16 @@
|
|||
"RequiredPermissionName": "App.DeveloperKit.DynamicServices",
|
||||
"IsDisabled": false
|
||||
},
|
||||
{
|
||||
"ParentCode": "App.Administration",
|
||||
"Code": "App.SqlQueryManager",
|
||||
"DisplayName": "App.SqlQueryManager",
|
||||
"Order": 9,
|
||||
"Url": "/admin/sqlQueryManager",
|
||||
"Icon": "FaDatabase",
|
||||
"RequiredPermissionName": "App.SqlQueryManager",
|
||||
"IsDisabled": false
|
||||
},
|
||||
{
|
||||
"ParentCode": "App.Administration",
|
||||
"Code": "App.Intranet",
|
||||
|
|
|
|||
|
|
@ -2263,6 +2263,15 @@
|
|||
"MultiTenancySide": 3,
|
||||
"MenuGroup": "Erp|Kurs"
|
||||
},
|
||||
{
|
||||
"GroupName": "App.Administration",
|
||||
"Name": "App.SqlQueryManager",
|
||||
"ParentName": null,
|
||||
"DisplayName": "App.SqlQueryManager",
|
||||
"IsEnabled": true,
|
||||
"MultiTenancySide": 3,
|
||||
"MenuGroup": "Erp|Kurs"
|
||||
},
|
||||
{
|
||||
"GroupName": "App.Administration",
|
||||
"Name": "App.DeveloperKit",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
|||
namespace Erp.Platform.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlatformDbContext))]
|
||||
[Migration("20251205085044_Initial")]
|
||||
[Migration("20251205115506_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
246
ui/src/proxy/sql-query-manager/models.ts
Normal file
246
ui/src/proxy/sql-query-manager/models.ts
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
import type { FullAuditedEntityDto, PagedAndSortedResultRequestDto } from '../index'
|
||||
|
||||
export enum SqlObjectType {
|
||||
Query = 1,
|
||||
StoredProcedure = 2,
|
||||
View = 3,
|
||||
Function = 4,
|
||||
}
|
||||
|
||||
export enum SqlFunctionType {
|
||||
ScalarFunction = 1,
|
||||
TableValuedFunction = 2,
|
||||
InlineTableValuedFunction = 3,
|
||||
}
|
||||
|
||||
export enum SqlQueryStatus {
|
||||
Draft = 1,
|
||||
Active = 2,
|
||||
Archived = 3,
|
||||
}
|
||||
|
||||
// SQL Function DTOs
|
||||
export interface SqlFunctionDto extends FullAuditedEntityDto<string> {
|
||||
functionName: string
|
||||
schemaName: string
|
||||
displayName: string
|
||||
description: string
|
||||
functionType: SqlFunctionType
|
||||
functionBody: string
|
||||
returnType: string
|
||||
dataSourceCode: string
|
||||
status: SqlQueryStatus
|
||||
category: string
|
||||
isDeployed: boolean
|
||||
lastDeployedAt?: string
|
||||
parameters: string
|
||||
}
|
||||
|
||||
export interface CreateSqlFunctionDto {
|
||||
functionName: string
|
||||
schemaName: string
|
||||
displayName: string
|
||||
description: string
|
||||
functionType: SqlFunctionType
|
||||
functionBody: string
|
||||
returnType: string
|
||||
dataSourceCode: string
|
||||
category: string
|
||||
parameters: string
|
||||
}
|
||||
|
||||
export interface UpdateSqlFunctionDto {
|
||||
displayName: string
|
||||
description: string
|
||||
functionBody: string
|
||||
returnType: string
|
||||
category: string
|
||||
parameters: string
|
||||
}
|
||||
|
||||
export interface DeployFunctionDto {
|
||||
id: string
|
||||
dropIfExists: boolean
|
||||
}
|
||||
|
||||
// SQL Query DTOs
|
||||
export interface SqlQueryDto extends FullAuditedEntityDto<string> {
|
||||
code: string
|
||||
name: string
|
||||
description: string
|
||||
queryText: string
|
||||
dataSourceCode: string
|
||||
status: SqlQueryStatus
|
||||
category: string
|
||||
tags: string
|
||||
lastExecutedAt?: string
|
||||
executionCount: number
|
||||
isModifyingData: boolean
|
||||
parameters: string
|
||||
}
|
||||
|
||||
export interface CreateSqlQueryDto {
|
||||
code: string
|
||||
name: string
|
||||
description: string
|
||||
queryText: string
|
||||
dataSourceCode: string
|
||||
category: string
|
||||
tags: string
|
||||
isModifyingData: boolean
|
||||
parameters: string
|
||||
}
|
||||
|
||||
export interface UpdateSqlQueryDto {
|
||||
code: string
|
||||
name: string
|
||||
description: string
|
||||
queryText: string
|
||||
dataSourceCode: string
|
||||
category: string
|
||||
tags: string
|
||||
isModifyingData: boolean
|
||||
parameters: string
|
||||
}
|
||||
|
||||
export interface ExecuteSqlQueryDto {
|
||||
queryText: string
|
||||
dataSourceCode: string
|
||||
parameters?: Record<string, any>
|
||||
}
|
||||
|
||||
export interface ExecuteSavedQueryDto {
|
||||
id: string
|
||||
parameters?: Record<string, any>
|
||||
}
|
||||
|
||||
export interface ValidateQueryDto {
|
||||
queryText: string
|
||||
dataSourceCode: string
|
||||
}
|
||||
|
||||
// SQL Stored Procedure DTOs
|
||||
export interface SqlStoredProcedureDto extends FullAuditedEntityDto<string> {
|
||||
procedureName: string
|
||||
schemaName: string
|
||||
displayName: string
|
||||
description: string
|
||||
procedureBody: string
|
||||
dataSourceCode: string
|
||||
status: SqlQueryStatus
|
||||
category: string
|
||||
isDeployed: boolean
|
||||
lastDeployedAt?: string
|
||||
parameters: string
|
||||
}
|
||||
|
||||
export interface CreateSqlStoredProcedureDto {
|
||||
procedureName: string
|
||||
schemaName: string
|
||||
displayName: string
|
||||
description: string
|
||||
procedureBody: string
|
||||
dataSourceCode: string
|
||||
category: string
|
||||
parameters: string
|
||||
}
|
||||
|
||||
export interface UpdateSqlStoredProcedureDto {
|
||||
displayName: string
|
||||
description: string
|
||||
procedureBody: string
|
||||
category: string
|
||||
parameters: string
|
||||
}
|
||||
|
||||
export interface DeployStoredProcedureDto {
|
||||
id: string
|
||||
dropIfExists: boolean
|
||||
}
|
||||
|
||||
// SQL View DTOs
|
||||
export interface SqlViewDto extends FullAuditedEntityDto<string> {
|
||||
viewName: string
|
||||
schemaName: string
|
||||
displayName: string
|
||||
description: string
|
||||
viewDefinition: string
|
||||
dataSourceCode: string
|
||||
status: SqlQueryStatus
|
||||
category: string
|
||||
isDeployed: boolean
|
||||
lastDeployedAt?: string
|
||||
withSchemaBinding: boolean
|
||||
}
|
||||
|
||||
export interface CreateSqlViewDto {
|
||||
viewName: string
|
||||
schemaName: string
|
||||
displayName: string
|
||||
description: string
|
||||
viewDefinition: string
|
||||
dataSourceCode: string
|
||||
category: string
|
||||
withSchemaBinding: boolean
|
||||
}
|
||||
|
||||
export interface UpdateSqlViewDto {
|
||||
displayName: string
|
||||
description: string
|
||||
viewDefinition: string
|
||||
category: string
|
||||
withSchemaBinding: boolean
|
||||
}
|
||||
|
||||
export interface DeployViewDto {
|
||||
id: string
|
||||
dropIfExists: boolean
|
||||
}
|
||||
|
||||
// SQL Template DTOs
|
||||
export interface SqlTemplateDto {
|
||||
name: string
|
||||
description: string
|
||||
template: string
|
||||
category: string
|
||||
}
|
||||
|
||||
// SQL Execution Result
|
||||
export interface SqlQueryExecutionResultDto {
|
||||
success: boolean
|
||||
message: string
|
||||
executionTimeMs: number
|
||||
rowsAffected: number
|
||||
data?: any[]
|
||||
metadata?: Record<string, any>
|
||||
error?: string
|
||||
}
|
||||
|
||||
// Request DTOs
|
||||
export interface GetSqlFunctionsInput extends PagedAndSortedResultRequestDto {
|
||||
filter?: string
|
||||
dataSourceCode?: string
|
||||
status?: SqlQueryStatus
|
||||
category?: string
|
||||
}
|
||||
|
||||
export interface GetSqlQueriesInput extends PagedAndSortedResultRequestDto {
|
||||
filter?: string
|
||||
dataSourceCode?: string
|
||||
status?: SqlQueryStatus
|
||||
category?: string
|
||||
}
|
||||
|
||||
export interface GetSqlStoredProceduresInput extends PagedAndSortedResultRequestDto {
|
||||
filter?: string
|
||||
dataSourceCode?: string
|
||||
status?: SqlQueryStatus
|
||||
category?: string
|
||||
}
|
||||
|
||||
export interface GetSqlViewsInput extends PagedAndSortedResultRequestDto {
|
||||
filter?: string
|
||||
dataSourceCode?: string
|
||||
status?: SqlQueryStatus
|
||||
category?: string
|
||||
}
|
||||
|
|
@ -233,6 +233,9 @@ export const ROUTES_ENUM = {
|
|||
bank: '/admin/accounting/bank',
|
||||
checkNote: '/admin/accounting/check-note',
|
||||
},
|
||||
|
||||
sqlManager: '/admin/sql-manager',
|
||||
|
||||
accessDenied: '/admin/access-denied',
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import apiService, { Config } from '@/services/api.service'
|
||||
import { PagedAndSortedResultRequestDto, PagedResultDto } from '../abp'
|
||||
import type { DataSourceDto } from './models'
|
||||
import { DataSourceDto } from '@/proxy/data-source'
|
||||
import { PagedAndSortedResultRequestDto, PagedResultDto } from '@/proxy'
|
||||
|
||||
export class DataSourceService {
|
||||
apiName = 'Default'
|
||||
442
ui/src/services/sql-query-manager.service.ts
Normal file
442
ui/src/services/sql-query-manager.service.ts
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
import apiService, { Config } from '@/services/api.service'
|
||||
import type { PagedResultDto } from '@/proxy'
|
||||
import type {
|
||||
SqlFunctionDto,
|
||||
CreateSqlFunctionDto,
|
||||
UpdateSqlFunctionDto,
|
||||
DeployFunctionDto,
|
||||
SqlQueryDto,
|
||||
CreateSqlQueryDto,
|
||||
UpdateSqlQueryDto,
|
||||
ExecuteSqlQueryDto,
|
||||
ExecuteSavedQueryDto,
|
||||
ValidateQueryDto,
|
||||
SqlStoredProcedureDto,
|
||||
CreateSqlStoredProcedureDto,
|
||||
UpdateSqlStoredProcedureDto,
|
||||
DeployStoredProcedureDto,
|
||||
SqlViewDto,
|
||||
CreateSqlViewDto,
|
||||
UpdateSqlViewDto,
|
||||
DeployViewDto,
|
||||
SqlTemplateDto,
|
||||
SqlQueryExecutionResultDto,
|
||||
GetSqlFunctionsInput,
|
||||
GetSqlQueriesInput,
|
||||
GetSqlStoredProceduresInput,
|
||||
GetSqlViewsInput,
|
||||
} from '@/proxy/sql-query-manager/models'
|
||||
|
||||
export class SqlFunctionService {
|
||||
apiName = 'Default'
|
||||
|
||||
create = (input: CreateSqlFunctionDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlFunctionDto, CreateSqlFunctionDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/api/app/sql-function',
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
getList = (input: GetSqlFunctionsInput, config?: Partial<Config>) =>
|
||||
apiService.fetchData<PagedResultDto<SqlFunctionDto>, GetSqlFunctionsInput>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: '/api/app/sql-function',
|
||||
params: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
get = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlFunctionDto, void>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `/api/app/sql-function/${id}`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
update = (id: string, input: UpdateSqlFunctionDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlFunctionDto, UpdateSqlFunctionDto>(
|
||||
{
|
||||
method: 'PUT',
|
||||
url: `/api/app/sql-function/${id}`,
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
delete = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<void, void>(
|
||||
{
|
||||
method: 'DELETE',
|
||||
url: `/api/app/sql-function/${id}`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
deploy = (input: DeployFunctionDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, DeployFunctionDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/api/app/sql-function/deploy',
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
checkExists = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<boolean, void>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/api/app/sql-function/${id}/check-exists`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
drop = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, void>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/api/app/sql-function/${id}/drop`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
}
|
||||
|
||||
export class SqlQueryService {
|
||||
apiName = 'Default'
|
||||
|
||||
create = (input: CreateSqlQueryDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryDto, CreateSqlQueryDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/api/app/sql-query',
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
getList = (input: GetSqlQueriesInput, config?: Partial<Config>) =>
|
||||
apiService.fetchData<PagedResultDto<SqlQueryDto>, GetSqlQueriesInput>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: '/api/app/sql-query',
|
||||
params: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
get = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryDto, void>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `/api/app/sql-query/${id}`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
update = (id: string, input: UpdateSqlQueryDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryDto, UpdateSqlQueryDto>(
|
||||
{
|
||||
method: 'PUT',
|
||||
url: `/api/app/sql-query/${id}`,
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
delete = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<void, void>(
|
||||
{
|
||||
method: 'DELETE',
|
||||
url: `/api/app/sql-query/${id}`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
executeQuery = (input: ExecuteSqlQueryDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, ExecuteSqlQueryDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/api/app/sql-query/execute-query',
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
executeSavedQuery = (id: string, parameters?: Record<string, any>, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, ExecuteSavedQueryDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/api/app/sql-query/${id}/execute-saved-query`,
|
||||
data: { id, parameters },
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
validateQuery = (input: ValidateQueryDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, ValidateQueryDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/api/app/sql-query/validate-query',
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
activate = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<void, void>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/api/app/sql-query/${id}/activate`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
archive = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<void, void>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/api/app/sql-query/${id}/archive`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
}
|
||||
|
||||
export class SqlStoredProcedureService {
|
||||
apiName = 'Default'
|
||||
|
||||
create = (input: CreateSqlStoredProcedureDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlStoredProcedureDto, CreateSqlStoredProcedureDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/api/app/sql-stored-procedure',
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
getList = (input: GetSqlStoredProceduresInput, config?: Partial<Config>) =>
|
||||
apiService.fetchData<PagedResultDto<SqlStoredProcedureDto>, GetSqlStoredProceduresInput>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: '/api/app/sql-stored-procedure',
|
||||
params: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
get = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlStoredProcedureDto, void>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `/api/app/sql-stored-procedure/${id}`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
update = (id: string, input: UpdateSqlStoredProcedureDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlStoredProcedureDto, UpdateSqlStoredProcedureDto>(
|
||||
{
|
||||
method: 'PUT',
|
||||
url: `/api/app/sql-stored-procedure/${id}`,
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
delete = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<void, void>(
|
||||
{
|
||||
method: 'DELETE',
|
||||
url: `/api/app/sql-stored-procedure/${id}`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
deploy = (input: DeployStoredProcedureDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, DeployStoredProcedureDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/api/app/sql-stored-procedure/deploy',
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
checkExists = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<boolean, void>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/api/app/sql-stored-procedure/${id}/check-exists`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
drop = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, void>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/api/app/sql-stored-procedure/${id}/drop`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
}
|
||||
|
||||
export class SqlViewService {
|
||||
apiName = 'Default'
|
||||
|
||||
create = (input: CreateSqlViewDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlViewDto, CreateSqlViewDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/api/app/sql-view',
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
getList = (input: GetSqlViewsInput, config?: Partial<Config>) =>
|
||||
apiService.fetchData<PagedResultDto<SqlViewDto>, GetSqlViewsInput>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: '/api/app/sql-view',
|
||||
params: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
get = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlViewDto, void>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: `/api/app/sql-view/${id}`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
update = (id: string, input: UpdateSqlViewDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlViewDto, UpdateSqlViewDto>(
|
||||
{
|
||||
method: 'PUT',
|
||||
url: `/api/app/sql-view/${id}`,
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
delete = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<void, void>(
|
||||
{
|
||||
method: 'DELETE',
|
||||
url: `/api/app/sql-view/${id}`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
deploy = (input: DeployViewDto, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, DeployViewDto>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: '/api/app/sql-view/deploy',
|
||||
data: input,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
checkExists = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<boolean, void>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/api/app/sql-view/${id}/check-exists`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
drop = (id: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlQueryExecutionResultDto, void>(
|
||||
{
|
||||
method: 'POST',
|
||||
url: `/api/app/sql-view/${id}/drop`,
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
}
|
||||
|
||||
export class SqlTemplateService {
|
||||
apiName = 'Default'
|
||||
|
||||
getQueryTemplates = (config?: Partial<Config>) =>
|
||||
apiService.fetchData<SqlTemplateDto[], void>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: '/api/app/sql-template/query-templates',
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
getStoredProcedureTemplate = (
|
||||
procedureName: string,
|
||||
schemaName = 'dbo',
|
||||
config?: Partial<Config>,
|
||||
) =>
|
||||
apiService.fetchData<string, void>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: '/api/app/sql-template/stored-procedure-template',
|
||||
params: { procedureName, schemaName },
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
getViewTemplate = (
|
||||
viewName: string,
|
||||
schemaName = 'dbo',
|
||||
withSchemaBinding = false,
|
||||
config?: Partial<Config>,
|
||||
) =>
|
||||
apiService.fetchData<string, void>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: '/api/app/sql-template/view-template',
|
||||
params: { viewName, schemaName, withSchemaBinding },
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
getFunctionTemplate = (
|
||||
functionName: string,
|
||||
functionType: number,
|
||||
schemaName = 'dbo',
|
||||
config?: Partial<Config>,
|
||||
) =>
|
||||
apiService.fetchData<string, void>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: '/api/app/sql-template/function-template',
|
||||
params: { functionName, functionType, schemaName },
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
|
||||
getQueryTemplate = (templateType: string, config?: Partial<Config>) =>
|
||||
apiService.fetchData<string, void>(
|
||||
{
|
||||
method: 'GET',
|
||||
url: '/api/app/sql-template/query-template',
|
||||
params: { templateType },
|
||||
},
|
||||
{ apiName: this.apiName, ...config },
|
||||
)
|
||||
}
|
||||
|
||||
// Export service instances
|
||||
export const sqlFunctionService = new SqlFunctionService()
|
||||
export const sqlQueryService = new SqlQueryService()
|
||||
export const sqlStoredProcedureService = new SqlStoredProcedureService()
|
||||
export const sqlViewService = new SqlViewService()
|
||||
export const sqlTemplateService = new SqlTemplateService()
|
||||
712
ui/src/views/sqlQueryManager/SqlQueryManager.tsx
Normal file
712
ui/src/views/sqlQueryManager/SqlQueryManager.tsx
Normal file
|
|
@ -0,0 +1,712 @@
|
|||
import { useState, useCallback, useEffect } from 'react'
|
||||
import { Button, Dialog, Input, Notification, toast } from '@/components/ui'
|
||||
import Container from '@/components/shared/Container'
|
||||
import AdaptableCard from '@/components/shared/AdaptableCard'
|
||||
import { getDataSources } from '@/services/data-source.service'
|
||||
import type { DataSourceDto } from '@/proxy/data-source'
|
||||
import type {
|
||||
SqlFunctionDto,
|
||||
SqlQueryDto,
|
||||
SqlStoredProcedureDto,
|
||||
SqlViewDto,
|
||||
SqlObjectType,
|
||||
SqlQueryExecutionResultDto,
|
||||
} from '@/proxy/sql-query-manager/models'
|
||||
import {
|
||||
sqlFunctionService,
|
||||
sqlQueryService,
|
||||
sqlStoredProcedureService,
|
||||
sqlViewService,
|
||||
} from '@/services/sql-query-manager.service'
|
||||
import { FaDatabase, FaPlay, FaSave, FaSyncAlt } from 'react-icons/fa'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import SqlObjectExplorer from './components/SqlObjectExplorer'
|
||||
import SqlEditor from './components/SqlEditor'
|
||||
import SqlResultsGrid from './components/SqlResultsGrid'
|
||||
import SqlObjectProperties from './components/SqlObjectProperties'
|
||||
import { FaCloudUploadAlt } from 'react-icons/fa'
|
||||
|
||||
export type SqlObject = SqlFunctionDto | SqlQueryDto | SqlStoredProcedureDto | SqlViewDto
|
||||
|
||||
interface SqlManagerState {
|
||||
dataSources: DataSourceDto[]
|
||||
selectedDataSource: DataSourceDto | null
|
||||
selectedObject: SqlObject | null
|
||||
selectedObjectType: SqlObjectType | null
|
||||
editorContent: string
|
||||
isExecuting: boolean
|
||||
executionResult: SqlQueryExecutionResultDto | null
|
||||
showProperties: boolean
|
||||
isDirty: boolean
|
||||
}
|
||||
|
||||
const SqlQueryManager = () => {
|
||||
const { translate } = useLocalization()
|
||||
|
||||
const [state, setState] = useState<SqlManagerState>({
|
||||
dataSources: [],
|
||||
selectedDataSource: null,
|
||||
selectedObject: null,
|
||||
selectedObjectType: null,
|
||||
editorContent:
|
||||
'-- SQL Query Editor\n-- Write your SQL query here and press F5 or click Execute to run\n\nSELECT * FROM YourTable\nWHERE 1=1',
|
||||
isExecuting: false,
|
||||
executionResult: null,
|
||||
showProperties: false,
|
||||
isDirty: false,
|
||||
})
|
||||
|
||||
const [showSaveDialog, setShowSaveDialog] = useState(false)
|
||||
const [saveDialogData, setSaveDialogData] = useState({ name: '', description: '' })
|
||||
const [showTemplateConfirmDialog, setShowTemplateConfirmDialog] = useState(false)
|
||||
const [pendingTemplate, setPendingTemplate] = useState<{ content: string; type: string } | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
loadDataSources()
|
||||
}, [])
|
||||
|
||||
const loadDataSources = async () => {
|
||||
try {
|
||||
const response = await getDataSources()
|
||||
const items = response.data.items || []
|
||||
if (items.length > 0) {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
dataSources: items,
|
||||
selectedDataSource: items[0],
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
toast.push(
|
||||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{translate('::App.Platform.FailedtoloadDatasources')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDataSourceChange = useCallback((dataSource: DataSourceDto) => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
selectedDataSource: dataSource,
|
||||
selectedObject: null,
|
||||
editorContent: '',
|
||||
executionResult: null,
|
||||
isDirty: false,
|
||||
}))
|
||||
}, [])
|
||||
|
||||
const handleObjectSelect = useCallback(
|
||||
(object: SqlObject | null, objectType: SqlObjectType | null) => {
|
||||
if (state.isDirty) {
|
||||
if (!confirm(translate('::App.Platform.UnsavedChangesConfirmation'))) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let content = ''
|
||||
if (object) {
|
||||
if (objectType === 1) {
|
||||
// Query
|
||||
content = (object as SqlQueryDto).queryText
|
||||
} else if (objectType === 2) {
|
||||
// Stored Procedure
|
||||
content = (object as SqlStoredProcedureDto).procedureBody
|
||||
} else if (objectType === 3) {
|
||||
// View
|
||||
content = (object as SqlViewDto).viewDefinition
|
||||
} else if (objectType === 4) {
|
||||
// Function
|
||||
content = (object as SqlFunctionDto).functionBody
|
||||
}
|
||||
}
|
||||
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
selectedObject: object,
|
||||
selectedObjectType: objectType,
|
||||
editorContent: content,
|
||||
executionResult: null,
|
||||
isDirty: false,
|
||||
}))
|
||||
},
|
||||
[state.isDirty, translate],
|
||||
)
|
||||
|
||||
const handleEditorChange = useCallback((value: string | undefined) => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
editorContent: value || '',
|
||||
isDirty: true,
|
||||
}))
|
||||
}, [])
|
||||
|
||||
const getTemplateContent = (templateType: string): string => {
|
||||
const templates: Record<string, string> = {
|
||||
'select': `-- Basic SELECT query
|
||||
SELECT
|
||||
Column1,
|
||||
Column2,
|
||||
Column3
|
||||
FROM
|
||||
TableName
|
||||
WHERE
|
||||
-- Add your conditions
|
||||
Column1 = 'value'
|
||||
AND IsActive = 1
|
||||
ORDER BY
|
||||
Column1 ASC;`,
|
||||
|
||||
'insert': `-- Basic INSERT query
|
||||
INSERT INTO TableName (Column1, Column2, Column3)
|
||||
VALUES
|
||||
('Value1', 'Value2', 'Value3');`,
|
||||
|
||||
'update': `-- Basic UPDATE query
|
||||
UPDATE TableName
|
||||
SET
|
||||
Column1 = 'NewValue1',
|
||||
Column2 = 'NewValue2'
|
||||
WHERE
|
||||
-- Add your conditions
|
||||
Id = 1;`,
|
||||
|
||||
'delete': `-- Basic DELETE query
|
||||
DELETE FROM TableName
|
||||
WHERE
|
||||
-- Add your conditions
|
||||
Id = 1;`,
|
||||
|
||||
'create-procedure': `-- Create Stored Procedure
|
||||
CREATE PROCEDURE [dbo].[ProcedureName]
|
||||
@Parameter1 INT,
|
||||
@Parameter2 NVARCHAR(100)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
|
||||
-- Add your logic here
|
||||
SELECT
|
||||
Column1,
|
||||
Column2
|
||||
FROM
|
||||
TableName
|
||||
WHERE
|
||||
Column1 = @Parameter1
|
||||
AND Column2 = @Parameter2;
|
||||
END
|
||||
GO`,
|
||||
|
||||
'create-view': `-- Create View
|
||||
CREATE VIEW [dbo].[ViewName]
|
||||
AS
|
||||
SELECT
|
||||
t1.Column1,
|
||||
t1.Column2,
|
||||
t2.Column3
|
||||
FROM
|
||||
TableName1 t1
|
||||
INNER JOIN TableName2 t2 ON t1.Id = t2.TableName1Id
|
||||
WHERE
|
||||
t1.IsActive = 1;
|
||||
GO`,
|
||||
|
||||
'create-function': `-- Create Scalar Function
|
||||
CREATE FUNCTION [dbo].[FunctionName]
|
||||
(
|
||||
@Parameter1 INT,
|
||||
@Parameter2 NVARCHAR(100)
|
||||
)
|
||||
RETURNS NVARCHAR(200)
|
||||
AS
|
||||
BEGIN
|
||||
DECLARE @Result NVARCHAR(200);
|
||||
|
||||
-- Add your logic here
|
||||
SELECT @Result = Column1 + ' ' + @Parameter2
|
||||
FROM TableName
|
||||
WHERE Id = @Parameter1;
|
||||
|
||||
RETURN @Result;
|
||||
END
|
||||
GO`
|
||||
}
|
||||
|
||||
return templates[templateType] || templates['select']
|
||||
}
|
||||
|
||||
const applyTemplate = useCallback((templateContent: string) => {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
editorContent: templateContent,
|
||||
selectedObject: null,
|
||||
selectedObjectType: null,
|
||||
executionResult: null,
|
||||
isDirty: false,
|
||||
}))
|
||||
|
||||
toast.push(
|
||||
<Notification type="success" title={translate('::App.Platform.Success')}>
|
||||
{translate('::App.Platform.TemplateLoaded')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
}, [translate])
|
||||
|
||||
const handleTemplateSelect = useCallback((template: string, templateType: string) => {
|
||||
const templateContent = getTemplateContent(templateType)
|
||||
|
||||
// Check if editor has content and it's not from a previous template
|
||||
const hasUserContent = state.editorContent.trim() && state.isDirty
|
||||
|
||||
if (hasUserContent) {
|
||||
// Ask for confirmation
|
||||
setPendingTemplate({ content: templateContent, type: templateType })
|
||||
setShowTemplateConfirmDialog(true)
|
||||
} else {
|
||||
// Apply template directly
|
||||
applyTemplate(templateContent)
|
||||
}
|
||||
}, [translate, state.editorContent, state.isDirty, applyTemplate])
|
||||
|
||||
const handleConfirmTemplateReplace = useCallback(() => {
|
||||
if (pendingTemplate) {
|
||||
applyTemplate(pendingTemplate.content)
|
||||
}
|
||||
setShowTemplateConfirmDialog(false)
|
||||
setPendingTemplate(null)
|
||||
}, [pendingTemplate, applyTemplate])
|
||||
|
||||
const handleCancelTemplateReplace = useCallback(() => {
|
||||
setShowTemplateConfirmDialog(false)
|
||||
setPendingTemplate(null)
|
||||
}, [])
|
||||
|
||||
const handleExecute = async () => {
|
||||
if (!state.selectedDataSource) {
|
||||
toast.push(
|
||||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
{translate('::App.Platform.PleaseSelectDataSource')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (!state.editorContent.trim()) {
|
||||
toast.push(
|
||||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
{translate('::App.Platform.PleaseEnterQuery')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
setState((prev) => ({ ...prev, isExecuting: true, executionResult: null }))
|
||||
|
||||
try {
|
||||
const result = await sqlQueryService.executeQuery({
|
||||
queryText: state.editorContent,
|
||||
dataSourceCode: state.selectedDataSource.code || '',
|
||||
})
|
||||
|
||||
setState((prev) => ({ ...prev, executionResult: result.data, isExecuting: false }))
|
||||
|
||||
toast.push(
|
||||
<Notification type="success" title={translate('::App.Platform.Success')}>
|
||||
{translate('::App.Platform.QueryExecutedSuccessfully')} ({result.data.executionTimeMs}ms)
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
} catch (error: any) {
|
||||
setState((prev) => ({ ...prev, isExecuting: false }))
|
||||
toast.push(
|
||||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{error.response?.data?.error?.message || translate('::App.Platform.FailedToExecuteQuery')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!state.selectedDataSource) {
|
||||
toast.push(
|
||||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
{translate('::App.Platform.PleaseSelectDataSource')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (!state.editorContent.trim()) {
|
||||
toast.push(
|
||||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
{translate('::App.Platform.PleaseEnterContentToSave')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (state.selectedObject && state.selectedObjectType) {
|
||||
// Update existing object
|
||||
await handleUpdate()
|
||||
} else {
|
||||
// Create new object - show dialog to choose type
|
||||
setSaveDialogData({ name: '', description: '' })
|
||||
setShowSaveDialog(true)
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdate = async () => {
|
||||
if (!state.selectedObject || !state.selectedObjectType || !state.selectedDataSource) return
|
||||
if (!state.selectedObject.id) return
|
||||
|
||||
try {
|
||||
const objectId = state.selectedObject.id
|
||||
|
||||
switch (state.selectedObjectType) {
|
||||
case 1: // Query
|
||||
await sqlQueryService.update(objectId, {
|
||||
...(state.selectedObject as SqlQueryDto),
|
||||
queryText: state.editorContent,
|
||||
})
|
||||
break
|
||||
case 2: // Stored Procedure
|
||||
await sqlStoredProcedureService.update(objectId, {
|
||||
displayName: (state.selectedObject as SqlStoredProcedureDto).displayName,
|
||||
description: (state.selectedObject as SqlStoredProcedureDto).description,
|
||||
procedureBody: state.editorContent,
|
||||
category: (state.selectedObject as SqlStoredProcedureDto).category,
|
||||
parameters: (state.selectedObject as SqlStoredProcedureDto).parameters,
|
||||
})
|
||||
break
|
||||
case 3: // View
|
||||
await sqlViewService.update(objectId, {
|
||||
displayName: (state.selectedObject as SqlViewDto).displayName,
|
||||
description: (state.selectedObject as SqlViewDto).description,
|
||||
viewDefinition: state.editorContent,
|
||||
category: (state.selectedObject as SqlViewDto).category,
|
||||
withSchemaBinding: (state.selectedObject as SqlViewDto).withSchemaBinding,
|
||||
})
|
||||
break
|
||||
case 4: // Function
|
||||
await sqlFunctionService.update(objectId, {
|
||||
displayName: (state.selectedObject as SqlFunctionDto).displayName,
|
||||
description: (state.selectedObject as SqlFunctionDto).description,
|
||||
functionBody: state.editorContent,
|
||||
returnType: (state.selectedObject as SqlFunctionDto).returnType,
|
||||
category: (state.selectedObject as SqlFunctionDto).category,
|
||||
parameters: (state.selectedObject as SqlFunctionDto).parameters,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
setState((prev) => ({ ...prev, isDirty: false }))
|
||||
|
||||
toast.push(
|
||||
<Notification type="success" title={translate('::App.Platform.Success')}>
|
||||
{translate('::App.Platform.ObjectUpdatedSuccessfully')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{error.response?.data?.error?.message || translate('::App.Platform.FailedToUpdateObject')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateNewQuery = async () => {
|
||||
if (!state.selectedDataSource || !saveDialogData.name) return
|
||||
|
||||
try {
|
||||
await sqlQueryService.create({
|
||||
code: saveDialogData.name.replace(/\s+/g, '_'),
|
||||
name: saveDialogData.name,
|
||||
description: saveDialogData.description,
|
||||
queryText: state.editorContent,
|
||||
dataSourceCode: state.selectedDataSource.code || '',
|
||||
category: '',
|
||||
tags: '',
|
||||
isModifyingData: false,
|
||||
parameters: '',
|
||||
})
|
||||
|
||||
setState((prev) => ({ ...prev, isDirty: false }))
|
||||
setShowSaveDialog(false)
|
||||
|
||||
toast.push(
|
||||
<Notification type="success" title={translate('::App.Platform.Success')}>
|
||||
{translate('::App.Platform.QuerySavedSuccessfully')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{error.response?.data?.error?.message || translate('::App.Platform.FailedToSaveQuery')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeploy = async () => {
|
||||
if (!state.selectedObject || !state.selectedObjectType) {
|
||||
toast.push(
|
||||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
{translate('::App.Platform.PleaseSelectAnObjectToDeploy')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
return
|
||||
}
|
||||
if (!state.selectedObject.id) return
|
||||
|
||||
try {
|
||||
const objectId = state.selectedObject.id
|
||||
let result: any
|
||||
|
||||
switch (state.selectedObjectType) {
|
||||
case 2: // Stored Procedure
|
||||
result = await sqlStoredProcedureService.deploy({ id: objectId, dropIfExists: true })
|
||||
break
|
||||
case 3: // View
|
||||
result = await sqlViewService.deploy({ id: objectId, dropIfExists: true })
|
||||
break
|
||||
case 4: // Function
|
||||
result = await sqlFunctionService.deploy({ id: objectId, dropIfExists: true })
|
||||
break
|
||||
default:
|
||||
toast.push(
|
||||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
{translate('::App.Platform.ThisObjectTypeCannotBeDeployed')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
toast.push(
|
||||
<Notification type="success" title={translate('::App.Platform.Success')}>
|
||||
{translate('::App.Platform.ObjectDeployedSuccessfully')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{error.response?.data?.error?.message || translate('::App.Platform.FailedToDeployObject')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="h-full overflow-hidden">
|
||||
<div className="flex flex-col h-full gap-4 p-1">
|
||||
{/* Toolbar */}
|
||||
<AdaptableCard className="flex-shrink-0 shadow-sm">
|
||||
<div className="flex items-center justify-between px-1 py-1">
|
||||
<div className="flex items-center gap-3">
|
||||
<FaDatabase className="text-lg text-blue-500" />
|
||||
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
{translate('::App.Platform.DataSource')}:
|
||||
</span>
|
||||
<select
|
||||
className="border border-gray-300 rounded px-1 py-1"
|
||||
value={state.selectedDataSource?.code || ''}
|
||||
onChange={(e) => {
|
||||
const ds = state.dataSources.find((d) => d.code === e.target.value)
|
||||
if (ds) handleDataSourceChange(ds)
|
||||
}}
|
||||
>
|
||||
{state.dataSources.map((ds) => (
|
||||
<option key={ds.code} value={ds.code}>
|
||||
{ds.code}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="solid"
|
||||
color="blue-600"
|
||||
icon={<FaPlay />}
|
||||
onClick={handleExecute}
|
||||
loading={state.isExecuting}
|
||||
disabled={!state.selectedDataSource}
|
||||
className="shadow-sm"
|
||||
>
|
||||
{translate('::App.Platform.Execute')}
|
||||
<span className="ml-1 text-xs opacity-75">(F5)</span>
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="solid"
|
||||
icon={<FaSave />}
|
||||
onClick={handleSave}
|
||||
disabled={!state.isDirty || !state.selectedDataSource}
|
||||
className="shadow-sm"
|
||||
>
|
||||
{translate('::App.Platform.Save')}
|
||||
<span className="ml-1 text-xs opacity-75">(Ctrl+S)</span>
|
||||
</Button>
|
||||
{state.selectedObject &&
|
||||
state.selectedObjectType &&
|
||||
state.selectedObjectType !== 1 && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="solid"
|
||||
icon={<FaCloudUploadAlt />}
|
||||
onClick={handleDeploy}
|
||||
disabled={!state.selectedDataSource}
|
||||
className="shadow-sm"
|
||||
>
|
||||
{translate('::App.Platform.Deploy')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="twoTone"
|
||||
icon={<FaSyncAlt />}
|
||||
onClick={() => window.location.reload()}
|
||||
className="shadow-sm"
|
||||
>
|
||||
{translate('::App.Platform.Refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</AdaptableCard>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<div className="flex-1 flex gap-4 min-h-0">
|
||||
{/* Left Panel - Object Explorer */}
|
||||
<div className="w-80 flex-shrink-0 flex flex-col min-h-0">
|
||||
<AdaptableCard className="h-full" bodyClass="p-0">
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="border-b px-4 py-3 bg-gray-50 dark:bg-gray-800 flex-shrink-0">
|
||||
<h6 className="font-semibold text-sm">
|
||||
{translate('::App.Platform.ObjectExplorer')}
|
||||
</h6>
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto p-2 min-h-0">
|
||||
<SqlObjectExplorer
|
||||
dataSource={state.selectedDataSource}
|
||||
onObjectSelect={handleObjectSelect}
|
||||
selectedObject={state.selectedObject}
|
||||
onTemplateSelect={handleTemplateSelect}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AdaptableCard>
|
||||
</div>
|
||||
|
||||
{/* Center Panel - Editor and Results */}
|
||||
<div className="flex-1 flex flex-col min-h-0">
|
||||
<div className="flex-1 border rounded-lg shadow-sm bg-white dark:bg-gray-800 flex flex-col overflow-hidden">
|
||||
<div className="border-b px-4 py-2 bg-gray-50 dark:bg-gray-800 flex-shrink-0">
|
||||
<h6 className="font-semibold text-sm">{translate('::App.Platform.QueryEditor')}</h6>
|
||||
</div>
|
||||
<div className="flex-1 min-h-0">
|
||||
<SqlEditor
|
||||
value={state.editorContent}
|
||||
onChange={handleEditorChange}
|
||||
onExecute={handleExecute}
|
||||
onSave={handleSave}
|
||||
readOnly={state.isExecuting}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{state.executionResult && (
|
||||
<div className="flex-1 mt-4 border rounded-lg shadow-sm bg-white dark:bg-gray-800 flex flex-col overflow-hidden">
|
||||
<div className="border-b px-4 py-2 bg-gray-50 dark:bg-gray-800 flex-shrink-0">
|
||||
<h6 className="font-semibold text-sm">{translate('::App.Platform.Results')}</h6>
|
||||
</div>
|
||||
<div className="flex-1 overflow-hidden min-h-0">
|
||||
<SqlResultsGrid result={state.executionResult} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Panel - Properties (Optional) */}
|
||||
{state.showProperties && state.selectedObject && (
|
||||
<div className="w-80 flex-shrink-0 flex flex-col min-h-0">
|
||||
<SqlObjectProperties object={state.selectedObject} type={state.selectedObjectType} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Template Confirmation Dialog */}
|
||||
<Dialog
|
||||
isOpen={showTemplateConfirmDialog}
|
||||
onClose={handleCancelTemplateReplace}
|
||||
onRequestClose={handleCancelTemplateReplace}
|
||||
>
|
||||
<h5 className="mb-4">{translate('::App.Platform.ConfirmTemplateReplace')}</h5>
|
||||
<p className="mb-6 text-gray-600 dark:text-gray-400">
|
||||
{translate('::App.Platform.TemplateReplaceWarning')}
|
||||
</p>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="plain" onClick={handleCancelTemplateReplace}>
|
||||
{translate('::App.Platform.Cancel')}
|
||||
</Button>
|
||||
<Button variant="solid" onClick={handleConfirmTemplateReplace}>
|
||||
{translate('::App.Platform.Replace')}
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
{/* Save Dialog */}
|
||||
<Dialog
|
||||
isOpen={showSaveDialog}
|
||||
onClose={() => setShowSaveDialog(false)}
|
||||
onRequestClose={() => setShowSaveDialog(false)}
|
||||
>
|
||||
<h5 className="mb-4">{translate('::App.Platform.SaveAsNewQuery')}</h5>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block mb-2">{translate('::App.Platform.Name')}</label>
|
||||
<Input
|
||||
value={saveDialogData.name}
|
||||
onChange={(e) => setSaveDialogData((prev) => ({ ...prev, name: e.target.value }))}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block mb-2">{translate('::App.Platform.Description')}</label>
|
||||
<Input
|
||||
value={saveDialogData.description}
|
||||
onChange={(e) =>
|
||||
setSaveDialogData((prev) => ({ ...prev, description: e.target.value }))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="plain" onClick={() => setShowSaveDialog(false)}>
|
||||
{translate('::App.Platform.Cancel')}
|
||||
</Button>
|
||||
<Button variant="solid" onClick={handleCreateNewQuery}>
|
||||
{translate('::App.Platform.Save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default SqlQueryManager
|
||||
216
ui/src/views/sqlQueryManager/components/SqlEditor.tsx
Normal file
216
ui/src/views/sqlQueryManager/components/SqlEditor.tsx
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
import { useEffect, useRef } from 'react'
|
||||
import Editor, { Monaco } from '@monaco-editor/react'
|
||||
import { useConfig } from '@/components/ui/ConfigProvider'
|
||||
import type { editor } from 'monaco-editor'
|
||||
|
||||
interface SqlEditorProps {
|
||||
value: string
|
||||
onChange: (value: string | undefined) => void
|
||||
onExecute?: () => void
|
||||
onSave?: () => void
|
||||
readOnly?: boolean
|
||||
height?: string
|
||||
}
|
||||
|
||||
const SqlEditor = ({
|
||||
value,
|
||||
onChange,
|
||||
onExecute,
|
||||
onSave,
|
||||
readOnly = false,
|
||||
height = '100%',
|
||||
}: SqlEditorProps) => {
|
||||
const { mode } = useConfig()
|
||||
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null)
|
||||
const monacoRef = useRef<Monaco | null>(null)
|
||||
|
||||
const handleEditorDidMount = (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => {
|
||||
editorRef.current = editor
|
||||
monacoRef.current = monaco
|
||||
|
||||
// Add keyboard shortcuts
|
||||
editor.addCommand(monaco.KeyCode.F5, () => {
|
||||
if (onExecute) onExecute()
|
||||
})
|
||||
|
||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
|
||||
if (onSave) onSave()
|
||||
})
|
||||
|
||||
// Configure SQL language features
|
||||
monaco.languages.registerCompletionItemProvider('sql', {
|
||||
provideCompletionItems: (model, position) => {
|
||||
const word = model.getWordUntilPosition(position)
|
||||
const range = {
|
||||
startLineNumber: position.lineNumber,
|
||||
endLineNumber: position.lineNumber,
|
||||
startColumn: word.startColumn,
|
||||
endColumn: word.endColumn,
|
||||
}
|
||||
|
||||
const suggestions = [
|
||||
// SQL Keywords
|
||||
{
|
||||
label: 'SELECT',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'SELECT ',
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'FROM',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'FROM ',
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'WHERE',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'WHERE ',
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'INSERT INTO',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'INSERT INTO ',
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'UPDATE',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'UPDATE ',
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'DELETE FROM',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'DELETE FROM ',
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'CREATE TABLE',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'CREATE TABLE ',
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'CREATE PROCEDURE',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'CREATE PROCEDURE ${1:ProcedureName}\nAS\nBEGIN\n\t$0\nEND',
|
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'CREATE FUNCTION',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText:
|
||||
'CREATE FUNCTION ${1:FunctionName}()\nRETURNS ${2:ReturnType}\nAS\nBEGIN\n\t$0\nEND',
|
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'CREATE VIEW',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'CREATE VIEW ${1:ViewName}\nAS\n\tSELECT $0',
|
||||
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'JOIN',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'JOIN ',
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'LEFT JOIN',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'LEFT JOIN ',
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'RIGHT JOIN',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'RIGHT JOIN ',
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'INNER JOIN',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'INNER JOIN ',
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'GROUP BY',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'GROUP BY ',
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'ORDER BY',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'ORDER BY ',
|
||||
range: range,
|
||||
},
|
||||
{
|
||||
label: 'HAVING',
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: 'HAVING ',
|
||||
range: range,
|
||||
},
|
||||
]
|
||||
|
||||
return { suggestions: suggestions }
|
||||
},
|
||||
})
|
||||
|
||||
// Focus the editor
|
||||
editor.focus()
|
||||
}
|
||||
|
||||
const editorOptions: editor.IStandaloneEditorConstructionOptions = {
|
||||
readOnly: readOnly,
|
||||
minimap: { enabled: true },
|
||||
fontSize: 14,
|
||||
lineNumbers: 'on',
|
||||
roundedSelection: false,
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
tabSize: 2,
|
||||
wordWrap: 'on',
|
||||
folding: true,
|
||||
glyphMargin: true,
|
||||
lineDecorationsWidth: 10,
|
||||
lineNumbersMinChars: 3,
|
||||
renderLineHighlight: 'all',
|
||||
scrollbar: {
|
||||
vertical: 'visible',
|
||||
horizontal: 'visible',
|
||||
useShadows: false,
|
||||
verticalHasArrows: false,
|
||||
horizontalHasArrows: false,
|
||||
verticalScrollbarSize: 10,
|
||||
horizontalScrollbarSize: 10,
|
||||
},
|
||||
suggest: {
|
||||
showKeywords: true,
|
||||
showSnippets: true,
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full w-full relative">
|
||||
<div className="absolute inset-0">
|
||||
<Editor
|
||||
height="100%"
|
||||
defaultLanguage="sql"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onMount={handleEditorDidMount}
|
||||
theme={mode === 'dark' ? 'vs-dark' : 'light'}
|
||||
options={editorOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SqlEditor
|
||||
495
ui/src/views/sqlQueryManager/components/SqlObjectExplorer.tsx
Normal file
495
ui/src/views/sqlQueryManager/components/SqlObjectExplorer.tsx
Normal file
|
|
@ -0,0 +1,495 @@
|
|||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { Dialog, Button, Notification, toast } from '@/components/ui'
|
||||
import {
|
||||
FaRegFolder,
|
||||
FaRegFolderOpen,
|
||||
FaRegFileAlt,
|
||||
FaCog,
|
||||
FaColumns,
|
||||
FaCode,
|
||||
FaSyncAlt,
|
||||
FaEdit,
|
||||
FaTrash,
|
||||
} from 'react-icons/fa'
|
||||
import type { DataSourceDto } from '@/proxy/data-source'
|
||||
import type {
|
||||
SqlFunctionDto,
|
||||
SqlQueryDto,
|
||||
SqlStoredProcedureDto,
|
||||
SqlViewDto,
|
||||
SqlObjectType,
|
||||
} from '@/proxy/sql-query-manager/models'
|
||||
import {
|
||||
sqlFunctionService,
|
||||
sqlQueryService,
|
||||
sqlStoredProcedureService,
|
||||
sqlViewService,
|
||||
} from '@/services/sql-query-manager.service'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
export type SqlObject = SqlFunctionDto | SqlQueryDto | SqlStoredProcedureDto | SqlViewDto
|
||||
|
||||
interface TreeNode {
|
||||
id: string
|
||||
label: string
|
||||
type: 'root' | 'folder' | 'object'
|
||||
objectType?: SqlObjectType
|
||||
data?: SqlObject
|
||||
children?: TreeNode[]
|
||||
expanded?: boolean
|
||||
}
|
||||
|
||||
interface SqlObjectExplorerProps {
|
||||
dataSource: DataSourceDto | null
|
||||
onObjectSelect: (object: SqlObject | null, objectType: SqlObjectType | null) => void
|
||||
selectedObject: SqlObject | null
|
||||
onTemplateSelect?: (template: string, templateType: string) => void
|
||||
}
|
||||
|
||||
const SqlObjectExplorer = ({
|
||||
dataSource,
|
||||
onObjectSelect,
|
||||
selectedObject,
|
||||
onTemplateSelect,
|
||||
}: SqlObjectExplorerProps) => {
|
||||
const { translate } = useLocalization()
|
||||
const [treeData, setTreeData] = useState<TreeNode[]>([])
|
||||
const [expandedNodes, setExpandedNodes] = useState<Set<string>>(
|
||||
new Set(['root', 'templates', 'queries', 'storedProcedures', 'views', 'functions']),
|
||||
)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [contextMenu, setContextMenu] = useState<{
|
||||
show: boolean
|
||||
x: number
|
||||
y: number
|
||||
node: TreeNode | null
|
||||
}>({ show: false, x: 0, y: 0, node: null })
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
|
||||
const [objectToDelete, setObjectToDelete] = useState<{
|
||||
object: SqlObject
|
||||
type: SqlObjectType
|
||||
} | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (dataSource) {
|
||||
loadObjects()
|
||||
} else {
|
||||
setTreeData([])
|
||||
}
|
||||
}, [dataSource])
|
||||
|
||||
const loadObjects = async () => {
|
||||
if (!dataSource) return
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const [queries, storedProcedures, views, functions] = await Promise.all([
|
||||
sqlQueryService.getList({
|
||||
skipCount: 0,
|
||||
maxResultCount: 1000,
|
||||
dataSourceCode: dataSource.code,
|
||||
}),
|
||||
sqlStoredProcedureService.getList({
|
||||
skipCount: 0,
|
||||
maxResultCount: 1000,
|
||||
dataSourceCode: dataSource.code,
|
||||
}),
|
||||
sqlViewService.getList({
|
||||
skipCount: 0,
|
||||
maxResultCount: 1000,
|
||||
dataSourceCode: dataSource.code,
|
||||
}),
|
||||
sqlFunctionService.getList({
|
||||
skipCount: 0,
|
||||
maxResultCount: 1000,
|
||||
dataSourceCode: dataSource.code,
|
||||
}),
|
||||
])
|
||||
const tree: TreeNode[] = [
|
||||
{
|
||||
id: 'root',
|
||||
label: dataSource.code || 'Database',
|
||||
type: 'root',
|
||||
expanded: true,
|
||||
children: [
|
||||
{
|
||||
id: 'templates',
|
||||
label: translate('::App.Platform.Templates'),
|
||||
type: 'folder',
|
||||
expanded: expandedNodes.has('templates'),
|
||||
children: [
|
||||
{
|
||||
id: 'template-select',
|
||||
label: 'SELECT Query',
|
||||
type: 'object' as const,
|
||||
data: { templateType: 'select' } as any,
|
||||
},
|
||||
{
|
||||
id: 'template-insert',
|
||||
label: 'INSERT Query',
|
||||
type: 'object' as const,
|
||||
data: { templateType: 'insert' } as any,
|
||||
},
|
||||
{
|
||||
id: 'template-update',
|
||||
label: 'UPDATE Query',
|
||||
type: 'object' as const,
|
||||
data: { templateType: 'update' } as any,
|
||||
},
|
||||
{
|
||||
id: 'template-delete',
|
||||
label: 'DELETE Query',
|
||||
type: 'object' as const,
|
||||
data: { templateType: 'delete' } as any,
|
||||
},
|
||||
{
|
||||
id: 'template-sp',
|
||||
label: 'Stored Procedure',
|
||||
type: 'object' as const,
|
||||
data: { templateType: 'create-procedure' } as any,
|
||||
},
|
||||
{
|
||||
id: 'template-view',
|
||||
label: 'View',
|
||||
type: 'object' as const,
|
||||
data: { templateType: 'create-view' } as any,
|
||||
},
|
||||
{
|
||||
id: 'template-function',
|
||||
label: 'Function',
|
||||
type: 'object' as const,
|
||||
data: { templateType: 'create-function' } as any,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'queries',
|
||||
label: `${translate('::App.Platform.Queries')} (${queries.data.totalCount})`,
|
||||
type: 'folder',
|
||||
objectType: 1,
|
||||
expanded: expandedNodes.has('queries'),
|
||||
children:
|
||||
queries.data.items?.map((q) => ({
|
||||
id: q.id || '',
|
||||
label: q.name,
|
||||
type: 'object' as const,
|
||||
objectType: 1 as SqlObjectType,
|
||||
data: q,
|
||||
})) || [],
|
||||
},
|
||||
{
|
||||
id: 'storedProcedures',
|
||||
label: `${translate('::App.Platform.StoredProcedures')} (${storedProcedures.data.totalCount})`,
|
||||
type: 'folder',
|
||||
objectType: 2,
|
||||
expanded: expandedNodes.has('storedProcedures'),
|
||||
children:
|
||||
storedProcedures.data.items?.map((sp) => ({
|
||||
id: sp.id || '',
|
||||
label: sp.displayName || sp.procedureName,
|
||||
type: 'object' as const,
|
||||
objectType: 2 as SqlObjectType,
|
||||
data: sp,
|
||||
})) || [],
|
||||
},
|
||||
{
|
||||
id: 'views',
|
||||
label: `${translate('::App.Platform.Views')} (${views.data.totalCount})`,
|
||||
type: 'folder',
|
||||
objectType: 3,
|
||||
expanded: expandedNodes.has('views'),
|
||||
children:
|
||||
views.data.items?.map((v) => ({
|
||||
id: v.id || '',
|
||||
label: v.displayName || v.viewName,
|
||||
type: 'object' as const,
|
||||
objectType: 3 as SqlObjectType,
|
||||
data: v,
|
||||
})) || [],
|
||||
},
|
||||
{
|
||||
id: 'functions',
|
||||
label: `${translate('::App.Platform.Functions')} (${functions.data.totalCount})`,
|
||||
type: 'folder',
|
||||
objectType: 4,
|
||||
expanded: expandedNodes.has('functions'),
|
||||
children:
|
||||
functions.data.items?.map((f) => ({
|
||||
id: f.id || '',
|
||||
label: f.displayName || f.functionName,
|
||||
type: 'object' as const,
|
||||
objectType: 4 as SqlObjectType,
|
||||
data: f,
|
||||
})) || [],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
setTreeData(tree)
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{error.response?.data?.error?.message || translate('::App.Platform.FailedToLoadObjects')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleNode = (nodeId: string) => {
|
||||
setExpandedNodes((prev) => {
|
||||
const newSet = new Set(prev)
|
||||
if (newSet.has(nodeId)) newSet.delete(nodeId)
|
||||
else newSet.add(nodeId)
|
||||
return newSet
|
||||
})
|
||||
}
|
||||
|
||||
const handleNodeClick = (node: TreeNode) => {
|
||||
if (node.type === 'folder' || node.type === 'root') {
|
||||
toggleNode(node.id)
|
||||
} else if (node.type === 'object' && node.data) {
|
||||
// Check if it's a template
|
||||
if ((node.data as any).templateType && onTemplateSelect) {
|
||||
const templateType = (node.data as any).templateType
|
||||
onTemplateSelect('', templateType) // Template content will be generated in parent
|
||||
} else if (node.objectType) {
|
||||
onObjectSelect(node.data, node.objectType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleContextMenu = (e: React.MouseEvent, node: TreeNode) => {
|
||||
e.preventDefault()
|
||||
setContextMenu({
|
||||
show: true,
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
node,
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!objectToDelete || !objectToDelete.object.id) return
|
||||
|
||||
try {
|
||||
const { object, type } = objectToDelete
|
||||
|
||||
switch (type) {
|
||||
case 1:
|
||||
await sqlQueryService.delete(object.id!)
|
||||
break
|
||||
case 2:
|
||||
await sqlStoredProcedureService.delete(object.id!)
|
||||
break
|
||||
case 3:
|
||||
await sqlViewService.delete(object.id!)
|
||||
break
|
||||
case 4:
|
||||
await sqlFunctionService.delete(object.id!)
|
||||
break
|
||||
}
|
||||
|
||||
toast.push(
|
||||
<Notification type="success" title={translate('::App.Platform.Success')}>
|
||||
{translate('::App.Platform.ObjectDeletedSuccessfully')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
|
||||
setShowDeleteDialog(false)
|
||||
setObjectToDelete(null)
|
||||
loadObjects()
|
||||
|
||||
if (selectedObject?.id === object.id) {
|
||||
onObjectSelect(null, null)
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{error.response?.data?.error?.message || translate('::App.Platform.FailedToDeleteObject')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const getIcon = (node: TreeNode) => {
|
||||
if (node.type === 'root') return <FaRegFolder className="text-blue-500" />
|
||||
|
||||
if (node.type === 'folder') {
|
||||
const isExpanded = expandedNodes.has(node.id)
|
||||
|
||||
// Templates folder
|
||||
if (node.id === 'templates')
|
||||
return isExpanded ? (
|
||||
<FaRegFolderOpen className="text-orange-500" />
|
||||
) : (
|
||||
<FaRegFolder className="text-orange-500" />
|
||||
)
|
||||
|
||||
if (node.objectType === 1)
|
||||
return isExpanded ? (
|
||||
<FaRegFolderOpen className="text-yellow-500" />
|
||||
) : (
|
||||
<FaRegFolder className="text-yellow-500" />
|
||||
)
|
||||
|
||||
if (node.objectType === 2)
|
||||
return isExpanded ? (
|
||||
<FaRegFolderOpen className="text-green-500" />
|
||||
) : (
|
||||
<FaRegFolder className="text-green-500" />
|
||||
)
|
||||
|
||||
if (node.objectType === 3)
|
||||
return isExpanded ? (
|
||||
<FaRegFolderOpen className="text-purple-500" />
|
||||
) : (
|
||||
<FaRegFolder className="text-purple-500" />
|
||||
)
|
||||
|
||||
if (node.objectType === 4)
|
||||
return isExpanded ? (
|
||||
<FaRegFolderOpen className="text-red-500" />
|
||||
) : (
|
||||
<FaRegFolder className="text-red-500" />
|
||||
)
|
||||
}
|
||||
|
||||
if (node.type === 'object') {
|
||||
// Check if it's a template
|
||||
if ((node.data as any)?.templateType) {
|
||||
return <FaCode className="text-orange-500" />
|
||||
}
|
||||
|
||||
if (node.objectType === 1) return <FaRegFileAlt className="text-gray-500" />
|
||||
if (node.objectType === 2) return <FaCog className="text-gray-500" />
|
||||
if (node.objectType === 3) return <FaColumns className="text-gray-500" />
|
||||
if (node.objectType === 4) return <FaCode className="text-gray-500" />
|
||||
}
|
||||
|
||||
return <FaRegFolder />
|
||||
}
|
||||
|
||||
const renderNode = (node: TreeNode, level = 0) => {
|
||||
const isExpanded = expandedNodes.has(node.id)
|
||||
const isSelected = node.type === 'object' && selectedObject?.id === node.id
|
||||
|
||||
return (
|
||||
<div key={node.id}>
|
||||
<div
|
||||
className={`flex items-center gap-2 py-1 px-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 rounded ${
|
||||
isSelected ? 'bg-blue-100 dark:bg-blue-900' : ''
|
||||
}`}
|
||||
style={{ paddingLeft: `${level * 16 + 8}px` }}
|
||||
onClick={() => handleNodeClick(node)}
|
||||
onContextMenu={(e) => handleContextMenu(e, node)}
|
||||
>
|
||||
{getIcon(node)}
|
||||
<span className="text-sm flex-1">{node.label}</span>
|
||||
</div>
|
||||
|
||||
{isExpanded && node.children && (
|
||||
<div>{node.children.map((child) => renderNode(child, level + 1))}</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full">
|
||||
{loading && <div className="text-center py-8 text-gray-500">{translate('::App.Platform.Loading')}</div>}
|
||||
{!loading && treeData.length === 0 && (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
{translate('::App.Platform.NoDataSourceSelected')}
|
||||
</div>
|
||||
)}
|
||||
{!loading && treeData.length > 0 && (
|
||||
<div className="space-y-1">{treeData.map((node) => renderNode(node))}</div>
|
||||
)}
|
||||
|
||||
{contextMenu.show && (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 z-40"
|
||||
onClick={() => setContextMenu({ show: false, x: 0, y: 0, node: null })}
|
||||
/>
|
||||
<div
|
||||
className="fixed z-50 bg-white dark:bg-gray-800 shadow-lg rounded border border-gray-200 dark:border-gray-700 py-1"
|
||||
style={{ top: contextMenu.y, left: contextMenu.x }}
|
||||
>
|
||||
{contextMenu.node?.type === 'object' && (
|
||||
<>
|
||||
<button
|
||||
className="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 text-sm"
|
||||
onClick={() => {
|
||||
if (contextMenu.node?.data && contextMenu.node?.objectType) {
|
||||
onObjectSelect(contextMenu.node.data, contextMenu.node.objectType)
|
||||
}
|
||||
setContextMenu({ show: false, x: 0, y: 0, node: null })
|
||||
}}
|
||||
>
|
||||
<FaEdit className="inline mr-2" />
|
||||
{translate('::App.Platform.Edit')}
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 text-sm text-red-600"
|
||||
onClick={() => {
|
||||
if (contextMenu.node?.data && contextMenu.node?.objectType) {
|
||||
setObjectToDelete({
|
||||
object: contextMenu.node.data,
|
||||
type: contextMenu.node.objectType,
|
||||
})
|
||||
setShowDeleteDialog(true)
|
||||
}
|
||||
setContextMenu({ show: false, x: 0, y: 0, node: null })
|
||||
}}
|
||||
>
|
||||
<FaTrash className="inline mr-2" />
|
||||
{translate('::App.Platform.Delete')}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{contextMenu.node?.type === 'folder' && (
|
||||
<button
|
||||
className="w-full px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 text-sm"
|
||||
onClick={() => {
|
||||
loadObjects()
|
||||
setContextMenu({ show: false, x: 0, y: 0, node: null })
|
||||
}}
|
||||
>
|
||||
<FaSyncAlt className="inline mr-2" />
|
||||
{translate('::App.Platform.Refresh')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Dialog
|
||||
isOpen={showDeleteDialog}
|
||||
onClose={() => setShowDeleteDialog(false)}
|
||||
onRequestClose={() => setShowDeleteDialog(false)}
|
||||
>
|
||||
<h5 className="mb-4">{translate('::App.Platform.ConfirmDelete')}</h5>
|
||||
<p className="mb-4">{translate('::App.Platform.DeleteConfirmationMessage')}</p>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="plain" onClick={() => setShowDeleteDialog(false)}>
|
||||
{translate('::App.Platform.Cancel')}
|
||||
</Button>
|
||||
<Button variant="solid" onClick={handleDelete}>
|
||||
{translate('::App.Platform.DeleteAction')}
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SqlObjectExplorer
|
||||
214
ui/src/views/sqlQueryManager/components/SqlObjectProperties.tsx
Normal file
214
ui/src/views/sqlQueryManager/components/SqlObjectProperties.tsx
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
import type {
|
||||
SqlFunctionDto,
|
||||
SqlQueryDto,
|
||||
SqlStoredProcedureDto,
|
||||
SqlViewDto,
|
||||
SqlObjectType,
|
||||
} from '@/proxy/sql-query-manager/models'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export type SqlObject = SqlFunctionDto | SqlQueryDto | SqlStoredProcedureDto | SqlViewDto
|
||||
|
||||
interface SqlObjectPropertiesProps {
|
||||
object: SqlObject | null
|
||||
type: SqlObjectType | null
|
||||
}
|
||||
|
||||
const SqlObjectProperties = ({ object, type }: SqlObjectPropertiesProps) => {
|
||||
const { translate } = useLocalization()
|
||||
|
||||
if (!object || !type) {
|
||||
return (
|
||||
<div className="p-4 text-center text-gray-500">
|
||||
{translate('::App.Platform.SelectAnObjectToViewProperties')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const PropertyRow = ({ label, value }: { label: string; value: any }) => (
|
||||
<div className="mb-3">
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 mb-1">{label}</div>
|
||||
<div className="text-sm font-medium">{value || '-'}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const getObjectTypeName = () => {
|
||||
switch (type) {
|
||||
case 1:
|
||||
return translate('::App.Platform.Query')
|
||||
case 2:
|
||||
return translate('::App.Platform.StoredProcedure')
|
||||
case 3:
|
||||
return translate('::App.Platform.View')
|
||||
case 4:
|
||||
return translate('::App.Platform.Function')
|
||||
default:
|
||||
return translate('::App.Platform.Object')
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusBadge = (status: number) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return <span className="px-2 py-1 text-xs rounded bg-gray-200 dark:bg-gray-700">{translate('::App.Platform.Draft')}</span>
|
||||
case 2:
|
||||
return <span className="px-2 py-1 text-xs rounded bg-green-200 dark:bg-green-700">{translate('::App.Platform.Active')}</span>
|
||||
case 3:
|
||||
return <span className="px-2 py-1 text-xs rounded bg-orange-200 dark:bg-orange-700">{translate('::App.Platform.Archived')}</span>
|
||||
default:
|
||||
return <span className="px-2 py-1 text-xs rounded bg-gray-200 dark:bg-gray-700">{translate('::App.Platform.Unknown')}</span>
|
||||
}
|
||||
}
|
||||
|
||||
const renderCommonProperties = () => (
|
||||
<>
|
||||
<PropertyRow label={translate('::App.Platform.ObjectType')} value={getObjectTypeName()} />
|
||||
<PropertyRow label={translate('::App.Platform.ID')} value={object.id} />
|
||||
{object.creationTime && (
|
||||
<PropertyRow
|
||||
label={translate('::App.Platform.Created')}
|
||||
value={dayjs(object.creationTime).format('DD/MM/YYYY HH:mm')}
|
||||
/>
|
||||
)}
|
||||
{object.lastModificationTime && (
|
||||
<PropertyRow
|
||||
label={translate('::App.Platform.Modified')}
|
||||
value={dayjs(object.lastModificationTime).format('DD/MM/YYYY HH:mm')}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
const renderQueryProperties = () => {
|
||||
const query = object as SqlQueryDto
|
||||
return (
|
||||
<>
|
||||
<PropertyRow label={translate('::App.Platform.Code')} value={query.code} />
|
||||
<PropertyRow label={translate('::App.Platform.Name')} value={query.name} />
|
||||
<PropertyRow label={translate('::App.Platform.Description')} value={query.description} />
|
||||
<PropertyRow label={translate('::App.Platform.DataSource')} value={query.dataSourceCode} />
|
||||
<PropertyRow label={translate('::App.Platform.Status')} value={getStatusBadge(query.status)} />
|
||||
<PropertyRow label={translate('::App.Platform.Category')} value={query.category} />
|
||||
<PropertyRow label={translate('::App.Platform.Tags')} value={query.tags} />
|
||||
<PropertyRow
|
||||
label={translate('::App.Platform.ModifiesData')}
|
||||
value={query.isModifyingData ? translate('::App.Platform.Yes') : translate('::App.Platform.No')}
|
||||
/>
|
||||
<PropertyRow label={translate('::App.Platform.ExecutionCount')} value={query.executionCount} />
|
||||
{query.lastExecutedAt && (
|
||||
<PropertyRow
|
||||
label={translate('::App.Platform.LastExecuted')}
|
||||
value={dayjs(query.lastExecutedAt).format('DD/MM/YYYY HH:mm')}
|
||||
/>
|
||||
)}
|
||||
{renderCommonProperties()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const renderStoredProcedureProperties = () => {
|
||||
const sp = object as SqlStoredProcedureDto
|
||||
return (
|
||||
<>
|
||||
<PropertyRow label={translate('::App.Platform.ProcedureName')} value={sp.procedureName} />
|
||||
<PropertyRow label={translate('::App.Platform.Schema')} value={sp.schemaName} />
|
||||
<PropertyRow label={translate('::App.Platform.DisplayName')} value={sp.displayName} />
|
||||
<PropertyRow label={translate('::App.Platform.Description')} value={sp.description} />
|
||||
<PropertyRow label={translate('::App.Platform.DataSource')} value={sp.dataSourceCode} />
|
||||
<PropertyRow label={translate('::App.Platform.Status')} value={getStatusBadge(sp.status)} />
|
||||
<PropertyRow label={translate('::App.Platform.Category')} value={sp.category} />
|
||||
<PropertyRow label={translate('::App.Platform.Deployed')} value={sp.isDeployed ? translate('::App.Platform.Yes') : translate('::App.Platform.No')} />
|
||||
{sp.lastDeployedAt && (
|
||||
<PropertyRow
|
||||
label={translate('::App.Platform.LastDeployed')}
|
||||
value={dayjs(sp.lastDeployedAt).format('DD/MM/YYYY HH:mm')}
|
||||
/>
|
||||
)}
|
||||
{renderCommonProperties()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const renderViewProperties = () => {
|
||||
const view = object as SqlViewDto
|
||||
return (
|
||||
<>
|
||||
<PropertyRow label={translate('::App.Platform.ViewName')} value={view.viewName} />
|
||||
<PropertyRow label={translate('::App.Platform.Schema')} value={view.schemaName} />
|
||||
<PropertyRow label={translate('::App.Platform.DisplayName')} value={view.displayName} />
|
||||
<PropertyRow label={translate('::App.Platform.Description')} value={view.description} />
|
||||
<PropertyRow label={translate('::App.Platform.DataSource')} value={view.dataSourceCode} />
|
||||
<PropertyRow label={translate('::App.Platform.Status')} value={getStatusBadge(view.status)} />
|
||||
<PropertyRow label={translate('::App.Platform.Category')} value={view.category} />
|
||||
<PropertyRow label={translate('::App.Platform.Deployed')} value={view.isDeployed ? translate('::App.Platform.Yes') : translate('::App.Platform.No')} />
|
||||
<PropertyRow
|
||||
label={translate('::App.Platform.SchemaBinding')}
|
||||
value={view.withSchemaBinding ? translate('::App.Platform.Yes') : translate('::App.Platform.No')}
|
||||
/>
|
||||
{view.lastDeployedAt && (
|
||||
<PropertyRow
|
||||
label={translate('::App.Platform.LastDeployed')}
|
||||
value={dayjs(view.lastDeployedAt).format('DD/MM/YYYY HH:mm')}
|
||||
/>
|
||||
)}
|
||||
{renderCommonProperties()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const renderFunctionProperties = () => {
|
||||
const func = object as SqlFunctionDto
|
||||
const getFunctionType = (funcType: number) => {
|
||||
switch (funcType) {
|
||||
case 1:
|
||||
return translate('::App.Platform.ScalarFunction')
|
||||
case 2:
|
||||
return translate('::App.Platform.TableValuedFunction')
|
||||
case 3:
|
||||
return translate('::App.Platform.InlineTableValuedFunction')
|
||||
default:
|
||||
return translate('::App.Platform.Unknown')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PropertyRow label={translate('::App.Platform.FunctionName')} value={func.functionName} />
|
||||
<PropertyRow label={translate('::App.Platform.Schema')} value={func.schemaName} />
|
||||
<PropertyRow label={translate('::App.Platform.DisplayName')} value={func.displayName} />
|
||||
<PropertyRow label={translate('::App.Platform.Description')} value={func.description} />
|
||||
<PropertyRow label={translate('::App.Platform.FunctionType')} value={getFunctionType(func.functionType)} />
|
||||
<PropertyRow label={translate('::App.Platform.ReturnType')} value={func.returnType} />
|
||||
<PropertyRow label={translate('::App.Platform.DataSource')} value={func.dataSourceCode} />
|
||||
<PropertyRow label={translate('::App.Platform.Status')} value={getStatusBadge(func.status)} />
|
||||
<PropertyRow label={translate('::App.Platform.Category')} value={func.category} />
|
||||
<PropertyRow label={translate('::App.Platform.Deployed')} value={func.isDeployed ? translate('::App.Platform.Yes') : translate('::App.Platform.No')} />
|
||||
{func.lastDeployedAt && (
|
||||
<PropertyRow
|
||||
label={translate('::App.Platform.LastDeployed')}
|
||||
value={dayjs(func.lastDeployedAt).format('DD/MM/YYYY HH:mm')}
|
||||
/>
|
||||
)}
|
||||
{renderCommonProperties()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="mb-4 pb-2 border-b">
|
||||
<h6 className="font-bold">{translate('::App.Platform.Properties')}</h6>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-auto p-2">
|
||||
{type === 1 && renderQueryProperties()}
|
||||
{type === 2 && renderStoredProcedureProperties()}
|
||||
{type === 3 && renderViewProperties()}
|
||||
{type === 4 && renderFunctionProperties()}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SqlObjectProperties
|
||||
142
ui/src/views/sqlQueryManager/components/SqlResultsGrid.tsx
Normal file
142
ui/src/views/sqlQueryManager/components/SqlResultsGrid.tsx
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import { useMemo } from 'react'
|
||||
import { DataGrid } from 'devextreme-react'
|
||||
import { Column, Paging, Scrolling, SearchPanel, Export, Selection } from 'devextreme-react/data-grid'
|
||||
import type { SqlQueryExecutionResultDto } from '@/proxy/sql-query-manager/models'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import { HiOutlineCheckCircle, HiOutlineXCircle } from 'react-icons/hi'
|
||||
|
||||
interface SqlResultsGridProps {
|
||||
result: SqlQueryExecutionResultDto
|
||||
}
|
||||
|
||||
const SqlResultsGrid = ({ result }: SqlResultsGridProps) => {
|
||||
const { translate } = useLocalization()
|
||||
|
||||
const columns = useMemo(() => {
|
||||
// Get columns from metadata if available
|
||||
if (result.metadata?.columns && Array.isArray(result.metadata.columns)) {
|
||||
return result.metadata.columns
|
||||
}
|
||||
|
||||
// Otherwise infer from data
|
||||
if (result.data && result.data.length > 0) {
|
||||
const firstRow = result.data[0]
|
||||
return Object.keys(firstRow).map((key) => ({
|
||||
name: key,
|
||||
dataType: typeof firstRow[key],
|
||||
isNullable: true,
|
||||
}))
|
||||
}
|
||||
|
||||
return []
|
||||
}, [result])
|
||||
|
||||
const dataSource = useMemo(() => {
|
||||
return result.data || []
|
||||
}, [result])
|
||||
|
||||
if (!result.success) {
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex items-center gap-2 mb-4 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded">
|
||||
<HiOutlineXCircle className="text-red-500 text-xl" />
|
||||
<div>
|
||||
<div className="font-semibold text-red-700 dark:text-red-400">
|
||||
{translate('::App.Platform.Error')}
|
||||
</div>
|
||||
<div className="text-sm text-red-600 dark:text-red-500">
|
||||
{result.error || result.message}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col">
|
||||
|
||||
<div className="flex items-center justify-between mb-2 p-2 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded">
|
||||
<div className="flex items-center gap-2">
|
||||
<HiOutlineCheckCircle className="text-green-500" />
|
||||
<span className="text-sm text-green-700 dark:text-green-400">
|
||||
{result.message || translate('::App.Platform.QueryExecutedSuccessfully')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
<span>
|
||||
{translate('::App.Platform.Rows')}: <strong>{result.rowsAffected || dataSource.length}</strong>
|
||||
</span>
|
||||
<span>
|
||||
{translate('::App.Platform.Time')}: <strong>{result.executionTimeMs}ms</strong>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{dataSource.length > 0 ? (
|
||||
<div className="flex-1 overflow-hidden relative">
|
||||
<div className="absolute inset-0">
|
||||
<DataGrid
|
||||
dataSource={dataSource}
|
||||
showBorders={true}
|
||||
showRowLines={true}
|
||||
showColumnLines={true}
|
||||
rowAlternationEnabled={true}
|
||||
columnAutoWidth={true}
|
||||
wordWrapEnabled={false}
|
||||
allowColumnReordering={true}
|
||||
allowColumnResizing={true}
|
||||
columnResizingMode="widget"
|
||||
height="100%"
|
||||
>
|
||||
<Scrolling mode="virtual" rowRenderingMode="virtual" />
|
||||
<Paging enabled={true} pageSize={50} />
|
||||
<SearchPanel visible={true} width={240} placeholder={translate('::App.Platform.Search')} />
|
||||
<Export enabled={true} allowExportSelectedData={true} />
|
||||
<Selection mode="multiple" showCheckBoxesMode="always" />
|
||||
|
||||
{columns.map((col, index) => (
|
||||
<Column
|
||||
key={col.name || index}
|
||||
dataField={col.name}
|
||||
caption={col.name}
|
||||
dataType={
|
||||
col.dataType?.toLowerCase().includes('int') ||
|
||||
col.dataType?.toLowerCase().includes('decimal') ||
|
||||
col.dataType?.toLowerCase().includes('numeric') ||
|
||||
col.dataType?.toLowerCase().includes('float') ||
|
||||
col.dataType?.toLowerCase().includes('money')
|
||||
? 'number'
|
||||
: col.dataType?.toLowerCase().includes('date') ||
|
||||
col.dataType?.toLowerCase().includes('time')
|
||||
? 'date'
|
||||
: col.dataType?.toLowerCase().includes('bit') ||
|
||||
col.dataType?.toLowerCase().includes('boolean')
|
||||
? 'boolean'
|
||||
: 'string'
|
||||
}
|
||||
allowSorting={true}
|
||||
allowFiltering={true}
|
||||
allowHeaderFiltering={true}
|
||||
/>
|
||||
))}
|
||||
</DataGrid>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 flex items-center justify-center text-gray-500">
|
||||
<div className="text-center">
|
||||
<div className="text-lg mb-2">{translate('::App.Platform.NoResults')}</div>
|
||||
<div className="text-sm">
|
||||
{result.rowsAffected > 0
|
||||
? translate('::App.Platform.RowCount', { count: result.rowsAffected })
|
||||
: translate('::App.Platform.NoResultsReturned')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SqlResultsGrid
|
||||
Loading…
Reference in a new issue