item in results)
+ {
+ if (table == "")
+ {
+ table = "";
+ table += "
";
+ foreach (KeyValuePair kvp in item)
+ {
+ table += "" + kvp.Key + " | ";
+ }
+ table += "
";
+ }
+ table += "";
+ foreach (KeyValuePair kvp in item)
+ {
+ table += "" + kvp.Value + " | ";
+ }
+ table += "
";
+ }
+ if (table != "")
+ {
+ table += "
";
+ }
+ else
+ {
+ table = "No Results Returned";
+ }
+ return table;
+ }
+}
diff --git a/Oqtane.Client/Modules/Controls/Pager.razor b/Oqtane.Client/Modules/Controls/Pager.razor
index 64ee715f..bfcc8cb3 100644
--- a/Oqtane.Client/Modules/Controls/Pager.razor
+++ b/Oqtane.Client/Modules/Controls/Pager.razor
@@ -1,6 +1,6 @@
@namespace Oqtane.Modules.Controls
@inherits ModuleBase
-@typeparam TAbleItem
+@typeparam TableItem
@if(Format == "Table")
@@ -78,13 +78,13 @@
public RenderFragment Header { get; set; }
[Parameter]
- public RenderFragment Row { get; set; }
+ public RenderFragment Row { get; set; }
[Parameter]
- public RenderFragment Detail { get; set; }
+ public RenderFragment Detail { get; set; }
[Parameter]
- public IEnumerable Items { get; set; }
+ public IEnumerable Items { get; set; }
[Parameter]
public string PageSize { get; set; }
@@ -95,7 +95,7 @@
[Parameter]
public string Class { get; set; }
- IEnumerable ItemList { get; set; }
+ IEnumerable ItemList { get; set; }
protected override void OnParametersSet()
{
diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj
index a95adf1b..66ba174f 100644
--- a/Oqtane.Client/Oqtane.Client.csproj
+++ b/Oqtane.Client/Oqtane.Client.csproj
@@ -27,6 +27,13 @@
TRACE;WASM
+
+
+
+
+
+
+
diff --git a/Oqtane.Client/Services/Interfaces/IModuleDefinitionService.cs b/Oqtane.Client/Services/Interfaces/IModuleDefinitionService.cs
index e0bc3a24..8560c661 100644
--- a/Oqtane.Client/Services/Interfaces/IModuleDefinitionService.cs
+++ b/Oqtane.Client/Services/Interfaces/IModuleDefinitionService.cs
@@ -13,5 +13,6 @@ namespace Oqtane.Services
Task InstallModuleDefinitionsAsync();
Task DeleteModuleDefinitionAsync(int moduleDefinitionId, int siteId);
Task LoadModuleDefinitionsAsync(int siteId, Runtime runtime);
+ Task CreateModuleDefinitionAsync(ModuleDefinition moduleDefinition, int moduleId);
}
}
diff --git a/Oqtane.Client/Services/Interfaces/ISqlService.cs b/Oqtane.Client/Services/Interfaces/ISqlService.cs
new file mode 100644
index 00000000..f563a795
--- /dev/null
+++ b/Oqtane.Client/Services/Interfaces/ISqlService.cs
@@ -0,0 +1,10 @@
+using Oqtane.Models;
+using System.Threading.Tasks;
+
+namespace Oqtane.Services
+{
+ public interface ISqlService
+ {
+ Task ExecuteQueryAsync(SqlQuery sqlquery);
+ }
+}
diff --git a/Oqtane.Client/Services/ModuleDefinitionService.cs b/Oqtane.Client/Services/ModuleDefinitionService.cs
index 0d3cc4aa..cc537e86 100644
--- a/Oqtane.Client/Services/ModuleDefinitionService.cs
+++ b/Oqtane.Client/Services/ModuleDefinitionService.cs
@@ -92,5 +92,9 @@ namespace Oqtane.Services
}
}
}
+ public async Task CreateModuleDefinitionAsync(ModuleDefinition moduleDefinition, int moduleId)
+ {
+ await _http.PostJsonAsync(Apiurl + "?moduleid=" + moduleId.ToString(), moduleDefinition);
+ }
}
}
diff --git a/Oqtane.Client/Services/SqlService.cs b/Oqtane.Client/Services/SqlService.cs
new file mode 100644
index 00000000..52371df8
--- /dev/null
+++ b/Oqtane.Client/Services/SqlService.cs
@@ -0,0 +1,34 @@
+using Oqtane.Models;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Components;
+using Oqtane.Shared;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Oqtane.Services
+{
+ public class SqlService : ServiceBase, ISqlService
+ {
+ private readonly HttpClient _http;
+ private readonly SiteState _siteState;
+ private readonly NavigationManager _navigationManager;
+
+ public SqlService(HttpClient http, SiteState siteState, NavigationManager navigationManager)
+ {
+ _http = http;
+ _siteState = siteState;
+ _navigationManager = navigationManager;
+ }
+
+ private string Apiurl
+ {
+ get { return CreateApiUrl(_siteState.Alias, _navigationManager.Uri, "Sql"); }
+ }
+
+ public async Task ExecuteQueryAsync(SqlQuery sqlquery)
+ {
+ return await _http.PostJsonAsync(Apiurl, sqlquery);
+ }
+ }
+}
diff --git a/Oqtane.Client/Startup.cs b/Oqtane.Client/Startup.cs
index b7991d90..8d4dfdeb 100644
--- a/Oqtane.Client/Startup.cs
+++ b/Oqtane.Client/Startup.cs
@@ -59,6 +59,7 @@ namespace Oqtane.Client
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
// dynamically register module contexts and repository services
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs
index 6e4ea17f..5e4659ce 100644
--- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs
+++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs
@@ -11,6 +11,7 @@ using Oqtane.Enums;
using Oqtane.Infrastructure.Interfaces;
using Oqtane.Repository;
using Oqtane.Security;
+using System;
// ReSharper disable StringIndexOfIsCultureSpecific.1
namespace Oqtane.Controllers
@@ -19,17 +20,23 @@ namespace Oqtane.Controllers
public class ModuleDefinitionController : Controller
{
private readonly IModuleDefinitionRepository _moduleDefinitions;
+ private readonly IModuleRepository _modules;
private readonly IUserPermissions _userPermissions;
private readonly IInstallationManager _installationManager;
private readonly IWebHostEnvironment _environment;
+ private readonly ITenantResolver _resolver;
+ private readonly ISqlRepository _sql;
private readonly ILogManager _logger;
- public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, ILogManager logger)
+ public ModuleDefinitionController(IModuleDefinitionRepository moduleDefinitions, IModuleRepository modules, IUserPermissions userPermissions, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantResolver resolver, ISqlRepository sql, ILogManager logger)
{
_moduleDefinitions = moduleDefinitions;
+ _modules = modules;
_userPermissions = userPermissions;
_installationManager = installationManager;
_environment = environment;
+ _resolver = resolver;
+ _sql = sql;
_logger = logger;
}
@@ -124,5 +131,69 @@ namespace Oqtane.Controllers
return File(file, "application/octet-stream", assemblyname);
}
+ // POST api/?moduleid=x
+ [HttpPost]
+ [Authorize(Roles = Constants.HostRole)]
+ public void Post([FromBody] ModuleDefinition moduleDefinition, string moduleid)
+ {
+ if (ModelState.IsValid)
+ {
+ string rootPath = Directory.GetParent(_environment.ContentRootPath).FullName;
+ string templatePath = Path.Combine(rootPath, "Oqtane.Client\\Modules\\Admin\\ModuleCreator\\Templates\\");
+ ProcessTemplatesRecursively(new DirectoryInfo(templatePath), rootPath, moduleDefinition);
+ moduleDefinition.ModuleDefinitionName = "Oqtane.Modules." + moduleDefinition.Name + "s, Oqtane.Client";
+ _logger.Log(LogLevel.Information, this, LogFunction.Create, "Module Definition Created {ModuleDefinition}", moduleDefinition);
+ Models.Module module = _modules.GetModule(int.Parse(moduleid));
+ module.ModuleDefinitionName = moduleDefinition.ModuleDefinitionName;
+ _modules.UpdateModule(module);
+ _installationManager.RestartApplication();
+ }
+ }
+
+ private void ProcessTemplatesRecursively(DirectoryInfo current, string rootPath, ModuleDefinition moduleDefinition)
+ {
+ // process folder
+ string folderPath = current.FullName.Replace("Oqtane.Client\\Modules\\Admin\\ModuleCreator\\Templates\\", "");
+ folderPath = folderPath.Replace("[Module]", moduleDefinition.Name);
+ if (!Directory.Exists(folderPath))
+ {
+ Directory.CreateDirectory(folderPath);
+ }
+
+ FileInfo[] files = current.GetFiles("*.*");
+ if (files != null)
+ {
+ foreach (FileInfo file in files)
+ {
+ // process file
+ string filePath = Path.Combine(folderPath, file.Name);
+ filePath = filePath.Replace("[Module]", moduleDefinition.Name);
+
+ string text = System.IO.File.ReadAllText(file.FullName);
+ text = text.Replace("[Module]", moduleDefinition.Name);
+ text = text.Replace("[Description]", moduleDefinition.Description);
+ text = text.Replace("[RootPath]", rootPath);
+ text = text.Replace("[Folder]", folderPath);
+ text = text.Replace("[File]", Path.GetFileName(filePath));
+ System.IO.File.WriteAllText(filePath, text);
+
+ if (Path.GetExtension(filePath) == ".sql")
+ {
+ // execute script
+ foreach (string query in text.Split("GO", StringSplitOptions.RemoveEmptyEntries))
+ {
+ _sql.ExecuteNonQuery(_resolver.GetTenant(), query);
+ }
+ }
+ }
+
+ DirectoryInfo[] folders = current.GetDirectories();
+
+ foreach (DirectoryInfo folder in folders.Reverse())
+ {
+ ProcessTemplatesRecursively(folder, rootPath, moduleDefinition);
+ }
+ }
+ }
}
}
diff --git a/Oqtane.Server/Controllers/SqlController.cs b/Oqtane.Server/Controllers/SqlController.cs
new file mode 100644
index 00000000..1ec75d19
--- /dev/null
+++ b/Oqtane.Server/Controllers/SqlController.cs
@@ -0,0 +1,65 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Authorization;
+using Oqtane.Models;
+using System.Collections.Generic;
+using Oqtane.Shared;
+using Oqtane.Infrastructure.Interfaces;
+using Oqtane.Repository;
+using Oqtane.Enums;
+using System.Data.SqlClient;
+using System.Data;
+using System.Dynamic;
+using Newtonsoft.Json;
+using System;
+
+namespace Oqtane.Controllers
+{
+ [Route("{site}/api/[controller]")]
+ public class SqlController : Controller
+ {
+ private readonly ITenantRepository _tenants;
+ private readonly ISqlRepository _sql;
+ private readonly ILogManager _logger;
+
+ public SqlController(ITenantRepository tenants, ISqlRepository sql, ILogManager logger)
+ {
+ _tenants = tenants;
+ _sql = sql;
+ _logger = logger;
+ }
+
+ // POST: api/
+ [HttpPost]
+ [Authorize(Roles = Constants.HostRole)]
+ public SqlQuery Post([FromBody] SqlQuery sqlquery)
+ {
+ var results = new List>();
+ Dictionary row;
+ Tenant tenant = _tenants.GetTenant(sqlquery.TenantId);
+ try
+ {
+ foreach (string query in sqlquery.Query.Split("GO", StringSplitOptions.RemoveEmptyEntries))
+ {
+ SqlDataReader dr = _sql.ExecuteReader(tenant, query);
+ _logger.Log(LogLevel.Information, this, LogFunction.Other, "Sql Query {Query} Executed on Tenant {TenantId}", query, sqlquery.TenantId);
+ while (dr.Read())
+ {
+ row = new Dictionary();
+ for (var field = 0; field < dr.FieldCount; field++)
+ {
+ row[dr.GetName(field)] = dr.IsDBNull(field) ? "" : dr.GetValue(field).ToString();
+ }
+ results.Add(row);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Other, "Sql Query {Query} Executed on Tenant {TenantId} Results In An Error {Error}", sqlquery.Query, sqlquery.TenantId, ex.Message);
+ }
+ sqlquery.Results = results;
+ return sqlquery;
+ }
+
+ }
+}
diff --git a/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs
new file mode 100644
index 00000000..4bd04e91
--- /dev/null
+++ b/Oqtane.Server/Repository/Interfaces/ISqlRepository.cs
@@ -0,0 +1,11 @@
+using System.Data.SqlClient;
+using Oqtane.Models;
+
+namespace Oqtane.Repository
+{
+ public interface ISqlRepository
+ {
+ int ExecuteNonQuery(Tenant tenant, string query);
+ SqlDataReader ExecuteReader(Tenant tenant, string query);
+ }
+}
diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs
index 09ec53fc..dc10fb3b 100644
--- a/Oqtane.Server/Repository/SiteRepository.cs
+++ b/Oqtane.Server/Repository/SiteRepository.cs
@@ -361,6 +361,34 @@ namespace Oqtane.Repository
}
});
pageTemplates.Add(new PageTemplate
+ {
+ Name = "Sql Management",
+ Parent = "Admin",
+ Path = "admin/sql",
+ Icon = "spreadsheet",
+ IsNavigation = false,
+ IsPersonalizable = false,
+ EditMode = true,
+ PagePermissions = _permissionRepository.EncodePermissions(new List
+ {
+ new Permission(PermissionNames.View, Constants.AdminRole, true),
+ new Permission(PermissionNames.Edit, Constants.AdminRole, true)
+ }),
+ PageTemplateModules = new List
+ {
+ new PageTemplateModule
+ {
+ ModuleDefinitionName = "Oqtane.Modules.Admin.Sql, Oqtane.Client", Title = "Sql Management", Pane = "Content",
+ ModulePermissions = _permissionRepository.EncodePermissions(new List
+ {
+ new Permission(PermissionNames.View, Constants.AdminRole, true),
+ new Permission(PermissionNames.Edit, Constants.AdminRole, true)
+ }),
+ Content = ""
+ }
+ }
+ });
+ pageTemplates.Add(new PageTemplate
{
Name = "Upgrade Service", Parent = "Admin", Path = "admin/upgrade", Icon = "aperture", IsNavigation = false, IsPersonalizable = false, EditMode = true,
PagePermissions = _permissionRepository.EncodePermissions(new List
diff --git a/Oqtane.Server/Repository/SqlRepository.cs b/Oqtane.Server/Repository/SqlRepository.cs
new file mode 100644
index 00000000..c9bdf734
--- /dev/null
+++ b/Oqtane.Server/Repository/SqlRepository.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Data;
+using System.Data.SqlClient;
+using Oqtane.Models;
+
+namespace Oqtane.Repository
+{
+ public class SqlRepository : ISqlRepository
+ {
+
+ public int ExecuteNonQuery(Tenant tenant, string query)
+ {
+ SqlConnection conn = new SqlConnection(FormatConnectionString(tenant.DBConnectionString));
+ SqlCommand cmd = conn.CreateCommand();
+ using (conn)
+ {
+ PrepareCommand(conn, cmd, query);
+ int val = cmd.ExecuteNonQuery();
+ return val;
+ }
+ }
+
+ public SqlDataReader ExecuteReader(Tenant tenant, string query)
+ {
+ SqlConnection conn = new SqlConnection(FormatConnectionString(tenant.DBConnectionString));
+ SqlCommand cmd = conn.CreateCommand();
+ PrepareCommand(conn, cmd, query);
+ var dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
+ return dr;
+ }
+
+ private void PrepareCommand(SqlConnection conn, SqlCommand cmd, string query)
+ {
+ if (conn.State != ConnectionState.Open)
+ {
+ conn.Open();
+ }
+ cmd.Connection = conn;
+ cmd.CommandText = query;
+ cmd.CommandType = CommandType.Text;
+ }
+
+ private string FormatConnectionString(string connectionString)
+ {
+ return connectionString.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString());
+ }
+ }
+}
diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs
index b961fd06..07a47ee7 100644
--- a/Oqtane.Server/Startup.cs
+++ b/Oqtane.Server/Startup.cs
@@ -107,6 +107,7 @@ namespace Oqtane
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddSingleton();
@@ -185,6 +186,7 @@ namespace Oqtane
services.AddTransient();
services.AddTransient();
services.AddTransient();
+ services.AddTransient();
services.AddOqtaneModules();
services.AddOqtaneThemes();
@@ -338,6 +340,7 @@ namespace Oqtane
services.AddTransient();
services.AddTransient();
services.AddTransient();
+ services.AddTransient();
services.AddOqtaneModules();
services.AddOqtaneThemes();
diff --git a/Oqtane.Shared/Models/SqlQuery.cs b/Oqtane.Shared/Models/SqlQuery.cs
new file mode 100644
index 00000000..035c1178
--- /dev/null
+++ b/Oqtane.Shared/Models/SqlQuery.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace Oqtane.Models
+{
+ public class SqlQuery
+ {
+ public int TenantId { get; set; }
+ public string Query { get; set; }
+ public List> Results { get; set; }
+ }
+}
diff --git a/Oqtane.Shared/Models/HtmlTextInfo.cs b/Oqtane.Shared/Modules/Models/HtmlText/HtmlTextInfo.cs
similarity index 100%
rename from Oqtane.Shared/Models/HtmlTextInfo.cs
rename to Oqtane.Shared/Modules/Models/HtmlText/HtmlTextInfo.cs