diff --git a/.gitignore b/.gitignore index 04ce32b7..fd007ecc 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,4 @@ Oqtane.Server/wwwroot/Themes/* !Oqtane.Server/wwwroot/Themes/Oqtane.Themes.* !Oqtane.Server/wwwroot/Themes/Templates Oqtane.Server/wwwroot/Themes/Templates/* -Oqtane.Server/wwwroot/Themes/Templates/External +!Oqtane.Server/wwwroot/Themes/Templates/External diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..223c5f9b --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,17 @@ + + + net9.0 + Debug;Release + 6.2.1 + Oqtane + Shaun Walker + .NET Foundation + CMS and Application Framework for Blazor and .NET MAUI + .NET Foundation + https://www.oqtane.org + https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE + https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1 + https://github.com/oqtane/oqtane.framework + Git + + \ No newline at end of file diff --git a/Oqtane.Application/.template.config/template.json b/Oqtane.Application/.template.config/template.json index a0191d09..ebcd1b03 100644 --- a/Oqtane.Application/.template.config/template.json +++ b/Oqtane.Application/.template.config/template.json @@ -7,13 +7,80 @@ "Blazor", "Oqtane" ], + "name": "Oqtane Application Template", + "shortName": "oqtane-app", + "defaultName": "MyCompany.MyProject", + "identity": "Oqtane.Application.Template", "tags": { "language": "C#", - "type": "project" + "type": "solution", + "editorTreatAs":"solution" }, - "identity": "Oqtane.Application.Template", - "name": "Oqtane Application Solution For Blazor", - "shortName": "oqtane-app", "sourceName": "Oqtane.Application", - "preferNameDirectory": true + "preferNameDirectory": true, + "guids": [ + "04B05448-788F-433D-92C0-FED35122D45A", + "AA8E58A1-CD09-4208-BF66-A8BB341FD669", + "18D73F73-D7BE-4388-85BA-FBD9AC96FCA2" + ], + "symbols": { + "Framework": { + "type": "parameter", + "description": "The target framework for the project", + "datatype": "choice", + "choices": [ + { + "choice": "net9.0", + "description": "Target net9.0" + } + ], + "replaces": "net9.0", + "defaultValue": "net9.0" + }, + "HttpPort": { + "type": "parameter", + "datatype": "integer", + "description": "Port number to use for the HTTP endpoint in launchSettings.json." + }, + "HttpPortGenerated": { + "type": "generated", + "generator": "port" + }, + "HttpPortReplacer": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "HttpPort", + "fallbackVariableName": "HttpPortGenerated" + }, + "replaces": "44358" + }, + "HttpsPort": { + "type": "parameter", + "datatype": "integer", + "description": "Port number to use for the HTTPS endpoint in launchSettings.json." + }, + "HttpsPortGenerated": { + "type": "generated", + "generator": "port", + "parameters": { + "low": 44300, + "high": 44399 + } + }, + "HttpsPortReplacer": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "HttpsPort", + "fallbackVariableName": "HttpsPortGenerated" + }, + "replaces": "44359" + } + }, + "primaryOutputs": [ + { + "path": "Oqtane.Application.sln" + } + ] } \ No newline at end of file diff --git a/Oqtane.Application/Client/Modules/MyModule/Settings.razor b/Oqtane.Application/Client/Modules/MyModule/Settings.razor index 2405a206..facaecae 100644 --- a/Oqtane.Application/Client/Modules/MyModule/Settings.razor +++ b/Oqtane.Application/Client/Modules/MyModule/Settings.razor @@ -13,7 +13,7 @@ @code { - private string resourceType = "Oqtane.Application.Settings, Oqtane.Application.Client.Oqtane"; // for localization + private string resourceType = "Oqtane.Application.MyModule.Settings, Oqtane.Application.Client.Oqtane"; // for localization public override string Title => "MyModdule Settings"; string _value; diff --git a/Oqtane.Application/Client/Oqtane.Application.Client.csproj b/Oqtane.Application/Client/Oqtane.Application.Client.csproj index 50b9b31e..c7bd2e5c 100644 --- a/Oqtane.Application/Client/Oqtane.Application.Client.csproj +++ b/Oqtane.Application/Client/Oqtane.Application.Client.csproj @@ -1,21 +1,30 @@ - - net9.0 - 1.0.0 - Oqtane.Application.Client.Oqtane - true - Default - false - true - + + net9.0 + Exe + 1.0.0 + Oqtane.Application.Client.Oqtane + Default + true + false + false + - - - + + + + + + + - - - + + + + + + + diff --git a/Oqtane.Application/Oqtane.Application.Template.nuspec b/Oqtane.Application/Oqtane.Application.Template.nuspec index 47fabbf3..47d1af9d 100644 --- a/Oqtane.Application/Oqtane.Application.Template.nuspec +++ b/Oqtane.Application/Oqtane.Application.Template.nuspec @@ -2,7 +2,7 @@ Oqtane.Application.Template - 6.2.0 + 6.2.1 Oqtane Application Template For Blazor Shaun Walker false diff --git a/Oqtane.Application/README.md b/Oqtane.Application/README.md index a6880aff..0a6927de 100644 --- a/Oqtane.Application/README.md +++ b/Oqtane.Application/README.md @@ -5,15 +5,18 @@ This is a Visual Studio Project Template designed for Oqtane development project ``` dotnet new install Oqtane.Application.Template dotnet new oqtane-app -o MyCompany.MyProject +cd MyCompany.MyProject +dotnet build +cd Server +dotnet run +browse to Url ``` When using this approach you do not need to have a local copy of the oqtane.framework source code - you simply utilize Oqtane as a standard application dependency. -The solution contains an AppHost project which must be identified as the Startup project. It is responsible for loading the development environment and launching the Oqtane framework. - -The solution also contains Build, Client, Server, and Shared folders which is where you you would implement your custom functionality. An example module and theme are included for reference, and you can add additional modules and themes within the same projects by following the standard Oqtane folder/namespace conventions. +The solution also contains Client, Server, and Shared folders which is where you you would implement your custom functionality. An example module and theme are included for reference, and you can add additional modules and themes within the same projects by following the standard Oqtane folder/namespace conventions. *Known Issues* -- do not use the term "Oqtane" in your output name or else you will experience namespace conflicts +- do not use the term "Oqtane" or "Module" in your output name or else you will experience namespace conflicts diff --git a/Oqtane.Application/Server/Oqtane.Application.Server.csproj b/Oqtane.Application/Server/Oqtane.Application.Server.csproj index 79d6749a..aaf443fa 100644 --- a/Oqtane.Application/Server/Oqtane.Application.Server.csproj +++ b/Oqtane.Application/Server/Oqtane.Application.Server.csproj @@ -1,18 +1,29 @@  - - net9.0 - 1.0.0 - Oqtane.Application.Server.Oqtane - + + net9.0 + 1.0.0 + Oqtane.Application.Server.Oqtane + true + none + false + false + - - - - + + + + + + - - - + + + + + + + + diff --git a/Oqtane.Application/Server/Properties/launchSettings.json b/Oqtane.Application/Server/Properties/launchSettings.json index dc8793f7..f56a17f7 100644 --- a/Oqtane.Application/Server/Properties/launchSettings.json +++ b/Oqtane.Application/Server/Properties/launchSettings.json @@ -6,7 +6,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "http://localhost:5000", + "applicationUrl": "http://localhost:44358", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -16,7 +16,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "https://localhost:5001;http://localhost:5000", + "applicationUrl": "https://localhost:44359;http://localhost:44358", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/Oqtane.Application/Server/wwwroot/Modules/Oqtane.Modules.Admin.Login/Module.css b/Oqtane.Application/Server/wwwroot/Modules/Oqtane.Modules.Admin.Login/Module.css deleted file mode 100644 index 086b246b..00000000 --- a/Oqtane.Application/Server/wwwroot/Modules/Oqtane.Modules.Admin.Login/Module.css +++ /dev/null @@ -1,5 +0,0 @@ -/* Login Module Custom Styles */ - -.Oqtane-Modules-Admin-Login { - width: 200px; -} diff --git a/Oqtane.Application/Server/wwwroot/Modules/Oqtane.Modules.HtmlText/Module.css b/Oqtane.Application/Server/wwwroot/Modules/Oqtane.Modules.HtmlText/Module.css deleted file mode 100644 index 5a93c043..00000000 --- a/Oqtane.Application/Server/wwwroot/Modules/Oqtane.Modules.HtmlText/Module.css +++ /dev/null @@ -1 +0,0 @@ -/* HtmlText Module Custom Styles */ \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/Oqtane.Server.lib.module.js b/Oqtane.Application/Server/wwwroot/Oqtane.Server.lib.module.js deleted file mode 100644 index 508f42a0..00000000 --- a/Oqtane.Application/Server/wwwroot/Oqtane.Server.lib.module.js +++ /dev/null @@ -1,90 +0,0 @@ -const pageScriptInfoBySrc = new Map(); - -function registerPageScriptElement(src) { - if (!src) { - throw new Error('Must provide a non-empty value for the "src" attribute.'); - } - - let pageScriptInfo = pageScriptInfoBySrc.get(src); - - if (pageScriptInfo) { - pageScriptInfo.referenceCount++; - } else { - pageScriptInfo = { referenceCount: 1, module: null }; - pageScriptInfoBySrc.set(src, pageScriptInfo); - initializePageScriptModule(src, pageScriptInfo); - } -} - -function unregisterPageScriptElement(src) { - if (!src) { - return; - } - - const pageScriptInfo = pageScriptInfoBySrc.get(src); - if (!pageScriptInfo) { - return; - } - - pageScriptInfo.referenceCount--; -} - -async function initializePageScriptModule(src, pageScriptInfo) { - // If the path is relative, normalize it by by making it an absolute URL - // with document's the base HREF. - if (src.startsWith("./")) { - src = new URL(src.substr(2), document.baseURI).toString(); - } - - const module = await import(src); - - if (pageScriptInfo.referenceCount <= 0) { - // All page-script elements with the same 'src' were - // unregistered while we were loading the module. - return; - } - - pageScriptInfo.module = module; - module.onLoad?.(); - module.onUpdate?.(); -} - -function onEnhancedLoad() { - // Start by invoking 'onDispose' on any modules that are no longer referenced. - for (const [src, { module, referenceCount }] of pageScriptInfoBySrc) { - if (referenceCount <= 0) { - module?.onDispose?.(); - pageScriptInfoBySrc.delete(src); - } - } - - // Then invoke 'onUpdate' on the remaining modules. - for (const { module } of pageScriptInfoBySrc.values()) { - module?.onUpdate?.(); - } -} - -export function afterWebStarted(blazor) { - customElements.define('page-script', class extends HTMLElement { - static observedAttributes = ['src']; - - // We use attributeChangedCallback instead of connectedCallback - // because a page-script element might get reused between enhanced - // navigations. - attributeChangedCallback(name, oldValue, newValue) { - if (name !== 'src') { - return; - } - - this.src = newValue; - unregisterPageScriptElement(oldValue); - registerPageScriptElement(newValue); - } - - disconnectedCallback() { - unregisterPageScriptElement(this.src); - } - }); - - blazor.addEventListener('enhancedload', onEnhancedLoad); -} \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css b/Oqtane.Application/Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css deleted file mode 100644 index 911362c5..00000000 --- a/Oqtane.Application/Server/wwwroot/Themes/Oqtane.Themes.BlazorTheme/Theme.css +++ /dev/null @@ -1,290 +0,0 @@ -.table > :not(caption) > * > * { - box-shadow: none; -} - -.table .form-control { - background-color: #ffffff !important; - border-width: 0.5px !important; - border-bottom-color: #ccc !important; - color: #000 !important; -} - -.table .form-select { - background-color: #ffffff !important; - border-width: 0.5px !important; - border-bottom-color: #ccc !important; - color: #000 !important; -} - -.table .btn-primary { - background-color: var(--bs-primary); -} - -.table .btn-secondary { - background-color: var(--bs-secondary); -} - -.alert-dismissible .btn-close { - z-index: 1; -} - -.breadcrumbs { - background-color: #e6e6e6; -} - -.top-row { - height: 3.5rem; - display: flex; - align-items: center; -} - -.main { - flex: 1; -} - - .main .top-row { - background-color: #e6e6e6; - border-bottom: 1px solid #d6d5d5; - } - -.sidebar { - background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); -} - - .sidebar .top-row { - background-color: rgba(0,0,0,0.4); - } - - .sidebar .navbar-toggler .navbar-toggler-icon { - background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); - } - - .sidebar .oi { - width: 2rem; - font-size: 1.1rem; - vertical-align: text-top; - top: -2px; - } - -.app-menu { - width: 100% -} - -.breadcrumb { - margin-bottom: 0; -} - -.app-menu .nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; -} - -.app-menu .nav-item:first-of-type { - padding-top: 1rem; -} - -.app-menu .nav-item:last-of-type { - padding-bottom: 1rem; -} - -.app-menu .nav-item a { - color: #d7d7d7; - border-radius: 4px; - height: 3rem; - display: flex; - align-items: center; - line-height: 3rem; - padding-left: 1rem; -} - -.app-menu .nav-item a.active { - background-color: rgba(255,255,255,0.25); - color: white; -} - -.app-menu .nav-item a:hover { - background-color: rgba(255,255,255,0.1); - color: white; -} - -.content { - padding-top: 1.1rem; -} - -.navbar-toggler { - background-color: rgba(255, 255, 255, 0.1); - margin: .5rem; -} - -.app-logo .navbar-brand { - color: white; -} -@media (max-width: 991.98px) { - .app-search { - border-radius: 6px; - } - .app-search input{ - display: none !important; - } - - .app-search input + button { - position: initial; - padding-top: 7px; - padding-bottom: 7px; - } - - .app-search:active, .app-search:hover { - display: block; - position: absolute; - color: #fff; - top: 0; - min-height: 60px; - width: 100%; - left: 0; - z-index: 999; - border-radius: 0; - background-color: #e6e6e6; - } - - .app-search:active .app-form-inline, .app-search:hover .app-form-inline { - margin: 10px auto; - position: relative; - display: block; - max-width: 80%; - } - - .app-search:active .app-form-inline input, .app-search:hover .app-form-inline input { - width: 100%; - display: block !important; - } - .app-search:active .app-form-inline input + button, .app-search:hover .app-form-inline input + button { - position: absolute; - color: rgb(42, 159, 214); - padding-top: 6px; - padding-bottom: 6px; - } -} -@media (max-width: 767.98px) { - .main .top-row { - display: none; - } -} - -@media (min-width: 768px) { - app { - flex-direction: row; - display: block; - } - - .app-logo { - display: block; - margin-left: auto; - margin-right: auto; - } - - .breadcrumbs { - position: fixed; - left: 275px; - top: 15px; - z-index: 6 - } - - .sidebar { - width: 250px; - height: 100vh; - position: sticky; - top: 0; - z-index: 4 - } - - .main .top-row { - position: sticky; - top: 0; - z-index: 5 - } - - .main > div { - padding-left: 2rem !important; - padding-right: 1.5rem !important; - } - - .navbar-toggler { - display: none; - } - - .sidebar .collapse { - /* Never collapse the sidebar for wide screens */ - display: block; - } - - .main > .container { - margin-top: 30px; - } -} - -@-webkit-keyframes sk-stretchdelay { - 0%, 40%, 100% { - -webkit-transform: scaleY(0.4) - } - - 20% { - -webkit-transform: scaleY(1.0) - } -} - -@keyframes sk-stretchdelay { - 0%, 40%, 100% { - transform: scaleY(0.4); - -webkit-transform: scaleY(0.4); - } - - 20% { - transform: scaleY(1.0); - -webkit-transform: scaleY(1.0); - } -} - -@media (max-width: 767.98px) { - .app-logo { - height: 80px; - display: flex; - align-items: center; - } - - .breadcrumbs { - position: fixed; - top: 150px; - width: 100%; - left: 0; - z-index: 4; - border-bottom: 1px solid #d6d5d5; - } - - .sidebar { - margin-top: 3.5rem; - position: fixed; - width: 100%; - z-index: 4; - } - - .main .top-row { - z-index: 4; - } - - .main > .top-row.px-4 { - display: flex; - position: fixed; - left: 0; - top: 0; - width: 100%; - } - - .ml-md-auto { - margin-left: auto; - } - - .main > .container { - margin-top: 200px; - } -} - diff --git a/Oqtane.Application/Server/wwwroot/app_offline.bak b/Oqtane.Application/Server/wwwroot/app_offline.bak deleted file mode 100644 index 4fcd2b88..00000000 --- a/Oqtane.Application/Server/wwwroot/app_offline.bak +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - Upgrade Framework - - - - - -
-

-

Please Wait... Upgrade In Progress...

-

(this process can take a few minutes... please be patient)

-
-
-
-
-
-
- [STATUS] -
-
- - - - \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/css/app.css b/Oqtane.Application/Server/wwwroot/css/app.css deleted file mode 100644 index 46e749f7..00000000 --- a/Oqtane.Application/Server/wwwroot/css/app.css +++ /dev/null @@ -1,288 +0,0 @@ -@import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); - -html, body { - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -app { - position: relative; - display: flex; - flex-direction: column; -} - -/* Admin Modal */ -.app-admin-modal .modal { - position: fixed; /* Stay in place */ - z-index: 9999; /* Sit on top */ - left: 0; - top: 0; - display: block; - width: 100%; /* Full width */ - height: 100%; /* Full height */ - overflow: auto; /* Enable scroll if needed */ - background: rgba(0,0,0,0.3); /* Dim background */ -} - - .app-admin-modal .modal-dialog { - width: 100%; /* Full width */ - height: 100%; /* Full height */ - max-width: none; /* Override default of 500px */ - } - - .app-admin-modal .modal-content { - margin: 5% auto; /* 5% from the top and centered */ - width: 80%; /* Could be more or less, depending on screen size */ - } - -/* Action Dialog */ -.app-actiondialog{ - position: absolute; -} -.app-actiondialog .modal { - position: fixed; /* Stay in place */ - z-index: 9999; /* Sit on top */ - left: 0; - top: 0; - display: block; - width: 100%; /* Full width */ - height: 100%; /* Full height */ - overflow: auto; /* Enable scroll if needed */ - background: rgba(0,0,0,0.3); /* Dim background */ -} - -.app-actiondialog .modal-dialog { - width: 100%; /* Full width */ - height: 100%; /* Full height */ - max-width: none; /* Override default of 500px */ -} - -.app-actiondialog .modal-content { - margin: 15% auto; /* 15% from the top and centered */ - width: 40%; /* Could be more or less, depending on screen size */ -} - -/* Admin Pane */ -.app-pane-admin-border { - width: 100%; - border-width: 1px; - border-style: dashed; - border-color: gray; -} - -.app-pane-admin-title { - width: 100%; - text-align: center; - color: gray; -} - -.app-moduleactions .dropdown-menu { - z-index: 9999; -} - -.app-moduleactions .dropdown-submenu { - position: relative; -} - - .app-moduleactions .dropdown-submenu > .dropdown-menu { - top: 0; - left: 100%; - margin-top: 0px; - margin-left: 0px; - } - -.app-progress-indicator { - background: rgba(0,0,0,0.2) url('../loading.gif') no-repeat 50% 50%; - width: 100%; - height: 100%; - position: fixed; - top: 0; - left: 0; - z-index: 9999; /* Sit on top */ -} - -.app-rule { - width: 100%; - color: gray; - height: 1px; - background-color: gray; - margin: 0.5rem; -} - -.app-link-unstyled, .app-link-unstyled:visited, .app-link-unstyled:hover, .app-link-unstyled:active, .app-link-unstyled:focus, .app-link-unstyled:active:hover { - font-style: inherit; - color: inherit; - background-color: transparent; - font-size: inherit; - text-decoration: none; - font-variant: inherit; - font-weight: inherit; - line-height: inherit; - font-family: inherit; - border-radius: inherit; - border: inherit; - outline: inherit; - box-shadow: inherit; - padding: inherit; - vertical-align: inherit; -} - -.app-alert { - padding: 20px; - background-color: #f44336; /* red */ - color: white; - margin-bottom: 15px; -} - -.app-moduletitle a { - scroll-margin-top: 7rem; -} - -/* Tooltips */ -.app-tooltip { - cursor: help; - position: relative; -} - - .app-tooltip::before, - .app-tooltip::after { - left: 25%; - opacity: 0; - position: absolute; - z-index: -100; - } - - .app-tooltip:hover::before, - .app-tooltip:focus::before, - .app-tooltip:hover::after, - .app-tooltip:focus::after { - opacity: 1; - transform: scale(1) translateY(0); - z-index: 100; - } - - .app-tooltip::before { - border-style: solid; - border-width: 1em 0.75em 0 0.75em; - border-color: #3E474F transparent transparent transparent; - bottom: 100%; - content: ""; - margin-left: -0.5em; - transition: all .65s cubic-bezier(.84,-0.18,.31,1.26), opacity .65s .5s; - transform: scale(.6) translateY(-90%); - } - - .app-tooltip:hover::before, - .app-tooltip:focus::before { - transition: all .65s cubic-bezier(.84,-0.18,.31,1.26) .2s; - } - - .app-tooltip::after { - background: #3E474F; - border-radius: .25em; - bottom: 140%; - color: #EDEFF0; - content: attr(data-tip); - margin-left: -8.75em; - padding: 1em; - transition: all .65s cubic-bezier(.84,-0.18,.31,1.26) .2s; - transform: scale(.6) translateY(50%); - width: 17.5em; - } - - .app-tooltip:hover::after, - .app-tooltip:focus::after { - transition: all .65s cubic-bezier(.84,-0.18,.31,1.26); - } - -@media (max-width: 760px) { - .app-tooltip::after { - font-size: .75em; - margin-left: -5em; - width: 10em; - } -} - -#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; -} - -/* Oqtane Control Styles */ - -/* Pager */ -.app-pager-pointer { - cursor: pointer; -} - -.app-sort-th { - cursor: pointer; -} - -.app-fas { - margin-left: 5px; -} - -.app-form-inline { - display: inline; -} - -.app-search { - display: inline-block; - position: relative; -} -.app-search input + button { - background: none; - border: none; - position: absolute; - right: 0; - top: 0; -} -.app-search input + button .oi { - top: 0; -} -.app-search-noinput { - display: inline-block; - position: relative; -} -.app-search-noinput button { - background: none; - border: none; - color: var(--bs-heading-color); -} -.app-search-noinput button:hover { - color: var(--bs-heading-color); -} - -/* Text Editor */ -.text-area-editor > textarea { - width: 100%; - min-height: 250px; -} - -.app-logo .navbar-brand { - padding: 5px 20px 5px 20px; -} - -/* cookie consent */ -.gdpr-consent-bar .btn-show { - bottom: -3px; - left: 5px; -} -.gdpr-consent-bar .btn-hide { - top: 0; - right: 5px; -} diff --git a/Oqtane.Application/Server/wwwroot/css/open-iconic/FONT-LICENSE b/Oqtane.Application/Server/wwwroot/css/open-iconic/FONT-LICENSE deleted file mode 100644 index a1dc03f3..00000000 --- a/Oqtane.Application/Server/wwwroot/css/open-iconic/FONT-LICENSE +++ /dev/null @@ -1,86 +0,0 @@ -SIL OPEN FONT LICENSE Version 1.1 - -Copyright (c) 2014 Waybury - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/Oqtane.Application/Server/wwwroot/css/open-iconic/ICON-LICENSE b/Oqtane.Application/Server/wwwroot/css/open-iconic/ICON-LICENSE deleted file mode 100644 index 2199f4a6..00000000 --- a/Oqtane.Application/Server/wwwroot/css/open-iconic/ICON-LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Waybury - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/css/open-iconic/README.md b/Oqtane.Application/Server/wwwroot/css/open-iconic/README.md deleted file mode 100644 index 6b810e47..00000000 --- a/Oqtane.Application/Server/wwwroot/css/open-iconic/README.md +++ /dev/null @@ -1,114 +0,0 @@ -[Open Iconic v1.1.1](http://useiconic.com/open) -=========== - -### Open Iconic is the open source sibling of [Iconic](http://useiconic.com). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](http://useiconic.com/open#icons) - - - -## What's in Open Iconic? - -* 223 icons designed to be legible down to 8 pixels -* Super-light SVG files - 61.8 for the entire set -* SVG sprite—the modern replacement for icon fonts -* Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats -* Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats -* PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. - - -## Getting Started - -#### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](http://useiconic.com/open#icons) and [Reference](http://useiconic.com/open#reference) sections. - -### General Usage - -#### Using Open Iconic's SVGs - -We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute). - -``` -icon name -``` - -#### Using Open Iconic's SVG Sprite - -Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack. - -Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.* - -``` - - - -``` - -Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions. - -``` -.icon { - width: 16px; - height: 16px; -} -``` - -Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. - -``` -.icon-account-login { - fill: #f00; -} -``` - -To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). - -#### Using Open Iconic's Icon Font... - - -##### …with Bootstrap - -You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` - - -``` - -``` - - -``` - -``` - -##### …with Foundation - -You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` - -``` - -``` - - -``` - -``` - -##### …on its own - -You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` - -``` - -``` - -``` - -``` - - -## License - -### Icons - -All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). - -### Fonts - -All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). diff --git a/Oqtane.Application/Server/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css b/Oqtane.Application/Server/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css deleted file mode 100644 index 4664f2e8..00000000 --- a/Oqtane.Application/Server/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css +++ /dev/null @@ -1 +0,0 @@ -@font-face{font-family:Icons;src:url(../fonts/open-iconic.eot);src:url(../fonts/open-iconic.eot?#iconic-sm) format('embedded-opentype'),url(../fonts/open-iconic.woff) format('woff'),url(../fonts/open-iconic.ttf) format('truetype'),url(../fonts/open-iconic.otf) format('opentype'),url(../fonts/open-iconic.svg#iconic-sm) format('svg');font-weight:400;font-style:normal}.oi{position:relative;top:1px;display:inline-block;speak:none;font-family:Icons;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.oi:empty:before{width:1em;text-align:center;box-sizing:content-box}.oi.oi-align-center:before{text-align:center}.oi.oi-align-left:before{text-align:left}.oi.oi-align-right:before{text-align:right}.oi.oi-flip-horizontal:before{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.oi.oi-flip-vertical:before{-webkit-transform:scale(1,-1);-ms-transform:scale(-1,1);transform:scale(1,-1)}.oi.oi-flip-horizontal-vertical:before{-webkit-transform:scale(-1,-1);-ms-transform:scale(-1,1);transform:scale(-1,-1)}.oi-account-login:before{content:'\e000'}.oi-account-logout:before{content:'\e001'}.oi-action-redo:before{content:'\e002'}.oi-action-undo:before{content:'\e003'}.oi-align-center:before{content:'\e004'}.oi-align-left:before{content:'\e005'}.oi-align-right:before{content:'\e006'}.oi-aperture:before{content:'\e007'}.oi-arrow-bottom:before{content:'\e008'}.oi-arrow-circle-bottom:before{content:'\e009'}.oi-arrow-circle-left:before{content:'\e00a'}.oi-arrow-circle-right:before{content:'\e00b'}.oi-arrow-circle-top:before{content:'\e00c'}.oi-arrow-left:before{content:'\e00d'}.oi-arrow-right:before{content:'\e00e'}.oi-arrow-thick-bottom:before{content:'\e00f'}.oi-arrow-thick-left:before{content:'\e010'}.oi-arrow-thick-right:before{content:'\e011'}.oi-arrow-thick-top:before{content:'\e012'}.oi-arrow-top:before{content:'\e013'}.oi-audio-spectrum:before{content:'\e014'}.oi-audio:before{content:'\e015'}.oi-badge:before{content:'\e016'}.oi-ban:before{content:'\e017'}.oi-bar-chart:before{content:'\e018'}.oi-basket:before{content:'\e019'}.oi-battery-empty:before{content:'\e01a'}.oi-battery-full:before{content:'\e01b'}.oi-beaker:before{content:'\e01c'}.oi-bell:before{content:'\e01d'}.oi-bluetooth:before{content:'\e01e'}.oi-bold:before{content:'\e01f'}.oi-bolt:before{content:'\e020'}.oi-book:before{content:'\e021'}.oi-bookmark:before{content:'\e022'}.oi-box:before{content:'\e023'}.oi-briefcase:before{content:'\e024'}.oi-british-pound:before{content:'\e025'}.oi-browser:before{content:'\e026'}.oi-brush:before{content:'\e027'}.oi-bug:before{content:'\e028'}.oi-bullhorn:before{content:'\e029'}.oi-calculator:before{content:'\e02a'}.oi-calendar:before{content:'\e02b'}.oi-camera-slr:before{content:'\e02c'}.oi-caret-bottom:before{content:'\e02d'}.oi-caret-left:before{content:'\e02e'}.oi-caret-right:before{content:'\e02f'}.oi-caret-top:before{content:'\e030'}.oi-cart:before{content:'\e031'}.oi-chat:before{content:'\e032'}.oi-check:before{content:'\e033'}.oi-chevron-bottom:before{content:'\e034'}.oi-chevron-left:before{content:'\e035'}.oi-chevron-right:before{content:'\e036'}.oi-chevron-top:before{content:'\e037'}.oi-circle-check:before{content:'\e038'}.oi-circle-x:before{content:'\e039'}.oi-clipboard:before{content:'\e03a'}.oi-clock:before{content:'\e03b'}.oi-cloud-download:before{content:'\e03c'}.oi-cloud-upload:before{content:'\e03d'}.oi-cloud:before{content:'\e03e'}.oi-cloudy:before{content:'\e03f'}.oi-code:before{content:'\e040'}.oi-cog:before{content:'\e041'}.oi-collapse-down:before{content:'\e042'}.oi-collapse-left:before{content:'\e043'}.oi-collapse-right:before{content:'\e044'}.oi-collapse-up:before{content:'\e045'}.oi-command:before{content:'\e046'}.oi-comment-square:before{content:'\e047'}.oi-compass:before{content:'\e048'}.oi-contrast:before{content:'\e049'}.oi-copywriting:before{content:'\e04a'}.oi-credit-card:before{content:'\e04b'}.oi-crop:before{content:'\e04c'}.oi-dashboard:before{content:'\e04d'}.oi-data-transfer-download:before{content:'\e04e'}.oi-data-transfer-upload:before{content:'\e04f'}.oi-delete:before{content:'\e050'}.oi-dial:before{content:'\e051'}.oi-document:before{content:'\e052'}.oi-dollar:before{content:'\e053'}.oi-double-quote-sans-left:before{content:'\e054'}.oi-double-quote-sans-right:before{content:'\e055'}.oi-double-quote-serif-left:before{content:'\e056'}.oi-double-quote-serif-right:before{content:'\e057'}.oi-droplet:before{content:'\e058'}.oi-eject:before{content:'\e059'}.oi-elevator:before{content:'\e05a'}.oi-ellipses:before{content:'\e05b'}.oi-envelope-closed:before{content:'\e05c'}.oi-envelope-open:before{content:'\e05d'}.oi-euro:before{content:'\e05e'}.oi-excerpt:before{content:'\e05f'}.oi-expand-down:before{content:'\e060'}.oi-expand-left:before{content:'\e061'}.oi-expand-right:before{content:'\e062'}.oi-expand-up:before{content:'\e063'}.oi-external-link:before{content:'\e064'}.oi-eye:before{content:'\e065'}.oi-eyedropper:before{content:'\e066'}.oi-file:before{content:'\e067'}.oi-fire:before{content:'\e068'}.oi-flag:before{content:'\e069'}.oi-flash:before{content:'\e06a'}.oi-folder:before{content:'\e06b'}.oi-fork:before{content:'\e06c'}.oi-fullscreen-enter:before{content:'\e06d'}.oi-fullscreen-exit:before{content:'\e06e'}.oi-globe:before{content:'\e06f'}.oi-graph:before{content:'\e070'}.oi-grid-four-up:before{content:'\e071'}.oi-grid-three-up:before{content:'\e072'}.oi-grid-two-up:before{content:'\e073'}.oi-hard-drive:before{content:'\e074'}.oi-header:before{content:'\e075'}.oi-headphones:before{content:'\e076'}.oi-heart:before{content:'\e077'}.oi-home:before{content:'\e078'}.oi-image:before{content:'\e079'}.oi-inbox:before{content:'\e07a'}.oi-infinity:before{content:'\e07b'}.oi-info:before{content:'\e07c'}.oi-italic:before{content:'\e07d'}.oi-justify-center:before{content:'\e07e'}.oi-justify-left:before{content:'\e07f'}.oi-justify-right:before{content:'\e080'}.oi-key:before{content:'\e081'}.oi-laptop:before{content:'\e082'}.oi-layers:before{content:'\e083'}.oi-lightbulb:before{content:'\e084'}.oi-link-broken:before{content:'\e085'}.oi-link-intact:before{content:'\e086'}.oi-list-rich:before{content:'\e087'}.oi-list:before{content:'\e088'}.oi-location:before{content:'\e089'}.oi-lock-locked:before{content:'\e08a'}.oi-lock-unlocked:before{content:'\e08b'}.oi-loop-circular:before{content:'\e08c'}.oi-loop-square:before{content:'\e08d'}.oi-loop:before{content:'\e08e'}.oi-magnifying-glass:before{content:'\e08f'}.oi-map-marker:before{content:'\e090'}.oi-map:before{content:'\e091'}.oi-media-pause:before{content:'\e092'}.oi-media-play:before{content:'\e093'}.oi-media-record:before{content:'\e094'}.oi-media-skip-backward:before{content:'\e095'}.oi-media-skip-forward:before{content:'\e096'}.oi-media-step-backward:before{content:'\e097'}.oi-media-step-forward:before{content:'\e098'}.oi-media-stop:before{content:'\e099'}.oi-medical-cross:before{content:'\e09a'}.oi-menu:before{content:'\e09b'}.oi-microphone:before{content:'\e09c'}.oi-minus:before{content:'\e09d'}.oi-monitor:before{content:'\e09e'}.oi-moon:before{content:'\e09f'}.oi-move:before{content:'\e0a0'}.oi-musical-note:before{content:'\e0a1'}.oi-paperclip:before{content:'\e0a2'}.oi-pencil:before{content:'\e0a3'}.oi-people:before{content:'\e0a4'}.oi-person:before{content:'\e0a5'}.oi-phone:before{content:'\e0a6'}.oi-pie-chart:before{content:'\e0a7'}.oi-pin:before{content:'\e0a8'}.oi-play-circle:before{content:'\e0a9'}.oi-plus:before{content:'\e0aa'}.oi-power-standby:before{content:'\e0ab'}.oi-print:before{content:'\e0ac'}.oi-project:before{content:'\e0ad'}.oi-pulse:before{content:'\e0ae'}.oi-puzzle-piece:before{content:'\e0af'}.oi-question-mark:before{content:'\e0b0'}.oi-rain:before{content:'\e0b1'}.oi-random:before{content:'\e0b2'}.oi-reload:before{content:'\e0b3'}.oi-resize-both:before{content:'\e0b4'}.oi-resize-height:before{content:'\e0b5'}.oi-resize-width:before{content:'\e0b6'}.oi-rss-alt:before{content:'\e0b7'}.oi-rss:before{content:'\e0b8'}.oi-script:before{content:'\e0b9'}.oi-share-boxed:before{content:'\e0ba'}.oi-share:before{content:'\e0bb'}.oi-shield:before{content:'\e0bc'}.oi-signal:before{content:'\e0bd'}.oi-signpost:before{content:'\e0be'}.oi-sort-ascending:before{content:'\e0bf'}.oi-sort-descending:before{content:'\e0c0'}.oi-spreadsheet:before{content:'\e0c1'}.oi-star:before{content:'\e0c2'}.oi-sun:before{content:'\e0c3'}.oi-tablet:before{content:'\e0c4'}.oi-tag:before{content:'\e0c5'}.oi-tags:before{content:'\e0c6'}.oi-target:before{content:'\e0c7'}.oi-task:before{content:'\e0c8'}.oi-terminal:before{content:'\e0c9'}.oi-text:before{content:'\e0ca'}.oi-thumb-down:before{content:'\e0cb'}.oi-thumb-up:before{content:'\e0cc'}.oi-timer:before{content:'\e0cd'}.oi-transfer:before{content:'\e0ce'}.oi-trash:before{content:'\e0cf'}.oi-underline:before{content:'\e0d0'}.oi-vertical-align-bottom:before{content:'\e0d1'}.oi-vertical-align-center:before{content:'\e0d2'}.oi-vertical-align-top:before{content:'\e0d3'}.oi-video:before{content:'\e0d4'}.oi-volume-high:before{content:'\e0d5'}.oi-volume-low:before{content:'\e0d6'}.oi-volume-off:before{content:'\e0d7'}.oi-warning:before{content:'\e0d8'}.oi-wifi:before{content:'\e0d9'}.oi-wrench:before{content:'\e0da'}.oi-x:before{content:'\e0db'}.oi-yen:before{content:'\e0dc'}.oi-zoom-in:before{content:'\e0dd'}.oi-zoom-out:before{content:'\e0de'} \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.eot b/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.eot deleted file mode 100644 index f98177db..00000000 Binary files a/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.eot and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.otf b/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.otf deleted file mode 100644 index f6bd6846..00000000 Binary files a/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.otf and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.svg b/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.svg deleted file mode 100644 index 32b2c4e9..00000000 --- a/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.svg +++ /dev/null @@ -1,543 +0,0 @@ - - - - - -Created by FontForge 20120731 at Tue Jul 1 20:39:22 2014 - By P.J. Onori -Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf b/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf deleted file mode 100644 index fab60486..00000000 Binary files a/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.woff b/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.woff deleted file mode 100644 index f9309988..00000000 Binary files a/Oqtane.Application/Server/wwwroot/css/open-iconic/font/fonts/open-iconic.woff and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/css/texteditors/radzen/radzentexteditor.css b/Oqtane.Application/Server/wwwroot/css/texteditors/radzen/radzentexteditor.css deleted file mode 100644 index e142053b..00000000 --- a/Oqtane.Application/Server/wwwroot/css/texteditors/radzen/radzentexteditor.css +++ /dev/null @@ -1,22 +0,0 @@ -.rz-text-editor { - outline: none !important; -} - -.rz-html-editor-dropdown-items, -.rz-popup, -.rz-editor-dialog-wrapper { - z-index: 9999 !important; -} - -.rz-html-editor-dropdown-items .rz-html-editor-dropdown-item, -.rz-html-editor-dropdown-items .rz-html-editor-dropdown-item > * { - color: var(--rz-editor-button-color); -} -.rz-text-editor .rz-html-editor-dropdown .rz-html-editor-dropdown-value, -.rz-text-editor .rz-html-editor-dropdown .rz-html-editor-dropdown-trigger, -.rz-text-editor .rz-html-editor-colorpicker .rz-html-editor-color { - color: var(--rz-editor-button-color); -} -.rz-text-editor .rz-colorpicker.rz-state-disabled { - border: none !important; -} \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/favicon.ico b/Oqtane.Application/Server/wwwroot/favicon.ico deleted file mode 100644 index 550d600e..00000000 Binary files a/Oqtane.Application/Server/wwwroot/favicon.ico and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/icon.png b/Oqtane.Application/Server/wwwroot/icon.png deleted file mode 100644 index 1ac7cee0..00000000 Binary files a/Oqtane.Application/Server/wwwroot/icon.png and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/images/checked.png b/Oqtane.Application/Server/wwwroot/images/checked.png deleted file mode 100644 index a4100c70..00000000 Binary files a/Oqtane.Application/Server/wwwroot/images/checked.png and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/images/error.png b/Oqtane.Application/Server/wwwroot/images/error.png deleted file mode 100644 index 0095d2f1..00000000 Binary files a/Oqtane.Application/Server/wwwroot/images/error.png and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/images/help.png b/Oqtane.Application/Server/wwwroot/images/help.png deleted file mode 100644 index f380be58..00000000 Binary files a/Oqtane.Application/Server/wwwroot/images/help.png and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/images/logo-black.png b/Oqtane.Application/Server/wwwroot/images/logo-black.png deleted file mode 100644 index 9463cbf0..00000000 Binary files a/Oqtane.Application/Server/wwwroot/images/logo-black.png and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/images/logo-white.png b/Oqtane.Application/Server/wwwroot/images/logo-white.png deleted file mode 100644 index 94454bf6..00000000 Binary files a/Oqtane.Application/Server/wwwroot/images/logo-white.png and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/images/null.png b/Oqtane.Application/Server/wwwroot/images/null.png deleted file mode 100644 index d0f50939..00000000 Binary files a/Oqtane.Application/Server/wwwroot/images/null.png and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/images/unchecked.png b/Oqtane.Application/Server/wwwroot/images/unchecked.png deleted file mode 100644 index 566e60a8..00000000 Binary files a/Oqtane.Application/Server/wwwroot/images/unchecked.png and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/js/app.js b/Oqtane.Application/Server/wwwroot/js/app.js deleted file mode 100644 index 2c5d837e..00000000 --- a/Oqtane.Application/Server/wwwroot/js/app.js +++ /dev/null @@ -1,8 +0,0 @@ -function subMenu(a) { - event.preventDefault(); - event.stopPropagation(); - - var li = a.parentElement, submenu = li.getElementsByTagName('ul')[0]; - submenu.style.display = submenu.style.display == "block" ? "none" : "block"; - return false; -} \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/js/interop.js b/Oqtane.Application/Server/wwwroot/js/interop.js deleted file mode 100644 index fecc4c99..00000000 --- a/Oqtane.Application/Server/wwwroot/js/interop.js +++ /dev/null @@ -1,520 +0,0 @@ -var Oqtane = Oqtane || {}; - -Oqtane.Interop = { - setCookie: function (name, value, days, secure, sameSite) { - var d = new Date(); - d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); - var expires = "expires=" + d.toUTCString(); - var cookieString = name + "=" + value + ";" + expires + ";path=/"; - if (secure) { - cookieString += "; secure"; - } - if (sameSite === "Lax" || sameSite === "Strict" || sameSite === "None") { - cookieString += "; SameSite=" + sameSite; - } - document.cookie = cookieString; - }, - setCookieString: function (cookieString) { - document.cookie = cookieString; - }, - getCookie: function (name) { - name = name + "="; - var decodedCookie = decodeURIComponent(document.cookie); - var ca = decodedCookie.split(';'); - for (var i = 0; i < ca.length; i++) { - var c = ca[i]; - while (c.charAt(0) === ' ') { - c = c.substring(1); - } - if (c.indexOf(name) === 0) { - return c.substring(name.length, c.length); - } - } - return ""; - }, - updateTitle: function (title) { - if (document.title !== title) { - document.title = title; - } - }, - includeMeta: function (id, attribute, name, content) { - var meta = document.querySelector("meta[" + attribute + "=\"" + CSS.escape(name) + "\"]"); - if (meta === null) { - meta = document.createElement("meta"); - meta.setAttribute(attribute, name); - if (id !== "") { - meta.id = id; - } - meta.content = content; - document.head.appendChild(meta); - } - else { - if (id !== "") { - meta.setAttribute("id", id); - } - if (meta.content !== content) { - meta.setAttribute("content", content); - } - } - }, - includeLink: function (id, rel, href, type, integrity, crossorigin, insertbefore) { - var link = document.querySelector("link[href=\"" + CSS.escape(href) + "\"]"); - if (link === null) { - link = document.createElement("link"); - if (id !== "") { - link.id = id; - } - link.rel = rel; - if (type !== "") { - link.type = type; - } - link.href = href; - if (integrity !== "") { - link.integrity = integrity; - } - if (crossorigin !== "") { - link.crossOrigin = crossorigin; - } - if (insertbefore === "") { - document.head.appendChild(link); - } - else { - var sibling = document.getElementById(insertbefore); - sibling.parentNode.insertBefore(link, sibling); - } - } - else { - if (link.id !== id) { - link.setAttribute('id', id); - } - if (link.rel !== rel) { - link.setAttribute('rel', rel); - } - if (type !== "") { - if (link.type !== type) { - link.setAttribute('type', type); - } - } else { - link.removeAttribute('type'); - } - if (link.href !== this.getAbsoluteUrl(href)) { - link.removeAttribute('integrity'); - link.removeAttribute('crossorigin'); - link.setAttribute('href', href); - } - if (integrity !== "") { - if (link.integrity !== integrity) { - link.setAttribute('integrity', integrity); - } - } else { - link.removeAttribute('integrity'); - } - if (crossorigin !== "") { - if (link.crossOrigin !== crossorigin) { - link.setAttribute('crossorigin', crossorigin); - } - } else { - link.removeAttribute('crossorigin'); - } - } - }, - includeLinks: function (links) { - for (let i = 0; i < links.length; i++) { - this.includeLink(links[i].id, links[i].rel, links[i].href, links[i].type, links[i].integrity, links[i].crossorigin, links[i].insertbefore); - } - }, - includeScript: function (id, src, integrity, crossorigin, type, content, location, dataAttributes) { - var script; - if (src !== "") { - script = document.querySelector("script[src=\"" + CSS.escape(src) + "\"]"); - } - else { - if (id !== "") { - script = document.getElementById(id); - } else { - const scripts = document.querySelectorAll("script:not([src])"); - for (let i = 0; i < scripts.length; i++) { - if (scripts[i].textContent.includes(content)) { - script = scripts[i]; - } - } - } - } - if (script !== null) { - script.remove(); - script = null; - } - if (script === null) { - script = document.createElement("script"); - if (id !== "") { - script.id = id; - } - if (type !== "") { - script.type = type; - } - if (src !== "") { - script.src = src; - if (integrity !== "") { - script.integrity = integrity; - } - if (crossorigin !== "") { - script.crossOrigin = crossorigin; - } - } - else { - script.innerHTML = content; - } - if (dataAttributes !== null) { - for (var key in dataAttributes) { - script.setAttribute(key, dataAttributes[key]); - } - } - - try { - this.addScript(script, location); - } catch (error) { - if (src !== "") { - console.error("Failed to load external script: ${src}", error); - } else { - console.error("Failed to load inline script: ${content}", error); - } - } - } - }, - addScript: function (script, location) { - return new Promise((resolve, reject) => { - script.async = false; - script.defer = false; - - script.onload = () => resolve(); - script.onerror = (error) => reject(error); - - if (location === 'head') { - document.head.appendChild(script); - } else { - document.body.appendChild(script); - } - }); - }, - includeScripts: async function (scripts) { - const bundles = []; - for (let s = 0; s < scripts.length; s++) { - if (scripts[s].bundle === '') { - scripts[s].bundle = scripts[s].href; - } - if (!bundles.includes(scripts[s].bundle)) { - bundles.push(scripts[s].bundle); - } - } - const promises = []; - for (let b = 0; b < bundles.length; b++) { - const urls = []; - for (let s = 0; s < scripts.length; s++) { - if (scripts[s].bundle === bundles[b]) { - urls.push(scripts[s].href); - } - } - promises.push(new Promise((resolve, reject) => { - if (loadjs.isDefined(bundles[b])) { - loadjs.ready(bundles[b], () => { - resolve(true); - }); - } - else { - loadjs(urls, bundles[b], { - async: false, - returnPromise: true, - before: function (path, element) { - for (let s = 0; s < scripts.length; s++) { - if (path === scripts[s].href) { - if (scripts[s].integrity !== '') { - element.integrity = scripts[s].integrity; - } - if (scripts[s].crossorigin !== '') { - element.crossOrigin = scripts[s].crossorigin; - } - if (scripts[s].type !== '') { - element.type = scripts[s].type; - } - if (scripts[s].dataAttributes !== null) { - for (var key in scripts[s].dataAttributes) { - element.setAttribute(key, scripts[s].dataAttributes[key]); - } - } - if (scripts[s].location === 'body') { - document.body.appendChild(element); - return false; // return false to bypass default DOM insertion mechanism - } - } - } - } - }) - .then(function () { resolve(true) }) - .catch(function (pathsNotFound) { reject(false) }); - } - })); - } - if (promises.length !== 0) { - await Promise.all(promises); - } - }, - getAbsoluteUrl: function (url) { - var a = document.createElement('a'); - getAbsoluteUrl = function (url) { - a.href = url; - return a.href; - } - return getAbsoluteUrl(url); - }, - removeElementsById: function (prefix, first, last) { - var elements = document.querySelectorAll('[id^=' + prefix + ']'); - for (var i = elements.length - 1; i >= 0; i--) { - var element = elements[i]; - if (element.id.startsWith(prefix) && (first === '' || element.id >= first) && (last === '' || element.id <= last)) { - element.parentNode.removeChild(element); - } - } - }, - getElementByName: function (name) { - var elements = document.getElementsByName(name); - if (elements.length) { - return elements[0].value; - } else { - return ""; - } - }, - submitForm: function (path, fields) { - const form = document.createElement('form'); - form.method = 'post'; - form.action = path; - - for (const key in fields) { - if (fields.hasOwnProperty(key)) { - const hiddenField = document.createElement('input'); - hiddenField.type = 'hidden'; - hiddenField.name = key; - hiddenField.value = fields[key]; - form.appendChild(hiddenField); - } - } - - document.body.appendChild(form); - form.submit(); - }, - getFiles: function (id) { - var files = []; - var fileinput = document.getElementById(id); - if (fileinput !== null) { - for (var i = 0; i < fileinput.files.length; i++) { - files.push(fileinput.files[i].name + ":" + fileinput.files[i].size); - } - } - return files; - }, - uploadFiles: async function (posturl, folder, id, antiforgerytoken, jwt, chunksize, anonymizeuploadfilenames) { - var success = true; - var fileinput = document.getElementById('FileInput_' + id); - var progressinfo = document.getElementById('ProgressInfo_' + id); - var progressbar = document.getElementById('ProgressBar_' + id); - - var totalSize = 0; - for (var i = 0; i < fileinput.files.length; i++) { - totalSize += fileinput.files[i].size; - } - let uploadSize = 0; - - if (!chunksize || chunksize < 1) { - chunksize = 1; // 1 MB default - } - - if (progressinfo !== null && progressbar !== null) { - progressinfo.setAttribute('style', 'display: inline;'); - if (fileinput.files.length > 1) { - progressinfo.innerHTML = fileinput.files[0].name + ', ...'; - } - else { - progressinfo.innerHTML = fileinput.files[0].name; - } - progressbar.setAttribute('style', 'width: 100%; display: inline;'); - progressbar.value = 0; - } - - const uploadFile = (file) => { - const chunkSize = chunksize * (1024 * 1024); - const totalParts = Math.ceil(file.size / chunkSize); - let partCount = 0; - - let filename = file.name; - if (anonymizeuploadfilenames) { - filename = crypto.randomUUID() + '.' + filename.split('.').pop(); - } - - const uploadPart = () => { - const start = partCount * chunkSize; - const end = Math.min(start + chunkSize, file.size); - const chunk = file.slice(start, end); - - return new Promise((resolve, reject) => { - - let formdata = new FormData(); - formdata.append('__RequestVerificationToken', antiforgerytoken); - formdata.append('folder', folder); - formdata.append('formfile', chunk, filename); - - var credentials = 'same-origin'; - var headers = new Headers(); - headers.append('PartCount', partCount + 1); - headers.append('TotalParts', totalParts); - if (jwt !== "") { - headers.append('Authorization', 'Bearer ' + jwt); - credentials = 'include'; - } - - return fetch(posturl, { - method: 'POST', - headers: headers, - credentials: credentials, - body: formdata - }) - .then(response => { - if (!response.ok) { - if (progressinfo !== null) { - progressinfo.innerHTML = 'Error: ' + response.statusText; - } - throw new Error('Failed'); - } - return; - }) - .then(data => { - partCount++; - if (progressbar !== null) { - uploadSize += chunk.size; - var percent = Math.ceil((uploadSize / totalSize) * 100); - progressbar.value = (percent / 100); - } - if (partCount < totalParts) { - uploadPart().then(resolve).catch(reject); - } - else { - resolve(data); - } - }) - .catch(error => { - reject(error); - }); - }); - }; - - return uploadPart(); - }; - - try { - for (const file of fileinput.files) { - await uploadFile(file); - } - } catch (error) { - success = false; - } - - fileinput.value = ''; - return success; - }, - refreshBrowser: function (verify, wait) { - async function attemptReload (verify) { - if (verify) { - await fetch(''); - } - window.location.reload(); - } - attemptReload(verify); - setInterval(attemptReload, wait * 1000); - }, - redirectBrowser: function (url, wait) { - setInterval(function () { - window.location.href = url; - }, wait * 1000); - }, - formValid: function (formRef) { - return formRef.checkValidity(); - }, - setElementAttribute: function (id, attribute, value) { - var element = document.getElementById(id); - if (element !== null) { - element.setAttribute(attribute, value); - } - }, - scrollTo: function (top, left, behavior) { - const modal = document.querySelector('.modal'); - if (modal) { - modal.scrollTo({ - top: top, - left: left, - behavior: behavior - }); - } else { - window.scrollTo({ - top: top, - left: left, - behavior: behavior - }); - } - }, - scrollToId: function (id) { - var element = document.getElementById(id); - if (element instanceof HTMLElement) { - element.scrollIntoView({ - behavior: "smooth", - block: "start", - inline: "nearest" - }); - } - }, - getCaretPosition: function (id) { - var element = document.getElementById(id); - return element.selectionStart; - }, - manageIndexedDBItems: async function (action, key, value) { - var idb = indexedDB.open("oqtane", 1); - - idb.onupgradeneeded = function () { - let db = idb.result; - db.createObjectStore("items"); - } - - if (action.startsWith("get")) { - let request = new Promise((resolve) => { - idb.onsuccess = function () { - let transaction = idb.result.transaction("items", "readonly"); - let collection = transaction.objectStore("items"); - let result; - if (action === "get") { - result = collection.get(key); - } - if (action === "getallkeys") { - result = collection.getAllKeys(); - } - - result.onsuccess = function (e) { - resolve(result.result); - } - } - }); - - let result = await request; - - return result; - } - else { - idb.onsuccess = function () { - let transaction = idb.result.transaction("items", "readwrite"); - let collection = transaction.objectStore("items"); - if (action === "put") { - collection.put(value, key); - } - if (action === "delete") { - collection.delete(key); - } - } - } - } -}; diff --git a/Oqtane.Application/Server/wwwroot/js/loadjs.min.js b/Oqtane.Application/Server/wwwroot/js/loadjs.min.js deleted file mode 100644 index b2165fc3..00000000 --- a/Oqtane.Application/Server/wwwroot/js/loadjs.min.js +++ /dev/null @@ -1 +0,0 @@ -loadjs=function(){var h=function(){},c={},u={},f={};function o(e,n){if(e){var r=f[e];if(u[e]=n,r)for(;r.length;)r[0](e,n),r.splice(0,1)}}function l(e,n){e.call&&(e={success:e}),n.length?(e.error||h)(n):(e.success||h)(e)}function d(r,t,s,i){var c,o,e=document,n=s.async,u=(s.numRetries||0)+1,f=s.before||h,l=r.replace(/[\?|#].*$/,""),a=r.replace(/^(css|img)!/,"");i=i||0,/(^css!|\.css$)/.test(l)?((o=e.createElement("link")).rel="stylesheet",o.href=a,(c="hideFocus"in o)&&o.relList&&(c=0,o.rel="preload",o.as="style")):/(^img!|\.(png|gif|jpg|svg|webp)$)/.test(l)?(o=e.createElement("img")).src=a:((o=e.createElement("script")).src=r,o.async=void 0===n||n),!(o.onload=o.onerror=o.onbeforeload=function(e){var n=e.type[0];if(c)try{o.sheet.cssText.length||(n="e")}catch(e){18!=e.code&&(n="e")}if("e"==n){if((i+=1) { - var newScript = document.createElement('script'); - - // replicate attributes and content - for (let i = 0; i < script.attributes.length; i++) { - if (script.attributes[i].name !== 'data-reload') { - newScript.setAttribute(script.attributes[i].name, script.attributes[i].value); - } - } - newScript.nonce = script.nonce; // must be referenced explicitly - newScript.innerHTML = script.innerHTML; - - // dynamically injected scripts cannot be async or deferred - newScript.async = false; - newScript.defer = false; - - newScript.onload = () => resolve(); - newScript.onerror = (error) => reject(error); - - // inject script element in head to force execution in Blazor - document.head.appendChild(newScript); - - // remove data-reload attribute - script.removeAttribute('data-reload'); - }); -} \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/loading.gif b/Oqtane.Application/Server/wwwroot/loading.gif deleted file mode 100644 index cc70a7a8..00000000 Binary files a/Oqtane.Application/Server/wwwroot/loading.gif and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/oqtane-black.png b/Oqtane.Application/Server/wwwroot/oqtane-black.png deleted file mode 100644 index 942ef8c0..00000000 Binary files a/Oqtane.Application/Server/wwwroot/oqtane-black.png and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/oqtane-glow.png b/Oqtane.Application/Server/wwwroot/oqtane-glow.png deleted file mode 100644 index 03942d55..00000000 Binary files a/Oqtane.Application/Server/wwwroot/oqtane-glow.png and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/oqtane-white.png b/Oqtane.Application/Server/wwwroot/oqtane-white.png deleted file mode 100644 index fa7950bc..00000000 Binary files a/Oqtane.Application/Server/wwwroot/oqtane-white.png and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/oqtane.ico b/Oqtane.Application/Server/wwwroot/oqtane.ico deleted file mode 100644 index 0e569015..00000000 Binary files a/Oqtane.Application/Server/wwwroot/oqtane.ico and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/oqtane.png b/Oqtane.Application/Server/wwwroot/oqtane.png deleted file mode 100644 index be8f48c9..00000000 Binary files a/Oqtane.Application/Server/wwwroot/oqtane.png and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/package.png b/Oqtane.Application/Server/wwwroot/package.png deleted file mode 100644 index ccc6f290..00000000 Binary files a/Oqtane.Application/Server/wwwroot/package.png and /dev/null differ diff --git a/Oqtane.Application/Server/wwwroot/service-worker.js b/Oqtane.Application/Server/wwwroot/service-worker.js deleted file mode 100644 index 0c55c1b1..00000000 --- a/Oqtane.Application/Server/wwwroot/service-worker.js +++ /dev/null @@ -1,2 +0,0 @@ -// always fetch from the network and do not enable offline support -self.addEventListener('fetch', () => { }); \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/users.txt b/Oqtane.Application/Server/wwwroot/users.txt deleted file mode 100644 index b1d55c74..00000000 --- a/Oqtane.Application/Server/wwwroot/users.txt +++ /dev/null @@ -1 +0,0 @@ -Email Username DisplayName Roles FirstName LastName Street City Region Country PostalCode Phone diff --git a/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj b/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj index a10df1dc..1125b6c0 100644 --- a/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj +++ b/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj @@ -1,13 +1,17 @@ - - net9.0 - 1.0.0 - Oqtane.Application.Shared.Oqtane - + + net9.0 + 1.0.0 + Oqtane.Application.Shared.Oqtane + - - - + + + + + + + diff --git a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs index 96d68d37..82186385 100644 --- a/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Client/Extensions/OqtaneServiceCollectionExtensions.cs @@ -56,6 +56,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); // providers diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index 66562e6c..36d9eaf1 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -94,7 +94,7 @@ else public override List Resources => new List() { - new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" } + new Stylesheet(ModulePath() + "Module.css") }; protected override async Task OnInitializedAsync() diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor index f8057caa..fa603a56 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Add.razor @@ -101,13 +101,20 @@ @SharedLocalizer["Search.By"]: @context.Owner
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)

- @if (!string.IsNullOrEmpty(context.PackageUrl)) + @if (_moduledefinitions.Exists(item => item.PackageName == context.PackageId)) { - + } - @if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl)) + else { - @SharedLocalizer["Buy"] + @if (!string.IsNullOrEmpty(context.PackageUrl)) + { + + } + @if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl)) + { + @SharedLocalizer["Buy"] + } }
@@ -171,6 +178,7 @@ @code { private bool _initialized = false; + private List _moduledefinitions; private int _page = 1; private List _packages; private string _price = "free"; @@ -187,7 +195,8 @@ { try { - await LoadModuleDefinitions(); + _moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId); + await LoadPackages(); _initialized = true; } catch (Exception ex) @@ -197,24 +206,10 @@ } } - private async Task LoadModuleDefinitions() + private async Task LoadPackages() { ShowProgressIndicator(); - - var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId); _packages = await PackageService.GetPackagesAsync("module", _search, _price, "", _sort); - - if (_packages != null) - { - foreach (Package package in _packages.ToArray()) - { - if (moduledefinitions.Exists(item => item.PackageName == package.PackageId)) - { - _packages.Remove(package); - } - } - } - HideProgressIndicator(); } @@ -222,25 +217,25 @@ { _price = price; _sort = "popularity"; - await LoadModuleDefinitions(); + await LoadPackages(); StateHasChanged(); } private async Task Search() { - await LoadModuleDefinitions(); + await LoadPackages(); } private async Task Reset() { _page = 1; _search = ""; - await LoadModuleDefinitions(); + await LoadPackages(); } private async Task Refresh() { - await LoadModuleDefinitions(); + await LoadPackages(); } private void OnPageChange(int page) @@ -251,7 +246,7 @@ private async void SortChanged(ChangeEventArgs e) { _sort = (string)e.Value; - await LoadModuleDefinitions(); + await LoadPackages(); } private void HideModal() diff --git a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor index ef8bbc95..e9caa80f 100644 --- a/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor +++ b/Oqtane.Client/Modules/Admin/ModuleDefinitions/Edit.razor @@ -14,7 +14,7 @@ @if (_initialized) { - +
@@ -236,11 +236,10 @@ private DateTime _createdon; private string _modifiedby; private DateTime _modifiedon; - private List _pagesWithModules; -#pragma warning disable 649 private PermissionGrid _permissionGrid; -#pragma warning restore 649 + + private List _pagesWithModules; private List _packages; private List _languages; diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor index a4bc6500..36385232 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Add.razor @@ -269,8 +269,16 @@ if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) || (_parent != null && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, _parent.PermissionList))) { _themetype = PageState.Site.DefaultThemeType; - _themes = ThemeService.GetThemeControls(PageState.Site.Themes); - _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype); + var themes = new List(); + foreach (var theme in PageState.Site.Themes) + { + if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList)) + { + themes.Add(theme); + } + } + _themes = ThemeService.GetThemeControls(themes); + _containers = ThemeService.GetContainerControls(themes, _themetype); _containertype = PageState.Site.DefaultContainerType; _children = new List(); foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid)))) diff --git a/Oqtane.Client/Modules/Admin/Pages/Edit.razor b/Oqtane.Client/Modules/Admin/Pages/Edit.razor index df9db9bb..2811bd87 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Edit.razor @@ -443,8 +443,16 @@ { _themetype = PageState.Site.DefaultThemeType; } - _themes = ThemeService.GetThemeControls(PageState.Site.Themes); - _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype); + var themes = new List(); + foreach (var theme in PageState.Site.Themes) + { + if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList)) + { + themes.Add(theme); + } + } + _themes = ThemeService.GetThemeControls(themes); + _containers = ThemeService.GetContainerControls(themes, _themetype); _containertype = _page.DefaultContainerType; if (string.IsNullOrEmpty(_containertype)) { diff --git a/Oqtane.Client/Modules/Admin/Profiles/Edit.razor b/Oqtane.Client/Modules/Admin/Profiles/Edit.razor index e2e16b55..467f0d82 100644 --- a/Oqtane.Client/Modules/Admin/Profiles/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Profiles/Edit.razor @@ -2,6 +2,7 @@ @inherits ModuleBase @inject NavigationManager NavigationManager @inject IProfileService ProfileService +@inject ISettingService SettingService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer @@ -56,9 +57,25 @@
- +
- +
+ @if (_optiontype == "Settings") + { + + } + else + { + + } + +
@@ -95,7 +112,7 @@
@SharedLocalizer["Cancel"] - @if (PageState.QueryString.ContainsKey("id")) + @if (PageState.QueryString.ContainsKey("id")) {

@@ -116,6 +133,8 @@ private string _rows = "1"; private string _defaultvalue = string.Empty; private string _options = string.Empty; + private string _optiontype = "Settings"; + private List _entitynames; private string _validation = string.Empty; private string _autocomplete = string.Empty; private string _isrequired = "False"; @@ -133,6 +152,8 @@ { try { + _entitynames = await SettingService.GetEntityNamesAsync(); + if (PageState.QueryString.ContainsKey("id")) { _profileid = Int32.Parse(PageState.QueryString["id"]); @@ -148,6 +169,11 @@ _rows = profile.Rows.ToString(); _defaultvalue = profile.DefaultValue; _options = profile.Options; + if (_options.StartsWith("EntityName:")) + { + _optiontype = "Options"; + _options = _options.Substring(11); + } _validation = profile.Validation; _autocomplete = profile.Autocomplete; _isrequired = profile.IsRequired.ToString(); @@ -166,6 +192,18 @@ } } + private void ToggleOptionType() + { + if (_optiontype == "Options") + { + _optiontype = "Settings"; + } + else + { + _optiontype = "Options"; + } + } + private async Task SaveProfile() { validated = true; @@ -193,7 +231,14 @@ profile.MaxLength = int.Parse(_maxlength); profile.Rows = int.Parse(_rows); profile.DefaultValue = _defaultvalue; - profile.Options = _options; + if (_optiontype == "Options" && !string.IsNullOrEmpty(_options)) + { + profile.Options = "EntityName:" + _options; + } + else + { + profile.Options = _options; + } profile.Validation = _validation; profile.Autocomplete = _autocomplete; profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired)); diff --git a/Oqtane.Client/Modules/Admin/SearchResults/Index.razor b/Oqtane.Client/Modules/Admin/SearchResults/Index.razor index bb58a9c1..001faaf1 100644 --- a/Oqtane.Client/Modules/Admin/SearchResults/Index.razor +++ b/Oqtane.Client/Modules/Admin/SearchResults/Index.razor @@ -40,6 +40,9 @@ { @@ -66,6 +69,7 @@ @code { public override string RenderMode => RenderModes.Static; + private const string SearchDefaultPageSize = "10"; private string _includeEntities; private string _excludeEntities; @@ -75,6 +79,8 @@ private string _sortField; private string _sortOrder; private string _bodyLength; + private string _currentPage = "0"; + private string _displayPages = "7"; private string _keywords; private SearchResults _searchResults; @@ -89,11 +95,16 @@ _excludeEntities = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ExcludeEntities", ""); _fromDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_FromDate", DateTime.MinValue.ToString()); _toDate = SettingService.GetSetting(ModuleState.Settings, "SearchResults_ToDate", DateTime.MaxValue.ToString()); - _pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", int.MaxValue.ToString()); + _pageSize = SettingService.GetSetting(ModuleState.Settings, "SearchResults_PageSize", SearchDefaultPageSize); _sortField = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortField", "Relevance"); _sortOrder = SettingService.GetSetting(ModuleState.Settings, "SearchResults_SortOrder", "Descending"); _bodyLength = SettingService.GetSetting(ModuleState.Settings, "SearchResults_BodyLength", "255"); + if (PageState.QueryString.ContainsKey("p")) + { + _currentPage = PageState.QueryString["p"]; + } + if (_keywords == null && PageState.QueryString.ContainsKey("q")) { _keywords = WebUtility.UrlDecode(PageState.QueryString["q"]); @@ -122,7 +133,7 @@ ExcludeEntities = _excludeEntities, FromDate = (!string.IsNullOrEmpty(_fromDate)) ? DateTime.Parse(_fromDate) : DateTime.MinValue, ToDate = (!string.IsNullOrEmpty(_toDate)) ? DateTime.Parse(_toDate) : DateTime.MaxValue, - PageSize = (!string.IsNullOrEmpty(_pageSize)) ? int.Parse(_pageSize) : int.MaxValue, + PageSize = int.MaxValue, PageIndex = 0, SortField = (!string.IsNullOrEmpty(_sortField)) ? (SearchSortField)Enum.Parse(typeof(SearchSortField), _sortField) : SearchSortField.Relevance, SortOrder = (!string.IsNullOrEmpty(_sortOrder)) ? (SearchSortOrder)Enum.Parse(typeof(SearchSortOrder), _sortOrder) : SearchSortOrder.Descending, diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor index b921ab38..525f45df 100644 --- a/Oqtane.Client/Modules/Admin/Site/Index.razor +++ b/Oqtane.Client/Modules/Admin/Site/Index.razor @@ -592,9 +592,17 @@ { _faviconfileid = site.FaviconFileId.Value; } - _themes = ThemeService.GetThemeControls(PageState.Site.Themes); + var themes = new List(); + foreach (var theme in PageState.Site.Themes) + { + if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, theme.PermissionList)) + { + themes.Add(theme); + } + } + _themes = ThemeService.GetThemeControls(themes); _themetype = (!string.IsNullOrEmpty(site.DefaultThemeType)) ? site.DefaultThemeType : Constants.DefaultTheme; - _containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype); + _containers = ThemeService.GetContainerControls(themes, _themetype); _containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer; _admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer; _cookieconsent = SettingService.GetSetting(settings, "CookieConsent", string.Empty); diff --git a/Oqtane.Client/Modules/Admin/Sites/Add.razor b/Oqtane.Client/Modules/Admin/Sites/Add.razor index 96949077..383a839e 100644 --- a/Oqtane.Client/Modules/Admin/Sites/Add.razor +++ b/Oqtane.Client/Modules/Admin/Sites/Add.razor @@ -216,7 +216,7 @@ else _tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString(); } _urls = PageState.Alias.Name; - _themeList = await ThemeService.GetThemesAsync(); + _themeList = await ThemeService.GetThemesAsync(PageState.Site.SiteId); _themes = ThemeService.GetThemeControls(_themeList); if (_themes.Any(item => item.TypeName == Constants.DefaultTheme)) { diff --git a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor index f4d347e9..6f7c4b02 100644 --- a/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor +++ b/Oqtane.Client/Modules/Admin/SystemInfo/Index.razor @@ -2,241 +2,280 @@ @inherits ModuleBase @inject ISystemService SystemService @inject IInstallationService InstallationService +@inject IMigrationHistoryService MigrationHistoryService +@inject ITenantService TenantService @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer - - -
-
- -
- +@if (_initialized) +{ + + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
-
- -
- +

+ + + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
-
- -
- +

+   + +

+ @Localizer["Swagger"]  + @Localizer["Endpoints"] + + +
+
+ +
+ + } +
+
+
+ +
+ + @SharedLocalizer["Cancel"] +
+
+ +
+ +
+
+
-
- -
- -
-
-
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- @if (_license.StartsWith("http") || _license.StartsWith("/") || _license.StartsWith("~")) - { - @Localizer["View License"] - } - else - { - - } -
-
-
-
-
- - @SharedLocalizer["Cancel"] -
-
- +
+ + @SharedLocalizer["Cancel"] + + } @code { @@ -103,11 +117,14 @@ private string _url = ""; private string _contact = ""; private string _license = ""; + private List _permissions = null; private string _createdby; private DateTime _createdon; private string _modifiedby; private DateTime _modifiedon; + private PermissionGrid _permissionGrid; + public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; protected override async Task OnInitializedAsync() @@ -126,6 +143,7 @@ _url = theme.Url; _contact = theme.Contact; _license = theme.License; + _permissions = theme.PermissionList; _createdby = theme.CreatedBy; _createdon = theme.CreatedOn; _modifiedby = theme.ModifiedBy; @@ -152,6 +170,7 @@ var theme = await ThemeService.GetThemeAsync(_themeId, ModuleState.SiteId); theme.Name = _name; theme.IsEnabled = (_isenabled == null ? true : bool.Parse(_isenabled)); + theme.PermissionList = _permissionGrid.GetPermissionList(); await ThemeService.UpdateThemeAsync(theme); await logger.LogInformation("Theme Saved {Theme}", theme); NavigationManager.NavigateTo(NavigateUrl()); diff --git a/Oqtane.Client/Modules/Admin/Themes/Index.razor b/Oqtane.Client/Modules/Admin/Themes/Index.razor index 719802bb..f3f081e0 100644 --- a/Oqtane.Client/Modules/Admin/Themes/Index.razor +++ b/Oqtane.Client/Modules/Admin/Themes/Index.razor @@ -78,7 +78,7 @@ else { try { - _themes = await ThemeService.GetThemesAsync(); + _themes = await ThemeService.GetThemesAsync(PageState.Site.SiteId); _packages = await PackageService.GetPackageUpdatesAsync("theme"); } catch (Exception ex) @@ -161,7 +161,7 @@ else { try { - await ThemeService.DeleteThemeAsync(Theme.ThemeName); + await ThemeService.DeleteThemeAsync(Theme.ThemeId, PageState.Site.SiteId); AddModuleMessage(Localizer["Success.Theme.Delete"], MessageType.Success); NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true)); } diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor index cb9dd0c4..2b5d01ee 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor @@ -124,7 +124,6 @@ @if (!string.IsNullOrEmpty(p.Autocomplete)) { - @foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { var values = option.Split(':'); @@ -417,7 +415,8 @@ if (profile.Options.ToLower().StartsWith("entityname:")) { var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1); - profile.Options = string.Join(",", options.Select(kvp => $"{kvp.Key}:{kvp.Value}")); + options.Add("", $"<{SharedLocalizer["Not Specified"]}>"); + profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}")); } } _timezones = TimeZoneService.GetTimeZones(); diff --git a/Oqtane.Client/Modules/Admin/Users/Add.razor b/Oqtane.Client/Modules/Admin/Users/Add.razor index 99f5694c..6e19d832 100644 --- a/Oqtane.Client/Modules/Admin/Users/Add.razor +++ b/Oqtane.Client/Modules/Admin/Users/Add.razor @@ -86,7 +86,6 @@ @if (!string.IsNullOrEmpty(p.Options)) { - @foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { var values = option.Split(':'); @@ -213,7 +212,8 @@ if (profile.Options.ToLower().StartsWith("entityname:")) { var options = await SettingService.GetSettingsAsync(profile.Options.Substring(11), -1); - profile.Options = string.Join(",", options.Select(kvp => $"{kvp.Key}:{kvp.Value}")); + options.Add("", $"<{SharedLocalizer["Not Specified"]}>"); + profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}")); } } _timezones = TimeZoneService.GetTimeZones(); diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor index 0e76dc49..5e568a4a 100644 --- a/Oqtane.Client/Modules/Admin/Users/Index.razor +++ b/Oqtane.Client/Modules/Admin/Users/Index.razor @@ -329,6 +329,15 @@ else
+
+ +
+ +
+
}
@@ -560,6 +569,7 @@ else private string _toggleclientsecret = string.Empty; private string _authresponsetype; private string _requirenonce; + private string _singlelogout; private string _scopes; private string _parameters; private string _pkce; @@ -648,6 +658,7 @@ else _toggleclientsecret = SharedLocalizer["ShowPassword"]; _authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code"); _requirenonce = SettingService.GetSetting(settings, "ExternalLogin:RequireNonce", "true"); + _singlelogout = SettingService.GetSetting(settings, "ExternalLogin:SingleLogout", "false"); _scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", ""); _parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", ""); _pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false"); @@ -771,6 +782,7 @@ else settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true); settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true); settings = SettingService.SetSetting(settings, "ExternalLogin:RequireNonce", _requirenonce, true); + settings = SettingService.SetSetting(settings, "ExternalLogin:SingleLogout", _singlelogout, true); settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true); settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true); settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true); diff --git a/Oqtane.Client/Modules/Controls/FileManager.razor b/Oqtane.Client/Modules/Controls/FileManager.razor index c3ac1846..4bd49824 100644 --- a/Oqtane.Client/Modules/Controls/FileManager.razor +++ b/Oqtane.Client/Modules/Controls/FileManager.razor @@ -345,11 +345,11 @@ try { FolderId = int.Parse((string)e.Value); - await OnSelectFolder.InvokeAsync(FolderId); FileId = -1; GetFolderPermission(); await SetImage(); await GetFiles(); + await OnSelectFolder.InvokeAsync(FolderId); StateHasChanged(); } catch (Exception ex) @@ -364,11 +364,11 @@ { _message = string.Empty; FileId = int.Parse((string)e.Value); + await SetImage(); #pragma warning disable CS0618 await OnSelect.InvokeAsync(FileId); #pragma warning restore CS0618 await OnSelectFile.InvokeAsync(FileId); - await SetImage(); StateHasChanged(); } @@ -460,13 +460,14 @@ } } + await SetImage(); + await OnUpload.InvokeAsync(FileId); #pragma warning disable CS0618 await OnSelect.InvokeAsync(FileId); #pragma warning restore CS0618 await OnSelectFile.InvokeAsync(FileId); - await SetImage(); await GetFiles(); StateHasChanged(); } @@ -518,12 +519,13 @@ } FileId = -1; + await SetImage(); + #pragma warning disable CS0618 await OnSelect.InvokeAsync(FileId); #pragma warning restore CS0618 await OnSelectFile.InvokeAsync(FileId); - await SetImage(); await GetFiles(); StateHasChanged(); } diff --git a/Oqtane.Client/Modules/Controls/ModuleMessage.razor b/Oqtane.Client/Modules/Controls/ModuleMessage.razor index 440feff0..be961bac 100644 --- a/Oqtane.Client/Modules/Controls/ModuleMessage.razor +++ b/Oqtane.Client/Modules/Controls/ModuleMessage.razor @@ -4,29 +4,43 @@ @if (!string.IsNullOrEmpty(Message)) { - + } + + @if (_style == MessageStyle.Toast) + { +
+ +
+ } } @code { private string _message = string.Empty; private string _classname = string.Empty; + private MessageStyle _style; [Parameter] public string Message { get; set; } @@ -34,6 +48,9 @@ [Parameter] public MessageType Type { get; set; } + [Parameter] + public MessageStyle Style { get; set; } = MessageStyle.Alert; + [Parameter] public RenderModeBoundary Parent { get; set; } @@ -43,6 +60,11 @@ if (!string.IsNullOrEmpty(_message)) { _classname = GetMessageType(Type); + _style = Style; + if (Type == MessageType.Error) + { + _style = MessageStyle.Alert; // errors should always be displayed as alerts + } } } @@ -67,9 +89,10 @@ return classname; } - private void CloseMessage(MouseEventArgs e) + + private void CloseMessage() { - if(Parent != null) + if (Parent != null) { Parent.DismissMessage(); } diff --git a/Oqtane.Client/Modules/Controls/FileManagerDialog.razor b/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenFileManagerDialog.razor similarity index 59% rename from Oqtane.Client/Modules/Controls/FileManagerDialog.razor rename to Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenFileManagerDialog.razor index 48fdbc37..d0948af9 100644 --- a/Oqtane.Client/Modules/Controls/FileManagerDialog.razor +++ b/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenFileManagerDialog.razor @@ -1,19 +1,23 @@ @namespace Oqtane.Modules.Controls -@using System.IO @using Radzen @using Radzen.Blazor @inject DialogService DialogService @inject IStringLocalizer Localizer -
+@if (!string.IsNullOrEmpty(_message)) +{ +
+ +
+} +
-
- -
-
- - +
+ +
@code { private FileManager _fileManager; @@ -22,12 +26,7 @@ [Parameter] public string Filters { get; set; } - private void OnCancelClick() - { - DialogService.Close(null); - } - - private void OnOkClick() + private void InsertImage() { _message = string.Empty; var file = _fileManager.GetFile(); diff --git a/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenInsertLinkDialog.razor b/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenInsertLinkDialog.razor new file mode 100644 index 00000000..06e36775 --- /dev/null +++ b/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenInsertLinkDialog.razor @@ -0,0 +1,148 @@ +@namespace Oqtane.Modules.Controls +@using Radzen +@using Radzen.Blazor +@using System.Text +@inject DialogService DialogService +@inject IStringLocalizer Localizer + +@if (_linkAttributes != null) +{ + @if (!string.IsNullOrWhiteSpace(_message)) + { +
+ +
+ } +
+ +
+ @if (_linkType == 0) + { +
+ +
+ } + else + { +
+ +
+ } + @if (_linkTextEditable) + { +
+ +
+ } +
+ +
+} +
+ + +
+ +@code { + class LinkAttributes + { + public string InnerText { get; set; } + public string InnerHtml { get; set; } + public string Href { get; set; } + public string Target { get; set; } + } + + [Parameter] + public RadzenHtmlEditor Editor { get; set; } + + private IDictionary _linkTypes; + private IDictionary _linkTargets; + private LinkAttributes _linkAttributes; + private bool _blank; + private int _linkType; + private string _message; + private bool _linkTextEditable; + private FileManager _fileManager; + private File _previousFile; + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + _linkAttributes = await Editor.GetSelectionAttributes("a", new[] { "innerText", "href", "target" }); + if (_linkAttributes.Target == "_blank") + { + _blank = true; + } + + _linkTextEditable = string.IsNullOrWhiteSpace(_linkAttributes.InnerHtml) || _linkAttributes.InnerHtml == "
"; + + _linkTypes = new Dictionary + { + { 0, Localizer["WebLink"] }, + { 1, Localizer["FileLink"] } + }; + + _linkTargets = new Dictionary + { + { false, Localizer["OpenInCurrentWindow"] }, + { true, Localizer["OpenInNewWindow"] } + }; + } + + private void SelectFile() + { + var file = _fileManager.GetFile(); + if(file != null) + { + _linkAttributes.Href = file.Url; + if ((string.IsNullOrWhiteSpace(_linkAttributes.InnerText) || _linkAttributes.InnerText == _previousFile?.Name) && _linkTextEditable) + { + _linkAttributes.InnerText = file.Name; + } + } + else + { + _linkAttributes.Href = string.Empty; + if (_linkAttributes.InnerText == _previousFile?.Name) + { + _linkAttributes.InnerText = string.Empty; + } + } + _previousFile = file; + + StateHasChanged(); + } + + private void InsertLink() + { + _message = string.Empty; + if (string.IsNullOrWhiteSpace(_linkAttributes.Href)) + { + _message = _linkType == 1 ? Localizer["Message.Require.File"] : Localizer["Message.Require.WebAddress"]; + } + else if (string.IsNullOrWhiteSpace(_linkAttributes.InnerText) && _linkTextEditable) + { + _message = Localizer["Message.Require.LinkText"]; + } + + if (string.IsNullOrWhiteSpace(_message)) + { + var html = new StringBuilder(); + html.AppendFormat("{0}", string.IsNullOrWhiteSpace(_linkAttributes.InnerText) ? _linkAttributes.InnerHtml : _linkAttributes.InnerText); + + DialogService.Close(html.ToString()); + } + else + { + StateHasChanged(); + } + } +} \ No newline at end of file diff --git a/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenTextEditor.placeholder.cs b/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenTextEditor.placeholder.cs new file mode 100644 index 00000000..b8623e51 --- /dev/null +++ b/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenTextEditor.placeholder.cs @@ -0,0 +1,12 @@ +// This is just a placeholder file +// It is necessary for the documentation to successfully build this project. +// Reason is that docfx will run the .net compiler and find references +// to this class in the project. +// But since the real class is just a .razor file, ATM docfx will fail. +// +// Note added 2025-09-23 by @tvatavuk. +// We hope that as .net and docfx improve, the razor-compiler will work in that scenario +// as well, and this file can be removed. + +namespace Oqtane.Modules.Controls; +public partial class RadzenTextEditor; diff --git a/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenTextEditor.razor b/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenTextEditor.razor index 3a16bc45..fc4e4f9f 100644 --- a/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenTextEditor.razor +++ b/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenTextEditor.razor @@ -51,8 +51,8 @@ public override List Resources { get; set; } = new List() { - new Resource { ResourceType = ResourceType.Script, Url = "_content/Radzen.Blazor/Radzen.Blazor.js", Location = ResourceLocation.Body }, - new Resource { ResourceType = ResourceType.Script, Url = "js/texteditors/radzen/radzen-interop.js", Location = ResourceLocation.Body } + new Script("_content/Radzen.Blazor/Radzen.Blazor.js"), + new Script("js/texteditors/radzen/radzen-interop.js") }; protected override void OnInitialized() @@ -147,13 +147,17 @@ private async Task OnExecute(HtmlEditorExecuteEventArgs args) { - if (args.CommandName == "InsertImage") + switch(args.CommandName) { - await InsertImage(args.Editor); - } - else if (args.CommandName == "Settings") - { - await UpdateSettings(args.Editor); + case "InsertImage": + await InsertImage(args.Editor); + break; + case "InsertLink": + await InsertLink(args.Editor); + break; + case "Settings": + await UpdateSettings(args.Editor); + break; } } @@ -161,10 +165,27 @@ { await editor.SaveSelectionAsync(); - var result = await DialogService.OpenAsync(Localizer["DialogTitle.SelectImage"], new Dictionary + var result = await DialogService.OpenAsync(Localizer["DialogTitle.SelectImage"], new Dictionary { { "Filters", PageState.Site.ImageFiles } - }); + }, new DialogOptions { CssClass = "rz-text-editor-dialog" }); + + await editor.RestoreSelectionAsync(); + + if (result != null) + { + await editor.ExecuteCommandAsync(HtmlEditorCommands.InsertHtml, result); + } + } + + private async Task InsertLink(RadzenHtmlEditor editor) + { + await editor.SaveSelectionAsync(); + + var result = await DialogService.OpenAsync(Localizer["DialogTitle.InsertLink"], new Dictionary + { + { "Editor", editor } + }, new DialogOptions { CssClass = "rz-text-editor-dialog" }); await editor.RestoreSelectionAsync(); @@ -178,7 +199,7 @@ { await editor.SaveSelectionAsync(); - var result = await DialogService.OpenAsync(Localizer["Settings"], null, new DialogOptions { Width = "650px" }); + var result = await DialogService.OpenAsync(Localizer["Settings"], null, new DialogOptions { Width = "650px" }); if (result == true) { NavigationManager.NavigateTo(NavigationManager.Uri); diff --git a/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenTextEditorDefinitions.cs b/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenTextEditorDefinitions.cs index 9fdd778d..2326bae7 100644 --- a/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenTextEditorDefinitions.cs +++ b/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenTextEditorDefinitions.cs @@ -33,7 +33,7 @@ namespace Oqtane.Modules.Controls { "InsertImage", (builder, sequence) => CreateFragment(builder, sequence, "InsertImage", "RadzenHtmlEditorCustomTool", "InsertImage", "image") }, { "Italic", (builder, sequence) => CreateFragment(builder, sequence, "Italic", "RadzenHtmlEditorItalic") }, { "Justify", (builder, sequence) => CreateFragment(builder, sequence, "Justify", "RadzenHtmlEditorJustify") }, - { "Link", (builder, sequence) => CreateFragment(builder, sequence, "Link", "RadzenHtmlEditorLink") }, + { "Link", (builder, sequence) => CreateFragment(builder, sequence, "InsertLink", "RadzenHtmlEditorCustomTool", "InsertLink", "insert_link") }, { "OrderedList", (builder, sequence) => CreateFragment(builder, sequence, "OrderedList", "RadzenHtmlEditorOrderedList") }, { "Outdent", (builder, sequence) => CreateFragment(builder, sequence, "Outdent", "RadzenHtmlEditorOutdent") }, { "Redo", (builder, sequence) => CreateFragment(builder, sequence, "Redo", "RadzenHtmlEditorRedo") }, diff --git a/Oqtane.Client/Modules/Controls/SettingsDialog.razor b/Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenTextEditorSettingsDialog.razor similarity index 100% rename from Oqtane.Client/Modules/Controls/SettingsDialog.razor rename to Oqtane.Client/Modules/Controls/TextEditors/Radzen/RadzenTextEditorSettingsDialog.razor diff --git a/Oqtane.Client/Modules/Enums/MessageStyle.cs b/Oqtane.Client/Modules/Enums/MessageStyle.cs new file mode 100644 index 00000000..dfabe13b --- /dev/null +++ b/Oqtane.Client/Modules/Enums/MessageStyle.cs @@ -0,0 +1,8 @@ +namespace Oqtane.Modules +{ + public enum MessageStyle + { + Alert, + Toast + } +} diff --git a/Oqtane.Client/Modules/MessageType.cs b/Oqtane.Client/Modules/Enums/MessageType.cs similarity index 100% rename from Oqtane.Client/Modules/MessageType.cs rename to Oqtane.Client/Modules/Enums/MessageType.cs diff --git a/Oqtane.Client/Modules/HtmlText/ModuleInfo.cs b/Oqtane.Client/Modules/HtmlText/ModuleInfo.cs index 59a473c4..3cba0000 100644 --- a/Oqtane.Client/Modules/HtmlText/ModuleInfo.cs +++ b/Oqtane.Client/Modules/HtmlText/ModuleInfo.cs @@ -18,7 +18,7 @@ namespace Oqtane.Modules.HtmlText SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client", Resources = new List() { - new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" } + new Stylesheet("~/Module.css") } }; } diff --git a/Oqtane.Client/Modules/ModuleBase.cs b/Oqtane.Client/Modules/ModuleBase.cs index 782a4b0a..aaf16cf6 100644 --- a/Oqtane.Client/Modules/ModuleBase.cs +++ b/Oqtane.Client/Modules/ModuleBase.cs @@ -379,7 +379,17 @@ namespace Oqtane.Modules public void AddModuleMessage(string message, MessageType type, string position) { - RenderModeBoundary.AddModuleMessage(message, type, position); + AddModuleMessage(message, type, position, MessageStyle.Alert); + } + + public void AddModuleMessage(string message, MessageType type, MessageStyle style) + { + AddModuleMessage(message, type, "top", style); + } + + public void AddModuleMessage(string message, MessageType type, string position, MessageStyle style) + { + RenderModeBoundary.AddModuleMessage(message, type, position, style); } public void ClearModuleMessage() diff --git a/Oqtane.Client/Oqtane.Client.csproj b/Oqtane.Client/Oqtane.Client.csproj index 5ff0e3e1..366741b0 100644 --- a/Oqtane.Client/Oqtane.Client.csproj +++ b/Oqtane.Client/Oqtane.Client.csproj @@ -1,32 +1,18 @@ - net9.0 Exe - Debug;Release - 6.2.0 - Oqtane - Shaun Walker - .NET Foundation - CMS and Application Framework for Blazor and .NET MAUI - .NET Foundation - https://www.oqtane.org - https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.0 - https://github.com/oqtane/oqtane.framework - Git Oqtane - true true Default - - - - - + + + + + diff --git a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Edit.resx index 4421b90a..eb32861b 100644 --- a/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/ModuleDefinitions/Edit.resx @@ -183,8 +183,8 @@ Runtimes: - - Definition + + Module Information diff --git a/Oqtane.Client/Resources/Modules/Admin/Profiles/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Profiles/Edit.resx index d67a4f7a..4810d519 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Profiles/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Profiles/Edit.resx @@ -157,7 +157,7 @@ The default value for this profile item - A comma delimited list of options. Options can contain a key and value if they are seperated by a colon (ie. key:value). You can also dynamically load your options from custom Settings (ie. 'EntityName:Countries'). + A comma delimited list of options. Options can contain a key and value if they are seperated by a colon (ie. key:value). You can also dynamically load your options from Settings. Should a user be required to provide a value for this profile item? @@ -201,4 +201,10 @@ Autocomplete: + + Options + + + Settings + diff --git a/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx b/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx index 19bdf93a..a4a292d4 100644 --- a/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/SystemInfo/Index.resx @@ -309,4 +309,19 @@ API Endpoints + + Migration + + + Date + + + Framework Version + + + Database: + + + The name of the current database. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database. + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Themes/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Themes/Edit.resx index 69f9bdd8..27b70ddd 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Themes/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Themes/Edit.resx @@ -180,4 +180,10 @@ View License + + Theme + + + Permissions + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx index e9bc7d13..310e6dc3 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx @@ -555,4 +555,10 @@ If you would like to share cookies across subdomains you will need to specify a root domain with a leading dot (ie. '.example.com') + + Allow Single Logout? + + + Specify if users should be logged out of both the application and provider (the default is false indicating they will only be logged out of the application) + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Controls/RadzenTextEditor.resx b/Oqtane.Client/Resources/Modules/Controls/RadzenTextEditor.resx index 4c84d4df..40b9cf62 100644 --- a/Oqtane.Client/Resources/Modules/Controls/RadzenTextEditor.resx +++ b/Oqtane.Client/Resources/Modules/Controls/RadzenTextEditor.resx @@ -216,4 +216,37 @@ Reset + + Insert Link + + + Insert Link + + + Enter Web Address + + + Enter Link Text + + + Open In New Window + + + Web Link + + + File Link + + + Open In Current Window + + + The Web Address is Empty + + + The Link Text is Empty + + + You Must Select a File + \ No newline at end of file diff --git a/Oqtane.Client/Resources/SharedResources.resx b/Oqtane.Client/Resources/SharedResources.resx index a0078ea5..b4b3ccfa 100644 --- a/Oqtane.Client/Resources/SharedResources.resx +++ b/Oqtane.Client/Resources/SharedResources.resx @@ -477,4 +477,7 @@ Path + + Installed + \ No newline at end of file diff --git a/Oqtane.Client/Services/FileService.cs b/Oqtane.Client/Services/FileService.cs index 2b45455a..75256fc8 100644 --- a/Oqtane.Client/Services/FileService.cs +++ b/Oqtane.Client/Services/FileService.cs @@ -101,7 +101,6 @@ namespace Oqtane.Services /// Unzips the contents of a zip file /// /// Reference to the - /// /// Task UnzipFileAsync(int fileId); } diff --git a/Oqtane.Client/Services/MigrationHistoryService.cs b/Oqtane.Client/Services/MigrationHistoryService.cs new file mode 100644 index 00000000..be3ed815 --- /dev/null +++ b/Oqtane.Client/Services/MigrationHistoryService.cs @@ -0,0 +1,34 @@ +using Oqtane.Models; +using System.Net.Http; +using System.Threading.Tasks; +using System.Collections.Generic; +using Oqtane.Documentation; +using Oqtane.Shared; + +namespace Oqtane.Services +{ + /// + /// Service to manage s + /// + /// + Task> GetMigrationHistoryAsync(); + } + + [PrivateApi("Don't show in the documentation, as everything should use the Interface")] + public class MigrationHistoryService : ServiceBase, IMigrationHistoryService + { + public MigrationHistoryService(HttpClient http, SiteState siteState) : base(http, siteState) { } + + private string Apiurl => CreateApiUrl("MigrationHistory"); + + public async Task> GetMigrationHistoryAsync() + { + return await GetJsonAsync>(Apiurl); + } + } +} diff --git a/Oqtane.Client/Services/SettingService.cs b/Oqtane.Client/Services/SettingService.cs index 5bcbf414..c16442af 100644 --- a/Oqtane.Client/Services/SettingService.cs +++ b/Oqtane.Client/Services/SettingService.cs @@ -429,49 +429,20 @@ namespace Oqtane.Services public async Task UpdateSettingsAsync(Dictionary settings, string entityName, int entityId) { - var settingsList = await GetSettingsAsync(entityName, entityId, ""); + var settingsList = new List(); foreach (KeyValuePair kvp in settings) { - string value = kvp.Value; - bool modified = false; - bool isprivate = false; - - // manage settings modified with SetSetting method - if (value.StartsWith("[Private]")) - { - modified = true; - isprivate = true; - value = value.Substring(9); - } - if (value.StartsWith("[Public]")) - { - modified = true; - isprivate = false; - value = value.Substring(8); - } - - Setting setting = settingsList.FirstOrDefault(item => item.SettingName.Equals(kvp.Key, StringComparison.OrdinalIgnoreCase)); - if (setting == null) - { - setting = new Setting(); - setting.EntityName = entityName; - setting.EntityId = entityId; - setting.SettingName = kvp.Key; - setting.SettingValue = value; - setting.IsPrivate = isprivate; - setting = await AddSettingAsync(setting); - } - else - { - if (setting.SettingValue != value || (modified && setting.IsPrivate != isprivate)) - { - setting.SettingValue = value; - setting.IsPrivate = isprivate; - setting = await UpdateSettingAsync(setting); - } - } + var setting = new Setting(); + setting.EntityName = entityName; + setting.EntityId = entityId; + setting.SettingName = kvp.Key; + setting.SettingValue = kvp.Value; + setting.IsPrivate = true; + settingsList.Add(setting); } + + await PutJsonAsync>($"{Apiurl}/{entityName}/{entityId}", settingsList); } public async Task AddOrUpdateSettingAsync(string entityName, int entityId, string settingName, string settingValue, bool isPrivate) diff --git a/Oqtane.Client/Services/ThemeService.cs b/Oqtane.Client/Services/ThemeService.cs index 4fd1d9eb..942ee7e9 100644 --- a/Oqtane.Client/Services/ThemeService.cs +++ b/Oqtane.Client/Services/ThemeService.cs @@ -17,8 +17,9 @@ namespace Oqtane.Services /// /// Returns a list of available themes /// + /// /// - Task> GetThemesAsync(); + Task> GetThemesAsync(int siteId); /// /// Returns a specific theme @@ -69,9 +70,10 @@ namespace Oqtane.Services /// /// Deletes a theme /// - /// + /// + /// /// - Task DeleteThemeAsync(string themeName); + Task DeleteThemeAsync(int themeId, int siteId); /// /// Creates a new theme @@ -103,9 +105,9 @@ namespace Oqtane.Services private string ApiUrl => CreateApiUrl("Theme"); - public async Task> GetThemesAsync() + public async Task> GetThemesAsync(int siteId) { - List themes = await GetJsonAsync>(ApiUrl); + List themes = await GetJsonAsync>($"{ApiUrl}?siteid={siteId}"); return themes.OrderBy(item => item.Name).ToList(); } public async Task GetThemeAsync(int themeId, int siteId) @@ -139,9 +141,9 @@ namespace Oqtane.Services await PutJsonAsync($"{ApiUrl}/{theme.ThemeId}", theme); } - public async Task DeleteThemeAsync(string themeName) + public async Task DeleteThemeAsync(int themeId, int siteId) { - await DeleteAsync($"{ApiUrl}/{themeName}"); + await DeleteAsync($"{ApiUrl}/{themeId}?siteid={siteId}"); } public async Task CreateThemeAsync(Theme theme) diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor index f38a66c3..762e457a 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanel.razor @@ -15,13 +15,13 @@ @if (PageState.EditMode) { - } else { - } diff --git a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor index 10ce97ed..b702ad8d 100644 --- a/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor +++ b/Oqtane.Client/Themes/Controls/Theme/ControlPanelInteractive.razor @@ -16,7 +16,7 @@ @inject IStringLocalizer Localizer @inject IStringLocalizer SharedLocalizer - diff --git a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor index eaf3f48c..03cdcbaa 100644 --- a/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor +++ b/Oqtane.Client/Themes/Controls/Theme/LanguageSwitcher.razor @@ -8,7 +8,7 @@ @if (_supportedCultures?.Count() > 1) { -
+
diff --git a/Oqtane.Client/UI/RenderModeBoundary.razor b/Oqtane.Client/UI/RenderModeBoundary.razor index 4e2f38f1..9289e5a0 100644 --- a/Oqtane.Client/UI/RenderModeBoundary.razor +++ b/Oqtane.Client/UI/RenderModeBoundary.razor @@ -12,7 +12,7 @@ { @if (!string.IsNullOrEmpty(_messageContent) && _messagePosition == "top") { - + } @DynamicComponent @if (_progressIndicator) @@ -21,7 +21,7 @@ } @if (!string.IsNullOrEmpty(_messageContent) && _messagePosition == "bottom") { - + } } } @@ -45,6 +45,7 @@ private string _messageContent; private MessageType _messageType; private string _messagePosition; + private MessageStyle _messageStyle; private bool _progressIndicator = false; private string _error; @@ -87,6 +88,7 @@ _messageContent = string.Format(Localizer["Error.Module.InvalidName"], Utilities.GetTypeNameLastSegment(ModuleState.ModuleType, 0)); _messageType = MessageType.Error; _messagePosition = "top"; + _messageStyle = MessageStyle.Alert; } } else @@ -94,6 +96,7 @@ _messageContent = string.Format(Localizer["Error.Module.InvalidType"], ModuleState.ModuleDefinitionName); _messageType = MessageType.Error; _messagePosition = "top"; + _messageStyle = MessageStyle.Alert; } } } @@ -105,13 +108,22 @@ public void AddModuleMessage(string message, MessageType type, string position) { - if (message != _messageContent - || type != _messageType - || position != _messagePosition) + AddModuleMessage(message, type, position, MessageStyle.Alert); + } + + public void AddModuleMessage(string message, MessageType type, MessageStyle style) + { + AddModuleMessage(message, type, "top", style); + } + + public void AddModuleMessage(string message, MessageType type, string position, MessageStyle style) + { + if (message != _messageContent || type != _messageType || position != _messagePosition || style != _messageStyle) { _messageContent = message; _messageType = type; _messagePosition = position; + _messageStyle = style; _progressIndicator = false; StateHasChanged(); diff --git a/Oqtane.Maui/Oqtane.Maui.csproj b/Oqtane.Maui/Oqtane.Maui.csproj index 144d85d3..9154ba05 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -6,17 +6,6 @@ Exe - 6.2.0 - Oqtane - Shaun Walker - .NET Foundation - Modular Application Framework for Blazor and MAUI - .NET Foundation - https://www.oqtane.org - https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.0 - https://github.com/oqtane/oqtane.framework - Git Oqtane.Maui true true @@ -30,7 +19,7 @@ com.oqtane.maui - 6.2.0 + 6.2.1 1 @@ -67,14 +56,14 @@ - - - - - - - - + + + + + + + + diff --git a/Oqtane.Maui/wwwroot/css/app.css b/Oqtane.Maui/wwwroot/css/app.css index 46e749f7..13c7d539 100644 --- a/Oqtane.Maui/wwwroot/css/app.css +++ b/Oqtane.Maui/wwwroot/css/app.css @@ -286,3 +286,34 @@ app { top: 0; right: 5px; } + +.app-modulemessage-toast { + position: fixed; + width: 350px; + z-index: 1000; + animation: slide-in 0.5s ease-out, slide-out 0.5s ease-in 5s forwards; +} + +@keyframes slide-in { + from { + transform: translateX(100%); + opacity: 0; + } + + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slide-out { + from { + transform: translateX(0); + opacity: 1; + } + + to { + transform: translateX(100%); + opacity: 0; + } +} diff --git a/Oqtane.Application/Server/wwwroot/css/texteditors/quilljs/quill.bubble.css b/Oqtane.Maui/wwwroot/css/texteditors/quilljs/quill.bubble.css similarity index 100% rename from Oqtane.Application/Server/wwwroot/css/texteditors/quilljs/quill.bubble.css rename to Oqtane.Maui/wwwroot/css/texteditors/quilljs/quill.bubble.css diff --git a/Oqtane.Application/Server/wwwroot/css/texteditors/quilljs/quill.snow.css b/Oqtane.Maui/wwwroot/css/texteditors/quilljs/quill.snow.css similarity index 100% rename from Oqtane.Application/Server/wwwroot/css/texteditors/quilljs/quill.snow.css rename to Oqtane.Maui/wwwroot/css/texteditors/quilljs/quill.snow.css diff --git a/Oqtane.Maui/wwwroot/css/texteditors/radzen/radzentexteditor.css b/Oqtane.Maui/wwwroot/css/texteditors/radzen/radzentexteditor.css new file mode 100644 index 00000000..dbdd62f8 --- /dev/null +++ b/Oqtane.Maui/wwwroot/css/texteditors/radzen/radzentexteditor.css @@ -0,0 +1,42 @@ +.rz-text-editor { + outline: none !important; +} + +.rz-html-editor-dropdown-items, +.rz-popup, +.rz-editor-dialog-wrapper { + z-index: 9999 !important; +} + +.rz-html-editor-dropdown-items .rz-html-editor-dropdown-item, +.rz-html-editor-dropdown-items .rz-html-editor-dropdown-item > * { + color: var(--rz-editor-button-color); +} +.rz-text-editor .rz-html-editor-dropdown .rz-html-editor-dropdown-value, +.rz-text-editor .rz-html-editor-dropdown .rz-html-editor-dropdown-trigger, +.rz-text-editor .rz-html-editor-colorpicker .rz-html-editor-color { + color: var(--rz-editor-button-color); +} +.rz-text-editor .rz-colorpicker.rz-state-disabled { + border: none !important; +} +.rz-text-editor-dialog .rz-html-editor-dialog-item select{ + border: var(--rz-input-border); + border-block-end: var(--rz-input-border-block-end); + border-radius: var(--rz-input-border-radius); + box-shadow: var(--rz-input-shadow); + background-color: var(--rz-input-background-color); + padding-block: var(--rz-input-padding-block); + padding-inline: var(--rz-input-padding-inline); + font-size: var(--rz-input-font-size); + color: var(--rz-input-value-color); +} +.rz-text-editor-dialog .rz-html-editor-dialog-item select:hover, .rz-text-edit-dialog .rz-html-editor-dialog-item select:active { + box-shadow: var(--rz-input-hover-shadow); + background-color: var(--rz-input-hover-background-color); + border: var(--rz-input-hover-border); + border-block-end: var(--rz-input-hover-border-block-end); +} +.rz-text-editor-dialog .alert form{ + display: none; +} \ No newline at end of file diff --git a/Oqtane.Application/Server/wwwroot/images/disabled.png b/Oqtane.Maui/wwwroot/images/disabled.png similarity index 100% rename from Oqtane.Application/Server/wwwroot/images/disabled.png rename to Oqtane.Maui/wwwroot/images/disabled.png diff --git a/Oqtane.Application/Server/wwwroot/js/texteditors/quilljs/quill-blot-formatter.min.js b/Oqtane.Maui/wwwroot/js/texteditors/quilljs/quill-blot-formatter.min.js similarity index 100% rename from Oqtane.Application/Server/wwwroot/js/texteditors/quilljs/quill-blot-formatter.min.js rename to Oqtane.Maui/wwwroot/js/texteditors/quilljs/quill-blot-formatter.min.js diff --git a/Oqtane.Application/Server/wwwroot/js/texteditors/quilljs/quill-interop.js b/Oqtane.Maui/wwwroot/js/texteditors/quilljs/quill-interop.js similarity index 100% rename from Oqtane.Application/Server/wwwroot/js/texteditors/quilljs/quill-interop.js rename to Oqtane.Maui/wwwroot/js/texteditors/quilljs/quill-interop.js diff --git a/Oqtane.Application/Server/wwwroot/js/texteditors/quilljs/quill.min.js b/Oqtane.Maui/wwwroot/js/texteditors/quilljs/quill.min.js similarity index 100% rename from Oqtane.Application/Server/wwwroot/js/texteditors/quilljs/quill.min.js rename to Oqtane.Maui/wwwroot/js/texteditors/quilljs/quill.min.js diff --git a/Oqtane.Application/Server/wwwroot/js/texteditors/quilljs/quill.min.js.map b/Oqtane.Maui/wwwroot/js/texteditors/quilljs/quill.min.js.map similarity index 100% rename from Oqtane.Application/Server/wwwroot/js/texteditors/quilljs/quill.min.js.map rename to Oqtane.Maui/wwwroot/js/texteditors/quilljs/quill.min.js.map diff --git a/Oqtane.Application/Server/wwwroot/js/texteditors/radzen/radzen-interop.js b/Oqtane.Maui/wwwroot/js/texteditors/radzen/radzen-interop.js similarity index 100% rename from Oqtane.Application/Server/wwwroot/js/texteditors/radzen/radzen-interop.js rename to Oqtane.Maui/wwwroot/js/texteditors/radzen/radzen-interop.js diff --git a/Oqtane.Maui/wwwroot/oqtane-glow.png b/Oqtane.Maui/wwwroot/oqtane-glow.png index d62a37dc..03942d55 100644 Binary files a/Oqtane.Maui/wwwroot/oqtane-glow.png and b/Oqtane.Maui/wwwroot/oqtane-glow.png differ diff --git a/Oqtane.Package/Oqtane.Application.Template.cmd b/Oqtane.Package/Oqtane.Application.Template.cmd deleted file mode 100644 index 2352d0e9..00000000 --- a/Oqtane.Package/Oqtane.Application.Template.cmd +++ /dev/null @@ -1,3 +0,0 @@ -nuget.exe pack ..\Oqtane.Application\Oqtane.Application.Template.nuspec -NoDefaultExcludes -pause - diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index 9b47c86a..e719802b 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -2,7 +2,7 @@ Oqtane.Client - 6.2.0 + 6.2.1 Shaun Walker .NET Foundation Oqtane Framework @@ -12,18 +12,18 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1 readme.md icon.png oqtane - - - - - - + + + + + + diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec index c8d8f1cb..c4d8b054 100644 --- a/Oqtane.Package/Oqtane.Framework.nuspec +++ b/Oqtane.Package/Oqtane.Framework.nuspec @@ -2,7 +2,7 @@ Oqtane.Framework - 6.2.0 + 6.2.1 Shaun Walker .NET Foundation Oqtane Framework @@ -11,8 +11,8 @@ .NET Foundation false MIT - https://github.com/oqtane/oqtane.framework/releases/download/v6.2.0/Oqtane.Framework.6.2.0.Upgrade.zip - https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.0 + https://github.com/oqtane/oqtane.framework/releases/download/v6.2.1/Oqtane.Framework.6.2.1.Upgrade.zip + https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1 readme.md icon.png oqtane framework diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index 7881d079..7a436eae 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -2,7 +2,7 @@ Oqtane.Server - 6.2.0 + 6.2.1 Shaun Walker .NET Foundation Oqtane Framework @@ -12,33 +12,33 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1 readme.md icon.png oqtane - - - - + + + + - - - + + + - - - + + + - + - - + + - + @@ -50,6 +50,12 @@ + + + + + + diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index ebef46ad..cb148f19 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -2,7 +2,7 @@ Oqtane.Shared - 6.2.0 + 6.2.1 Shaun Walker .NET Foundation Oqtane Framework @@ -12,18 +12,18 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1 readme.md icon.png oqtane - - - + + + - + diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec index d5ed33fa..0acd762e 100644 --- a/Oqtane.Package/Oqtane.Updater.nuspec +++ b/Oqtane.Package/Oqtane.Updater.nuspec @@ -2,7 +2,7 @@ Oqtane.Updater - 6.2.0 + 6.2.1 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.0 + https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1 readme.md icon.png oqtane diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1 index 67aab1f3..70f1001b 100644 --- a/Oqtane.Package/install.ps1 +++ b/Oqtane.Package/install.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.2.0.Install.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.2.1.Install.zip" -Force diff --git a/Oqtane.Package/release.cmd b/Oqtane.Package/release.cmd index 2c725f40..f32f8178 100644 --- a/Oqtane.Package/release.cmd +++ b/Oqtane.Package/release.cmd @@ -1,59 +1,31 @@ -del "*.nupkg" -del "*.zip" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish" -dotnet clean -c Release ..\Oqtane.sln dotnet build -c Release ..\Oqtane.sln nuget.exe pack Oqtane.Client.nuspec nuget.exe pack Oqtane.Server.nuspec nuget.exe pack Oqtane.Shared.nuspec nuget.exe pack Oqtane.Framework.nuspec dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\Content" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-arm" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-arm64" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-x64" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-x86" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\ios-arm" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\ios-arm64" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-arm64" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-x64" -rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-x86" -setlocal ENABLEDELAYEDEXPANSION -set retain=Radzen.Blazor -for /D %%i in ("..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\_content\*") do ( -set /A found=0 -for %%j in (%retain%) do ( -if "%%~nxi" == "%%j" set /A found=1 -) -if not !found! == 1 rmdir /Q/S "%%i" -) -set retain=Oqtane.Modules.Admin.Login,Oqtane.Modules.HtmlText -for /D %%i in ("..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Modules\*") do ( -set /A found=0 -for %%j in (%retain%) do ( -if "%%~nxi" == "%%j" set /A found=1 -) -if not !found! == 1 rmdir /Q/S "%%i" -) -set retain=Oqtane.Themes.BlazorTheme,Oqtane.Themes.OqtaneTheme -for /D %%i in ("..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Themes\*") do ( -set /A found=0 -for %%j in (%retain%) do ( -if "%%~nxi" == "%%j" set /A found=1 -) -if not !found! == 1 rmdir /Q/S "%%i" -) -del "..\Oqtane.Server\bin\Release\net9.0\publish\Oqtane.Server.staticwebassets.endpoints.json" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\Content" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-arm" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-arm64" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-x64" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-x86" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\ios-arm" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\ios-arm64" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-arm64" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-x64" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-x86" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Modules\Templates" +rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Themes\Templates" del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json" ren "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.release.json" "appsettings.json" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1" del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json" del "..\Oqtane.Server\bin\Release\net9.0\publish\web.config" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\upgrade.ps1" -dotnet clean -c Release ..\Oqtane.Updater.sln dotnet build -c Release ..\Oqtane.Updater.sln dotnet publish ..\Oqtane.Updater\Oqtane.Updater.csproj /p:Configuration=Release nuget.exe pack Oqtane.Updater.nuspec +nuget.exe pack ..\Oqtane.Application\Oqtane.Application.Template.nuspec -NoDefaultExcludes pause diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1 index af2b5571..cc87a261 100644 --- a/Oqtane.Package/upgrade.ps1 +++ b/Oqtane.Package/upgrade.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.2.0.Upgrade.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.2.1.Upgrade.zip" -Force diff --git a/Oqtane.Server/Controllers/MigrationHistoryController.cs b/Oqtane.Server/Controllers/MigrationHistoryController.cs new file mode 100644 index 00000000..b2ba5f5c --- /dev/null +++ b/Oqtane.Server/Controllers/MigrationHistoryController.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Oqtane.Models; +using System.Collections.Generic; +using Oqtane.Shared; +using Oqtane.Repository; + +namespace Oqtane.Controllers +{ + [Route(ControllerRoutes.ApiRoute)] + public class MigrationHistoryController : Controller + { + private readonly IMigrationHistoryRepository _history; + + public MigrationHistoryController(IMigrationHistoryRepository history) + { + _history = history; + } + + // GET: api/ + [HttpGet] + [Authorize(Roles = RoleNames.Host)] + public IEnumerable Get() + { + return _history.GetMigrationHistory(); + } + } +} diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index 6446af85..486af9fc 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -252,7 +252,7 @@ namespace Oqtane.Controllers } else { - _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized ModuleDefinition Delete Attempt {ModuleDefinitionId}", id); + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized ModuleDefinition Delete Attempt {ModuleDefinitionId} {SiteId}", id, siteid); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } } diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index ee777360..041c70cf 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -17,6 +17,8 @@ using Microsoft.Extensions.Options; using System.IO; using System.Text.RegularExpressions; using Oqtane.Migrations.Tenant; +using Google.Protobuf.WellKnownTypes; +using System; namespace Oqtane.Controllers { @@ -204,7 +206,60 @@ namespace Oqtane.Controllers } else { - _logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Add Or Update Setting For EntityName {EntityName} EntityId {EntityId} SettingName {SettingName}", entityName, entityId, settingName); + _logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Add Or Update Setting For {EntityName}:{EntityId}", entityName, entityId); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } + } + + // PUT api//site/1 + [HttpPut("{entityName}/{entityId}")] + public void Put(string entityName, int entityId, [FromBody] List settings) + { + if (ModelState.IsValid && IsAuthorized(entityName,entityId, PermissionNames.Edit)) + { + var existingSettings = _settings.GetSettings(entityName, entityId).ToList(); + foreach (Setting setting in settings) + { + bool modified = false; + + // manage settings modified with SetSetting method + if (setting.SettingValue.StartsWith("[Private]")) + { + modified = true; + setting.IsPrivate = true; + setting.SettingValue = setting.SettingValue.Substring(9); + } + if (setting.SettingValue.StartsWith("[Public]")) + { + modified = true; + setting.IsPrivate = false; + setting.SettingValue = setting.SettingValue.Substring(8); + } + + Setting existingSetting = existingSettings.FirstOrDefault(item => item.SettingName.Equals(setting.SettingName, StringComparison.OrdinalIgnoreCase)); + if (existingSetting == null) + { + _settings.AddSetting(setting); + AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Create); + _logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Created {Setting}", setting); + + } + else + { + if (existingSetting.SettingValue != setting.SettingValue || (modified && existingSetting.IsPrivate != setting.IsPrivate)) + { + existingSetting.SettingValue = setting.SettingValue; + existingSetting.IsPrivate = setting.IsPrivate; + _settings.UpdateSetting(existingSetting); + AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Update); + _logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", setting); + } + } + } + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Update Settings For {EntityName}:{EntityId}", entityName, entityId); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } } @@ -253,7 +308,7 @@ namespace Oqtane.Controllers // GET: api//entitynames [HttpGet("entitynames")] - [Authorize(Roles = RoleNames.Host)] + [Authorize(Roles = RoleNames.Admin)] public IEnumerable GetEntityNames() { return _settings.GetEntityNames(); @@ -261,7 +316,7 @@ namespace Oqtane.Controllers // GET: api//entityids?entityname=x [HttpGet("entityids")] - [Authorize(Roles = RoleNames.Host)] + [Authorize(Roles = RoleNames.Admin)] public IEnumerable GetEntityIds(string entityName) { return _settings.GetEntityIds(entityName); diff --git a/Oqtane.Server/Controllers/ThemeController.cs b/Oqtane.Server/Controllers/ThemeController.cs index cf1c24fc..eb250069 100644 --- a/Oqtane.Server/Controllers/ThemeController.cs +++ b/Oqtane.Server/Controllers/ThemeController.cs @@ -14,6 +14,9 @@ using System.Text.Json; using System.Net; using System; using Microsoft.Extensions.DependencyInjection; +using System.Reflection.Metadata; +using Oqtane.Security; +using System.Security.Policy; // ReSharper disable StringIndexOfIsCultureSpecific.1 @@ -26,30 +29,50 @@ namespace Oqtane.Controllers private readonly IInstallationManager _installationManager; private readonly IWebHostEnvironment _environment; private readonly ITenantManager _tenantManager; + private readonly IUserPermissions _userPermissions; private readonly ISyncManager _syncManager; private readonly ILogManager _logger; private readonly Alias _alias; private readonly IServiceProvider _serviceProvider; - public ThemeController(IThemeRepository themes, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantManager tenantManager, ISyncManager syncManager, ILogManager logger, IServiceProvider serviceProvider) + public ThemeController(IThemeRepository themes, IInstallationManager installationManager, IWebHostEnvironment environment, ITenantManager tenantManager, IUserPermissions userPermissions, ISyncManager syncManager, ILogManager logger, IServiceProvider serviceProvider) { _themes = themes; _installationManager = installationManager; _environment = environment; _tenantManager = tenantManager; + _userPermissions = userPermissions; _syncManager = syncManager; _logger = logger; _alias = tenantManager.GetAlias(); _serviceProvider = serviceProvider; } - // GET: api/ + // GET: api/?siteid=x [HttpGet] [Authorize(Roles = RoleNames.Registered)] - public IEnumerable Get() + public IEnumerable Get(string siteid) { - return _themes.GetThemes(); - } + int SiteId; + if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId) + { + List themes = new List(); + foreach (Theme theme in _themes.GetThemes(SiteId)) + { + if (_userPermissions.IsAuthorized(User, PermissionNames.Utilize, theme.PermissionList)) + { + themes.Add(theme); + } + } + return themes; + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Get Attempt {SiteId}", siteid); + HttpContext.Response.StatusCode = (int) HttpStatusCode.Forbidden; + return null; + } +} // GET api//5?siteid=x [HttpGet("{id}")] @@ -58,7 +81,24 @@ namespace Oqtane.Controllers int SiteId; if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId) { - return _themes.GetTheme(id, SiteId); + Theme theme = _themes.GetTheme(id, SiteId); + if (theme != null && _userPermissions.IsAuthorized(User, PermissionNames.Utilize, theme.PermissionList)) + { + return theme; + } + else + { + if (theme != null) + { + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Get Attempt {ThemeId} {SiteId}", id, siteid); + HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + } + else + { + HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; + } + return null; + } } else { @@ -86,14 +126,13 @@ namespace Oqtane.Controllers } } - // DELETE api//xxx + // DELETE api//5?siteid=x [HttpDelete("{themename}")] [Authorize(Roles = RoleNames.Host)] - public void Delete(string themename) + public void Delete(int id, int siteid) { - List themes = _themes.GetThemes().ToList(); - Theme theme = themes.Where(item => item.ThemeName == themename).FirstOrDefault(); - if (theme != null && Utilities.GetAssemblyName(theme.ThemeName) != Constants.ClientId) + Theme theme = _themes.GetTheme(id, siteid); + if (theme != null && theme.SiteId == _alias.SiteId && Utilities.GetAssemblyName(theme.ThemeName) != Constants.ClientId) { // remove theme assets if (_installationManager.UninstallPackage(theme.PackageName)) @@ -126,7 +165,7 @@ namespace Oqtane.Controllers } else { - _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Delete Attempt {Themename}", themename); + _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Theme Delete Attempt {ThemeId} {SiteId}", id, siteid); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } } diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index dbafdab7..115abee0 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -228,6 +228,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); // providers services.AddScoped(); @@ -276,6 +277,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // managers services.AddTransient(); diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs index 65b53cc0..7771d8c1 100644 --- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs +++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs @@ -103,7 +103,7 @@ namespace Oqtane.Infrastructure break; } - await client.ConnectAsync(settingRepository.GetSettingValue(settings, "SMTPHost", ""), + await client.ConnectAsync(settingRepository.GetSettingValue(settings, "SMTPHost", ""), int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")), secureSocketOptions); @@ -143,69 +143,74 @@ namespace Oqtane.Infrastructure List notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList(); foreach (Notification notification in notifications) { - // get sender and receiver information from user object if not provided - if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null) + var fromEmail = notification.FromEmail ?? ""; + var fromName = notification.FromDisplayName ?? ""; + var toEmail = notification.ToEmail ?? ""; + var toName = notification.ToDisplayName ?? ""; + + // get sender and receiver information from user information if available + if ((string.IsNullOrEmpty(fromEmail) || string.IsNullOrEmpty(fromName)) && notification.FromUserId != null) { var user = userRepository.GetUser(notification.FromUserId.Value); if (user != null) { - notification.FromEmail = (string.IsNullOrEmpty(notification.FromEmail)) ? user.Email : notification.FromEmail; - notification.FromDisplayName = (string.IsNullOrEmpty(notification.FromDisplayName)) ? user.DisplayName : notification.FromDisplayName; + fromEmail = string.IsNullOrEmpty(fromEmail) ? user.Email ?? "" : fromEmail; + fromName = string.IsNullOrEmpty(fromName) ? user.DisplayName ?? "" : fromName; } } - if ((string.IsNullOrEmpty(notification.ToEmail) || string.IsNullOrEmpty(notification.ToDisplayName)) && notification.ToUserId != null) + if ((string.IsNullOrEmpty(toEmail) || string.IsNullOrEmpty(toName)) && notification.ToUserId != null) { var user = userRepository.GetUser(notification.ToUserId.Value); if (user != null) { - notification.ToEmail = (string.IsNullOrEmpty(notification.ToEmail)) ? user.Email : notification.ToEmail; - notification.ToDisplayName = (string.IsNullOrEmpty(notification.ToDisplayName)) ? user.DisplayName : notification.ToDisplayName; + toEmail = string.IsNullOrEmpty(toEmail) ? user.Email ?? "" : toEmail; + toName = string.IsNullOrEmpty(toName) ? user.DisplayName ?? "" : toName; } } - // validate recipient - if (string.IsNullOrEmpty(notification.ToEmail) || !MailboxAddress.TryParse(notification.ToEmail, out _)) + // create mailbox addresses + MailboxAddress to = null; + MailboxAddress from = null; + var mailboxAddressValidationError = ""; + + // sender + if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") != "True") { - log += $"NotificationId: {notification.NotificationId} - Has Missing Or Invalid Recipient {notification.ToEmail}
"; - notification.IsDeleted = true; - notificationRepository.UpdateNotification(notification); + fromEmail = settingRepository.GetSettingValue(settings, "SMTPSender", ""); + fromName = string.IsNullOrEmpty(fromName) ? site.Name : fromName; + } + if (MailboxAddress.TryParse(fromEmail, out from)) + { + from.Name = fromName; } else { + + mailboxAddressValidationError += $" Invalid Sender: {fromName} <{fromEmail}>"; + } + + // recipient + if (MailboxAddress.TryParse(toEmail, out to)) + { + to.Name = toName; + } + else + { + mailboxAddressValidationError += $" Invalid Recipient: {toName} <{toEmail}>"; + } + + // if mailbox addresses are valid + if (from != null && to != null) + { + // create mail message MimeMessage mailMessage = new MimeMessage(); - - // sender - if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") == "True" && !string.IsNullOrEmpty(notification.FromEmail)) - { - if (!string.IsNullOrEmpty(notification.FromDisplayName)) - { - mailMessage.From.Add(new MailboxAddress(notification.FromDisplayName, notification.FromEmail)); - } - else - { - mailMessage.From.Add(new MailboxAddress("", notification.FromEmail)); - } - } - else - { - mailMessage.From.Add(new MailboxAddress((!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name, - settingRepository.GetSettingValue(settings, "SMTPSender", ""))); - } - - // recipient - if (!string.IsNullOrEmpty(notification.ToDisplayName)) - { - mailMessage.To.Add(new MailboxAddress(notification.ToDisplayName, notification.ToEmail)); - } - else - { - mailMessage.To.Add(new MailboxAddress("", notification.ToEmail)); - } + mailMessage.From.Add(from); + mailMessage.To.Add(to); // subject mailMessage.Subject = notification.Subject; - //body + // body var bodyText = notification.Body; if (!bodyText.Contains('<') || !bodyText.Contains('>')) @@ -230,14 +235,22 @@ namespace Oqtane.Infrastructure } catch (Exception ex) { - // error - log += $"NotificationId: {notification.NotificationId} - {ex.Message}
"; + log += $"Error Sending Notification Id: {notification.NotificationId} - {ex.Message}
"; } } + else + { + // invalid mailbox address + log += $"Notification Id: {notification.NotificationId} Has An {mailboxAddressValidationError} And Has Been Deleted
"; + notification.IsDeleted = true; + notificationRepository.UpdateNotification(notification); + } } - await client.DisconnectAsync(true); + log += "Notifications Delivered: " + sent + "
"; } + + await client.DisconnectAsync(true); } } else diff --git a/Oqtane.Server/Infrastructure/UpgradeManager.cs b/Oqtane.Server/Infrastructure/UpgradeManager.cs index df18a555..532bc3d0 100644 --- a/Oqtane.Server/Infrastructure/UpgradeManager.cs +++ b/Oqtane.Server/Infrastructure/UpgradeManager.cs @@ -7,11 +7,15 @@ using Oqtane.Infrastructure.SiteTemplates; using Oqtane.Models; using Oqtane.Repository; using Oqtane.Shared; +using Oqtane.UI; +using Org.BouncyCastle.Pqc.Crypto.Lms; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Reflection.Metadata; +using System.Runtime.Serialization; namespace Oqtane.Infrastructure { @@ -90,6 +94,9 @@ namespace Oqtane.Infrastructure case "6.2.0": Upgrade_6_2_0(tenant, scope); break; + case "6.2.1": + Upgrade_6_2_1(tenant, scope); + break; } } } @@ -598,6 +605,25 @@ namespace Oqtane.Infrastructure AddPagesToSites(scope, tenant, pageTemplates); } + private void Upgrade_6_2_1(Tenant tenant, IServiceScope scope) + { + // remove text editor files moved to new location + string[] files = { + "js/quill.min.js.map", + "js/quill1.3.7.min.js", + "js/quill.min.js", + "js/quill-blot-formatter.min.js", + "js/quill-interop.js", + "css/quill/quill1.3.7.bubble.css", + "css/quill/quill.bubble.css", + "css/quill/quill1.3.7.snow.css", + "css/quill/quill.snow.css", + "oqtane-black.png" + }; + + RemoveFiles(tenant, files, "6.2.1"); + } + private void AddPagesToSites(IServiceScope scope, Tenant tenant, List pageTemplates) { var tenants = scope.ServiceProvider.GetRequiredService(); @@ -618,8 +644,8 @@ namespace Oqtane.Infrastructure { try { - var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - var filepath = Path.Combine(binFolder, assembly); + var bin = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + var filepath = Path.Combine(bin, assembly); if (System.IO.File.Exists(filepath)) System.IO.File.Delete(filepath); } catch (Exception ex) @@ -630,5 +656,26 @@ namespace Oqtane.Infrastructure } } } + + private void RemoveFiles(Tenant tenant, string[] files, string version) + { + if (tenant.Name == TenantNames.Master) + { + foreach (var file in files) + { + try + { + var wwwroot = _environment.WebRootPath; + var filepath = Path.Combine(wwwroot, file); + if (System.IO.File.Exists(filepath)) System.IO.File.Delete(filepath); + } + catch (Exception ex) + { + // error deleting file + _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: {version} Upgrade Error Removing {file} - {ex}")); + } + } + } + } } } diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index 7933c9e4..6dde2259 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -1,25 +1,14 @@ - net9.0 - Debug;Release - 6.2.0 - Oqtane - Shaun Walker - .NET Foundation - CMS and Application Framework for Blazor and .NET MAUI - .NET Foundation - https://www.oqtane.org - https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.0 - https://github.com/oqtane/oqtane.framework - Git Oqtane true - $(DefineConstants);OQTANE;OQTANE3 + $(DefineConstants);OQTANE true none false + false + / @@ -35,20 +24,20 @@ - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - - - + + + @@ -57,14 +46,14 @@ - + - - + + - + diff --git a/Oqtane.Server/Pages/Logout.cshtml.cs b/Oqtane.Server/Pages/Logout.cshtml.cs index 72329c11..3d72d2ec 100644 --- a/Oqtane.Server/Pages/Logout.cshtml.cs +++ b/Oqtane.Server/Pages/Logout.cshtml.cs @@ -1,5 +1,8 @@ +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; @@ -8,6 +11,7 @@ using Oqtane.Extensions; using Oqtane.Infrastructure; using Oqtane.Managers; using Oqtane.Shared; +using Radzen.Blazor.Markdown; namespace Oqtane.Pages { @@ -28,6 +32,9 @@ namespace Oqtane.Pages public async Task OnPostAsync(string returnurl, string everywhere) { + returnurl = (returnurl == null) ? "/" : returnurl; + returnurl = (!returnurl.StartsWith("/")) ? "/" + returnurl : returnurl; + if (HttpContext.User != null) { var alias = HttpContext.GetAlias(); @@ -43,13 +50,25 @@ namespace Oqtane.Pages _logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout For Username {Username}", user.Username); } - await HttpContext.SignOutAsync(Constants.AuthenticationScheme); + var authenticationProperties = new AuthenticationProperties + { + RedirectUri = returnurl + }; + + var authenticationSchemes = new List(); + authenticationSchemes.Add(Constants.AuthenticationScheme); + if (HttpContext.GetSiteSettings().GetValue("ExternalLogin:ProviderType", "") == AuthenticationProviderTypes.OpenIDConnect && + HttpContext.GetSiteSettings().GetValue("ExternalLogin:SingleLogout", "false") == "true") + { + authenticationSchemes.Add(AuthenticationProviderTypes.OpenIDConnect); + } + + return SignOut(authenticationProperties, authenticationSchemes.ToArray()); + } + else + { + return LocalRedirect(Url.Content("~" + returnurl)); } - - returnurl = (returnurl == null) ? "/" : returnurl; - returnurl = (!returnurl.StartsWith("/")) ? "/" + returnurl : returnurl; - - return LocalRedirect(Url.Content("~" + returnurl)); } } } diff --git a/Oqtane.Server/Repository/Context/TenantDBContext.cs b/Oqtane.Server/Repository/Context/TenantDBContext.cs index 599485f7..bb0e1c72 100644 --- a/Oqtane.Server/Repository/Context/TenantDBContext.cs +++ b/Oqtane.Server/Repository/Context/TenantDBContext.cs @@ -33,5 +33,6 @@ namespace Oqtane.Repository public virtual DbSet SearchContentProperty { get; set; } public virtual DbSet SearchContentWord { get; set; } public virtual DbSet SearchWord { get; set; } + public virtual DbSet MigrationHistory { get; set; } } } diff --git a/Oqtane.Server/Repository/MigrationHistoryRepository.cs b/Oqtane.Server/Repository/MigrationHistoryRepository.cs new file mode 100644 index 00000000..5465dc08 --- /dev/null +++ b/Oqtane.Server/Repository/MigrationHistoryRepository.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Oqtane.Models; + +namespace Oqtane.Repository +{ + public interface IMigrationHistoryRepository + { + IEnumerable GetMigrationHistory(); + } + public class MigrationHistoryRepository : IMigrationHistoryRepository + { + private readonly IDbContextFactory _dbContextFactory; + + public MigrationHistoryRepository(IDbContextFactory dbContextFactory) + { + _dbContextFactory = dbContextFactory; + } + + public IEnumerable GetMigrationHistory() + { + using var db = _dbContextFactory.CreateDbContext(); + return db.MigrationHistory.ToList(); + } + } +} diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index 10226d28..d8bb7f4e 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -386,6 +386,7 @@ namespace Oqtane.Repository moduledefinition.Categories = "Common"; } + // default permissions if (moduledefinition.Categories == "Admin") { var shortName = moduledefinition.ModuleDefinitionName.Replace("Oqtane.Modules.Admin.", "").Replace(", Oqtane.Client", ""); @@ -455,18 +456,21 @@ namespace Oqtane.Repository private List ClonePermissions(int siteId, List permissionList) { var permissions = new List(); - foreach (var p in permissionList) + if (permissionList != null) { - var permission = new Permission(); - permission.SiteId = siteId; - permission.EntityName = p.EntityName; - permission.EntityId = p.EntityId; - permission.PermissionName = p.PermissionName; - permission.RoleId = null; - permission.RoleName = p.RoleName; - permission.UserId = p.UserId; - permission.IsAuthorized = p.IsAuthorized; - permissions.Add(permission); + foreach (var p in permissionList) + { + var permission = new Permission(); + permission.SiteId = siteId; + permission.EntityName = p.EntityName; + permission.EntityId = p.EntityId; + permission.PermissionName = p.PermissionName; + permission.RoleId = null; + permission.RoleName = p.RoleName; + permission.UserId = p.UserId; + permission.IsAuthorized = p.IsAuthorized; + permissions.Add(permission); + } } return permissions; } diff --git a/Oqtane.Server/Repository/NotificationRepository.cs b/Oqtane.Server/Repository/NotificationRepository.cs index 1c20faee..92e8fc74 100644 --- a/Oqtane.Server/Repository/NotificationRepository.cs +++ b/Oqtane.Server/Repository/NotificationRepository.cs @@ -144,14 +144,14 @@ namespace Oqtane.Repository // delete notifications in batches of 100 records var count = 0; var purgedate = DateTime.UtcNow.AddDays(-age); - var notifications = db.Notification.Where(item => item.SiteId == siteId && item.FromUserId == null && item.IsDelivered && item.DeliveredOn < purgedate) + var notifications = db.Notification.Where(item => item.SiteId == siteId && item.FromUserId == null && (item.IsDeleted || item.IsDelivered && item.DeliveredOn < purgedate)) .OrderBy(item => item.DeliveredOn).Take(100).ToList(); while (notifications.Count > 0) { count += notifications.Count; db.Notification.RemoveRange(notifications); db.SaveChanges(); - notifications = db.Notification.Where(item => item.SiteId == siteId && item.FromUserId == null && item.IsDelivered && item.DeliveredOn < purgedate) + notifications = db.Notification.Where(item => item.SiteId == siteId && item.FromUserId == null && (item.IsDeleted || item.IsDelivered && item.DeliveredOn < purgedate)) .OrderBy(item => item.DeliveredOn).Take(100).ToList(); } return count; diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs index f791aeac..bd84354e 100644 --- a/Oqtane.Server/Repository/SiteRepository.cs +++ b/Oqtane.Server/Repository/SiteRepository.cs @@ -135,7 +135,7 @@ namespace Oqtane.Repository if (site != null) { // initialize theme Assemblies - site.Themes = _themeRepository.GetThemes().ToList(); + site.Themes = _themeRepository.GetThemes(site.SiteId).ToList(); // initialize module Assemblies var moduleDefinitions = _moduleDefinitionRepository.GetModuleDefinitions(alias.SiteId); diff --git a/Oqtane.Server/Repository/ThemeRepository.cs b/Oqtane.Server/Repository/ThemeRepository.cs index 56331a37..ba901dd9 100644 --- a/Oqtane.Server/Repository/ThemeRepository.cs +++ b/Oqtane.Server/Repository/ThemeRepository.cs @@ -15,7 +15,7 @@ namespace Oqtane.Repository { public interface IThemeRepository { - IEnumerable GetThemes(); + IEnumerable GetThemes(int siteId); Theme GetTheme(int themeId, int siteId); void UpdateTheme(Theme theme); void DeleteTheme(int themeId); @@ -26,24 +26,25 @@ namespace Oqtane.Repository { private MasterDBContext _db; private readonly IMemoryCache _cache; + private readonly IPermissionRepository _permissions; private readonly ITenantManager _tenants; private readonly ISettingRepository _settings; private readonly IServerStateManager _serverState; private readonly string settingprefix = "SiteEnabled:"; - public ThemeRepository(MasterDBContext context, IMemoryCache cache, ITenantManager tenants, ISettingRepository settings, IServerStateManager serverState) + public ThemeRepository(MasterDBContext context, IMemoryCache cache, IPermissionRepository permissions, ITenantManager tenants, ISettingRepository settings, IServerStateManager serverState) { _db = context; _cache = cache; + _permissions = permissions; _tenants = tenants; _settings = settings; _serverState = serverState; } - public IEnumerable GetThemes() + public IEnumerable GetThemes(int siteId) { - // for consistency siteid should be passed in as parameter, but this would require breaking change - return LoadThemes(_tenants.GetAlias().SiteId); + return LoadThemes(siteId); } public Theme GetTheme(int themeId, int siteId) @@ -56,6 +57,7 @@ namespace Oqtane.Repository { _db.Entry(theme).State = EntityState.Modified; _db.SaveChanges(); + _permissions.UpdatePermissions(theme.SiteId, EntityNames.Theme, theme.ThemeId, theme.PermissionList); var settingname = $"{settingprefix}{_tenants.GetAlias().SiteKey}"; var setting = _settings.GetSetting(EntityNames.Theme, theme.ThemeId, settingname); @@ -96,6 +98,7 @@ namespace Oqtane.Repository Theme.ThemeSettingsType = theme.ThemeSettingsType; Theme.ContainerSettingsType = theme.ContainerSettingsType; Theme.PackageName = theme.PackageName; + Theme.PermissionList = theme.PermissionList; Theme.Fingerprint = Utilities.GenerateSimpleHash(theme.ModifiedOn.ToString("yyyyMMddHHmm")); Themes.Add(Theme); } @@ -176,6 +179,9 @@ namespace Oqtane.Repository var siteKey = _tenants.GetAlias().SiteKey; var assemblies = new List(); + // get all module definition permissions for site + List permissions = _permissions.GetPermissions(siteId, EntityNames.Theme).ToList(); + // get settings for site var settings = _settings.GetSettings(EntityNames.Theme).ToList(); @@ -212,6 +218,26 @@ namespace Oqtane.Repository } } } + + if (permissions.Count == 0) + { + // no module definition permissions exist for this site + theme.PermissionList = ClonePermissions(siteId, theme.PermissionList); + _permissions.UpdatePermissions(siteId, EntityNames.Theme, theme.ThemeId, theme.PermissionList); + } + else + { + if (permissions.Any(item => item.EntityId == theme.ThemeId)) + { + theme.PermissionList = permissions.Where(item => item.EntityId == theme.ThemeId).ToList(); + } + else + { + // permissions for theme do not exist for this site + theme.PermissionList = ClonePermissions(siteId, theme.PermissionList); + _permissions.UpdatePermissions(siteId, EntityNames.Theme, theme.ThemeId, theme.PermissionList); + } + } } // cache site assemblies @@ -220,6 +246,20 @@ namespace Oqtane.Repository { if (!serverState.Assemblies.Contains(assembly)) serverState.Assemblies.Add(assembly); } + + // clean up any orphaned permissions + var ids = new HashSet(Themes.Select(item => item.ThemeId)); + foreach (var permission in permissions.Where(item => !ids.Contains(item.EntityId))) + { + try + { + _permissions.DeletePermission(permission.PermissionId); + } + catch + { + // multi-threading can cause a race condition to occur + } + } } return Themes; @@ -295,6 +335,14 @@ namespace Oqtane.Repository } } } + + // default permissions + theme.PermissionList = new List + { + new Permission(PermissionNames.Utilize, RoleNames.Admin, true), + new Permission(PermissionNames.Utilize, RoleNames.Registered, true) + }; + Debug.WriteLine($"Oqtane Info: Registering Theme {theme.ThemeName}"); themes.Add(theme); index = themes.FindIndex(item => item.ThemeName == qualifiedThemeType); @@ -335,5 +383,27 @@ namespace Oqtane.Repository } return themes; } + + private List ClonePermissions(int siteId, List permissionList) + { + var permissions = new List(); + if (permissionList != null) + { + foreach (var p in permissionList) + { + var permission = new Permission(); + permission.SiteId = siteId; + permission.EntityName = p.EntityName; + permission.EntityId = p.EntityId; + permission.PermissionName = p.PermissionName; + permission.RoleId = null; + permission.RoleName = p.RoleName; + permission.UserId = p.UserId; + permission.IsAuthorized = p.IsAuthorized; + permissions.Add(permission); + } + } + return permissions; + } } } diff --git a/Oqtane.Server/Services/SiteService.cs b/Oqtane.Server/Services/SiteService.cs index f316b766..25a9ec92 100644 --- a/Oqtane.Server/Services/SiteService.cs +++ b/Oqtane.Server/Services/SiteService.cs @@ -144,7 +144,7 @@ namespace Oqtane.Services } // themes - site.Themes = _themes.FilterThemes(_themes.GetThemes().ToList()); + site.Themes = _themes.FilterThemes(_themes.GetThemes(site.SiteId).ToList()); // installation date used for fingerprinting static assets site.Fingerprint = Utilities.GenerateSimpleHash(_configManager.GetSetting("InstallationDate", DateTime.UtcNow.ToString("yyyyMMddHHmm"))); diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj index 46602f40..3d047143 100644 --- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj +++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj @@ -11,6 +11,7 @@ [Owner] [Owner].Module.[Module].Server.Oqtane true + false diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj index 99587a54..de8fd5cc 100644 --- a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj @@ -10,6 +10,7 @@ [Owner] [Owner].Theme.[Theme].Client.Oqtane true + false diff --git a/Oqtane.Application/Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/wwwroot/Theme.css similarity index 58% rename from Oqtane.Application/Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css rename to Oqtane.Server/wwwroot/Themes/Templates/External/Client/wwwroot/Theme.css index c40b7140..f17ea0a3 100644 --- a/Oqtane.Application/Server/wwwroot/Themes/Oqtane.Themes.OqtaneTheme/Theme.css +++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/wwwroot/Theme.css @@ -1,4 +1,4 @@ -/* Oqtane Styles */ +/* Oqtane Styles */ body { padding-top: 7rem; @@ -18,14 +18,12 @@ body { background-color: #ffffff !important; border-width: 0.5px !important; border-bottom-color: #ccc !important; - color: #000 !important; } .table .form-select { background-color: #ffffff !important; border-width: 0.5px !important; border-bottom-color: #ccc !important; - color: #000 !important; } .table .btn-primary { @@ -79,69 +77,19 @@ body { top: -2px; } -.app-search input{ - width: auto; -} - .navbar-toggler { background-color: rgba(255, 255, 255, 0.1); margin: .5rem; } div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu { - color:#ffffff; + color: #000000; } -.footer { - padding-top: 15px; - min-height: 40px; - text-align: center; - color: #ffffff; - z-index: 1000; +.dropdown-menu span { + mix-blend-mode: difference; } -@media (max-width: 991.98px) { - .app-search { - border-radius: 6px; - } - .app-search input{ - display: none !important; - } - .app-search input + button { - position: initial; - padding-top: 7px; - padding-bottom: 7px; - } - - .app-search:active, .app-search:hover { - display: block; - position: fixed; - top: 0; - min-height: 96px; - width: 100%; - left: 0; - z-index: 999; - border-radius: 0; - } - - .app-search:active .app-form-inline, .app-search:hover .app-form-inline { - margin: 10px auto; - position: relative; - display: block; - max-width: 80%; - } - - .app-search:active .app-form-inline input, .app-search:hover .app-form-inline input { - width: 100%; - display: block !important; - } - .app-search:active .app-form-inline input + button, .app-search:hover .app-form-inline input + button { - position: absolute; - color: rgb(42, 159, 214); - padding-top: 6px; - padding-bottom: 6px; - } -} @media (max-width: 767.98px) { .app-menu { @@ -172,7 +120,4 @@ div.app-moduleactions a.dropdown-toggle, div.app-moduleactions div.dropdown-menu position: relative; top: 60px; } - .app-search:active, .app-search:hover{ - min-height: 60px; - } } diff --git a/Oqtane.Server/wwwroot/css/app.css b/Oqtane.Server/wwwroot/css/app.css index 46e749f7..13c7d539 100644 --- a/Oqtane.Server/wwwroot/css/app.css +++ b/Oqtane.Server/wwwroot/css/app.css @@ -286,3 +286,34 @@ app { top: 0; right: 5px; } + +.app-modulemessage-toast { + position: fixed; + width: 350px; + z-index: 1000; + animation: slide-in 0.5s ease-out, slide-out 0.5s ease-in 5s forwards; +} + +@keyframes slide-in { + from { + transform: translateX(100%); + opacity: 0; + } + + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slide-out { + from { + transform: translateX(0); + opacity: 1; + } + + to { + transform: translateX(100%); + opacity: 0; + } +} diff --git a/Oqtane.Server/wwwroot/css/texteditors/radzen/radzentexteditor.css b/Oqtane.Server/wwwroot/css/texteditors/radzen/radzentexteditor.css index e142053b..dbdd62f8 100644 --- a/Oqtane.Server/wwwroot/css/texteditors/radzen/radzentexteditor.css +++ b/Oqtane.Server/wwwroot/css/texteditors/radzen/radzentexteditor.css @@ -19,4 +19,24 @@ } .rz-text-editor .rz-colorpicker.rz-state-disabled { border: none !important; +} +.rz-text-editor-dialog .rz-html-editor-dialog-item select{ + border: var(--rz-input-border); + border-block-end: var(--rz-input-border-block-end); + border-radius: var(--rz-input-border-radius); + box-shadow: var(--rz-input-shadow); + background-color: var(--rz-input-background-color); + padding-block: var(--rz-input-padding-block); + padding-inline: var(--rz-input-padding-inline); + font-size: var(--rz-input-font-size); + color: var(--rz-input-value-color); +} +.rz-text-editor-dialog .rz-html-editor-dialog-item select:hover, .rz-text-edit-dialog .rz-html-editor-dialog-item select:active { + box-shadow: var(--rz-input-hover-shadow); + background-color: var(--rz-input-hover-background-color); + border: var(--rz-input-hover-border); + border-block-end: var(--rz-input-hover-border-block-end); +} +.rz-text-editor-dialog .alert form{ + display: none; } \ No newline at end of file diff --git a/Oqtane.Shared/Models/MigrationHistory.cs b/Oqtane.Shared/Models/MigrationHistory.cs new file mode 100644 index 00000000..e35a8be3 --- /dev/null +++ b/Oqtane.Shared/Models/MigrationHistory.cs @@ -0,0 +1,16 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Oqtane.Models +{ + [Table("__EFMigrationsHistory")] + [Keyless] + public class MigrationHistory + { + public string MigrationId { get; set; } + public string ProductVersion { get; set; } + public DateTime AppliedDate { get; set; } + public string AppliedVersion { get; set; } + } +} diff --git a/Oqtane.Shared/Models/Theme.cs b/Oqtane.Shared/Models/Theme.cs index a67a2d97..61b84595 100644 --- a/Oqtane.Shared/Models/Theme.cs +++ b/Oqtane.Shared/Models/Theme.cs @@ -94,6 +94,9 @@ namespace Oqtane.Models [NotMapped] public List Containers { get; set; } + [NotMapped] + public List PermissionList { get; set; } + [NotMapped] public string Template { get; set; } diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index 30e64740..78b6ba46 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -1,30 +1,16 @@ - net9.0 - Debug;Release - 6.2.0 - Oqtane - Shaun Walker - .NET Foundation - CMS and Application Framework for Blazor and .NET MAUI - .NET Foundation - https://www.oqtane.org - https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.0 - https://github.com/oqtane/oqtane.framework - Git Oqtane - true - - - + + + - + diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 6fc4296e..9fc43717 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -4,8 +4,8 @@ namespace Oqtane.Shared { public class Constants { - public static readonly string Version = "6.2.0"; - public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2,6.1.3,6.1.4,6.1.5,6.2.0"; + public static readonly string Version = "6.2.1"; + public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2,6.1.3,6.1.4,6.1.5,6.2.0,6.2.1"; public const string PackageId = "Oqtane.Framework"; public const string ClientId = "Oqtane.Client"; public const string UpdaterPackageId = "Oqtane.Updater"; diff --git a/Oqtane.Updater/Oqtane.Updater.csproj b/Oqtane.Updater/Oqtane.Updater.csproj index cdc1f556..d409139a 100644 --- a/Oqtane.Updater/Oqtane.Updater.csproj +++ b/Oqtane.Updater/Oqtane.Updater.csproj @@ -1,21 +1,8 @@ - net9.0 Exe - 6.2.0 - Oqtane - Shaun Walker - .NET Foundation - Modular Application Framework for Blazor and MAUI - .NET Foundation - https://www.oqtane.org - https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.0 - https://github.com/oqtane/oqtane.framework - Git Oqtane - false diff --git a/README.md b/README.md index 4e68f190..024dd4cb 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Oqtane is being developed based on some fundamental principles which are outline # Latest Release -[6.2.0](https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.0) was released on September 9, 2025 and is a major release including 57 pull requests by 4 different contributors, pushing the total number of project commits all-time over 7000. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[6.2.1](https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1) was released on September 29, 2025 and is a maintenance release including 65 pull requests by 6 different contributors, pushing the total number of project commits all-time over 7100. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. # Try It Now! @@ -22,19 +22,31 @@ Microsoft's Public Cloud (requires an Azure account) A free ASP.NET hosting account. No hidden fees. No credit card required. [![Deploy to MonsterASP.NET](https://www.oqtane.org/files/Public/MonsterASPNET.png)](https://www.monsterasp.net/) -# Getting Started (Version 6) +# Getting Started (Version 6.2+) + +**Installing using the Oqtane Application Template:** + +``` +dotnet new install Oqtane.Application.Template +dotnet new oqtane-app -o MyCompany.MyProject +cd MyCompany.MyProject +dotnet build +cd Server +dotnet run +``` + +- Browse to http://localhost:5001 to run the application (an Installation Wizard screen will be displayed the first time you run the application) +- To develop/debug the application, open the MyCompany.MyProject.sln file in the root folder and hit F5 **Installing using source code from the Dev/Master branch:** -- Install **[.NET 9.0.8 SDK](https://dotnet.microsoft.com/download/dotnet/9.0)**. +- Install Latest **[.NET 9.0 SDK](https://dotnet.microsoft.com/en-us/download)**. - Install the latest edition (v17.12 or higher) of [Visual Studio 2022](https://visualstudio.microsoft.com/downloads) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**. - Clone (or download) the Oqtane Master or Dev branch source code to your local system. - Open the **Oqtane.sln** solution file. - -- **Important:** Rebuild the entire solution before running it (ie. Build / Rebuild Solution). - Make sure you specify Oqtane.Server as the Startup Project. @@ -92,6 +104,9 @@ Connect with other developers, get support, and share ideas by joining the Oqtan # Roadmap This project is open source, and therefore is a work in progress... +[6.2.1](https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1) (Sep 29, 2025) +- [x] Stabilization improvements + [6.2.0](https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.0) (Sep 9, 2025) - [x] Oqtane Application Template - [x] Radzen Text Editor diff --git a/azuredeploy.json b/azuredeploy.json index 05388b1c..49a1e1f2 100644 --- a/azuredeploy.json +++ b/azuredeploy.json @@ -220,7 +220,7 @@ "apiVersion": "2024-04-01", "name": "[concat(parameters('BlazorWebsiteName'), '/ZipDeploy')]", "properties": { - "packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v6.2.0/Oqtane.Framework.6.2.0.Install.zip" + "packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v6.2.1/Oqtane.Framework.6.2.1.Install.zip" }, "dependsOn": [ "[resourceId('Microsoft.Web/sites', parameters('BlazorWebsiteName'))]"