changeset 0:689cde763395

init cimmit
author Franklin Schmit <meokcin@gmail.com>
date Thu, 05 Sep 2024 10:16:16 +0800
parents
children a2855cd2d97e
files .hgignore Components/App.razor Components/Layout/MainLayout.razor Components/Layout/MainLayout.razor.cs Components/Pages/Index.razor Components/Pages/Index.razor.cs Components/Routes.razor Components/_Imports.razor Controllers/ReportController.cs Controllers/UploadController.cs Grille2.csproj Grille2.sln Program.cs Properties/launchSettings.json appsettings.Development.json appsettings.json wwwroot/css/site.css wwwroot/favicon.ico wwwroot/images/login.jpg wwwroot/images/logo-login.png wwwroot/images/logo.png
diffstat 21 files changed, 819 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,62 @@
+syntax: glob
+
+# Rider
+.idea/
+*.iml
+*.iws
+*.ipr
+*.iws
+
+# Rider 编译输出
+bin/
+obj/
+*.suo
+*.user
+*.userprefs
+
+# Rider 缓存
+*.DS_Store
+
+# Rider 项目设置
+.idea/
+
+# Visual Studio
+.vs/
+*.user
+*.sln.docstates
+*.suo
+*.userprefs
+*.usertasks
+*.vssscc
+
+# Visual Studio 编译输出
+bin/
+obj/
+Debug/
+Release/
+
+# Visual Studio 临时文件
+*.tmp
+*.bak
+*.cache
+Thumbs.db
+
+# Visual Studio 项目设置
+*.csproj.user
+*.filters
+*.ncb
+*.sln.docstates
+
+# Visual Studio 调试文件
+*.pdb
+*.pdbx
+*.bak2
+*.bak3
+
+# 其他通用忽略
+*.log
+*.swp
+*.swo
+*.tmp
+*.bak
+*.DS_Store
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Components/App.razor	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,44 @@
+@inject NavigationManager NavigationManager
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <base href="@NavigationManager.BaseUri" />
+    <RadzenTheme @rendermode="@InteractiveServer" Theme="material" />
+    <link rel="stylesheet" href="css/site.css" />
+    <link rel="icon" href="favicon.ico" />
+    <HeadOutlet @rendermode="@InteractiveServer" />
+</head>
+
+<body>
+    <Routes @rendermode="@InteractiveServer" />
+    <script src="_framework/blazor.web.js"></script>
+    <script src="_content/Radzen.Blazor/Radzen.Blazor.js?v=@(typeof(Radzen.Colors).Assembly.GetName().Version)"></script>
+</body>
+
+</html>
+
+@code {
+    [CascadingParameter]
+    private HttpContext HttpContext { get; set; }
+
+    [Inject]
+    private ThemeService ThemeService { get; set; }
+
+    protected override void OnInitialized()
+    {
+        base.OnInitialized();
+
+        if (HttpContext != null)
+        {
+            var theme = HttpContext.Request.Cookies["Grille2Theme"];
+
+            if (!string.IsNullOrEmpty(theme))
+            {
+                ThemeService.SetTheme(theme, false);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Components/Layout/MainLayout.razor	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,38 @@
+@inherits LayoutComponentBase
+@inject CookieThemeService CookieThemeService
+<RadzenComponents />
+
+<RadzenLayout style="grid-template-areas: 'rz-sidebar rz-header' 'rz-sidebar rz-body';">
+    <RadzenHeader>
+        <RadzenRow JustifyContent="JustifyContent.Start" AlignItems="AlignItems.Center" Gap="0">
+            <RadzenColumn Size="5">
+                <RadzenSidebarToggle Click="@SidebarToggleClick"></RadzenSidebarToggle>
+            </RadzenColumn>
+            <RadzenColumn Size="7">
+                <RadzenStack AlignItems="AlignItems.Center" Orientation="Orientation.Horizontal" JustifyContent="JustifyContent.End" Gap="0.5rem" class="rz-px-2">
+                    <RadzenAppearanceToggle />
+                </RadzenStack>
+            </RadzenColumn>
+        </RadzenRow>
+    </RadzenHeader>
+    <RadzenBody Expanded="@sidebarExpanded">
+        <RadzenRow class="rz-mx-auto rz-px-4 rz-pt-2 rz-pt-md-4 rz-pt-lg-6 rz-pt-xl-12 rz-pb-2 rz-pb-lg-12" Style="max-width: 1440px;">
+            <RadzenColumn Size="12">
+            @Body
+            </RadzenColumn>
+        </RadzenRow>
+    </RadzenBody>
+    <RadzenSidebar Expanded="@sidebarExpanded" style="z-index: 2">
+        <RadzenStack AlignItems="Radzen.AlignItems.Center" class="rz-py-4 rz-py-lg-6" Style="padding: var(--rz-panel-menu-item-padding); border-bottom: var(--rz-panel-menu-item-border);">
+            <RadzenImage Path="images/logo.png"  style="width: 48px; height: 48px;" AlternateText="Application logo"></RadzenImage>
+            <RadzenText Text="Grille2" TextStyle="Radzen.Blazor.TextStyle.Subtitle1" class="rz-mb-0" style="color: var(--rz-sidebar-color);" />
+        </RadzenStack>
+        <RadzenPanelMenu>
+            <RadzenPanelMenuItem Text="Home" Path="" />
+        </RadzenPanelMenu>
+        <RadzenStack AlignItems="Radzen.AlignItems.Center" Gap="0" class="rz-py-4 rz-py-lg-6" Style="padding: var(--rz-panel-menu-item-padding);">
+            <RadzenText Text="Grille2 v1.0.0" TextStyle="Radzen.Blazor.TextStyle.Caption" TagName="Radzen.Blazor.TagName.P" TextAlign="Radzen.TextAlign.Center" />
+            <RadzenText Text="Copyright Ⓒ 2024" TextStyle="Radzen.Blazor.TextStyle.Caption" class="rz-mb-0" TagName="Radzen.Blazor.TagName.P" TextAlign="Radzen.TextAlign.Center" />
+        </RadzenStack>
+    </RadzenSidebar>
+</RadzenLayout>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Components/Layout/MainLayout.razor.cs	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,42 @@
+using System.Net.Http;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Authorization;
+using Microsoft.AspNetCore.Components.Forms;
+using Microsoft.AspNetCore.Components.Routing;
+using Microsoft.AspNetCore.Components.Web;
+using Microsoft.AspNetCore.Components.Web.Virtualization;
+using Microsoft.JSInterop;
+using Radzen;
+using Radzen.Blazor;
+
+namespace Grille2.Components.Layout
+{
+    public partial class MainLayout
+    {
+        [Inject]
+        protected IJSRuntime JSRuntime { get; set; }
+
+        [Inject]
+        protected NavigationManager NavigationManager { get; set; }
+
+        [Inject]
+        protected DialogService DialogService { get; set; }
+
+        [Inject]
+        protected TooltipService TooltipService { get; set; }
+
+        [Inject]
+        protected ContextMenuService ContextMenuService { get; set; }
+
+        [Inject]
+        protected NotificationService NotificationService { get; set; }
+
+        private bool sidebarExpanded = true;
+
+        void SidebarToggleClick()
+        {
+            sidebarExpanded = !sidebarExpanded;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Components/Pages/Index.razor	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,31 @@
+@page "/"
+<PageTitle>Index</PageTitle>
+<RadzenRow Gap="5" RowGap="5">
+    <RadzenColumn Size="12">
+        <RadzenText Text="Welcome to your new app" TextStyle="Radzen.Blazor.TextStyle.DisplayH2" TagName="Radzen.Blazor.TagName.Auto"></RadzenText>
+        <RadzenText Text="Your Success Is Our Biggest Priority" TextStyle="Radzen.Blazor.TextStyle.H4" TagName="Radzen.Blazor.TagName.H2" class="mt-5"></RadzenText>
+        <RadzenText Text="With Radzen Blazor Studio, application development is just one-click away." TextStyle="Radzen.Blazor.TextStyle.Body1" TagName="Radzen.Blazor.TagName.Auto" class="mt-4" Style="font-size: 20px; line-height: 24px"></RadzenText>
+        <RadzenText Text="Simple to setup, maintain and update." TextStyle="Radzen.Blazor.TextStyle.Body1" TagName="Radzen.Blazor.TagName.Auto" class="mt-3" Style="font-size: 20px; line-height: 24px"></RadzenText>
+        <RadzenCard Style="margin-top: 40px; padding: 20px 40px 40px;">
+            <RadzenRow>
+                <RadzenColumn Size="12" SizeMD="4">
+                    <RadzenIcon Icon="smart_button" Style="margin-top: 20px; width: 48px; height: 48px; font-size: 48px; color: var(--rz-text-title-color);"></RadzenIcon>
+                    <RadzenText Text="Learn the Fundamentals" TextStyle="Radzen.Blazor.TextStyle.DisplayH6" TagName="Radzen.Blazor.TagName.H3"></RadzenText>
+                    <RadzenLink Text="Docs" Path="https://www.radzen.com/blazor-studio/documentation/" Icon="east" Target="_blank" Style="font-size: 18px"></RadzenLink>
+                    <RadzenLink Text="Videos" Path="https://youtube.com/playlist?list=PLQyJPeb3xHcBij_RbHD__2TOo8Jv5HQVc" Icon="east" Target="_blank" Style="display: block; font-size: 18px"></RadzenLink>
+                </RadzenColumn>
+                <RadzenColumn Size="12" SizeMD="4">
+                    <RadzenIcon Icon="code" Style="margin-top: 20px; width: 48px; height: 48px; font-size: 48px; color: var(--rz-text-title-color);"></RadzenIcon>
+                    <RadzenText Text="Radzen Blazor Components" TextStyle="Radzen.Blazor.TextStyle.DisplayH6" TagName="Radzen.Blazor.TagName.H3"></RadzenText>
+                    <RadzenLink Text="Demos" Path="https://blazor.radzen.com" Icon="east" Target="_blank" Style="font-size: 18px"></RadzenLink>
+                </RadzenColumn>
+                <RadzenColumn Size="12" SizeMD="4">
+                    <RadzenIcon Icon="people_outline" Style="margin-top: 20px; width: 48px; height: 48px; font-size: 48px; color: var(--rz-text-title-color);"></RadzenIcon>
+                    <RadzenText Text="Radzen Community" TextStyle="Radzen.Blazor.TextStyle.DisplayH6" TagName="Radzen.Blazor.TagName.H3"></RadzenText>
+                    <RadzenLink Text="Radzen Blazor Studio Forums" Path="https://forum.radzen.com/c/radzen-blazor-studio/12" Icon="east" Target="_blank" Style="font-size: 18px"></RadzenLink>
+                </RadzenColumn>
+            </RadzenRow>
+        </RadzenCard>
+    </RadzenColumn>
+</RadzenRow>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Components/Pages/Index.razor.cs	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,35 @@
+using System.Net.Http;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Authorization;
+using Microsoft.AspNetCore.Components.Forms;
+using Microsoft.AspNetCore.Components.Routing;
+using Microsoft.AspNetCore.Components.Web;
+using Microsoft.AspNetCore.Components.Web.Virtualization;
+using Microsoft.JSInterop;
+using Radzen;
+using Radzen.Blazor;
+
+namespace Grille2.Components.Pages
+{
+    public partial class Index
+    {
+        [Inject]
+        protected IJSRuntime JSRuntime { get; set; }
+
+        [Inject]
+        protected NavigationManager NavigationManager { get; set; }
+
+        [Inject]
+        protected DialogService DialogService { get; set; }
+
+        [Inject]
+        protected TooltipService TooltipService { get; set; }
+
+        [Inject]
+        protected ContextMenuService ContextMenuService { get; set; }
+
+        [Inject]
+        protected NotificationService NotificationService { get; set; }
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Components/Routes.razor	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,16 @@
+<Router AppAssembly="@typeof(Program).Assembly">
+    <Found Context="routeData">
+        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
+    </Found>
+    <NotFound>
+        <PageTitle>Not found</PageTitle>
+        <LayoutView Layout="@typeof(MainLayout)">
+            <RadzenRow>
+                <RadzenColumn Size="12" style="margin-top: 5rem; margin-bottom: 5rem">
+                    <RadzenText Text="Page not found" TextStyle="TextStyle.DisplayH1" style="margin: 0; margin-bottom: 2rem" TextAlign="TextAlign.Center" />
+                    <RadzenText Text="Sorry, but there's nothing here!" TextStyle="TextStyle.H6" style="margin: 0" TextAlign="TextAlign.Center" TagName="TagName.P" />
+                </RadzenColumn>
+            </RadzenRow>
+        </LayoutView>
+    </NotFound>
+</Router>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Components/_Imports.razor	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,14 @@
+@using System.Net.Http
+@using Microsoft.AspNetCore.Authorization
+@using Microsoft.AspNetCore.Components.Authorization
+@using Microsoft.AspNetCore.Components.Forms
+@using Microsoft.AspNetCore.Components.Routing
+@using Microsoft.AspNetCore.Components.Web
+@using Microsoft.AspNetCore.Components.Web.Virtualization
+@using Microsoft.JSInterop
+@using Radzen
+@using Radzen.Blazor
+@using static Microsoft.AspNetCore.Components.Web.RenderMode
+@using Grille2
+@using Grille2.Components
+@using Grille2.Components.Layout
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Controllers/ReportController.cs	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,243 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Grille2.Controllers
+{
+    public partial class ReportController : Controller
+    {
+        [HttpGet("/__ssrsreport")]
+        public async Task Get(string url)
+        {
+            if (!String.IsNullOrEmpty(url))
+            {
+                using (var httpClient = CreateHttpClient())
+                {
+                    var responseMessage = await ForwardRequest(httpClient, Request, url);
+
+                    CopyResponseHeaders(responseMessage, Response);
+
+                    await WriteResponse(Request, url, responseMessage, Response, false);
+                }
+            }
+        }
+
+        [Route("/ssrsproxy/{*url}")]
+        public async Task Proxy()
+        {
+            var urlToReplace = String.Format("{0}://{1}{2}/{3}/", Request.Scheme, Request.Host.Value, Request.PathBase, "ssrsproxy");
+            var requestedUrl = Request.GetDisplayUrl().Replace(urlToReplace, "", StringComparison.InvariantCultureIgnoreCase);
+            var reportServerIndex = requestedUrl.IndexOf("/ReportServer", StringComparison.InvariantCultureIgnoreCase);
+            if (reportServerIndex == -1)
+            {
+                reportServerIndex = requestedUrl.IndexOf("/Reports", StringComparison.InvariantCultureIgnoreCase);
+            }
+            var reportUrlParts = requestedUrl.Substring(0, reportServerIndex).Split('/');
+
+            var url = String.Format("{0}://{1}:{2}{3}", reportUrlParts[0], reportUrlParts[1], reportUrlParts[2],
+                requestedUrl.Substring(reportServerIndex, requestedUrl.Length - reportServerIndex));
+
+            using (var httpClient = CreateHttpClient())
+            {
+                var responseMessage = await ForwardRequest(httpClient, Request, url);
+
+                CopyResponseHeaders(responseMessage, Response);
+
+                if (Request.Method == "POST")
+                {
+                    await WriteResponse(Request, url, responseMessage, Response, true);
+                }
+                else
+                {
+                    if (responseMessage.Content.Headers.ContentType != null && responseMessage.Content.Headers.ContentType.MediaType == "text/html")
+                    {
+                        await WriteResponse(Request, url, responseMessage, Response, false);
+                    }
+                    else
+                    {
+                        using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
+                        {
+                            await responseStream.CopyToAsync(Response.Body, 81920, HttpContext.RequestAborted);
+                        }
+                    }
+                }
+            }
+        }
+
+        partial void OnHttpClientHandlerCreate(ref HttpClientHandler handler);
+
+        private HttpClient CreateHttpClient()
+        {
+            var httpClientHandler = new HttpClientHandler();
+
+            httpClientHandler.AllowAutoRedirect = true;
+            httpClientHandler.UseDefaultCredentials = true;
+
+            if (httpClientHandler.SupportsAutomaticDecompression)
+            {
+                httpClientHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
+            }
+
+            OnHttpClientHandlerCreate(ref httpClientHandler);
+
+            return new HttpClient(httpClientHandler);
+        }
+
+        partial void OnReportRequest(ref HttpRequestMessage requestMessage);
+
+        async Task<HttpResponseMessage> ForwardRequest(HttpClient httpClient, HttpRequest currentReqest, string url)
+        {
+            var proxyRequestMessage = new HttpRequestMessage(new HttpMethod(currentReqest.Method), url);
+
+            foreach (var header in currentReqest.Headers)
+            {
+                if (header.Key != "Host")
+                {
+                    proxyRequestMessage.Headers.TryAddWithoutValidation(header.Key, new string[] { header.Value });
+                }
+            }
+
+            this.OnReportRequest(ref proxyRequestMessage);
+
+            if (currentReqest.Method == "POST")
+            {
+                using (var stream = new MemoryStream())
+                {
+                    await currentReqest.Body.CopyToAsync(stream);
+                    stream.Position = 0;
+
+                    string body = new StreamReader(stream).ReadToEnd();
+                    proxyRequestMessage.Content = new StringContent(body);
+
+                    if (body.IndexOf("AjaxScriptManager") != -1)
+                    {
+                        proxyRequestMessage.Content.Headers.Remove("Content-Type");
+                        proxyRequestMessage.Content.Headers.Add("Content-Type", new string[] { currentReqest.ContentType });
+                    }
+                }
+            }
+
+            return await httpClient.SendAsync(proxyRequestMessage);
+        }
+
+        static void CopyResponseHeaders(HttpResponseMessage responseMessage, HttpResponse response)
+        {
+            response.StatusCode = (int)responseMessage.StatusCode;
+            foreach (var header in responseMessage.Headers)
+            {
+                response.Headers[header.Key] = header.Value.ToArray();
+            }
+
+            foreach (var header in responseMessage.Content.Headers)
+            {
+                response.Headers[header.Key] = header.Value.ToArray();
+            }
+
+            response.Headers.Remove("transfer-encoding");
+        }
+
+        static async Task WriteResponse(HttpRequest currentReqest, string url, HttpResponseMessage responseMessage, HttpResponse response, bool isAjax)
+        {
+            var result = await responseMessage.Content.ReadAsStringAsync();
+
+            var ReportServer = url.Contains("/ReportServer/", StringComparison.InvariantCultureIgnoreCase) ? "ReportServer" : "Reports";
+
+            var reportUri = new Uri(url);
+            var proxyUrl = String.Format("{0}://{1}{2}/ssrsproxy/{3}/{4}/{5}", currentReqest.Scheme, currentReqest.Host.Value, currentReqest.PathBase,
+                reportUri.Scheme, reportUri.Host, reportUri.Port);
+
+            if (isAjax && result.IndexOf("|") != -1)
+            {
+                var builder = new StringBuilder();
+
+                var delimiterIndex = 0;
+                var length = 0;
+                var index = 0;
+
+                var type = "";
+                var id = "";
+                var content = "";
+
+                while (index < result.Length)
+                {
+                    delimiterIndex = result.IndexOf("|", index);
+                    if (delimiterIndex == -1)
+                    {
+                        break;
+                    }
+                    length = int.Parse(result.Substring(index, delimiterIndex - index));
+                    if ((length % 1) != 0)
+                    {
+                        break;
+                    }
+                    index = delimiterIndex + 1;
+                    delimiterIndex = result.IndexOf("|", index);
+                    if (delimiterIndex == -1)
+                    {
+                        break;
+                    }
+                    type = result.Substring(index, delimiterIndex - index);
+                    index = delimiterIndex + 1;
+                    delimiterIndex = result.IndexOf("|", index);
+                    if (delimiterIndex == -1)
+                    {
+                        break;
+                    }
+                    id = result.Substring(index, delimiterIndex - index);
+                    index = delimiterIndex + 1;
+                    if ((index + length) >= result.Length)
+                    {
+                        break;
+                    }
+                    content = result.Substring(index, length);
+                    index += length;
+                    if (result.Substring(index, 1) != "|")
+                    {
+                        break;
+                    }
+                    index++;
+
+                    content = content.Replace($"/{ReportServer}/", $"{proxyUrl}/{ReportServer}/", StringComparison.InvariantCultureIgnoreCase);
+                    if (content.Contains("./ReportViewer.aspx", StringComparison.InvariantCultureIgnoreCase))
+                    {
+                        content = content.Replace("./ReportViewer.aspx", $"{proxyUrl}/{ReportServer}/Pages/ReportViewer.aspx", StringComparison.InvariantCultureIgnoreCase);
+                    }
+                    else
+                    {
+                        content = content.Replace("ReportViewer.aspx", $"{proxyUrl}/{ReportServer}/Pages/ReportViewer.aspx", StringComparison.InvariantCultureIgnoreCase);
+                    }
+
+                    builder.Append(String.Format("{0}|{1}|{2}|{3}|", content.Length, type, id, content));
+                }
+
+                result = builder.ToString();
+            }
+            else
+            {
+                result = result.Replace($"/{ReportServer}/", $"{proxyUrl}/{ReportServer}/", StringComparison.InvariantCultureIgnoreCase);
+
+                if (result.Contains("./ReportViewer.aspx", StringComparison.InvariantCultureIgnoreCase))
+                {
+                    result = result.Replace("./ReportViewer.aspx", $"{proxyUrl}/{ReportServer}/Pages/ReportViewer.aspx", StringComparison.InvariantCultureIgnoreCase);
+                }
+                else
+                {
+                    result = result.Replace("ReportViewer.aspx", $"{proxyUrl}/{ReportServer}/Pages/ReportViewer.aspx", StringComparison.InvariantCultureIgnoreCase);
+                }
+            }
+
+            response.Headers.Remove("Content-Length");
+            response.Headers.Append("Content-Length", new string[] { System.Text.Encoding.UTF8.GetByteCount(result).ToString() });
+
+            await response.WriteAsync(result);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Controllers/UploadController.cs	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,88 @@
+using System;
+using System.IO;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Hosting;
+
+namespace Grille2.Controllers
+{
+    public partial class UploadController : Controller
+    {
+        private readonly IWebHostEnvironment environment;
+
+        public UploadController(IWebHostEnvironment environment)
+        {
+            this.environment = environment;
+        }
+
+        // Single file upload
+        [HttpPost("upload/single")]
+        public IActionResult Single(IFormFile file)
+        {
+            try
+            {
+                // Put your code here
+                return StatusCode(200);
+            }
+            catch (Exception ex)
+            {
+                return StatusCode(500, ex.Message);
+            }
+        }
+
+        // Multiple files upload
+        [HttpPost("upload/multiple")]
+        public IActionResult Multiple(IFormFile[] files)
+        {
+            try
+            {
+                // Put your code here
+                return StatusCode(200);
+            }
+            catch (Exception ex)
+            {
+                return StatusCode(500, ex.Message);
+            }
+        }
+
+        // Multiple files upload with parameter
+        [HttpPost("upload/{id}")]
+        public IActionResult Post(IFormFile[] files, int id)
+        {
+            try
+            {
+                // Put your code here
+                return StatusCode(200);
+            }
+            catch (Exception ex)
+            {
+                return StatusCode(500, ex.Message);
+            }
+        }
+
+        // Image file upload (used by HtmlEditor components)
+        [HttpPost("upload/image")]
+        public IActionResult Image(IFormFile file)
+        {
+            try
+            {
+                var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
+
+                using (var stream = new FileStream(Path.Combine(environment.WebRootPath, fileName), FileMode.Create))
+                {
+                    // Save the file
+                    file.CopyTo(stream);
+
+                    // Return the URL of the file
+                    var url = Url.Content($"~/{fileName}");
+
+                    return Ok(new { Url = url });
+                }
+            }
+            catch (Exception ex)
+            {
+                return StatusCode(500, ex.Message);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Grille2.csproj	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <NoWarn>CS0168,CS1998,BL9993,CS0649,CS0436,0436</NoWarn>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Radzen.Blazor" Version="*" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Grille2.sln	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,25 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.0.0
+MinimumVisualStudioVersion = 17.0.0.0
+
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grille2", "Grille2.csproj", "{9068EABA-8418-4A53-8824-06A1F0C4AD3D}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{9068EABA-8418-4A53-8824-06A1F0C4AD3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9068EABA-8418-4A53-8824-06A1F0C4AD3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9068EABA-8418-4A53-8824-06A1F0C4AD3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9068EABA-8418-4A53-8824-06A1F0C4AD3D}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {9B7C0C45-4433-4390-826E-00A3CED4FCBF}
+	EndGlobalSection
+EndGlobal
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Program.cs	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,37 @@
+using Radzen;
+using Grille2.Components;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+builder.Services.AddRazorComponents()
+      .AddInteractiveServerComponents().AddHubOptions(options => options.MaximumReceiveMessageSize = 10 * 1024 * 1024);
+
+builder.Services.AddControllers();
+builder.Services.AddRadzenComponents();
+
+builder.Services.AddRadzenCookieThemeService(options =>
+{
+    options.Name = "Grille2Theme";
+    options.Duration = TimeSpan.FromDays(365);
+});
+builder.Services.AddHttpClient();
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (!app.Environment.IsDevelopment())
+{
+    app.UseExceptionHandler("/Error", createScopeForErrors: true);
+    // 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.MapControllers();
+app.UseStaticFiles();
+app.UseAntiforgery();
+
+app.MapRazorComponents<App>()
+   .AddInteractiveServerRenderMode();
+
+app.Run();
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Properties/launchSettings.json	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,28 @@
+{
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:19712",
+      "sslPort": 44395
+    }
+  },
+  "profiles": {
+    "Grille2.Server": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "applicationUrl": "https://localhost:5001;http://localhost:5000",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/appsettings.Development.json	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,9 @@
+{
+  "DetailedErrors": true,
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/appsettings.json	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,9 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  },
+  "AllowedHosts": "*"
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wwwroot/css/site.css	Thu Sep 05 10:16:16 2024 +0800
@@ -0,0 +1,86 @@
+#blazor-error-ui {
+    background: lightyellow;
+    bottom: 0;
+    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
+    display: none;
+    left: 0;
+    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
+    position: fixed;
+    width: 100%;
+    z-index: 1000;
+}
+
+#blazor-error-ui .dismiss {
+    cursor: pointer;
+    position: absolute;
+    right: 0.75rem;
+    top: 0.5rem;
+}
+
+:root {
+    font-size: var(--rz-root-font-size);
+}
+
+body {
+    font-family: var(--rz-text-font-family);
+    color: var(--rz-text-color);
+    font-size: var(--rz-body-font-size);
+    line-height: var(--rz-body-line-height);
+    background-color: var(--rz-body-background-color);
+}
+
+.rz-body {
+    --rz-body-padding: 0;
+}
+
+a {
+    color: var(--rz-link-color);
+}
+
+a:hover,
+a:focus {
+    color: var(--rz-link-hover-color);
+}
+
+.blazor-error-boundary {
+    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
+    padding: 1rem 1rem 1rem 3.7rem;
+    color: white;
+}
+
+.blazor-error-boundary::after {
+    content: "An error has occurred."
+}
+
+.loading-progress {
+    position: relative;
+    display: block;
+    width: 8rem;
+    height: 8rem;
+    margin: 20vh auto 1rem auto;
+}
+
+.loading-progress circle {
+    fill: none;
+    stroke: #e0e0e0;
+    stroke-width: 0.6rem;
+    transform-origin: 50% 50%;
+    transform: rotate(-90deg);
+}
+
+.loading-progress circle:last-child {
+    stroke: #1b6ec2;
+    stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
+    transition: stroke-dasharray 0.05s ease-in-out;
+}
+
+.loading-progress-text {
+    position: absolute;
+    text-align: center;
+    font-weight: bold;
+    inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
+}
+
+.loading-progress-text:after {
+    content: var(--blazor-load-percentage-text, "Loading");
+}
\ No newline at end of file
Binary file wwwroot/favicon.ico has changed
Binary file wwwroot/images/login.jpg has changed
Binary file wwwroot/images/logo-login.png has changed
Binary file wwwroot/images/logo.png has changed