diff --git a/Api/Api.csproj b/Api/Api.csproj new file mode 100644 index 0000000..50ad0d7 --- /dev/null +++ b/Api/Api.csproj @@ -0,0 +1,29 @@ + + + netcoreapp3.1 + v3 + blazoract.Api + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + \ No newline at end of file diff --git a/Api/KernelFunction.cs b/Api/KernelFunction.cs new file mode 100644 index 0000000..7c7eeec --- /dev/null +++ b/Api/KernelFunction.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using System.Linq; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.Extensions.Logging; +using Microsoft.DotNet.Interactive.CSharp; +using Microsoft.DotNet.Interactive; +using Microsoft.DotNet.Interactive.Events; +using Microsoft.DotNet.Interactive.Commands; +using blazoract.Shared; +using System.Threading; +using System.Threading.Tasks; +using System.Net.Http; +using Newtonsoft.Json; + +namespace blazoract.Api +{ + public class KernelFunction + { + private CompositeKernel _kernel; + public KernelFunction(CompositeKernel kernel) + { + this._kernel = kernel; + } + + [FunctionName("RunCode")] + public async Task Run( + [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "code/run")] HttpRequest req, + ILogger log) + { + string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); + var cell = JsonConvert.DeserializeObject(requestBody); + + Console.WriteLine(requestBody); + + var request = await _kernel.SendAsync(new SubmitCode(cell.Input), new CancellationToken()); + var result = new ExecuteResult(); + request.KernelEvents.Subscribe(x => + { + Console.WriteLine($"Received event: {x}"); + switch (x) + { + case DisplayEvent displayEvent: + result.Output = displayEvent.Value?.ToString(); + break; + case CommandFailed commandFailed: + result.CommandFailedMessage = commandFailed.Message; + break; + } + }); + + return new OkObjectResult(result); + } + } +} diff --git a/Api/KernelFunctions.cs b/Api/KernelFunctions.cs new file mode 100644 index 0000000..d3a5e8c --- /dev/null +++ b/Api/KernelFunctions.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using System.Linq; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.Extensions.Http; +using Microsoft.Extensions.Logging; +using Microsoft.DotNet.Interactive.CSharp; +using Microsoft.DotNet.Interactive; +using Microsoft.DotNet.Interactive.Events; +using Microsoft.DotNet.Interactive.Commands; +using blazoract.Shared; +using System.Threading; +using System.Threading.Tasks; +using System.Net.Http; +using Newtonsoft.Json; + +namespace blazoract.Api +{ + public class KernelFunction + { + private CompositeKernel _kernel; + public KernelFunction(CompositeKernel kernel) + { + this._kernel = kernel; + } + + [FunctionName("RunCode")] + public async Task Run( + [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "code/run")] HttpRequest req, + ILogger log) + { + string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); + var cell = JsonConvert.DeserializeObject(requestBody); + + var request = await _kernel.SendAsync(new SubmitCode(cell.Input), new CancellationToken()); + var result = new ExecuteResult(); + request.KernelEvents.Subscribe(x => + { + Console.WriteLine($"Received event: {x}"); + switch (x) + { + case DisplayEvent displayEvent: + result.Output = displayEvent.Value?.ToString(); + break; + case CommandFailed commandFailed: + result.CommandFailedMessage = commandFailed.Message; + break; + } + }); + + return new OkObjectResult(result); + } + } +} diff --git a/Api/Properties/launchSettings.json b/Api/Properties/launchSettings.json new file mode 100644 index 0000000..afe7059 --- /dev/null +++ b/Api/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Api": { + "commandName": "Project", + "commandLineArgs": "start --cors *" + } + } +} \ No newline at end of file diff --git a/Api/Properties/serviceDependencies.json b/Api/Properties/serviceDependencies.json new file mode 100644 index 0000000..fcc92d1 --- /dev/null +++ b/Api/Properties/serviceDependencies.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "storage1": { + "type": "storage", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/Api/Properties/serviceDependencies.local.json b/Api/Properties/serviceDependencies.local.json new file mode 100644 index 0000000..155d87e --- /dev/null +++ b/Api/Properties/serviceDependencies.local.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "storage1": { + "type": "storage.emulator", + "connectionId": "AzureWebJobsStorage" + } + } +} \ No newline at end of file diff --git a/Api/Startup.cs b/Api/Startup.cs new file mode 100644 index 0000000..5c67858 --- /dev/null +++ b/Api/Startup.cs @@ -0,0 +1,19 @@ +using Microsoft.Azure.Functions.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.DotNet.Interactive.CSharp; +using Microsoft.DotNet.Interactive; + +[assembly: FunctionsStartup(typeof(blazoract.Api.Startup))] + +namespace blazoract.Api +{ + public class Startup : FunctionsStartup + { + public override void Configure(IFunctionsHostBuilder builder) + { + builder.Services.AddSingleton(new CompositeKernel() { + new CSharpKernel().UseDefaultFormatting().UseDotNetVariableSharing() + }); + } + } +} \ No newline at end of file diff --git a/Api/host.json b/Api/host.json new file mode 100644 index 0000000..bb3b8da --- /dev/null +++ b/Api/host.json @@ -0,0 +1,11 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingExcludedTypes": "Request", + "samplingSettings": { + "isEnabled": true + } + } + } +} \ No newline at end of file diff --git a/Api/local.settings.json b/Api/local.settings.json new file mode 100644 index 0000000..938bfcc --- /dev/null +++ b/Api/local.settings.json @@ -0,0 +1,12 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet" + }, + "Host": { + "LocalHttpPort": 7071, + "CORS": "*", + "CORSCredentials": false + } +} \ No newline at end of file diff --git a/Client/Program.cs b/Client/Program.cs index 641b497..8cd926f 100644 --- a/Client/Program.cs +++ b/Client/Program.cs @@ -19,11 +19,16 @@ public static async Task Main(string[] args) var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); - builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + // Configure HTTP client to send requests to the local function server + Console.WriteLine(builder.Configuration["BaseAddress"]); + Console.WriteLine(builder.Configuration); + var baseAddress = builder.Configuration["BaseAddress"] ?? builder.HostEnvironment.BaseAddress; + builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(baseAddress) }); + + // Services for managing local notebook storage builder.Services.AddBlazoredLocalStorage(); builder.Services.AddScoped(); - await builder.Build().RunAsync(); } } diff --git a/Client/Properties/launchSettings.json b/Client/Properties/launchSettings.json index b3c42ac..39e8ff0 100644 --- a/Client/Properties/launchSettings.json +++ b/Client/Properties/launchSettings.json @@ -11,6 +11,8 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -19,6 +21,7 @@ "commandName": "Project", "dotnetRunMessages": "true", "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/Client/Shared/CodeCell.razor b/Client/Shared/CodeCell.razor index 47731ad..84dde92 100644 --- a/Client/Shared/CodeCell.razor +++ b/Client/Shared/CodeCell.razor @@ -50,7 +50,7 @@ { isEvaluating = true; var request = new ExecuteRequest(cell.Content); - var response = await Http.PostAsJsonAsync("run/evaluatecell", request); + var response = await Http.PostAsJsonAsync("api/code/run", request); result = await response.Content.ReadFromJsonAsync(); } finally diff --git a/Client/appsettings.Development.json b/Client/wwwroot/appsettings.Development.json similarity index 75% rename from Client/appsettings.Development.json rename to Client/wwwroot/appsettings.Development.json index 2defd04..2c22f43 100644 --- a/Client/appsettings.Development.json +++ b/Client/wwwroot/appsettings.Development.json @@ -6,5 +6,6 @@ "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } - } + }, + "BaseAddress": "http://localhost:7071/" } diff --git a/Client/appsettings.json b/Client/wwwroot/appsettings.json similarity index 100% rename from Client/appsettings.json rename to Client/wwwroot/appsettings.json diff --git a/README.md b/README.md index 55b41b5..26a7b40 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,35 @@ Blazoract is an interactive notebook user interface implemented in Blazor WebAss ## Development Setup +Before starting development, be sure that you have the following installed: + +- [.NET Core](https://dotnet.microsoft.com/download) +- [Azure Functions Core Tools](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#install-the-azure-functions-core-tools) + 1. Fork and clone this repository locally using Git. ``` $ git clone https://github.com/{yourusername}/blazoract ``` -2. Restore the project's dependencies by running `dotnet restore` in the root. +1. Restore the project's dependencies by running `dotnet restore` in the root. + +1. Copy the sample configuration file for running Azure functions locally. + +``` +$ cp `Api/local.settings.example.json` file into `Api/local.settings.json` +``` + +1. Open a terminal and run the following to launch a local instance of the Azure Functions for this app. You will need to the Azure Functions Core Tools mentioned above to enable this. + +``` +$ cd Api +$ func start --build +``` -3. Run `dotnet run --project Server` to launch the application server. +1. In another terminal window, run `dotnet run --project Client` to start the client application. -4. Navigate to https://localhost:5001 where the contents of the default notebook should load. +1. Navigate to https://localhost:5001 where the contents of the default notebook should load. ![Screen Shot 2020-10-25 at 10 03 24 PM](https://user-images.githubusercontent.com/1857993/97135602-f194b300-170d-11eb-87e4-af81bda68ad5.png) diff --git a/Server/Controllers/RunController.cs b/Server/Controllers/RunController.cs deleted file mode 100644 index 71789aa..0000000 --- a/Server/Controllers/RunController.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Microsoft.DotNet.Interactive; -using Microsoft.DotNet.Interactive.Commands; -using System.Threading; -using Microsoft.DotNet.Interactive.Events; -using blazoract.Shared; -using MonacoRazor; -using Microsoft.CodeAnalysis.Text; -using System.Linq; - -namespace blazoract.Server.Controllers -{ - [ApiController] - [Route("[controller]/[action]")] - public class RunController : ControllerBase - { - private readonly ILogger _logger; - - private CompositeKernel _kernel; - - public RunController(ILogger logger, CompositeKernel kernel) - { - _logger = logger; - _kernel = kernel; - } - - [HttpPost] - public async Task EvaluateCellAsync([FromBody] ExecuteRequest cell) - { - var request = await _kernel.SendAsync(new SubmitCode(cell.Input), new CancellationToken()); - var result = new ExecuteResult(); - request.KernelEvents.Subscribe(x => - { - Console.WriteLine($"Received event: {x}"); - switch (x) - { - case DisplayEvent displayEvent: - result.Output = displayEvent.Value?.ToString(); - break; - case CommandFailed commandFailed: - result.CommandFailedMessage = commandFailed.Message; - break; - } - }); - return result; - } - - [HttpPost] - public async Task GetCompletionsAsync([FromBody] GetCompletionsRequest req) - { - var request = await _kernel.SendAsync(new RequestCompletions(req.Code, new LinePosition(req.LineNumber - 1, req.Column - 1))); - var result = Array.Empty(); - request.KernelEvents.Subscribe(x => - { - if (x is CompletionsProduced completions) - { - result = completions.Completions.Select(c => new Suggestion - { - Label = c.DisplayText, - InsertText = c.InsertText, - Kind = Enum.TryParse(c.Kind, out var parsedKind) ? parsedKind : CompletionItemKind.Property, - Documentation = c.Documentation, - }).ToArray(); - } - }); - return result; - } - } -} diff --git a/Server/Pages/Error.cshtml b/Server/Pages/Error.cshtml deleted file mode 100644 index d8d0c12..0000000 --- a/Server/Pages/Error.cshtml +++ /dev/null @@ -1,42 +0,0 @@ -@page -@model blazoract.Server.Pages.ErrorModel - - - - - - - - Error - - - - - -
-
-

