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",
|
"en": "Forum Management",
|
||||||
"tr": "Forum Yönetimi"
|
"tr": "Forum Yönetimi"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"resourceName": "Platform",
|
||||||
|
"key": "App.SqlQueryManager",
|
||||||
|
"en": "SQL Query Manager",
|
||||||
|
"tr": "SQL Sorgu Yöneticisi"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"resourceName": "Platform",
|
"resourceName": "Platform",
|
||||||
"key": "App.DeveloperKit",
|
"key": "App.DeveloperKit",
|
||||||
|
|
@ -10314,6 +10320,552 @@
|
||||||
"key": "ListForms.SchedulerOptions.Agenda",
|
"key": "ListForms.SchedulerOptions.Agenda",
|
||||||
"tr": "Ajanda",
|
"tr": "Ajanda",
|
||||||
"en": "Agenda"
|
"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",
|
"routeType": "protected",
|
||||||
"authority": []
|
"authority": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"key": "admin.sqlQueryManager",
|
||||||
|
"path": "/admin/sqlQueryManager",
|
||||||
|
"componentPath": "@/views/sqlQueryManager/SqlQueryManager",
|
||||||
|
"routeType": "protected",
|
||||||
|
"authority": [
|
||||||
|
"App.SqlQueryManager"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"key": "admin.developerkit",
|
"key": "admin.developerkit",
|
||||||
"path": "/admin/developerkit",
|
"path": "/admin/developerkit",
|
||||||
|
|
@ -1698,6 +1707,16 @@
|
||||||
"RequiredPermissionName": "App.DeveloperKit.DynamicServices",
|
"RequiredPermissionName": "App.DeveloperKit.DynamicServices",
|
||||||
"IsDisabled": false
|
"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",
|
"ParentCode": "App.Administration",
|
||||||
"Code": "App.Intranet",
|
"Code": "App.Intranet",
|
||||||
|
|
|
||||||
|
|
@ -2263,6 +2263,15 @@
|
||||||
"MultiTenancySide": 3,
|
"MultiTenancySide": 3,
|
||||||
"MenuGroup": "Erp|Kurs"
|
"MenuGroup": "Erp|Kurs"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"GroupName": "App.Administration",
|
||||||
|
"Name": "App.SqlQueryManager",
|
||||||
|
"ParentName": null,
|
||||||
|
"DisplayName": "App.SqlQueryManager",
|
||||||
|
"IsEnabled": true,
|
||||||
|
"MultiTenancySide": 3,
|
||||||
|
"MenuGroup": "Erp|Kurs"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"GroupName": "App.Administration",
|
"GroupName": "App.Administration",
|
||||||
"Name": "App.DeveloperKit",
|
"Name": "App.DeveloperKit",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Erp.Platform.Migrations
|
namespace Erp.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20251205085044_Initial")]
|
[Migration("20251205115506_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <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',
|
bank: '/admin/accounting/bank',
|
||||||
checkNote: '/admin/accounting/check-note',
|
checkNote: '/admin/accounting/check-note',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sqlManager: '/admin/sql-manager',
|
||||||
|
|
||||||
accessDenied: '/admin/access-denied',
|
accessDenied: '/admin/access-denied',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import apiService, { Config } from '@/services/api.service'
|
import apiService, { Config } from '@/services/api.service'
|
||||||
import { PagedAndSortedResultRequestDto, PagedResultDto } from '../abp'
|
import { DataSourceDto } from '@/proxy/data-source'
|
||||||
import type { DataSourceDto } from './models'
|
import { PagedAndSortedResultRequestDto, PagedResultDto } from '@/proxy'
|
||||||
|
|
||||||
export class DataSourceService {
|
export class DataSourceService {
|
||||||
apiName = 'Default'
|
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