Error.

-

An error occurred while processing your request.

- - @if (Model.ShowRequestId) - { -

- Request ID: @Model.RequestId -

- } - -

Development Mode

-

- Swapping to the Development environment displays detailed information about the error that occurred. -

-

- The Development environment shouldn't be enabled for deployed applications. - It can result in displaying sensitive information from exceptions to end users. - For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development - and restarting the app. -

-
-
- - - diff --git a/Server/Pages/Error.cshtml.cs b/Server/Pages/Error.cshtml.cs deleted file mode 100644 index ec6b05b..0000000 --- a/Server/Pages/Error.cshtml.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.Extensions.Logging; - -namespace blazoract.Server.Pages -{ - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - [IgnoreAntiforgeryToken] - public class ErrorModel : PageModel - { - public string RequestId { get; set; } - - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - - private readonly ILogger _logger; - - public ErrorModel(ILogger logger) - { - _logger = logger; - } - - public void OnGet() - { - RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - } - } -} diff --git a/Server/Pages/Shared/_Layout.cshtml b/Server/Pages/Shared/_Layout.cshtml deleted file mode 100644 index c781b2c..0000000 --- a/Server/Pages/Shared/_Layout.cshtml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - @ViewBag.Title - - - - -
-
- @RenderBody() -
-
- - - diff --git a/Server/Program.cs b/Server/Program.cs deleted file mode 100644 index ca49e28..0000000 --- a/Server/Program.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; - -namespace blazoract.Server -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} diff --git a/Server/Properties/launchSettings.json b/Server/Properties/launchSettings.json deleted file mode 100644 index 7e89226..0000000 --- a/Server/Properties/launchSettings.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:12879", - "sslPort": 44318 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "blazoract.Server": { - "commandName": "Project", - "dotnetRunMessages": "true", - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } - } diff --git a/Server/Startup.cs b/Server/Startup.cs deleted file mode 100644 index 5f65989..0000000 --- a/Server/Startup.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.DotNet.Interactive.CSharp; -using Microsoft.DotNet.Interactive; - -namespace blazoract.Server -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - - services.AddControllersWithViews(); - services.AddRazorPages(); - services.AddSingleton(new CompositeKernel() { - new CSharpKernel().UseDefaultFormatting().UseDotNetVariableSharing() - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseWebAssemblyDebugging(); - } - else - { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseBlazorFrameworkFiles(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapRazorPages(); - endpoints.MapControllers(); - endpoints.MapFallbackToFile("index.html"); - }); - } - } -} diff --git a/Server/appsettings.Development.json b/Server/appsettings.Development.json deleted file mode 100644 index 7073400..0000000 --- a/Server/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/Server/appsettings.json b/Server/appsettings.json deleted file mode 100644 index 904e499..0000000 --- a/Server/appsettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, -"AllowedHosts": "*" -} diff --git a/Server/blazoract.Server.csproj b/Server/blazoract.Server.csproj deleted file mode 100644 index 9358f8d..0000000 --- a/Server/blazoract.Server.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - net5.0 - - - - - - - - - - - - - - diff --git a/Shared/blazoract.Shared.csproj b/Shared/blazoract.Shared.csproj index 75d742d..3658ad1 100644 --- a/Shared/blazoract.Shared.csproj +++ b/Shared/blazoract.Shared.csproj @@ -1,7 +1,7 @@ - net5.0 + netstandard2.0 diff --git a/nuget.config b/nuget.config index e25df9e..8975d14 100644 --- a/nuget.config +++ b/nuget.config @@ -5,5 +5,6 @@ +