├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── dotnet-core.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── README.zh-cn.md ├── SilkierQuartz.sln ├── _config.yml ├── delete-bin-obj-folders.bat ├── index.html ├── params.json ├── sample ├── AppSettings.cs ├── DemoScheduler.cs ├── InjectProperty.cs ├── Jobs │ ├── HelloJob.cs │ ├── HelloJobAuto.cs │ ├── HelloJobManual.cs │ ├── HelloJobSingle.cs │ ├── InjectSampleJob.cs │ ├── InjectSampleJobSingle.cs │ └── LongRunningJob.cs ├── Pages │ ├── Error.cshtml │ ├── Error.cshtml.cs │ ├── Index.cshtml │ ├── Index.cshtml.cs │ ├── Privacy.cshtml │ ├── Privacy.cshtml.cs │ ├── Shared │ │ ├── _Layout.cshtml │ │ └── _ValidationScriptsPartial.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── app.config ├── appsettings.Development.json ├── appsettings.json ├── example.csproj └── wwwroot │ ├── css │ ├── silkierquartz.custom.css │ └── site.css │ ├── favicon.ico │ ├── js │ └── site.js │ └── lib │ ├── bootstrap │ ├── LICENSE │ └── dist │ │ ├── css │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.css.map │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ │ └── js │ │ ├── bootstrap.bundle.js │ │ ├── bootstrap.bundle.js.map │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ ├── bootstrap.js │ │ ├── bootstrap.js.map │ │ ├── bootstrap.min.js │ │ └── bootstrap.min.js.map │ ├── jquery-validation-unobtrusive │ ├── LICENSE.txt │ ├── jquery.validate.unobtrusive.js │ └── jquery.validate.unobtrusive.min.js │ ├── jquery-validation │ ├── LICENSE.md │ └── dist │ │ ├── additional-methods.js │ │ ├── additional-methods.min.js │ │ ├── jquery.validate.js │ │ └── jquery.validate.min.js │ └── jquery │ ├── LICENSE.txt │ └── dist │ ├── jquery.js │ ├── jquery.min.js │ └── jquery.min.map ├── sample2 ├── AppSettings.cs ├── Areas │ └── Identity │ │ └── Pages │ │ └── _ViewStart.cshtml ├── Data │ ├── ApplicationDbContext.cs │ └── Migrations │ │ ├── 00000000000000_CreateIdentitySchema.Designer.cs │ │ ├── 00000000000000_CreateIdentitySchema.cs │ │ └── ApplicationDbContextModelSnapshot.cs ├── DemoScheduler.cs ├── InjectProperty.cs ├── Jobs │ ├── HelloJob.cs │ ├── HelloJobAuto.cs │ ├── HelloJobManual.cs │ ├── HelloJobSingle.cs │ ├── InjectSampleJob.cs │ ├── InjectSampleJobSingle.cs │ └── LongRunningJob.cs ├── Pages │ ├── Error.cshtml │ ├── Error.cshtml.cs │ ├── Index.cshtml │ ├── Index.cshtml.cs │ ├── Privacy.cshtml │ ├── Privacy.cshtml.cs │ ├── Shared │ │ ├── _Layout.cshtml │ │ ├── _Layout.cshtml.css │ │ ├── _LoginPartial.cshtml │ │ └── _ValidationScriptsPartial.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── Program.cs ├── Properties │ ├── serviceDependencies.json │ └── serviceDependencies.local.json ├── appsettings.Development.json ├── appsettings.json ├── sample2.csproj └── wwwroot │ ├── css │ ├── silkierquartz.custom.css │ └── site.css │ ├── favicon.ico │ ├── js │ └── site.js │ └── lib │ ├── bootstrap │ ├── LICENSE │ └── dist │ │ ├── css │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.css.map │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-grid.rtl.css │ │ ├── bootstrap-grid.rtl.css.map │ │ ├── bootstrap-grid.rtl.min.css │ │ ├── bootstrap-grid.rtl.min.css.map │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap-reboot.rtl.css │ │ ├── bootstrap-reboot.rtl.css.map │ │ ├── bootstrap-reboot.rtl.min.css │ │ ├── bootstrap-reboot.rtl.min.css.map │ │ ├── bootstrap-utilities.css │ │ ├── bootstrap-utilities.css.map │ │ ├── bootstrap-utilities.min.css │ │ ├── bootstrap-utilities.min.css.map │ │ ├── bootstrap-utilities.rtl.css │ │ ├── bootstrap-utilities.rtl.css.map │ │ ├── bootstrap-utilities.rtl.min.css │ │ ├── bootstrap-utilities.rtl.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ ├── bootstrap.min.css.map │ │ ├── bootstrap.rtl.css │ │ ├── bootstrap.rtl.css.map │ │ ├── bootstrap.rtl.min.css │ │ └── bootstrap.rtl.min.css.map │ │ └── js │ │ ├── bootstrap.bundle.js │ │ ├── bootstrap.bundle.js.map │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ ├── bootstrap.esm.js │ │ ├── bootstrap.esm.js.map │ │ ├── bootstrap.esm.min.js │ │ ├── bootstrap.esm.min.js.map │ │ ├── bootstrap.js │ │ ├── bootstrap.js.map │ │ ├── bootstrap.min.js │ │ └── bootstrap.min.js.map │ ├── jquery-validation-unobtrusive │ ├── LICENSE.txt │ ├── jquery.validate.unobtrusive.js │ └── jquery.validate.unobtrusive.min.js │ ├── jquery-validation │ ├── LICENSE.md │ └── dist │ │ ├── additional-methods.js │ │ ├── additional-methods.min.js │ │ ├── jquery.validate.js │ │ └── jquery.validate.min.js │ └── jquery │ ├── LICENSE.txt │ └── dist │ ├── jquery.js │ ├── jquery.min.js │ └── jquery.min.map ├── src ├── Quartz.Plugins.RecentHistory │ ├── ExecutionHistoryPlugin.cs │ ├── Extensions.cs │ ├── IExecutionHistoryStore.cs │ ├── Impl │ │ └── InProcExecutionHistoryStore.cs │ └── Quartz.Plugins.RecentHistory.csproj └── SilkierQuartz │ ├── Authorization │ ├── SilkierQuartzAuthenticationOptions.cs │ ├── SilkierQuartzDefaultAuthorizationHandler.cs │ └── SilkierQuartzDefaultAuthorizationRequirement.cs │ ├── Cache.cs │ ├── Configuration │ ├── ApplicationBuilderExtensions.cs │ ├── JobsListHelper.cs │ └── ServiceCollectionExtensions.cs │ ├── Content │ ├── Fonts │ │ ├── Lato.css │ │ ├── lato-v14-latin-700.eot │ │ ├── lato-v14-latin-700.svg │ │ ├── lato-v14-latin-700.ttf │ │ ├── lato-v14-latin-700.woff │ │ ├── lato-v14-latin-700.woff2 │ │ ├── lato-v14-latin-700italic.eot │ │ ├── lato-v14-latin-700italic.svg │ │ ├── lato-v14-latin-700italic.ttf │ │ ├── lato-v14-latin-700italic.woff │ │ ├── lato-v14-latin-700italic.woff2 │ │ ├── lato-v14-latin-italic.eot │ │ ├── lato-v14-latin-italic.svg │ │ ├── lato-v14-latin-italic.ttf │ │ ├── lato-v14-latin-italic.woff │ │ ├── lato-v14-latin-italic.woff2 │ │ ├── lato-v14-latin-regular.eot │ │ ├── lato-v14-latin-regular.svg │ │ ├── lato-v14-latin-regular.ttf │ │ ├── lato-v14-latin-regular.woff │ │ └── lato-v14-latin-regular.woff2 │ ├── Images │ │ ├── favicons │ │ │ ├── favicon-16.png │ │ │ ├── favicon-192.png │ │ │ ├── favicon-24.png │ │ │ ├── favicon-256.png │ │ │ ├── favicon-32.png │ │ │ ├── favicon-48.png │ │ │ └── favicon-64.png │ │ ├── logo.png │ │ └── type.png │ ├── Lib │ │ ├── FileSaver.js │ │ ├── jquery-3.3.1.min.js │ │ ├── moment.min.js │ │ ├── semanticui │ │ │ ├── calendar.min.css │ │ │ ├── calendar.min.js │ │ │ ├── semantic.min.css │ │ │ ├── semantic.min.js │ │ │ ├── state.min.js │ │ │ └── themes │ │ │ │ └── default │ │ │ │ └── assets │ │ │ │ └── fonts │ │ │ │ ├── brand-icons.eot │ │ │ │ ├── brand-icons.svg │ │ │ │ ├── brand-icons.ttf │ │ │ │ ├── brand-icons.woff │ │ │ │ ├── brand-icons.woff2 │ │ │ │ ├── icons.eot │ │ │ │ ├── icons.otf │ │ │ │ ├── icons.svg │ │ │ │ ├── icons.ttf │ │ │ │ ├── icons.woff │ │ │ │ ├── icons.woff2 │ │ │ │ ├── outline-icons.eot │ │ │ │ ├── outline-icons.svg │ │ │ │ ├── outline-icons.ttf │ │ │ │ ├── outline-icons.woff │ │ │ │ └── outline-icons.woff2 │ │ └── src │ ├── Scripts │ │ ├── calendar-editor.js │ │ ├── global.js │ │ ├── job-data-map.js │ │ └── post-validation.js │ └── Site.css │ ├── Controllers │ ├── AuthenticateController.cs │ ├── CalendarsController.cs │ ├── ExecutionsController.cs │ ├── HistoryController.cs │ ├── JobDataMapController.cs │ ├── JobsController.cs │ ├── PageControllerBase.cs │ ├── SchedulerController.cs │ └── TriggersController.cs │ ├── DateTimeSettings.cs │ ├── Extensions.cs │ ├── Helpers │ ├── CronExpressionDescriptor │ │ ├── DescriptionTypeEnum.cs │ │ ├── ExpressionDescriptor.cs │ │ ├── ExpressionParser.cs │ │ ├── Options.cs │ │ └── Resources.cs │ ├── FromFormAttribute.cs │ ├── HandlebarsHelpers.cs │ ├── JobDataMapRequest.cs │ ├── JsonErrorResponse.cs │ ├── JsonPascalCaseNamingPolicy.cs │ ├── ModelValidator.cs │ └── MultipartDataMediaFormatter │ │ ├── Converters │ │ ├── FormDataToObjectConverter.cs │ │ ├── HttpContentToFormDataConverter.cs │ │ └── ObjectToMultipartDataByteArrayConverter.cs │ │ ├── FormMultipartEncodedMediaTypeFormatter.cs │ │ └── Infrastructure │ │ ├── Extensions │ │ └── TypeExtensions.cs │ │ ├── FormData.cs │ │ ├── HttpFile.cs │ │ ├── Logger │ │ ├── FormDataConverterLogger.cs │ │ ├── FormatterLoggerAdapter.cs │ │ └── IFormDataConverterLogger.cs │ │ ├── MultipartFormatterSettings.cs │ │ └── TypeConverters │ │ ├── BooleanConverterEx.cs │ │ ├── DateTimeConverterISO8601.cs │ │ └── FromStringConverterAdapter.cs │ ├── HostedService │ ├── IJobRegistrator.cs │ ├── IJobRegistratorExtensions.cs │ ├── IScheduleJob.cs │ ├── IServiceCollectionExtensions.cs │ ├── JobOptions.cs │ ├── JobRegistrator.cs │ ├── QuartzHostedService.cs │ ├── ScheduleJob.cs │ ├── ServiceCollectionJobFactory.cs │ └── TriggerOptions.cs │ ├── Models │ ├── AuthenticateViewModel.cs │ ├── CalendarListItem.cs │ ├── CalendarViewModel.cs │ ├── FormFile.cs │ ├── Histogram.cs │ ├── IHasValidation.cs │ ├── JobDataMapItem.cs │ ├── JobDataMapItemBase.cs │ ├── JobDataMapModel.cs │ ├── JobListItem.cs │ ├── JobViewModel.cs │ ├── KeyModel.cs │ ├── TriggerListItem.cs │ ├── TriggerViewModel.cs │ └── ValidationResult.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Services.cs │ ├── SilkierQuartz.csproj │ ├── SilkierQuartz.xml │ ├── SilkierQuartzAttribute.cs │ ├── SilkierQuartzOptions.cs │ ├── TypeHandlerService.cs │ ├── TypeHandlers │ ├── Attributes.cs │ ├── BooleanHandler.cs │ ├── BooleanHandler.hbs │ ├── BooleanHandler.js │ ├── DateTimeHandler.cs │ ├── DateTimeHandler.hbs │ ├── DateTimeHandler.js │ ├── EnumHandler.cs │ ├── FileHandler.cs │ ├── FileHandler.hbs │ ├── FileHandler.js │ ├── NumberHandler.cs │ ├── NumberHandler.hbs │ ├── OptionSetHandler.cs │ ├── OptionSetHandler.hbs │ ├── OptionSetHandler.js │ ├── StringHandler.cs │ ├── StringHandler.hbs │ ├── StringHandler.js │ ├── TypeHandlerBase.cs │ ├── UnsupportedTypeHandler.cs │ └── UnsupportedTypeHandler.hbs │ ├── ViewEngine.cs │ ├── ViewFileSystemFactory.cs │ └── Views │ ├── Authenticate │ └── Login.hbs │ ├── Calendars │ ├── Edit.hbs │ └── Index.hbs │ ├── Error.hbs │ ├── Executions │ └── Index.hbs │ ├── History │ └── Index.hbs │ ├── Jobs │ ├── AdditionalData.hbs │ ├── Edit.hbs │ ├── Index.hbs │ └── Trigger.hbs │ ├── Layout.hbs │ ├── Partials │ ├── DropdownOptions.hbs │ ├── EmptyList.hbs │ ├── GroupActions.hbs │ ├── Histogram.hbs │ ├── JobDataMap.hbs │ ├── JobDataMapRow.hbs │ └── UnitDropdown.hbs │ ├── Scheduler │ └── Index.hbs │ └── Triggers │ ├── AdditionalData.hbs │ ├── Edit.hbs │ └── Index.hbs ├── stylesheets ├── github-light.css ├── normalize.css └── stylesheet.css └── test ├── ExpressionDescriptorUnitTests.cs ├── IServiceCollectionExtensionsUnitTest.cs ├── QuartzHostedServiceUnitTest.cs └── SilkierQuartz.Test.csproj /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | .env 3 | .git 4 | .gitignore 5 | .vs 6 | .vscode 7 | */bin 8 | */obj 9 | **/.toolstarget -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # Default severity for analyzer diagnostics with category 'Style' 4 | dotnet_analyzer_diagnostic.category-Style.severity = none 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET Core SDK 17 | uses: actions/setup-dotnet@v1.8.1 18 | with: 19 | dotnet-version: 9.0.100 20 | - name: Install dependencies 21 | run: dotnet restore ./src/SilkierQuartz/SilkierQuartz.csproj 22 | - name: Build 23 | run: dotnet build ./src/SilkierQuartz/SilkierQuartz.csproj --configuration Release 24 | - name: Test 25 | run: dotnet test ./test/SilkierQuartz.Test.csproj --verbosity normal 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | sudo: required 3 | dist: xenial 4 | mono: none 5 | dotnet: 3.0 6 | before_script: 7 | - dotnet restore 8 | script: 9 | - dotnet test ./test/QuartzHostedService.Test -c Release 10 | - dotnet build -c Release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2019 Jan Lucansky 4 | Copyright (c) 2019 mukmyash 5 | Copyright (c) 2019-2020 Yanhong Ma 6 | 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight -------------------------------------------------------------------------------- /delete-bin-obj-folders.bat: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | cls 3 | 4 | ECHO Deleting all BIN and OBJ folders... 5 | ECHO. 6 | 7 | FOR /d /r . %%d in (bin,obj,LocalNuget) DO ( 8 | IF EXIST "%%d" ( 9 | ECHO %%d | FIND /I "\node_modules\" > Nul && ( 10 | ECHO.Skipping: %%d 11 | ) || ( 12 | ECHO.Deleting: %%d 13 | rd /s/q "%%d" 14 | ) 15 | ) 16 | ) 17 | 18 | ECHO. 19 | ECHO.BIN and OBJ folders have been successfully deleted. Press any key to exit. 20 | pause > nul -------------------------------------------------------------------------------- /sample/AppSettings.cs: -------------------------------------------------------------------------------- 1 | namespace SilkierQuartz.Example 2 | { 3 | public class AppSettings 4 | { 5 | public bool EnableHelloJob { get; set; } 6 | public bool EnableHelloSingleJob { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample/InjectProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SilkierQuartz.Example 6 | { 7 | public class InjectProperty 8 | { 9 | public string WriteText { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/Jobs/HelloJob.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SilkierQuartz.Example.Jobs 8 | { 9 | public class HelloJob : IJob 10 | { 11 | public Task Execute(IJobExecutionContext context) 12 | { 13 | Console.WriteLine("Hello"); 14 | return Task.CompletedTask; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample/Jobs/HelloJobAuto.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using SilkierQuartz; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SilkierQuartz.Example.Jobs 9 | { 10 | 11 | [SilkierQuartz(5, Group = "example", Description = "this e sq test", TriggerDescription = "_hellojobauto", TriggerGroup = "SilkierQuartz")] 12 | public class HelloJobAuto : IJob 13 | { 14 | public Task Execute(IJobExecutionContext context) 15 | { 16 | Console.WriteLine($"Hello {DateTime.Now}"); 17 | return Task.CompletedTask; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sample/Jobs/HelloJobManual.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using SilkierQuartz; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SilkierQuartz.Example.Jobs 9 | { 10 | 11 | [SilkierQuartz(Manual = true)] 12 | public class HelloJobManual : IJob 13 | { 14 | public Task Execute(IJobExecutionContext context) 15 | { 16 | Console.WriteLine($"Hello {DateTime.Now}"); 17 | return Task.CompletedTask; 18 | } 19 | } 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /sample/Jobs/HelloJobSingle.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SilkierQuartz.Example.Jobs 8 | { 9 | public class HelloJobSingle : IJob 10 | { 11 | public Task Execute(IJobExecutionContext context) 12 | { 13 | Console.WriteLine("Hello Single"); 14 | return Task.CompletedTask; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample/Jobs/InjectSampleJob.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using Quartz; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SilkierQuartz.Example.Jobs 9 | { 10 | public class InjectSampleJob : IJob 11 | { 12 | InjectProperty _options; 13 | 14 | public InjectSampleJob(IOptions options) 15 | { 16 | _options = options.Value; 17 | } 18 | public Task Execute(IJobExecutionContext context) 19 | { 20 | Console.WriteLine(_options.WriteText); 21 | return Task.CompletedTask; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample/Jobs/InjectSampleJobSingle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using Quartz; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SilkierQuartz.Example.Jobs 9 | { 10 | public class InjectSampleJobSingle : IJob 11 | { 12 | InjectProperty _options; 13 | 14 | public InjectSampleJobSingle(IOptions options) 15 | { 16 | _options = options.Value; 17 | } 18 | public Task Execute(IJobExecutionContext context) 19 | { 20 | Console.WriteLine($"InjectSampleJobSingle {_options.WriteText} "); 21 | return Task.CompletedTask; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample/Jobs/LongRunningJob.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SilkierQuartz.Example.Jobs 8 | { 9 | public class LongRunningJob : IJob 10 | { 11 | public async Task Execute(IJobExecutionContext context) 12 | { 13 | Console.WriteLine("Long Started"); 14 | await Task.Delay(30000);//half min 15 | Console.WriteLine("Long Running"); 16 | await Task.Delay(30000);//half min 17 | Console.WriteLine("Long Complete"); 18 | await Task.CompletedTask; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to the Development environment displays detailed information about the error that occurred. 20 |

21 |

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

27 | -------------------------------------------------------------------------------- /sample/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace SilkierQuartz.Example.Pages 11 | { 12 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 13 | public class ErrorModel : PageModel 14 | { 15 | public string RequestId { get; set; } 16 | 17 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 18 | 19 | private readonly ILogger _logger; 20 | 21 | public ErrorModel(ILogger logger) 22 | { 23 | _logger = logger; 24 | } 25 | 26 | public void OnGet() 27 | { 28 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sample/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model IndexModel 3 | @{ 4 | ViewData["Title"] = "Home page"; 5 | } 6 | 7 |
8 |

Welcome

9 |

Learn about building Web apps with ASP.NET Core.

10 |
11 | -------------------------------------------------------------------------------- /sample/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace SilkierQuartz.Example.Pages 10 | { 11 | public class IndexModel : PageModel 12 | { 13 | private readonly ILogger _logger; 14 | 15 | public IndexModel(ILogger logger) 16 | { 17 | _logger = logger; 18 | } 19 | 20 | public void OnGet() 21 | { 22 | 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample/Pages/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model PrivacyModel 3 | @{ 4 | ViewData["Title"] = "Privacy Policy"; 5 | } 6 |

@ViewData["Title"]

7 | 8 |

Use this page to detail your site's privacy policy.

9 | -------------------------------------------------------------------------------- /sample/Pages/Privacy.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace SilkierQuartz.Example.Pages 5 | { 6 | public class PrivacyModel : PageModel 7 | { 8 | private readonly ILogger _logger; 9 | 10 | public PrivacyModel(ILogger logger) 11 | { 12 | _logger = logger; 13 | } 14 | 15 | public void OnGet() 16 | { 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sample/Pages/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - SilkierQuartz.Example 7 | 8 | 9 | 10 | 11 |
12 | 34 |
35 |
36 |
37 | @RenderBody() 38 |
39 |
40 | 41 |
42 |
43 | © 2020 - SilkierQuartz.Example - Privacy 44 |
45 |
46 | 47 | 48 | 49 | 50 | 51 | @RenderSection("Scripts", required: false) 52 | 53 | 54 | -------------------------------------------------------------------------------- /sample/Pages/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /sample/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using SilkierQuartz.Example 2 | @namespace SilkierQuartz.Example.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /sample/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /sample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | using SilkierQuartz; 10 | using SilkierQuartz.HostedService; 11 | 12 | namespace SilkierQuartz.Example 13 | { 14 | public class Program 15 | { 16 | public static void Main(string[] args) 17 | { 18 | CreateHostBuilder(args).Build().Run(); 19 | } 20 | 21 | private static IHostBuilder CreateHostBuilder(string[] args) => 22 | Host.CreateDefaultBuilder(args) 23 | .ConfigureWebHostDefaults(webBuilder => 24 | { 25 | webBuilder.UseStartup(); 26 | }) 27 | .ConfigureSilkierQuartzHost(); 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:64450", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "SilkierQuartz.Example": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sample/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /sample/example.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | TRACE;ENABLE_AUTH 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /sample/wwwroot/css/silkierquartz.custom.css: -------------------------------------------------------------------------------- 1 | .top.menu .header.item img.logo { 2 | margin-right: 0.8em; 3 | width: 20px; 4 | height: 20px; 5 | } 6 | -------------------------------------------------------------------------------- /sample/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | /* Provide sufficient contrast against white background */ 11 | a { 12 | color: #0366d6; 13 | } 14 | 15 | .btn-primary { 16 | color: #fff; 17 | background-color: #1b6ec2; 18 | border-color: #1861ac; 19 | } 20 | 21 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link { 22 | color: #fff; 23 | background-color: #1b6ec2; 24 | border-color: #1861ac; 25 | } 26 | 27 | /* Sticky footer styles 28 | -------------------------------------------------- */ 29 | html { 30 | font-size: 14px; 31 | } 32 | @media (min-width: 768px) { 33 | html { 34 | font-size: 16px; 35 | } 36 | } 37 | 38 | .border-top { 39 | border-top: 1px solid #e5e5e5; 40 | } 41 | .border-bottom { 42 | border-bottom: 1px solid #e5e5e5; 43 | } 44 | 45 | .box-shadow { 46 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 47 | } 48 | 49 | button.accept-policy { 50 | font-size: 1rem; 51 | line-height: inherit; 52 | } 53 | 54 | /* Sticky footer styles 55 | -------------------------------------------------- */ 56 | html { 57 | position: relative; 58 | min-height: 100%; 59 | } 60 | 61 | body { 62 | /* Margin bottom by footer height */ 63 | margin-bottom: 60px; 64 | } 65 | .footer { 66 | position: absolute; 67 | bottom: 0; 68 | width: 100%; 69 | white-space: nowrap; 70 | line-height: 60px; /* Vertically center the text there */ 71 | } 72 | -------------------------------------------------------------------------------- /sample/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/sample/wwwroot/favicon.ico -------------------------------------------------------------------------------- /sample/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your Javascript code. 5 | -------------------------------------------------------------------------------- /sample/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2018 Twitter, Inc. 4 | Copyright (c) 2011-2018 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /sample/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /sample/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /sample/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors, https://js.foundation/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /sample2/AppSettings.cs: -------------------------------------------------------------------------------- 1 | namespace SilkierQuartz.Example 2 | { 3 | public class AppSettings 4 | { 5 | public bool EnableHelloJob { get; set; } 6 | public bool EnableHelloSingleJob { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sample2/Areas/Identity/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Pages/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /sample2/Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace WebApplication1.Data 5 | { 6 | public class ApplicationDbContext : IdentityDbContext 7 | { 8 | public ApplicationDbContext(DbContextOptions options) 9 | : base(options) 10 | { 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /sample2/InjectProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SilkierQuartz.Example 6 | { 7 | public class InjectProperty 8 | { 9 | public string WriteText { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample2/Jobs/HelloJob.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SilkierQuartz.Example.Jobs 8 | { 9 | public class HelloJob : IJob 10 | { 11 | public Task Execute(IJobExecutionContext context) 12 | { 13 | Console.WriteLine("Hello"); 14 | return Task.CompletedTask; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample2/Jobs/HelloJobAuto.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using SilkierQuartz; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SilkierQuartz.Example.Jobs 9 | { 10 | [SilkierQuartz(5, Group = "sample", Description = "this e sq test", TriggerDescription = "_hellojobauto")] 11 | public class HelloJobAuto : IJob 12 | { 13 | public Task Execute(IJobExecutionContext context) 14 | { 15 | Console.WriteLine($"Hello {DateTime.Now}"); 16 | return Task.CompletedTask; 17 | } 18 | } 19 | 20 | [DisallowConcurrentExecution] 21 | [SilkierQuartz(5, 0, 0, Group = "sample", Description = "自动下载欢迎信息")] 22 | public class HelloJobAuto1 : IJob 23 | { 24 | public Task Execute(IJobExecutionContext context) 25 | { 26 | Console.WriteLine($"Hello {DateTime.Now}"); 27 | return Task.CompletedTask; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /sample2/Jobs/HelloJobManual.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using SilkierQuartz; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SilkierQuartz.Example.Jobs 9 | { 10 | [SilkierQuartz(Manual = true)] 11 | public class HelloJobManual : IJob 12 | { 13 | public Task Execute(IJobExecutionContext context) 14 | { 15 | Console.WriteLine($"Hello {DateTime.Now}"); 16 | return Task.CompletedTask; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /sample2/Jobs/HelloJobSingle.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SilkierQuartz.Example.Jobs 8 | { 9 | public class HelloJobSingle : IJob 10 | { 11 | public Task Execute(IJobExecutionContext context) 12 | { 13 | Console.WriteLine("Hello Single"); 14 | return Task.CompletedTask; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample2/Jobs/InjectSampleJob.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using Quartz; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SilkierQuartz.Example.Jobs 9 | { 10 | public class InjectSampleJob : IJob 11 | { 12 | InjectProperty _options; 13 | 14 | public InjectSampleJob(IOptions options) 15 | { 16 | _options = options.Value; 17 | } 18 | public Task Execute(IJobExecutionContext context) 19 | { 20 | Console.WriteLine(_options.WriteText); 21 | return Task.CompletedTask; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample2/Jobs/InjectSampleJobSingle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using Quartz; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SilkierQuartz.Example.Jobs 9 | { 10 | public class InjectSampleJobSingle : IJob 11 | { 12 | InjectProperty _options; 13 | 14 | public InjectSampleJobSingle(IOptions options) 15 | { 16 | _options = options.Value; 17 | } 18 | public Task Execute(IJobExecutionContext context) 19 | { 20 | Console.WriteLine($"InjectSampleJobSingle {_options.WriteText} "); 21 | return Task.CompletedTask; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample2/Jobs/LongRunningJob.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SilkierQuartz.Example.Jobs 8 | { 9 | public class LongRunningJob : IJob 10 | { 11 | public async Task Execute(IJobExecutionContext context) 12 | { 13 | Console.WriteLine("Long Started"); 14 | await Task.Delay(30000);//half min 15 | Console.WriteLine("Long Running"); 16 | await Task.Delay(30000);//half min 17 | Console.WriteLine("Long Complete"); 18 | await Task.CompletedTask; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample2/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to the Development environment displays detailed information about the error that occurred. 20 |

21 |

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

27 | -------------------------------------------------------------------------------- /sample2/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | using System.Diagnostics; 4 | 5 | namespace WebApplication1.Pages 6 | { 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | public class ErrorModel : PageModel 10 | { 11 | public string? RequestId { get; set; } 12 | 13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 14 | 15 | private readonly ILogger _logger; 16 | 17 | public ErrorModel(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /sample2/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model IndexModel 3 | @{ 4 | ViewData["Title"] = "Home page"; 5 | } 6 | 7 |
8 |

Welcome

9 |

Learn about building Web apps with ASP.NET Core.

10 |
11 | -------------------------------------------------------------------------------- /sample2/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | 4 | namespace WebApplication1.Pages 5 | { 6 | public class IndexModel : PageModel 7 | { 8 | private readonly ILogger _logger; 9 | 10 | public IndexModel(ILogger logger) 11 | { 12 | _logger = logger; 13 | } 14 | 15 | public void OnGet() 16 | { 17 | 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /sample2/Pages/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model PrivacyModel 3 | @{ 4 | ViewData["Title"] = "Privacy Policy"; 5 | } 6 |

@ViewData["Title"]

7 | 8 |

Use this page to detail your site's privacy policy.

9 | -------------------------------------------------------------------------------- /sample2/Pages/Privacy.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | 4 | namespace WebApplication1.Pages 5 | { 6 | public class PrivacyModel : PageModel 7 | { 8 | private readonly ILogger _logger; 9 | 10 | public PrivacyModel(ILogger logger) 11 | { 12 | _logger = logger; 13 | } 14 | 15 | public void OnGet() 16 | { 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /sample2/Pages/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - WebApplication1 7 | 8 | 9 | 10 | 11 | 12 |
13 | 33 |
34 |
35 |
36 | @RenderBody() 37 |
38 |
39 | 40 |
41 |
42 | © 2022 - WebApplication1 - Privacy 43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | @await RenderSectionAsync("Scripts", required: false) 51 | 52 | -------------------------------------------------------------------------------- /sample2/Pages/Shared/_Layout.cshtml.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | a { 11 | color: #0077cc; 12 | } 13 | 14 | .btn-primary { 15 | color: #fff; 16 | background-color: #1b6ec2; 17 | border-color: #1861ac; 18 | } 19 | 20 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link { 21 | color: #fff; 22 | background-color: #1b6ec2; 23 | border-color: #1861ac; 24 | } 25 | 26 | .border-top { 27 | border-top: 1px solid #e5e5e5; 28 | } 29 | .border-bottom { 30 | border-bottom: 1px solid #e5e5e5; 31 | } 32 | 33 | .box-shadow { 34 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 35 | } 36 | 37 | button.accept-policy { 38 | font-size: 1rem; 39 | line-height: inherit; 40 | } 41 | 42 | .footer { 43 | position: absolute; 44 | bottom: 0; 45 | width: 100%; 46 | white-space: nowrap; 47 | line-height: 60px; 48 | } 49 | -------------------------------------------------------------------------------- /sample2/Pages/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @inject SignInManager SignInManager 3 | @inject UserManager UserManager 4 | 5 | 27 | -------------------------------------------------------------------------------- /sample2/Pages/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /sample2/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using WebApplication1 3 | @using WebApplication1.Data 4 | @namespace WebApplication1.Pages 5 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 6 | -------------------------------------------------------------------------------- /sample2/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /sample2/Properties/serviceDependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "mssql1": { 4 | "type": "mssql", 5 | "connectionId": "ConnectionStrings:DefaultConnection" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /sample2/Properties/serviceDependencies.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "mssql1": { 4 | "type": "mssql.local", 5 | "connectionId": "ConnectionStrings:DefaultConnection" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /sample2/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sample2/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-WebApplication1-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft.AspNetCore": "Warning" 9 | } 10 | }, 11 | "AllowedHosts": "*" 12 | } 13 | -------------------------------------------------------------------------------- /sample2/sample2.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | aspnet-WebApplication1-A527D1EB-9052-4CB4-84DF-96A390FE8CC1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /sample2/wwwroot/css/silkierquartz.custom.css: -------------------------------------------------------------------------------- 1 | .top.menu .header.item img.logo { 2 | margin-right: 0.8em; 3 | width: 20px; 4 | height: 20px; 5 | } 6 | -------------------------------------------------------------------------------- /sample2/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 14px; 3 | } 4 | 5 | @media (min-width: 768px) { 6 | html { 7 | font-size: 16px; 8 | } 9 | } 10 | 11 | html { 12 | position: relative; 13 | min-height: 100%; 14 | } 15 | 16 | body { 17 | margin-bottom: 60px; 18 | } -------------------------------------------------------------------------------- /sample2/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/sample2/wwwroot/favicon.ico -------------------------------------------------------------------------------- /sample2/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /sample2/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2021 Twitter, Inc. 4 | Copyright (c) 2011-2021 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /sample2/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /sample2/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /sample2/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors, https://js.foundation/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /src/Quartz.Plugins.RecentHistory/Extensions.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Quartz.Plugins.RecentHistory 3 | { 4 | public static class Extensions 5 | { 6 | public static void SetExecutionHistoryStore(this SchedulerContext context, IExecutionHistoryStore store) 7 | { 8 | context.Put(typeof(IExecutionHistoryStore).FullName, store); 9 | } 10 | 11 | public static IExecutionHistoryStore GetExecutionHistoryStore(this SchedulerContext context) 12 | { 13 | return (IExecutionHistoryStore)context.Get(typeof(IExecutionHistoryStore).FullName); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Quartz.Plugins.RecentHistory/IExecutionHistoryStore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace Quartz.Plugins.RecentHistory 6 | { 7 | [Serializable] 8 | public class ExecutionHistoryEntry 9 | { 10 | public string FireInstanceId { get; set; } 11 | public string SchedulerInstanceId { get; set; } 12 | public string SchedulerName { get; set; } 13 | public string Job { get; set; } 14 | public string Trigger { get; set; } 15 | public DateTimeOffset? ScheduledFireTimeUtc { get; set; } 16 | public DateTimeOffset ActualFireTimeUtc { get; set; } 17 | public bool Recovering { get; set; } 18 | public bool Vetoed { get; set; } 19 | public DateTimeOffset? FinishedTimeUtc { get; set; } 20 | public string ExceptionMessage { get; set; } 21 | } 22 | 23 | public interface IExecutionHistoryStore 24 | { 25 | string SchedulerName { get; set; } 26 | 27 | Task Get(string fireInstanceId); 28 | Task Save(ExecutionHistoryEntry entry); 29 | Task Purge(); 30 | 31 | Task> FilterLastOfEveryJob(int limitPerJob); 32 | Task> FilterLastOfEveryTrigger(int limitPerTrigger); 33 | Task> FilterLast(int limit); 34 | 35 | Task GetTotalJobsExecuted(); 36 | Task GetTotalJobsFailed(); 37 | 38 | Task IncrementTotalJobsExecuted(); 39 | Task IncrementTotalJobsFailed(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Quartz.Plugins.RecentHistory/Quartz.Plugins.RecentHistory.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0;net8.0;net9.0 4 | PackageReference 5 | true 6 | true 7 | 1591 8 | 9 | 9.0.0 10 | SilkierQuartz 11 | Quartz.NET plugin to persist recent job execution history 12 | Jan Lucansky,Maikebing 13 | https://github.com/jlucansky/SilkierQuartz 14 | This is supporting package for SilkierQuartz 15 | quartz;recent;history 16 | 1.0.0.0 17 | 1.0.0.0 18 | LICENSE 19 | Copyright © 2018 Jan Lucansky, Copyright © 2024 Maikebing 20 | git 21 | https://github.com/jlucansky/SilkierQuartz 22 | SilkierQuartz.Plugins.RecentHistory 23 | true 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Authorization/SilkierQuartzAuthenticationOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Authentication.Cookies; 3 | using SilkierQuartz.Authorization; 4 | 5 | namespace SilkierQuartz 6 | { 7 | /// 8 | /// Configuration for SilkierQuartz authentication 9 | /// 10 | public class SilkierQuartzAuthenticationOptions 11 | { 12 | /// 13 | /// The simple requirements for authorization when authorization is enabled 14 | /// 15 | public enum SimpleAccessRequirement 16 | { 17 | AllowAnonymous, 18 | AllowOnlyAuthenticated, 19 | AllowOnlyUsersWithClaim 20 | } 21 | 22 | public const string AuthorizationPolicyName = "SilkierQuartz"; 23 | 24 | public string UserName { get; set; } = "admin"; 25 | public string Password { get; set; } = "password"; 26 | 27 | public Func Authenticate = (reqUserName, 28 | reqPassword, verifyUserName, verifyPassword) => 29 | { 30 | return 31 | string.Compare(reqUserName, verifyUserName, StringComparison.InvariantCulture) == 0 && 32 | string.Compare(reqPassword, verifyPassword, StringComparison.InvariantCulture) == 0; 33 | }; 34 | 35 | /// 36 | /// Sets the authentication scheme for the SilkierQuartz authentication signin. 37 | /// Defaults to 38 | /// 39 | public string AuthScheme { get; set; } = CookieAuthenticationDefaults.AuthenticationScheme; 40 | 41 | /// 42 | /// Sets the claim key used for authorization when is set to 43 | /// 44 | public string SilkierQuartzClaim { get; set; } = "SilkierQuartzManage"; 45 | 46 | /// 47 | /// Sets the claim value used for authorization when is set to 48 | /// 49 | public string SilkierQuartzClaimValue { get; set; } = "Authorized"; 50 | 51 | /// 52 | /// Used to poplate the Access Requirement for 53 | /// 54 | public SimpleAccessRequirement AccessRequirement { get; set; } = SimpleAccessRequirement.AllowOnlyUsersWithClaim; 55 | 56 | /// 57 | /// Set to true to skip all requirement checks in 58 | /// 59 | public bool SkipDefaultRequirementHandler { get; set; } = false; 60 | } 61 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/Authorization/SilkierQuartzDefaultAuthorizationHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace SilkierQuartz.Authorization 6 | { 7 | public class SilkierQuartzDefaultAuthorizationHandler : AuthorizationHandler 8 | { 9 | private readonly SilkierQuartzAuthenticationOptions options; 10 | 11 | public SilkierQuartzDefaultAuthorizationHandler(SilkierQuartzAuthenticationOptions options) 12 | { 13 | this.options = options ?? throw new ArgumentNullException(nameof(options)); 14 | } 15 | 16 | protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SilkierQuartzDefaultAuthorizationRequirement requirement) 17 | { 18 | if (context.HasSucceeded || options.SkipDefaultRequirementHandler) 19 | { 20 | return Task.CompletedTask; 21 | } 22 | 23 | if (options.AccessRequirement == SilkierQuartzAuthenticationOptions.SimpleAccessRequirement.AllowAnonymous) 24 | { 25 | context.Succeed(requirement); 26 | 27 | return Task.CompletedTask; 28 | } 29 | 30 | if (!context.User.Identity.IsAuthenticated && 31 | options.AccessRequirement == SilkierQuartzAuthenticationOptions.SimpleAccessRequirement.AllowOnlyAuthenticated) 32 | { 33 | context.Fail(); 34 | 35 | return Task.CompletedTask; 36 | } 37 | 38 | if (!context.User.HasClaim(options.SilkierQuartzClaim, options.SilkierQuartzClaimValue) && 39 | options.AccessRequirement == SilkierQuartzAuthenticationOptions.SimpleAccessRequirement.AllowOnlyUsersWithClaim) 40 | { 41 | context.Fail(); 42 | 43 | return Task.CompletedTask; 44 | } 45 | 46 | context.Succeed(requirement); 47 | 48 | return Task.CompletedTask; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Authorization/SilkierQuartzDefaultAuthorizationRequirement.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using static SilkierQuartz.SilkierQuartzAuthenticationOptions; 3 | 4 | namespace SilkierQuartz.Authorization 5 | { 6 | public class SilkierQuartzDefaultAuthorizationRequirement : IAuthorizationRequirement 7 | { 8 | public SimpleAccessRequirement AccessRequirement { get; } 9 | 10 | public SilkierQuartzDefaultAuthorizationRequirement(SimpleAccessRequirement accessRequirement) 11 | { 12 | AccessRequirement = accessRequirement; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Cache.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using Quartz.Impl.Matchers; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace SilkierQuartz 7 | { 8 | internal class Cache 9 | { 10 | private readonly Services _services; 11 | public Cache(Services services) 12 | { 13 | _services = services; 14 | } 15 | 16 | private string[] _jobTypes; 17 | public string[] JobTypes 18 | { 19 | get 20 | { 21 | if (_jobTypes == null) 22 | { 23 | lock (this) 24 | { 25 | if (_jobTypes == null) 26 | { 27 | var keys = _services.Scheduler.GetJobKeys(GroupMatcher.AnyGroup()).GetAwaiter().GetResult(); 28 | var knownTypes = new List(); 29 | foreach (var key in keys) 30 | { 31 | var detail = _services.Scheduler.GetJobDetail(key).GetAwaiter().GetResult(); 32 | knownTypes.Add(detail.JobType.RemoveAssemblyDetails()); 33 | } 34 | UpdateJobTypes(knownTypes); 35 | } 36 | } 37 | } 38 | return _jobTypes; 39 | } 40 | } 41 | 42 | public void UpdateJobTypes(IEnumerable list) 43 | { 44 | if (_jobTypes != null) 45 | list = list.Concat(_jobTypes); // append existing types 46 | _jobTypes = list.Distinct().OrderBy(x => x).ToArray(); 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Configuration/JobsListHelper.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace SilkierQuartz.Configuration 8 | { 9 | internal static class JobsListHelper 10 | { 11 | private static List _silkierQuartzJobs = null; 12 | public static List GetSilkierQuartzJobs(List lists = null) 13 | { 14 | if (_silkierQuartzJobs == null) 15 | { 16 | try 17 | { 18 | var types1 = from t in Assembly.GetEntryAssembly().GetTypes() where t.GetTypeInfo().ImplementedInterfaces.Any(tx => tx == typeof(IJob)) && t.GetTypeInfo().IsDefined(typeof(SilkierQuartzAttribute), true) select t; 19 | var types = from t in Assembly.GetCallingAssembly().GetTypes() where t.GetTypeInfo().ImplementedInterfaces.Any(tx => tx == typeof(IJob)) && t.GetTypeInfo().IsDefined(typeof(SilkierQuartzAttribute), true) select t; 20 | _silkierQuartzJobs = new List(); 21 | _silkierQuartzJobs.AddRange(types.ToList()); 22 | _silkierQuartzJobs.AddRange(types1.ToList()); 23 | lists?.ForEach(asm => 24 | { 25 | var typeasm = from t in asm.GetTypes() where t.GetTypeInfo().ImplementedInterfaces.Any(tx => tx == typeof(IJob)) && t.GetTypeInfo().IsDefined(typeof(SilkierQuartzAttribute), true) select t; 26 | _silkierQuartzJobs.AddRange(typeasm); 27 | }); 28 | } 29 | catch (Exception ex) 30 | { 31 | throw new Exception("Can't find type with IJob and have SilkierQuartzAttribute", ex); 32 | } 33 | } 34 | return _silkierQuartzJobs; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Configuration/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using SilkierQuartz; 3 | using SilkierQuartz.Authorization; 4 | using SilkierQuartz.Configuration; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Collections.Specialized; 8 | using System.Reflection; 9 | 10 | namespace Microsoft.Extensions.DependencyInjection 11 | { 12 | public static class ServiceCollectionExtensions 13 | { 14 | [Obsolete("We recommend AddSilkierQuartz")] 15 | public static IServiceCollection AddQuartzmin(this IServiceCollection services, Action stdSchedulerFactoryOptions = null) 16 | => services.AddSilkierQuartz(stdSchedulerFactoryOptions: stdSchedulerFactoryOptions); 17 | 18 | public static IServiceCollection AddSilkierQuartz( 19 | this IServiceCollection services, 20 | Action configureOptions = null, 21 | Action configureAuthenticationOptions = null, 22 | Action stdSchedulerFactoryOptions = null, 23 | Func> jobsasmlist = null) 24 | { 25 | var options = new SilkierQuartzOptions(); 26 | configureOptions?.Invoke(options); 27 | services.AddSingleton(options); 28 | 29 | var authenticationOptions = new SilkierQuartzAuthenticationOptions(); 30 | configureAuthenticationOptions?.Invoke(authenticationOptions); 31 | 32 | services.AddSingleton(authenticationOptions); 33 | if (authenticationOptions.AccessRequirement != SilkierQuartzAuthenticationOptions.SimpleAccessRequirement.AllowAnonymous) 34 | { 35 | services 36 | .AddAuthentication(authenticationOptions.AuthScheme) 37 | .AddCookie(authenticationOptions.AuthScheme, cfg => 38 | { 39 | cfg.Cookie.Name = $"sq_authenticationOptions.AuthScheme"; 40 | cfg.LoginPath = $"{options.VirtualPathRoot}{(options.VirtualPathRoot.EndsWith('/') ? "" : "/")}Authenticate/Login"; 41 | cfg.AccessDeniedPath = $"{options.VirtualPathRoot}{(options.VirtualPathRoot.EndsWith('/') ? "" : "/")}Authenticate/Login"; 42 | cfg.ExpireTimeSpan = TimeSpan.FromDays(7); 43 | cfg.SlidingExpiration = true; 44 | }); 45 | } 46 | services.AddAuthorization(opts => 47 | { 48 | opts.AddPolicy(SilkierQuartzAuthenticationOptions.AuthorizationPolicyName, builder => 49 | { 50 | builder.AddRequirements(new SilkierQuartzDefaultAuthorizationRequirement(authenticationOptions.AccessRequirement)); 51 | }); 52 | }); 53 | services.AddScoped(); 54 | 55 | 56 | services.UseQuartzHostedService(stdSchedulerFactoryOptions); 57 | 58 | var types = JobsListHelper.GetSilkierQuartzJobs(jobsasmlist?.Invoke()); 59 | types.ForEach(t => 60 | { 61 | var so = t.GetCustomAttribute(); 62 | services.AddQuartzJob(t, so.Identity ?? t.Name, so.Manual ? true : false, so.Group, so.Description ?? t.FullName); 63 | }); 64 | return services; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/Lato.css: -------------------------------------------------------------------------------- 1 | /* lato-regular - latin */ 2 | @font-face { 3 | font-family: 'Lato'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: url('lato-v14-latin-regular.eot'); /* IE9 Compat Modes */ 7 | src: local('Lato Regular'), local('Lato-Regular'), url('lato-v14-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 8 | url('lato-v14-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ 9 | url('lato-v14-latin-regular.woff') format('woff'), /* Modern Browsers */ 10 | url('lato-v14-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */ 11 | url('lato-v14-latin-regular.svg#Lato') format('svg'); /* Legacy iOS */ 12 | } 13 | /* lato-italic - latin */ 14 | @font-face { 15 | font-family: 'Lato'; 16 | font-style: italic; 17 | font-weight: 400; 18 | src: url('lato-v14-latin-italic.eot'); /* IE9 Compat Modes */ 19 | src: local('Lato Italic'), local('Lato-Italic'), url('lato-v14-latin-italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 20 | url('lato-v14-latin-italic.woff2') format('woff2'), /* Super Modern Browsers */ 21 | url('lato-v14-latin-italic.woff') format('woff'), /* Modern Browsers */ 22 | url('lato-v14-latin-italic.ttf') format('truetype'), /* Safari, Android, iOS */ 23 | url('lato-v14-latin-italic.svg#Lato') format('svg'); /* Legacy iOS */ 24 | } 25 | /* lato-700 - latin */ 26 | @font-face { 27 | font-family: 'Lato'; 28 | font-style: normal; 29 | font-weight: 700; 30 | src: url('lato-v14-latin-700.eot'); /* IE9 Compat Modes */ 31 | src: local('Lato Bold'), local('Lato-Bold'), url('lato-v14-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 32 | url('lato-v14-latin-700.woff2') format('woff2'), /* Super Modern Browsers */ 33 | url('lato-v14-latin-700.woff') format('woff'), /* Modern Browsers */ 34 | url('lato-v14-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */ 35 | url('lato-v14-latin-700.svg#Lato') format('svg'); /* Legacy iOS */ 36 | } 37 | /* lato-700italic - latin */ 38 | @font-face { 39 | font-family: 'Lato'; 40 | font-style: italic; 41 | font-weight: 700; 42 | src: url('lato-v14-latin-700italic.eot'); /* IE9 Compat Modes */ 43 | src: local('Lato Bold Italic'), local('Lato-BoldItalic'), url('lato-v14-latin-700italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 44 | url('lato-v14-latin-700italic.woff2') format('woff2'), /* Super Modern Browsers */ 45 | url('lato-v14-latin-700italic.woff') format('woff'), /* Modern Browsers */ 46 | url('lato-v14-latin-700italic.ttf') format('truetype'), /* Safari, Android, iOS */ 47 | url('lato-v14-latin-700italic.svg#Lato') format('svg'); /* Legacy iOS */ 48 | } 49 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-700.eot -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-700.ttf -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-700.woff -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-700.woff2 -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-700italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-700italic.eot -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-700italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-700italic.ttf -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-700italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-700italic.woff -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-700italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-700italic.woff2 -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-italic.eot -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-italic.ttf -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-italic.woff -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-italic.woff2 -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-regular.eot -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-regular.ttf -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-regular.woff -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Fonts/lato-v14-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Fonts/lato-v14-latin-regular.woff2 -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Images/favicons/favicon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Images/favicons/favicon-16.png -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Images/favicons/favicon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Images/favicons/favicon-192.png -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Images/favicons/favicon-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Images/favicons/favicon-24.png -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Images/favicons/favicon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Images/favicons/favicon-256.png -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Images/favicons/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Images/favicons/favicon-32.png -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Images/favicons/favicon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Images/favicons/favicon-48.png -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Images/favicons/favicon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Images/favicons/favicon-64.png -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Images/logo.png -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Images/type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Images/type.png -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/FileSaver.js: -------------------------------------------------------------------------------- 1 | FileSaver = { saveAs: function () { "use strict"; var u = "object" == typeof window && window.window === window ? window : "object" == typeof self && self.self === self ? self : "object" == typeof global && global.global === global ? global : void 0; function d(e, t, o) { var n = new XMLHttpRequest; n.open("GET", e), n.responseType = "blob", n.onload = function () { a(n.response, t, o) }, n.onerror = function () { console.error("could not download file") }, n.send() } function i(e) { var t = new XMLHttpRequest; return t.open("HEAD", e, !1), t.send(), 200 <= t.status && t.status <= 299 } function l(t) { try { t.dispatchEvent(new MouseEvent("click")) } catch (e) { var o = document.createEvent("MouseEvents"); o.initMouseEvent("click", !0, !0, window, 0, 0, 0, 80, 20, !1, !1, !1, !1, 0, null), t.dispatchEvent(o) } } var a = u.saveAs || "object" != typeof window || window !== u ? function () { } : "download" in HTMLAnchorElement.prototype ? function (e, t, o) { var n = u.URL || u.webkitURL, a = document.createElement("a"); t = t || e.name || "download", a.download = t, a.rel = "noopener", "string" == typeof e ? (a.href = e, a.origin !== location.origin ? i(a.href) ? d(e, t, o) : l(a, a.target = "_blank") : l(a)) : (a.href = n.createObjectURL(e), setTimeout(function () { n.revokeObjectURL(a.href) }, 4e4), setTimeout(function () { l(a) }, 0)) } : "msSaveOrOpenBlob" in navigator ? function (e, t, o) { if (t = t || e.name || "download", "string" == typeof e) if (i(e)) d(e, t, o); else { var n = document.createElement("a"); n.href = e, n.target = "_blank", setTimeout(function () { l(n) }) } else navigator.msSaveOrOpenBlob((a = e, void 0 === (r = o) ? r = { autoBom: !1 } : "object" != typeof r && (console.warn("Depricated: Expected third argument to be a object"), r = { autoBom: !r }), r.autoBom && /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type) ? new Blob([String.fromCharCode(65279), a], { type: a.type }) : a), t); var a, r } : function (e, t, o, n) { if ((n = n || open("", "_blank")) && (n.document.title = n.document.body.innerText = "downloading..."), "string" == typeof e) return d(e, t, o); var a = "application/octet-stream" === e.type, r = /constructor/i.test(u.HTMLElement) || u.safari, i = /CriOS\/[\d]+/.test(navigator.userAgent); if ((i || a && r) && "object" == typeof FileReader) { var l = new FileReader; l.onloadend = function () { var e = l.result; e = i ? e : e.replace(/^data:[^;]*;/, "data:attachment/file;"), n ? n.location.href = e : location = e, n = null }, l.readAsDataURL(e) } else { var c = u.URL || u.webkitURL, s = c.createObjectURL(e); n ? n.location = s : location.href = s, n = null, setTimeout(function () { c.revokeObjectURL(s) }, 4e4) } }; return u.saveAs = a.saveAs = a, a }() }; -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/semanticui/calendar.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * # Semantic UI 0.0.1 - Calendar 3 | * http://github.com/semantic-org/semantic-ui/ 4 | * 5 | * 6 | * Released under the MIT license 7 | * http://opensource.org/licenses/MIT 8 | * 9 | */.ui.calendar .ui.popup{max-width:none;padding:0;border:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ui.calendar .calendar:focus{outline:0}.ui.calendar .ui.table.minute,.ui.calendar .ui.table.month,.ui.calendar .ui.table.year{min-width:15em}.ui.calendar .ui.table.day{min-width:18em}.ui.calendar .ui.table.hour{min-width:20em}.ui.calendar .ui.table tr td,.ui.calendar .ui.table tr th{padding:.5em;white-space:nowrap}.ui.calendar .ui.table tr th{border-left:none}.ui.calendar .ui.table tr th .icon{margin:0}.ui.calendar .ui.table tr:first-child th{position:relative;padding-left:0;padding-right:0}.ui.calendar .ui.table.day tr:first-child th{border:none}.ui.calendar .ui.table.day tr:nth-child(2) th{padding-top:.2em;padding-bottom:.3em}.ui.calendar .ui.table tr td{padding-left:.1em;padding-right:.1em}.ui.calendar .ui.table tr .link{cursor:pointer}.ui.calendar .ui.table tr .prev.link{width:14.28571429%;position:absolute;left:0}.ui.calendar .ui.table tr .next.link{width:14.28571429%;position:absolute;right:0}.ui.calendar .ui.table tr .disabled{pointer-events:none;color:rgba(40,40,40,.3)}.ui.calendar .ui.table tr td.today{font-weight:700}.ui.calendar .ui.table tr td.range{background:rgba(0,0,0,.05);color:rgba(0,0,0,.95);box-shadow:none}.ui.calendar .ui.table.inverted tr td.range{background:rgba(255,255,255,.08);color:#fff;box-shadow:none}.ui.calendar .calendar.active .ui.table tbody tr td.focus,.ui.calendar .calendar.active .ui.table.inverted tbody tr td.focus,.ui.calendar .calendar:focus .ui.table tbody tr td.focus,.ui.calendar .calendar:focus .ui.table.inverted tbody tr td.focus{box-shadow:inset 0 0 0 1px #85B7D9} -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/brand-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/brand-icons.eot -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/brand-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/brand-icons.ttf -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/brand-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/brand-icons.woff -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/brand-icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/brand-icons.woff2 -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/icons.eot -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/icons.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/icons.otf -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/icons.ttf -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/icons.woff -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/icons.woff2 -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/outline-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/outline-icons.eot -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/outline-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/outline-icons.ttf -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/outline-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/outline-icons.woff -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/outline-icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IoTSharp/SilkierQuartz/4356f79ca2b024f4a859176217b1b4d8fbd2f600/src/SilkierQuartz/Content/Lib/semanticui/themes/default/assets/fonts/outline-icons.woff2 -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Lib/src: -------------------------------------------------------------------------------- 1 | https://cdn.jsdelivr.net/npm/semanticui@2.3.3/dist/semantic.min.css 2 | https://cdn.rawgit.com/mdehoog/semanticui-Calendar/76959c6f7d33a527b49be76789e984a0a407350b/dist/calendar.min.css 3 | 4 | https://code.jquery.com/jquery-3.3.1.min.js" 5 | https://cdn.jsdelivr.net/npm/semanticui@2.3.3/dist/semantic.min.js 6 | https://cdnjs.cloudflare.com/ajax/libs/semanticui/2.3.0/components/state.min.js 7 | https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js 8 | https://cdn.rawgit.com/mdehoog/semanticui-Calendar/76959c6f7d33a527b49be76789e984a0a407350b/dist/calendar.min.js 9 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Content/Scripts/post-validation.js: -------------------------------------------------------------------------------- 1 | 2 | function processValidationResponse(data, segmentRoot) { 3 | 4 | $('.accept-error.error').each(function () { $(this).removeClass('error'); }); 5 | 6 | function cleanup() { 7 | $(this) 8 | .off('focusin', cleanup) 9 | .removeClass('error'); 10 | } 11 | 12 | if (data.Success === false) { 13 | 14 | for (i = 0; i < data.Errors.length; i++) { 15 | var 16 | err = data.Errors[i], 17 | field = err.Field, 18 | errElm; 19 | 20 | if (field.startsWith('data-map[value]')) { 21 | var n = field.lastIndexOf(':'); 22 | if (n === -1) 23 | continue; 24 | var rowId = field.substring(n); 25 | errElm = $("[name='data-map[name]" + rowId + "']").closest('tr').find('.value-col.accept-error'); 26 | } else { 27 | if (segmentRoot && (err.SegmentIndex > 0 || err.SegmentIndex === 0)) { 28 | errElm = $('.segment:nth-child(' + (err.SegmentIndex + 1) + ') ' + "[name='" + field + "']", segmentRoot); 29 | } else { 30 | errElm = $("[name='" + field + "']"); 31 | } 32 | 33 | if (err.FieldIndex > 0 || err.FieldIndex === 0) 34 | errElm = $(errElm[err.FieldIndex]); 35 | 36 | if (!errElm.hasClass('accept-error')) 37 | errElm = errElm.closest('.accept-error'); 38 | } 39 | 40 | errElm.addClass('error'); 41 | errElm.on('focusin', cleanup); 42 | 43 | //errElm.popup({ 44 | // content: err.Reason, 45 | // hoverable: true, 46 | // position: 'top left', 47 | // variation: 'inverted', 48 | // distanceAway: -20 49 | //}); 50 | } 51 | } 52 | 53 | return data.Success; 54 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/Controllers/ExecutionsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using SilkierQuartz.Helpers; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | 8 | namespace SilkierQuartz.Controllers 9 | { 10 | [Authorize(Policy = SilkierQuartzAuthenticationOptions.AuthorizationPolicyName)] 11 | public class ExecutionsController : PageControllerBase 12 | { 13 | [HttpGet] 14 | public async Task Index() 15 | { 16 | var currentlyExecutingJobs = await Scheduler.GetCurrentlyExecutingJobs(); 17 | 18 | var list = new List(); 19 | 20 | foreach (var exec in currentlyExecutingJobs) 21 | { 22 | list.Add(new 23 | { 24 | Id = exec.FireInstanceId, 25 | JobGroup = exec.JobDetail.Key.Group, 26 | JobName = exec.JobDetail.Key.Name, 27 | TriggerGroup = exec.Trigger.Key.Group, 28 | TriggerName = exec.Trigger.Key.Name, 29 | ScheduledFireTime = exec.ScheduledFireTimeUtc?.UtcDateTime.ToDefaultFormat(), 30 | ActualFireTime = exec.FireTimeUtc.UtcDateTime.ToDefaultFormat(), 31 | RunTime = exec.JobRunTime.ToString("hh\\:mm\\:ss"), 32 | EnableEdit = EnableEdit 33 | }); 34 | } 35 | 36 | return View(list); 37 | } 38 | 39 | public class InterruptArgs 40 | { 41 | public string Id { get; set; } 42 | } 43 | 44 | [HttpPost, JsonErrorResponse] 45 | public async Task Interrupt([FromBody] InterruptArgs args) 46 | { 47 | if (!await Scheduler.Interrupt(args.Id)) 48 | throw new InvalidOperationException("Cannot interrupt execution " + args.Id); 49 | 50 | return NoContent(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Controllers/HistoryController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Quartz.Plugins.RecentHistory; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace SilkierQuartz.Controllers 10 | { 11 | [Authorize(Policy = SilkierQuartzAuthenticationOptions.AuthorizationPolicyName)] 12 | public class HistoryController : PageControllerBase 13 | { 14 | [HttpGet] 15 | public async Task Index() 16 | { 17 | var store = Scheduler.Context.GetExecutionHistoryStore(); 18 | 19 | ViewBag.HistoryEnabled = store != null; 20 | 21 | if (store == null) 22 | return View(null); 23 | 24 | var history = await store.FilterLast(100); 25 | 26 | var list = new List(); 27 | 28 | foreach (var h in history.OrderByDescending(x => x.ActualFireTimeUtc)) 29 | { 30 | string state = "Finished", icon = "check"; 31 | var endTime = h.FinishedTimeUtc; 32 | 33 | if (h.Vetoed) 34 | { 35 | state = "Vetoed"; 36 | icon = "ban"; 37 | } 38 | else if (!string.IsNullOrEmpty(h.ExceptionMessage)) 39 | { 40 | state = "Failed"; 41 | icon = "close"; 42 | } 43 | else if (h.FinishedTimeUtc == null) 44 | { 45 | state = "Running"; 46 | icon = "play"; 47 | endTime = DateTime.UtcNow; 48 | } 49 | 50 | var jobKey = h.Job.Split('.'); 51 | var triggerKey = h.Trigger.Split('.'); 52 | 53 | list.Add(new 54 | { 55 | Entity = h, 56 | 57 | JobGroup = jobKey[0], 58 | JobName = h.Job.Substring(jobKey[0].Length + 1), 59 | TriggerGroup = triggerKey[0], 60 | TriggerName = h.Trigger.Substring(triggerKey[0].Length + 1), 61 | 62 | ScheduledFireTimeUtc = h.ScheduledFireTimeUtc?.ToDefaultFormat(), 63 | ActualFireTimeUtc = h.ActualFireTimeUtc.ToDefaultFormat(), 64 | FinishedTimeUtc = h.FinishedTimeUtc?.ToDefaultFormat(), 65 | Duration = (endTime - h.ActualFireTimeUtc)?.ToString("hh\\:mm\\:ss"), 66 | State = state, 67 | StateIcon = icon 68 | }); 69 | } 70 | 71 | return View(list); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Controllers/JobDataMapController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | using SilkierQuartz.Helpers; 6 | using SilkierQuartz.TypeHandlers; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace SilkierQuartz.Controllers 12 | { 13 | [Authorize(Policy = SilkierQuartzAuthenticationOptions.AuthorizationPolicyName)] 14 | public class JobDataMapController : PageControllerBase 15 | { 16 | [HttpPost, JsonErrorResponse] 17 | public async Task ChangeType() 18 | { 19 | var formData = await Request.GetFormData(); 20 | 21 | TypeHandlerBase selectedType, targetType; 22 | try 23 | { 24 | selectedType = Services.TypeHandlers.Deserialize((string)formData.First(x => x.Key == "selected-type").Value); 25 | targetType = Services.TypeHandlers.Deserialize((string)formData.First(x => x.Key == "target-type").Value); 26 | } 27 | catch (JsonSerializationException ex) when (ex.Message.StartsWith("Could not create an instance of type")) 28 | { 29 | return new BadRequestResult(); 30 | } 31 | 32 | var dataMapForm = (await formData.GetJobDataMapForm(includeRowIndex: false)).SingleOrDefault(); // expected single row 33 | 34 | var oldValue = selectedType.ConvertFrom(dataMapForm); 35 | 36 | // phase 1: direct conversion 37 | var newValue = targetType.ConvertFrom(oldValue); 38 | 39 | if (oldValue != null && newValue == null) // if phase 1 failed 40 | { 41 | // phase 2: conversion using invariant string 42 | var str = selectedType.ConvertToString(oldValue); 43 | newValue = targetType.ConvertFrom(str); 44 | } 45 | 46 | return Html(targetType.RenderView(Services, newValue)); 47 | } 48 | 49 | [HttpGet, ActionName("TypeHandlers.js")] 50 | public IActionResult TypeHandlersScript() 51 | { 52 | var etag = Services.TypeHandlers.LastModified.ETag(); 53 | 54 | if (etag.Equals(GetETag())) 55 | return NotModified(); 56 | 57 | var execStubBuilder = new StringBuilder(); 58 | execStubBuilder.AppendLine(); 59 | foreach (var func in new[] { "init" }) 60 | execStubBuilder.AppendLine(string.Format("if (f === '{0}' && {0} !== 'undefined') {{ {0}.call(this); }}", func)); 61 | 62 | var execStub = execStubBuilder.ToString(); 63 | 64 | var js = Services.TypeHandlers.GetScripts().ToDictionary(x => x.Key, 65 | x => new JRaw("function(f) {" + x.Value + execStub + "}")); 66 | 67 | return TextFile("var $typeHandlerScripts = " + JsonConvert.SerializeObject(js) + ";", 68 | "application/javascript", Services.TypeHandlers.LastModified, etag); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/SilkierQuartz/DateTimeSettings.cs: -------------------------------------------------------------------------------- 1 | namespace SilkierQuartz 2 | { 3 | internal static class DateTimeSettings 4 | { 5 | public static string DefaultDateFormat { get; set; } = "MM/dd/yyyy"; 6 | public static string DefaultTimeFormat { get; set; } = "HH:mm:ss"; 7 | public static bool UseLocalTime { get; set; } = false; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/CronExpressionDescriptor/DescriptionTypeEnum.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace CronExpressionDescriptor 7 | { 8 | /// 9 | /// Enum to define the description "parts" of a Cron Expression 10 | /// 11 | public enum DescriptionTypeEnum 12 | { 13 | FULL, 14 | TIMEOFDAY, 15 | SECONDS, 16 | MINUTES, 17 | HOURS, 18 | DAYOFWEEK, 19 | MONTH, 20 | DAYOFMONTH, 21 | YEAR 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/CronExpressionDescriptor/Options.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Threading; 3 | 4 | namespace CronExpressionDescriptor 5 | { 6 | /// 7 | /// Options for parsing and describing a Cron Expression 8 | /// 9 | public class Options 10 | { 11 | public Options() 12 | { 13 | this.ThrowExceptionOnParseError = true; 14 | this.Verbose = false; 15 | this.DayOfWeekStartIndexZero = true; 16 | this.Use24HourTimeFormat = true; 17 | } 18 | 19 | public bool ThrowExceptionOnParseError { get; set; } 20 | public bool Verbose { get; set; } 21 | public bool DayOfWeekStartIndexZero { get; set; } 22 | public bool? Use24HourTimeFormat { get; set; } 23 | public string Locale { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/CronExpressionDescriptor/Resources.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CronExpressionDescriptor 4 | { 5 | internal static class Resources 6 | { 7 | private static readonly Dictionary _strings = new Dictionary() 8 | { 9 | ["AMPeriod"] = "AM", 10 | ["AnErrorOccuredWhenGeneratingTheExpressionD"] = "An error occured when generating the expression description. Check the cron expression syntax.", 11 | ["At"] = "At", 12 | ["AtSpace"] = "At ", 13 | ["AtX0"] = "at {0}", 14 | ["AtX0MinutesPastTheHour"] = "at {0} minutes past the hour", 15 | ["AtX0SecondsPastTheMinute"] = "at {0} seconds past the minute", 16 | ["BetweenX0AndX1"] = "between {0} and {1}", 17 | ["ComaBetweenDayX0AndX1OfTheMonth"] = ", between day {0} and {1} of the month", 18 | ["ComaEveryDay"] = ", every day", 19 | ["ComaEveryHour"] = ", every hour", 20 | ["ComaEveryMinute"] = ", every minute", 21 | ["ComaEveryX0Days"] = ", every {0} days", 22 | ["ComaEveryX0DaysOfTheWeek"] = ", every {0} days of the week", 23 | ["ComaEveryX0Months"] = ", every {0} months", 24 | ["ComaEveryX0Years"] = ", every {0} years", 25 | ["ComaOnDayX0OfTheMonth"] = ", on day {0} of the month", 26 | ["ComaOnlyInX0"] = ", only in {0}", 27 | ["ComaOnlyOnX0"] = ", only on {0}", 28 | ["ComaOnThe"] = ", on the ", 29 | ["ComaOnTheLastDayOfTheMonth"] = ", on the last day of the month", 30 | ["ComaOnTheLastWeekdayOfTheMonth"] = ", on the last weekday of the month", 31 | ["ComaOnTheLastX0OfTheMonth"] = ", on the last {0} of the month", 32 | ["ComaOnTheX0OfTheMonth"] = ", on the {0} of the month", 33 | ["ComaX0ThroughX1"] = ", {0} through {1}", 34 | ["CommaDaysBeforeTheLastDayOfTheMonth"] = ", {0} days before the last day of the month", 35 | ["CommaStartingX0"] = ", starting {0}", 36 | ["EveryHour"] = "every hour", 37 | ["EveryMinute"] = "every minute", 38 | ["EveryMinuteBetweenX0AndX1"] = "Every minute between {0} and {1}", 39 | ["EverySecond"] = "every second", 40 | ["EveryX0Hours"] = "every {0} hours", 41 | ["EveryX0Minutes"] = "every {0} minutes", 42 | ["EveryX0Seconds"] = "every {0} seconds", 43 | ["Fifth"] = "fifth", 44 | ["First"] = "first", 45 | ["FirstWeekday"] = "first weekday", 46 | ["Fourth"] = "fourth", 47 | ["MinutesX0ThroughX1PastTheHour"] = "minutes {0} through {1} past the hour", 48 | ["PMPeriod"] = "PM", 49 | ["Second"] = "second", 50 | ["SecondsX0ThroughX1PastTheMinute"] = "seconds {0} through {1} past the minute", 51 | ["SpaceAnd"] = " and", 52 | ["SpaceAndSpace"] = " and ", 53 | ["SpaceX0OfTheMonth"] = " {0} of the month", 54 | ["Third"] = "third", 55 | ["WeekdayNearestDayX0"] = "weekday nearest day {0}", 56 | }; 57 | 58 | public static string GetString(string name) 59 | { 60 | if (_strings.TryGetValue(name, out var result)) 61 | return result; 62 | return null; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/FromFormAttribute.cs: -------------------------------------------------------------------------------- 1 | #if NETFRAMEWORK 2 | 3 | using System; 4 | 5 | namespace SilkierQuartz.Helpers 6 | { 7 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] 8 | public class FromFormAttribute : Attribute 9 | { 10 | // just dummy attribute for full .NET 11 | } 12 | } 13 | 14 | #endif -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/JobDataMapRequest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using FormFile = SilkierQuartz.Models.FormFile; 6 | 7 | namespace SilkierQuartz.Helpers 8 | { 9 | internal static class JobDataMapRequest 10 | { 11 | public static Task[]> GetJobDataMapForm(this IEnumerable> formData, bool includeRowIndex = true) 12 | { 13 | // key1 is group, key2 is field 14 | var map = new Dictionary>(); 15 | 16 | foreach (var item in formData) 17 | { 18 | var g = GetJobDataMapFieldGroup(item.Key); 19 | if (g != null) 20 | { 21 | var field = item.Key.Substring(0, item.Key.Length - g.Length - 1); 22 | if (!map.ContainsKey(g)) 23 | map[g] = new Dictionary(); 24 | map[g][field] = item.Value; 25 | } 26 | } 27 | 28 | if (includeRowIndex) 29 | { 30 | foreach (var g in map.Keys) 31 | map[g]["data-map[index]"] = g; 32 | } 33 | 34 | return Task.FromResult(map.Values.ToArray()); 35 | } 36 | 37 | public static async Task[]> GetJobDataMapForm(this HttpRequest request, bool includeRowIndex = true) 38 | { 39 | return await GetJobDataMapForm(await request.GetFormData(), includeRowIndex); 40 | } 41 | 42 | static string GetJobDataMapFieldGroup(string field) 43 | { 44 | var n = field.LastIndexOf(':'); 45 | if (n == -1) 46 | return null; 47 | 48 | return field.Substring(n + 1); 49 | } 50 | 51 | public static Task>> GetFormData(this HttpRequest request) 52 | { 53 | var result = new List>(); 54 | 55 | foreach (var key in request.Form.Keys) 56 | { 57 | foreach (var strValue in request.Form[key]) 58 | result.Add(new KeyValuePair(key, strValue)); 59 | } 60 | foreach (var file in request.Form.Files) 61 | result.Add(new KeyValuePair(file.Name, new FormFile(file))); 62 | 63 | return Task.FromResult(result); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/JsonErrorResponse.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.Filters; 3 | using Microsoft.AspNetCore.Mvc.Infrastructure; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Serialization; 7 | using System.Text.Json; 8 | 9 | namespace SilkierQuartz.Helpers 10 | { 11 | public class JsonErrorResponseAttribute : ActionFilterAttribute 12 | { 13 | private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings() 14 | { 15 | ContractResolver = new DefaultContractResolver(), // PascalCase as default 16 | }; 17 | private static readonly object _systemTextSerializerOptions = new JsonSerializerOptions(); 18 | 19 | public override void OnActionExecuted(ActionExecutedContext context) 20 | { 21 | if (context.Exception != null) 22 | { 23 | var executor = context.HttpContext.RequestServices.GetRequiredService>(); 24 | var serializerOptions = executor.GetType().FullName.Contains("SystemTextJson") 25 | ? _systemTextSerializerOptions 26 | : _serializerSettings; 27 | 28 | context.Result = new JsonResult(new { ExceptionMessage = context.Exception.Message }, serializerOptions) { StatusCode = 400 }; 29 | context.ExceptionHandled = true; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/JsonPascalCaseNamingPolicy.cs: -------------------------------------------------------------------------------- 1 | #if NETCOREAPP 2 | using System; 3 | using System.Text.Json; 4 | 5 | namespace SilkierQuartz.Helpers 6 | { 7 | public class JsonPascalCaseNamingPolicy : JsonNamingPolicy 8 | { 9 | public override string ConvertName( string name ) 10 | { 11 | return name; 12 | } 13 | } 14 | } 15 | #endif -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/ModelValidator.cs: -------------------------------------------------------------------------------- 1 | using SilkierQuartz.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Linq; 6 | using System.Reflection; 7 | 8 | namespace SilkierQuartz.Helpers 9 | { 10 | public static class ModelValidator 11 | { 12 | public static void ValidateObject(IEnumerable collection, ICollection errors, string ownerField = null) 13 | { 14 | foreach (var item in collection) 15 | { 16 | ValidateObject(item, errors, ownerField); 17 | } 18 | } 19 | 20 | public static void Validate(IEnumerable collection, ICollection errors) 21 | where T: IHasValidation 22 | { 23 | foreach (var item in collection) 24 | { 25 | item.Validate(errors); 26 | } 27 | } 28 | 29 | public static void ValidateObject(object obj, ICollection errors, params string[] ownerField) 30 | { 31 | ValidateObject(obj, errors, true, ownerField); 32 | } 33 | 34 | public static void ValidateObject(object obj, ICollection errors, bool camelCase, params string[] ownerField) 35 | { 36 | var members = obj.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy); 37 | 38 | foreach (var p in members.OfType()) 39 | { 40 | if (p.GetCustomAttribute() != null) 41 | continue; 42 | 43 | var required = p.GetCustomAttribute(); 44 | 45 | if (required?.IsValid(p.GetValue(obj)) == false) 46 | { 47 | errors.Add(ValidationError.EmptyField(GetFieldName(p.Name, camelCase, ownerField))); 48 | } 49 | 50 | if (p.GetValue(obj) is IHasValidation nestedValidation) 51 | { 52 | nestedValidation.Validate(errors); 53 | } 54 | } 55 | } 56 | 57 | public static string GetFieldName(string field, bool camelCase, params string[] ownerField) 58 | { 59 | if (camelCase) 60 | { 61 | field = FirstCharToLower(field); 62 | ownerField = FirstCharToLower(ownerField); 63 | } 64 | 65 | if (ownerField == null || ownerField.Length == 0) 66 | return field; 67 | 68 | var path = string.Join(".", ownerField.Skip(1)); 69 | 70 | if (!string.IsNullOrEmpty(path)) 71 | path += "."; 72 | 73 | return $"{ownerField[0]}[{path}{field}]"; 74 | } 75 | 76 | static string[] FirstCharToLower(params string[] inputs) 77 | { 78 | if (inputs == null) 79 | return null; 80 | return inputs.Select(x => FirstCharToLower(x)).ToArray(); 81 | } 82 | 83 | static string FirstCharToLower(string input) 84 | { 85 | if (String.IsNullOrEmpty(input)) 86 | return input; 87 | 88 | return input[0].ToString().ToLower() + input.Substring(1); 89 | } 90 | 91 | 92 | } 93 | 94 | [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field , AllowMultiple = false)] 95 | public class SkipValidationAttribute : Attribute 96 | { 97 | 98 | } 99 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/MultipartDataMediaFormatter/Infrastructure/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | #if NETFRAMEWORK 2 | 3 | using System; 4 | using System.Collections; 5 | using System.ComponentModel; 6 | using MultipartDataMediaFormatter.Infrastructure.TypeConverters; 7 | 8 | namespace MultipartDataMediaFormatter.Infrastructure.Extensions 9 | { 10 | internal static class TypeExtensions 11 | { 12 | internal static FromStringConverterAdapter GetFromStringConverter(this Type type) 13 | { 14 | TypeConverter typeConverter = TypeDescriptor.GetConverter(type); 15 | 16 | if (typeConverter is BooleanConverter) 17 | { 18 | //replace default boolean converter for deserializing on/off values received from html page 19 | typeConverter = new BooleanConverterEx(); 20 | } 21 | if (typeConverter != null && !typeConverter.CanConvertFrom(typeof(String))) 22 | { 23 | typeConverter = null; 24 | } 25 | 26 | return typeConverter == null ? null : new FromStringConverterAdapter(type, typeConverter); 27 | } 28 | 29 | internal static TypeConverter GetToStringConverter(this Type type) 30 | { 31 | TypeConverter typeConverter = TypeDescriptor.GetConverter(type); 32 | if (typeConverter is DateTimeConverter) 33 | { 34 | //replace default datetime converter for serializing datetime in ISO 8601 format 35 | typeConverter = new DateTimeConverterISO8601(); 36 | } 37 | if (typeConverter != null && !typeConverter.CanConvertTo(typeof(String))) 38 | { 39 | typeConverter = null; 40 | } 41 | return typeConverter; 42 | } 43 | 44 | internal static bool IsCustomNonEnumerableType(this Type type) 45 | { 46 | var nullType = Nullable.GetUnderlyingType(type); 47 | if (nullType != null) 48 | { 49 | type = nullType; 50 | } 51 | if (type.IsGenericType) 52 | { 53 | type = type.GetGenericTypeDefinition(); 54 | } 55 | return type != typeof(object) 56 | && Type.GetTypeCode(type) == TypeCode.Object 57 | && type != typeof(HttpFile) 58 | && type != typeof(Guid) 59 | && type.GetInterface(typeof(IEnumerable).Name) == null; 60 | } 61 | } 62 | } 63 | #endif -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/MultipartDataMediaFormatter/Infrastructure/HttpFile.cs: -------------------------------------------------------------------------------- 1 | #if NETFRAMEWORK 2 | 3 | namespace MultipartDataMediaFormatter.Infrastructure 4 | { 5 | public class HttpFile 6 | { 7 | public string FileName { get; set; } 8 | public string MediaType { get; set; } 9 | public byte[] Buffer { get; set; } 10 | 11 | public HttpFile() { } 12 | 13 | public HttpFile(string fileName, string mediaType, byte[] buffer) 14 | { 15 | FileName = fileName; 16 | MediaType = mediaType; 17 | Buffer = buffer; 18 | } 19 | } 20 | } 21 | #endif -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/MultipartDataMediaFormatter/Infrastructure/Logger/FormDataConverterLogger.cs: -------------------------------------------------------------------------------- 1 | #if NETFRAMEWORK 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace MultipartDataMediaFormatter.Infrastructure.Logger 8 | { 9 | public class FormDataConverterLogger : IFormDataConverterLogger 10 | { 11 | private Dictionary> Errors { get; set; } 12 | 13 | public FormDataConverterLogger() 14 | { 15 | Errors = new Dictionary>(); 16 | } 17 | 18 | public void LogError(string errorPath, Exception exception) 19 | { 20 | AddError(errorPath, new LogErrorInfo(exception)); 21 | } 22 | 23 | public void LogError(string errorPath, string errorMessage) 24 | { 25 | AddError(errorPath, new LogErrorInfo(errorMessage)); 26 | } 27 | 28 | public List GetErrors() 29 | { 30 | return Errors.Select(m => new LogItem() 31 | { 32 | ErrorPath = m.Key, 33 | Errors = m.Value.Select(t => t).ToList() 34 | }).ToList(); 35 | } 36 | 37 | public void EnsureNoErrors() 38 | { 39 | if (Errors.Any()) 40 | { 41 | var errors = Errors 42 | .Select(m => String.Format("{0}: {1}", m.Key, String.Join(". ", m.Value.Select(x => (x.ErrorMessage ?? (x.Exception != null ? x.Exception.Message : "")))))) 43 | .ToList(); 44 | 45 | string errorMessage = String.Join(" ", errors); 46 | 47 | throw new Exception(errorMessage); 48 | } 49 | } 50 | 51 | private void AddError(string errorPath, LogErrorInfo info) 52 | { 53 | List listErrors; 54 | if (!Errors.TryGetValue(errorPath, out listErrors)) 55 | { 56 | listErrors = new List(); 57 | Errors.Add(errorPath, listErrors); 58 | } 59 | listErrors.Add(info); 60 | } 61 | 62 | public class LogItem 63 | { 64 | public string ErrorPath { get; set; } 65 | public List Errors { get; set; } 66 | } 67 | 68 | public class LogErrorInfo 69 | { 70 | public string ErrorMessage { get; private set; } 71 | public Exception Exception { get; private set; } 72 | public bool IsException { get; private set; } 73 | 74 | public LogErrorInfo(string errorMessage) 75 | { 76 | ErrorMessage = errorMessage; 77 | IsException = false; 78 | } 79 | 80 | public LogErrorInfo(Exception exception) 81 | { 82 | Exception = exception; 83 | IsException = true; 84 | } 85 | } 86 | } 87 | } 88 | #endif -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/MultipartDataMediaFormatter/Infrastructure/Logger/FormatterLoggerAdapter.cs: -------------------------------------------------------------------------------- 1 | #if NETFRAMEWORK 2 | 3 | using System; 4 | using System.Net.Http.Formatting; 5 | 6 | namespace MultipartDataMediaFormatter.Infrastructure.Logger 7 | { 8 | internal class FormatterLoggerAdapter : IFormDataConverterLogger 9 | { 10 | private IFormatterLogger FormatterLogger { get; set; } 11 | 12 | public FormatterLoggerAdapter(IFormatterLogger formatterLogger) 13 | { 14 | if(formatterLogger == null) 15 | throw new ArgumentNullException("formatterLogger"); 16 | FormatterLogger = formatterLogger; 17 | } 18 | 19 | public void LogError(string errorPath, Exception exception) 20 | { 21 | FormatterLogger.LogError(errorPath, exception); 22 | } 23 | 24 | public void LogError(string errorPath, string errorMessage) 25 | { 26 | FormatterLogger.LogError(errorPath, errorMessage); 27 | } 28 | 29 | public void EnsureNoErrors() 30 | { 31 | //nothing to do 32 | } 33 | } 34 | } 35 | #endif -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/MultipartDataMediaFormatter/Infrastructure/Logger/IFormDataConverterLogger.cs: -------------------------------------------------------------------------------- 1 | #if NETFRAMEWORK 2 | 3 | using System; 4 | 5 | namespace MultipartDataMediaFormatter.Infrastructure.Logger 6 | { 7 | public interface IFormDataConverterLogger 8 | { 9 | /// 10 | /// Logs an error. 11 | /// 12 | /// The path to the member for which the error is being logged. 13 | /// The exception to be logged. 14 | void LogError(string errorPath, Exception exception); 15 | 16 | /// 17 | /// Logs an error. 18 | /// 19 | /// The path to the member for which the error is being logged. 20 | /// The error message to be logged. 21 | void LogError(string errorPath, string errorMessage); 22 | 23 | /// 24 | /// throw exception if errors found 25 | /// 26 | void EnsureNoErrors(); 27 | } 28 | } 29 | #endif -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/MultipartDataMediaFormatter/Infrastructure/MultipartFormatterSettings.cs: -------------------------------------------------------------------------------- 1 | #if NETFRAMEWORK 2 | 3 | using System.Globalization; 4 | 5 | namespace MultipartDataMediaFormatter.Infrastructure 6 | { 7 | public class MultipartFormatterSettings 8 | { 9 | /// 10 | /// serialize byte array property as HttpFile when sending data if true or as indexed array if false 11 | /// (default value is "false) 12 | /// 13 | public bool SerializeByteArrayAsHttpFile { get; set; } 14 | 15 | /// 16 | /// add validation error "The value is required." if no value is present in request for non-nullable property if this parameter is "true" 17 | /// (default value is "false) 18 | /// 19 | public bool ValidateNonNullableMissedProperty { get; set; } 20 | 21 | private CultureInfo _CultureInfo; 22 | /// 23 | /// default is CultureInfo.CurrentCulture 24 | /// 25 | public CultureInfo CultureInfo 26 | { 27 | get { return _CultureInfo ?? CultureInfo.CurrentCulture; } 28 | set { _CultureInfo = value; } 29 | } 30 | } 31 | } 32 | #endif -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/MultipartDataMediaFormatter/Infrastructure/TypeConverters/BooleanConverterEx.cs: -------------------------------------------------------------------------------- 1 | #if NETFRAMEWORK 2 | 3 | using System; 4 | using System.ComponentModel; 5 | using System.Globalization; 6 | 7 | namespace MultipartDataMediaFormatter.Infrastructure.TypeConverters 8 | { 9 | public class BooleanConverterEx : BooleanConverter 10 | { 11 | public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) 12 | { 13 | if (value != null) 14 | { 15 | var str = value.ToString(); 16 | 17 | if (String.Compare(str, "on", culture, CompareOptions.IgnoreCase) == 0) 18 | return true; 19 | 20 | if (String.Compare(str, "off", culture, CompareOptions.IgnoreCase) == 0) 21 | return false; 22 | } 23 | 24 | return base.ConvertFrom(context, culture, value); 25 | } 26 | } 27 | } 28 | #endif -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/MultipartDataMediaFormatter/Infrastructure/TypeConverters/DateTimeConverterISO8601.cs: -------------------------------------------------------------------------------- 1 | #if NETFRAMEWORK 2 | 3 | using System; 4 | using System.ComponentModel; 5 | using System.Globalization; 6 | 7 | namespace MultipartDataMediaFormatter.Infrastructure.TypeConverters 8 | { 9 | /// 10 | /// convert datetime to ISO 8601 format string 11 | /// 12 | internal class DateTimeConverterISO8601 : DateTimeConverter 13 | { 14 | public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) 15 | { 16 | if (value != null && value is DateTime && destinationType == typeof (string)) 17 | { 18 | return ((DateTime)value).ToString("O"); // ISO 8601 19 | } 20 | return base.ConvertTo(context, culture, value, destinationType); 21 | } 22 | } 23 | } 24 | #endif -------------------------------------------------------------------------------- /src/SilkierQuartz/Helpers/MultipartDataMediaFormatter/Infrastructure/TypeConverters/FromStringConverterAdapter.cs: -------------------------------------------------------------------------------- 1 | #if NETFRAMEWORK 2 | 3 | using System; 4 | using System.ComponentModel; 5 | using System.Globalization; 6 | 7 | namespace MultipartDataMediaFormatter.Infrastructure.TypeConverters 8 | { 9 | public class FromStringConverterAdapter 10 | { 11 | private readonly Type Type; 12 | private readonly TypeConverter TypeConverter; 13 | public FromStringConverterAdapter(Type type, TypeConverter typeConverter) 14 | { 15 | if(type == null) 16 | throw new ArgumentNullException("type"); 17 | if (typeConverter == null) 18 | throw new ArgumentNullException("typeConverter"); 19 | 20 | Type = type; 21 | TypeConverter = typeConverter; 22 | } 23 | 24 | public object ConvertFromString(string src, CultureInfo culture) 25 | { 26 | var isUndefinedNullable = Nullable.GetUnderlyingType(Type) != null && src == "undefined"; 27 | if (isUndefinedNullable) 28 | return null; 29 | 30 | return TypeConverter.ConvertFromString(null, culture, src); 31 | } 32 | } 33 | } 34 | #endif -------------------------------------------------------------------------------- /src/SilkierQuartz/HostedService/IJobRegistrator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace SilkierQuartz.HostedService 4 | { 5 | public interface IJobRegistrator 6 | { 7 | IServiceCollection Services { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/HostedService/IScheduleJob.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Quartz; 3 | 4 | namespace SilkierQuartz.HostedService 5 | { 6 | internal interface IScheduleJob 7 | { 8 | IJobDetail JobDetail { get; set; } 9 | IEnumerable Triggers { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/HostedService/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using Quartz; 4 | using Quartz.Impl; 5 | using Quartz.Spi; 6 | using SilkierQuartz.HostedService; 7 | using System; 8 | using System.Collections.Specialized; 9 | 10 | namespace SilkierQuartz 11 | { 12 | public static class IServiceCollectionExtensions 13 | { 14 | private static bool _quartzHostedServiceIsAdded = false; 15 | /// 16 | /// Must be call after Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults() 17 | /// 18 | /// 19 | /// 20 | public static IHostBuilder ConfigureSilkierQuartzHost(this IHostBuilder builder) 21 | { 22 | _quartzHostedServiceIsAdded = true; 23 | return builder.ConfigureServices(services => services.AddHostedService()); 24 | } 25 | [Obsolete("We recommend ConfigureSilkierQuartzHost")] 26 | public static IHostBuilder ConfigureQuartzHost(this IHostBuilder builder) => builder.ConfigureSilkierQuartzHost(); 27 | 28 | public static IJobRegistrator UseQuartzHostedService(this IServiceCollection services, 29 | Action stdSchedulerFactoryOptions) 30 | { 31 | if (!_quartzHostedServiceIsAdded) 32 | { 33 | services.AddHostedService(); 34 | } 35 | services.AddTransient(provider => 36 | { 37 | var options = new NameValueCollection(); 38 | stdSchedulerFactoryOptions?.Invoke(options); 39 | var result = new StdSchedulerFactory(); 40 | if (options.Count > 0) 41 | result.Initialize(options); 42 | return result; 43 | }); 44 | services.AddSingleton(); 45 | return new JobRegistrator(services); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/SilkierQuartz/HostedService/JobOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SilkierQuartz.HostedService 6 | { 7 | public class JobOptions 8 | { 9 | public TriggerOptions[] Triggers { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/SilkierQuartz/HostedService/JobRegistrator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace SilkierQuartz.HostedService 7 | { 8 | internal class JobRegistrator : IJobRegistrator 9 | { 10 | public JobRegistrator(IServiceCollection services) 11 | { 12 | Services = services; 13 | } 14 | 15 | public IServiceCollection Services { get; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SilkierQuartz/HostedService/QuartzHostedService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Quartz.Impl; 9 | using Quartz; 10 | using Quartz.Spi; 11 | using System.Linq; 12 | 13 | namespace SilkierQuartz.HostedService 14 | { 15 | internal class QuartzHostedService : IHostedService 16 | { 17 | private IServiceProvider Services { get; } 18 | private IScheduler _scheduler; 19 | private ISchedulerFactory _schedulerFactory; 20 | private IJobFactory _jobFactory { get; } 21 | 22 | public QuartzHostedService( 23 | IServiceProvider services, 24 | ISchedulerFactory schedulerFactory, 25 | IJobFactory jobFactory) 26 | { 27 | Services = services; 28 | _schedulerFactory = schedulerFactory; 29 | _jobFactory = jobFactory; 30 | } 31 | 32 | public async Task StartAsync(CancellationToken cancellationToken) 33 | { 34 | var _scheduleJobs = Services.GetService>(); 35 | 36 | _scheduler = await _schedulerFactory.GetScheduler(); 37 | _scheduler.JobFactory = _jobFactory; 38 | 39 | await _scheduler.Start(cancellationToken); 40 | 41 | if (_scheduleJobs == null || !_scheduleJobs.Any()) 42 | return; 43 | 44 | foreach (var scheduleJob in _scheduleJobs) 45 | { 46 | if (scheduleJob.Triggers != null && scheduleJob.Triggers.Any()) 47 | { 48 | var isNewJob = true; 49 | foreach (var trigger in scheduleJob.Triggers) 50 | { 51 | 52 | if (isNewJob) 53 | { 54 | if (!(await _scheduler.CheckExists(scheduleJob.JobDetail.Key, cancellationToken))) 55 | { 56 | await _scheduler.ScheduleJob(scheduleJob.JobDetail, trigger, cancellationToken); 57 | } 58 | } 59 | else 60 | { 61 | if (!(await _scheduler.CheckExists(trigger.Key, cancellationToken))) 62 | { 63 | await _scheduler.ScheduleJob(trigger, cancellationToken); 64 | } 65 | } 66 | isNewJob = false; 67 | } 68 | } 69 | else 70 | { 71 | await _scheduler.AddJob(scheduleJob.JobDetail, true, cancellationToken); 72 | } 73 | } 74 | } 75 | 76 | public async Task StopAsync(CancellationToken cancellationToken) 77 | { 78 | if (_scheduler.IsStarted) 79 | await _scheduler.Shutdown(cancellationToken); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/SilkierQuartz/HostedService/ScheduleJob.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace SilkierQuartz.HostedService 6 | { 7 | internal class ScheduleJob : IScheduleJob 8 | { 9 | public ScheduleJob(IJobDetail jobDetail, IEnumerable triggers) 10 | { 11 | JobDetail = jobDetail; 12 | Triggers = triggers; 13 | } 14 | 15 | public IJobDetail JobDetail { get; set; } 16 | public IEnumerable Triggers { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/SilkierQuartz/HostedService/ServiceCollectionJobFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Quartz; 3 | using Quartz.Spi; 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | 9 | namespace SilkierQuartz.HostedService 10 | { 11 | public class ServiceCollectionJobFactory : IJobFactory 12 | { 13 | protected readonly IServiceProvider Container; 14 | private ConcurrentDictionary _createdJob = new ConcurrentDictionary(); 15 | 16 | public ServiceCollectionJobFactory(IServiceProvider container) 17 | { 18 | Container = container; 19 | } 20 | 21 | public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) 22 | { 23 | var scoped = Container.CreateScope(); 24 | var result = scoped.ServiceProvider.GetService(bundle.JobDetail.JobType) as IJob; 25 | if (result != null) 26 | { 27 | _createdJob.AddOrUpdate(result, scoped, (j, s) => scoped); 28 | } 29 | return result; 30 | } 31 | 32 | public void ReturnJob(IJob job) 33 | { 34 | if (_createdJob.TryRemove(job, out var scope)) 35 | { 36 | scope.Dispose(); 37 | } 38 | 39 | var disposable = job as IDisposable; 40 | disposable?.Dispose(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/HostedService/TriggerOptions.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace SilkierQuartz.HostedService 7 | { 8 | public class TriggerOptions 9 | { 10 | public string CRONExpression { get; set; } 11 | 12 | public int? Priority { get; set; } 13 | 14 | public virtual TriggerBuilder CreateTriggerBuilder() 15 | { 16 | var result = TriggerBuilder.Create(); 17 | if (Priority.HasValue) 18 | result.WithPriority(Priority.Value); 19 | 20 | if(!string.IsNullOrEmpty(CRONExpression)) 21 | result.WithCronSchedule(CRONExpression); 22 | 23 | return result; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Models/AuthenticateViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace SilkierQuartz.Models 4 | { 5 | public class AuthenticateViewModel 6 | { 7 | [Required] 8 | public string UserName { get; set; } 9 | [Required] 10 | public string Password { get; set; } 11 | 12 | public bool IsPersist { get; set; } 13 | 14 | public bool IsLoginError { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/Models/CalendarListItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace SilkierQuartz.Models 7 | { 8 | public class CalendarListItem 9 | { 10 | public string Name { get; set; } 11 | 12 | public string Description { get; set; } 13 | 14 | public Type Type { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Models/FormFile.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SilkierQuartz.Models 4 | { 5 | public class FormFile 6 | { 7 | #if ( NETSTANDARD || NETCOREAPP ) 8 | readonly Microsoft.AspNetCore.Http.IFormFile _file; 9 | public FormFile(Microsoft.AspNetCore.Http.IFormFile file) => _file = file; 10 | 11 | public Stream GetStream() => _file.OpenReadStream(); 12 | #endif 13 | #if NETFRAMEWORK 14 | readonly System.Net.Http.HttpContent _content; 15 | public FormFile(System.Net.Http.HttpContent content) => _content = content; 16 | 17 | public Stream GetStream() => _content.ReadAsStreamAsync().GetAwaiter().GetResult(); 18 | #endif 19 | 20 | public byte[] GetBytes() 21 | { 22 | using (var stream = new MemoryStream()) 23 | { 24 | GetStream().CopyTo(stream); 25 | return stream.ToArray(); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Models/Histogram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace SilkierQuartz.Models 7 | { 8 | public class Histogram 9 | { 10 | public List Bars { get; set; } = new List(); 11 | 12 | public int ComputedWidth => Bars.Count * BarWidth; 13 | 14 | public int BarWidth { get; set; } = 6; 15 | 16 | public class Bar 17 | { 18 | public int ComputedLeft { get; internal set; } 19 | 20 | public double Value { get; set; } 21 | 22 | public double Percentage { get; set; } 23 | 24 | public string Tooltip { get; set; } 25 | 26 | public string CssClass { get; set; } 27 | } 28 | 29 | public void AddBar(double value, string tooltip, string cssClass) 30 | { 31 | Bars.Add(new Bar() { Value = value, Tooltip = tooltip, CssClass = cssClass }); 32 | } 33 | 34 | internal void Layout() 35 | { 36 | var max = Bars.Max(x => x.Value); 37 | var i = 0; 38 | foreach (var b in Bars) 39 | { 40 | b.ComputedLeft = i * BarWidth; 41 | b.Percentage = Math.Round(b.Value / max * 100); 42 | 43 | i++; 44 | } 45 | } 46 | 47 | public static Histogram CreateEmpty() 48 | { 49 | var hst = new Histogram(); 50 | 51 | for (var i = 0; i < 10; i++) 52 | { 53 | hst.Bars.Add(new Bar() 54 | { 55 | Value = i % 3 + i % 5 + 1, 56 | CssClass = "grey", 57 | }); 58 | } 59 | return hst; 60 | } 61 | 62 | private static readonly Lazy _empty = new Lazy(CreateEmpty); 63 | 64 | public static Histogram Empty => _empty.Value; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Models/IHasValidation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SilkierQuartz.Models 4 | { 5 | public interface IHasValidation 6 | { 7 | void Validate(ICollection errors); 8 | } 9 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/Models/JobDataMapItem.cs: -------------------------------------------------------------------------------- 1 | using SilkierQuartz.TypeHandlers; 2 | 3 | namespace SilkierQuartz.Models 4 | { 5 | public class JobDataMapItem : JobDataMapItemBase 6 | { 7 | public TypeHandlerBase[] SupportedTypes { get; set; } 8 | 9 | public string Description { get; set; } 10 | 11 | public bool Enabled { get; set; } = true; 12 | 13 | public bool EditableName { get; set; } = true; 14 | 15 | public bool Mandatory { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Models/JobDataMapItemBase.cs: -------------------------------------------------------------------------------- 1 | using SilkierQuartz.TypeHandlers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel.DataAnnotations; 5 | 6 | namespace SilkierQuartz.Models 7 | { 8 | public class JobDataMapItemBase : IHasValidation 9 | { 10 | [Required] 11 | public string Name { get; set; } 12 | 13 | public object Value { get; set; } 14 | 15 | [Required] 16 | public TypeHandlerBase SelectedType { get; set; } 17 | 18 | public bool IsLast { get; set; } 19 | 20 | public string RowId { get; set; } 21 | 22 | const string NameField = "data-map[name]"; 23 | const string HandlerField = "data-map[handler]"; 24 | const string TypeField = "data-map[type]"; 25 | const string IndexField = "data-map[index]"; 26 | const string ValueField = "data-map[value]"; 27 | const string LastItemField = "data-map[lastItem]"; 28 | 29 | public static JobDataMapItemBase FromDictionary(Dictionary formData, Services services) 30 | { 31 | var valueFormData = new Dictionary(); 32 | 33 | var result = new JobDataMapItemBase(); 34 | 35 | foreach (var item in formData) 36 | { 37 | if (item.Key == NameField) 38 | { 39 | result.Name = (string)item.Value; 40 | continue; 41 | } 42 | if (item.Key == HandlerField) 43 | { 44 | result.SelectedType = services.TypeHandlers.Deserialize((string)item.Value); 45 | continue; 46 | } 47 | if (item.Key == TypeField) 48 | { 49 | continue; 50 | } 51 | if (item.Key == IndexField) 52 | { 53 | result.RowId = (string)item.Value; 54 | continue; 55 | } 56 | if (item.Key == LastItemField) 57 | { 58 | result.IsLast = Convert.ToBoolean(item.Value); 59 | continue; 60 | } 61 | 62 | valueFormData.Add(item.Key, item.Value); 63 | } 64 | 65 | if (result.SelectedType != null) 66 | result.Value = result.SelectedType.ConvertFrom(valueFormData); 67 | 68 | return result; 69 | } 70 | 71 | public override string ToString() 72 | { 73 | if (Name != null) 74 | { 75 | if (Value != null) 76 | return $"{Name} = {Value}"; 77 | else 78 | return Name; 79 | } 80 | 81 | return base.ToString(); 82 | } 83 | 84 | public void Validate(ICollection errors) 85 | { 86 | if (string.IsNullOrEmpty(Name)) 87 | AddValidationError(NameField, errors); 88 | 89 | if (SelectedType == null) 90 | AddValidationError(TypeField, errors); 91 | 92 | if (SelectedType?.IsValid(Value) == false) 93 | AddValidationError(ValueField, errors); 94 | } 95 | 96 | void AddValidationError(string field, ICollection errors) 97 | { 98 | errors.Add(ValidationError.EmptyField(field + ":" + RowId)); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Models/JobDataMapModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SilkierQuartz.Models 4 | { 5 | public class JobDataMapModel 6 | { 7 | public List Items { get; } = new List(); 8 | public JobDataMapItem Template { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Models/JobListItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace SilkierQuartz.Models 7 | { 8 | public class JobListItem 9 | { 10 | public string JobName { get; set; } 11 | 12 | public string Group { get; set; } 13 | 14 | public string Type { get; set; } 15 | 16 | public string Description { get; set; } 17 | 18 | 19 | public bool Recovery { get; set; } 20 | 21 | public bool Persist { get; set; } // Persist job data 22 | 23 | public bool Concurrent { get; set; } 24 | 25 | public Histogram History { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Models/JobViewModel.cs: -------------------------------------------------------------------------------- 1 | using SilkierQuartz.Helpers; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace SilkierQuartz.Models 6 | { 7 | public class JobViewModel : IHasValidation 8 | { 9 | public JobPropertiesViewModel Job { get; set; } 10 | public JobDataMapModel DataMap { get; set; } 11 | 12 | public void Validate(ICollection errors) => ModelValidator.ValidateObject(this, errors); 13 | } 14 | 15 | public class JobPropertiesViewModel : IHasValidation 16 | { 17 | public bool IsNew { get; set; } 18 | 19 | public bool IsCopy { get; set; } 20 | 21 | [Required] 22 | public string JobName { get; set; } 23 | 24 | [Required] 25 | public string Group { get; set; } 26 | 27 | public string OldJobName { get; set; } 28 | public string OldGroup { get; set; } 29 | 30 | public IEnumerable GroupList { get; set; } 31 | 32 | [Required] 33 | public string Type { get; set; } 34 | 35 | public IEnumerable TypeList { get; set; } 36 | 37 | public string Description { get; set; } 38 | 39 | public bool Recovery { get; set; } 40 | 41 | public bool Concurrent { get; set; } 42 | 43 | public bool Durable { get; set; } 44 | 45 | public bool Persist { get; set; } 46 | 47 | 48 | public void Validate(ICollection errors) => ModelValidator.ValidateObject(this, errors, nameof(JobViewModel.Job)); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Models/KeyModel.cs: -------------------------------------------------------------------------------- 1 | using Quartz; 2 | 3 | namespace SilkierQuartz.Models 4 | { 5 | public class KeyModel 6 | { 7 | public string Name { get; set; } 8 | 9 | public string Group { get; set; } 10 | 11 | public JobKey ToJobKey() => new JobKey(Name, Group); 12 | 13 | public TriggerKey ToTriggerKey() => new TriggerKey(Name, Group); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Models/TriggerListItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace SilkierQuartz.Models 7 | { 8 | public class TriggerListItem 9 | { 10 | public string JobKey { get; set; } 11 | 12 | public string JobName { get; set; } 13 | 14 | public string JobGroup { get; set; } 15 | 16 | public string TriggerName { get; set; } 17 | 18 | public string TriggerGroup { get; set; } 19 | 20 | public bool IsPaused { get; set; } 21 | 22 | public TriggerType Type { get; set; } 23 | 24 | public string ClrType { get; set; } 25 | 26 | public string Description { get; set; } 27 | 28 | public string StartTime { get; set; } 29 | public string EndTime { get; set; } 30 | 31 | 32 | public string LastFireTime { get; set; } 33 | public string NextFireTime { get; set; } 34 | 35 | public string ScheduleDescription { get; set; } 36 | 37 | public Histogram History { get; set; } 38 | 39 | public bool JobHeaderSeparator { get; set; } 40 | 41 | public string TypeIcon 42 | { 43 | get 44 | { 45 | switch (Type) 46 | { 47 | case TriggerType.Cron: 48 | return "clock outline"; 49 | case TriggerType.Simple: 50 | return "redo"; 51 | case TriggerType.Daily: 52 | return "tasks"; 53 | case TriggerType.Calendar: 54 | return "calendar alternate outline"; 55 | default: 56 | return "bug"; 57 | } 58 | } 59 | } 60 | 61 | public string TypeString 62 | { 63 | get 64 | { 65 | if (Type == TriggerType.Unknown) 66 | return ClrType; 67 | else 68 | return Type.ToString(); 69 | } 70 | } 71 | 72 | public bool EnableEdit { get; set; } 73 | } 74 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/Models/ValidationResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace SilkierQuartz.Models 5 | { 6 | public class ValidationResult 7 | { 8 | public bool Success => !Errors.Any(); 9 | 10 | public List Errors { get; set; } = new List(); 11 | } 12 | 13 | public class ValidationError 14 | { 15 | public string Field { get; set; } 16 | public string Reason { get; set; } 17 | public int? SegmentIndex { get; set; } 18 | public int? FieldIndex { get; set; } 19 | 20 | public static ValidationError EmptyField(string field) 21 | { 22 | return new ValidationError() { Field = field, Reason = "The field cannot be empty." }; 23 | } 24 | 25 | public override string ToString() 26 | { 27 | return $"{Field}: {Reason}"; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("SilkierQuartz.Test")] -------------------------------------------------------------------------------- /src/SilkierQuartz/Services.cs: -------------------------------------------------------------------------------- 1 | using HandlebarsDotNet; 2 | using Quartz; 3 | using SilkierQuartz.Helpers; 4 | 5 | namespace SilkierQuartz 6 | { 7 | public class Services 8 | { 9 | internal const string ContextKey = "SilkierQuartz.services"; 10 | 11 | public SilkierQuartzOptions Options { get; set; } 12 | 13 | public ViewEngine ViewEngine { get; set; } 14 | 15 | public IHandlebars Handlebars { get; set; } 16 | 17 | public TypeHandlerService TypeHandlers { get; set; } 18 | 19 | public IScheduler Scheduler { get; set; } 20 | 21 | internal Cache Cache { get; private set; } 22 | 23 | public static Services Create(SilkierQuartzOptions options, SilkierQuartzAuthenticationOptions authenticationOptions) 24 | { 25 | var handlebarsConfiguration = new HandlebarsConfiguration() 26 | { 27 | FileSystem = ViewFileSystemFactory.Create(options), 28 | ThrowOnUnresolvedBindingExpression = true, 29 | }; 30 | 31 | var services = new Services() 32 | { 33 | Options = options, 34 | Scheduler = options.Scheduler, 35 | Handlebars = HandlebarsDotNet.Handlebars.Create(handlebarsConfiguration), 36 | }; 37 | 38 | HandlebarsHelpers.Register(services, authenticationOptions); 39 | 40 | services.ViewEngine = new ViewEngine(services); 41 | services.TypeHandlers = new TypeHandlerService(services); 42 | services.Cache = new Cache(services); 43 | 44 | return services; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/Attributes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Text; 5 | 6 | namespace SilkierQuartz.TypeHandlers 7 | { 8 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 9 | public class TypeHandlerResourcesAttribute : Attribute 10 | { 11 | public string Template { get; set; } 12 | public string Script { get; set; } 13 | 14 | public static TypeHandlerResourcesAttribute GetResolved(Type type) 15 | { 16 | var attr = type.GetCustomAttribute(inherit: true) 17 | ?? throw new ArgumentException(type.FullName + " missing attribute " + nameof(TypeHandlerResourcesAttribute)); 18 | attr.Resolve(); 19 | return attr; 20 | } 21 | 22 | protected virtual void Resolve() { } 23 | } 24 | 25 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 26 | public class EmbeddedTypeHandlerResourcesAttribute : TypeHandlerResourcesAttribute 27 | { 28 | /// 29 | /// Should override when used in another assembly. 30 | /// 31 | protected virtual Assembly Assembly => Assembly.GetExecutingAssembly(); 32 | /// 33 | /// Should override when used in another assembly. 34 | /// 35 | protected virtual string Namespace => typeof(EmbeddedTypeHandlerResourcesAttribute).Namespace; 36 | 37 | protected override void Resolve() 38 | { 39 | Script = Script ?? GetManifestResourceString($"{_name}.js"); 40 | Template = Template ?? GetManifestResourceString($"{_name}.hbs"); 41 | } 42 | 43 | private readonly string _name; 44 | public EmbeddedTypeHandlerResourcesAttribute(string name) 45 | { 46 | _name = name; 47 | } 48 | 49 | protected string GetManifestResourceString(string name) 50 | { 51 | var fullName = $"{Namespace}.{name}"; 52 | using (var stream = Assembly.GetManifestResourceStream(fullName)) 53 | { 54 | if (stream == null) 55 | throw new InvalidOperationException("Embedded resource not found: " + fullName + " in assembly: " + Assembly.FullName); 56 | 57 | using (var reader = new StreamReader(stream, Encoding.UTF8)) 58 | return reader.ReadToEnd(); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/BooleanHandler.cs: -------------------------------------------------------------------------------- 1 | namespace SilkierQuartz.TypeHandlers 2 | { 3 | [EmbeddedTypeHandlerResources(nameof(BooleanHandler))] 4 | public class BooleanHandler : TypeHandlerBase 5 | { 6 | public override bool CanHandle(object value) 7 | { 8 | return value is bool; 9 | } 10 | 11 | public override object ConvertFrom(object value) 12 | { 13 | if (value is bool) 14 | return value; 15 | 16 | if (value is string str && bool.TryParse(str, out var result)) 17 | return result; 18 | 19 | return null; 20 | } 21 | 22 | public override string ConvertToString(object value) => base.ConvertToString(value ?? false); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/BooleanHandler.hbs: -------------------------------------------------------------------------------- 1 | 
2 | -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/BooleanHandler.js: -------------------------------------------------------------------------------- 1 | function init() { 2 | var $input = $('input', this); 3 | 4 | var $toggle = $('.ui.toggle.button', this) 5 | .state({ text: { inactive: 'False', active: 'True' } }) 6 | .click(function () { $input.val($toggle.text()); }); 7 | 8 | if ($input.val().toLowerCase() === 'true') { 9 | $toggle.text('True'); 10 | $toggle.addClass('active'); 11 | } else { 12 | $toggle.text('False'); 13 | } 14 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/DateTimeHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace SilkierQuartz.TypeHandlers 5 | { 6 | [EmbeddedTypeHandlerResources(nameof(DateTimeHandler))] 7 | public class DateTimeHandler : TypeHandlerBase 8 | { 9 | public bool IgnoreTimeComponent { get; set; } 10 | 11 | public bool IsUtc { get; set; } 12 | 13 | private string _dateFormat = null; 14 | public string DateFormat 15 | { 16 | get => _dateFormat ?? DateTimeSettings.DefaultDateFormat; 17 | set => _dateFormat = value; 18 | } 19 | 20 | private string _timeFormat = null; 21 | public string TimeFormat 22 | { 23 | get => _timeFormat ?? DateTimeSettings.DefaultTimeFormat; 24 | set => _timeFormat = value; 25 | } 26 | 27 | public string GetExpectedFormat() => IgnoreTimeComponent ? DateFormat : $"{DateFormat} {TimeFormat}"; 28 | 29 | public override bool CanHandle(object value) 30 | { 31 | if (value is DateTime dt) 32 | { 33 | var missingTime = dt.TimeOfDay.Ticks == 0; 34 | 35 | if (IgnoreTimeComponent == missingTime || IsUtc) 36 | return (IsUtc == (dt.Kind == DateTimeKind.Utc)); 37 | } 38 | 39 | return false; 40 | } 41 | 42 | public override object ConvertFrom(object value) 43 | { 44 | if (value is DateTime dt) 45 | return Normalize(dt); 46 | 47 | if (value is string str && DateTime.TryParseExact(str, new[] { $"{DateFormat} {TimeFormat}", DateFormat }, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)) 48 | return Normalize(result); 49 | 50 | return null; 51 | } 52 | 53 | DateTime Normalize(DateTime dt) 54 | { 55 | if (IgnoreTimeComponent) 56 | return DateTime.SpecifyKind(dt.Date, DateTimeKind.Unspecified); 57 | else 58 | return DateTime.SpecifyKind(dt, IsUtc ? DateTimeKind.Utc : DateTimeKind.Local); 59 | } 60 | 61 | public override string ConvertToString(object value) 62 | { 63 | return String.Format(CultureInfo.InvariantCulture, $"{{0:{GetExpectedFormat()}}}", value); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/DateTimeHandler.hbs: -------------------------------------------------------------------------------- 1 | 
2 |
3 | 4 | 5 |
6 |
7 | -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/DateTimeHandler.js: -------------------------------------------------------------------------------- 1 | function init() { 2 | var 3 | $cal = $('.ui.calendar', this), 4 | dateFmt = $cal.data('date-format').toUpperCase(), 5 | timeFmt = $cal.data('time-format'), 6 | fmt = dateFmt + ' ' + timeFmt, 7 | calType = 'datetime'; 8 | 9 | if ($cal.data('date-only').toLowerCase() === 'true') 10 | calType = 'date'; 11 | 12 | $cal.calendar({ 13 | type: calType, 14 | ampm: false, 15 | formatter: { 16 | date: function (date, settings) { 17 | return moment(date).format(dateFmt); 18 | }, 19 | time: function (date, settings) { 20 | return moment(date).format(timeFmt); 21 | } 22 | }, 23 | parser: { 24 | date: function (text, settings) { 25 | return moment(text, fmt).toDate(); 26 | } 27 | } 28 | }); 29 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/EnumHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace SilkierQuartz.TypeHandlers 8 | { 9 | public class EnumHandler : OptionSetHandler 10 | { 11 | public Type EnumType { get; set; } 12 | 13 | public EnumHandler() { } 14 | public EnumHandler(Type enumType) 15 | { 16 | EnumType = enumType; 17 | Name = EnumType.FullName; 18 | DisplayName = EnumType.Name; 19 | } 20 | 21 | public override bool CanHandle(object value) 22 | { 23 | if (value == null) 24 | return false; 25 | 26 | return EnumType.IsAssignableFrom(value.GetType()); 27 | } 28 | 29 | public override object ConvertFrom(object value) 30 | { 31 | if (value == null) 32 | return null; 33 | 34 | if (EnumType.IsAssignableFrom(value.GetType())) 35 | return value; 36 | 37 | if (value is string str) 38 | { 39 | try 40 | { 41 | return Enum.Parse(EnumType, str, true); 42 | } 43 | catch 44 | { 45 | return null; 46 | } 47 | } 48 | 49 | return null; 50 | } 51 | 52 | string GetDisplayName(string enumValue) 53 | { 54 | return EnumType? 55 | .GetMember(enumValue)?.First()? 56 | .GetCustomAttribute()? 57 | .Name ?? enumValue; 58 | } 59 | 60 | public override KeyValuePair[] GetItems() 61 | { 62 | return Enum.GetNames(EnumType) 63 | .Select(x => new KeyValuePair(x, GetDisplayName(x))) 64 | .ToArray(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/FileHandler.cs: -------------------------------------------------------------------------------- 1 | using SilkierQuartz.Models; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System; 6 | 7 | namespace SilkierQuartz.TypeHandlers 8 | { 9 | [EmbeddedTypeHandlerResources(nameof(FileHandler))] 10 | public class FileHandler : TypeHandlerBase 11 | { 12 | public override bool CanHandle(object value) 13 | { 14 | return value is byte[]; 15 | } 16 | 17 | public override object ConvertFrom(Dictionary formData) 18 | { 19 | if (formData.TryGetValue("data-map[old-file-value]", out var oldData)) 20 | return ConvertFrom(Convert.FromBase64String((string)oldData)); 21 | else 22 | return base.ConvertFrom(formData); 23 | } 24 | 25 | public override object ConvertFrom(object value) 26 | { 27 | if (value is byte[]) 28 | return value; 29 | 30 | if (value is string str) 31 | return Encoding.UTF8.GetBytes(str); 32 | 33 | if (value is FormFile file) 34 | { 35 | return file.GetBytes(); 36 | } 37 | 38 | return null; 39 | } 40 | 41 | public override string ConvertToString(object value) 42 | { 43 | if (value is byte[] bytes) 44 | { 45 | var str = Encoding.UTF8.GetString(bytes); 46 | if (HasBinaryContent(str) == false) 47 | return str; 48 | } 49 | 50 | return null; 51 | } 52 | 53 | bool HasBinaryContent(string content) 54 | { 55 | return content.Take(1024).Any(ch => char.IsControl(ch) && ch != '\r' && ch != '\n' && ch != '\t'); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/FileHandler.hbs: -------------------------------------------------------------------------------- 1 | 
2 | 3 | 4 | 5 |
6 | 7 | {{#if Value}} 8 |
9 | 10 | 11 |
12 | Download 13 |
14 | 15 |
16 | {{/if}} -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/FileHandler.js: -------------------------------------------------------------------------------- 1 | function init() { 2 | $('input:text', this).click(function () { 3 | $(this).closest('.value-container').find('input:file').click(); 4 | }); 5 | 6 | $('.download-button', this).click(function () { 7 | const data_b64 = $(this).closest('.value-container').find('textarea').val(); 8 | 9 | function b64toBlob(b64Data, contentType, sliceSize) { 10 | contentType = contentType || ''; 11 | sliceSize = sliceSize || 512; 12 | 13 | var byteCharacters = atob(b64Data); 14 | var byteArrays = []; 15 | 16 | for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) { 17 | var slice = byteCharacters.slice(offset, offset + sliceSize); 18 | 19 | var byteNumbers = new Array(slice.length); 20 | for (var i = 0; i < slice.length; i++) { 21 | byteNumbers[i] = slice.charCodeAt(i); 22 | } 23 | 24 | var byteArray = new Uint8Array(byteNumbers); 25 | 26 | byteArrays.push(byteArray); 27 | } 28 | 29 | var blob = new Blob(byteArrays, { type: contentType }); 30 | return blob; 31 | } 32 | 33 | FileSaver.saveAs(b64toBlob(data_b64, 'application/octet-stream'), "file"); 34 | }); 35 | 36 | $('input:file', '.ui.input', this) 37 | .on('change', function (e) { 38 | 39 | const container = $(this).closest('.value-container'); 40 | container.find('.old-file').remove(); 41 | container.find('.file-selector').show(); 42 | 43 | var name = e.target.files[0].name; 44 | $('input:text', $(e.target).parent()).val(name); 45 | }); 46 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/NumberHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | 5 | namespace SilkierQuartz.TypeHandlers 6 | { 7 | [EmbeddedTypeHandlerResources(nameof(NumberHandler), Script = "")] 8 | public class NumberHandler : TypeHandlerBase 9 | { 10 | static readonly Dictionary _clrTypes = new Dictionary() 11 | { 12 | [UnderlyingType.Decimal] = typeof(decimal), 13 | [UnderlyingType.Double] = typeof(double), 14 | [UnderlyingType.Float] = typeof(float), 15 | [UnderlyingType.Integer] = typeof(int), 16 | [UnderlyingType.Long] = typeof(long), 17 | }; 18 | 19 | public UnderlyingType NumberType { get; set; } 20 | 21 | public Type GetClrType() => _clrTypes[NumberType]; 22 | 23 | public NumberHandler() { } 24 | 25 | public NumberHandler(UnderlyingType numberType) 26 | { 27 | NumberType = numberType; 28 | Name = NumberType.ToString(); 29 | DisplayName = Name; 30 | } 31 | 32 | public override bool CanHandle(object value) 33 | { 34 | if (value == null) 35 | return false; 36 | 37 | return GetClrType().IsAssignableFrom(value.GetType()); 38 | } 39 | 40 | public override object ConvertFrom(object value) 41 | { 42 | var cult = CultureInfo.InvariantCulture; 43 | 44 | if (value is string str) 45 | { 46 | str = str.Replace(" ", "").Replace(",", "."); 47 | 48 | if (NumberType == UnderlyingType.Decimal && decimal.TryParse(str, NumberStyles.Any, cult, out var decimalResult)) 49 | return decimalResult; 50 | if (NumberType == UnderlyingType.Double && double.TryParse(str, NumberStyles.Any, cult, out var dResult)) 51 | return dResult; 52 | if (NumberType == UnderlyingType.Float && float.TryParse(str, NumberStyles.Any, cult, out var fResult)) 53 | return fResult; 54 | if (NumberType == UnderlyingType.Integer && int.TryParse(str, NumberStyles.Any, cult, out var iResult)) 55 | return iResult; 56 | if (NumberType == UnderlyingType.Long && long.TryParse(str, NumberStyles.Any, cult, out var lResult)) 57 | return lResult; 58 | } 59 | 60 | if (value is decimal || value is double || value is float || value is int || value is long) 61 | { 62 | return Convert.ChangeType(value, GetClrType(), cult); 63 | } 64 | 65 | return null; 66 | } 67 | 68 | public enum UnderlyingType 69 | { 70 | Decimal, 71 | Double, 72 | Float, 73 | Integer, 74 | Long 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/NumberHandler.hbs: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/OptionSetHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SilkierQuartz.TypeHandlers 4 | { 5 | [EmbeddedTypeHandlerResources(nameof(OptionSetHandler))] 6 | public abstract class OptionSetHandler : TypeHandlerBase 7 | { 8 | /// 9 | /// Return Key->DisplayName 10 | /// 11 | public abstract KeyValuePair[] GetItems(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/OptionSetHandler.hbs: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/OptionSetHandler.js: -------------------------------------------------------------------------------- 1 | function init() { 2 | $('.ui.dropdown', this).dropdown(); 3 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/StringHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SilkierQuartz.TypeHandlers 4 | { 5 | [EmbeddedTypeHandlerResources(nameof(StringHandler))] 6 | public class StringHandler : TypeHandlerBase 7 | { 8 | public bool IsMultiline { get; set; } 9 | 10 | public override bool CanHandle(object value) 11 | { 12 | if (value is string str) 13 | return (str.IndexOf('\n') != -1) == IsMultiline; 14 | 15 | return false; 16 | } 17 | 18 | public override object ConvertFrom(object value) 19 | { 20 | if (value is string str) 21 | { 22 | if (IsMultiline == false) 23 | return str.Substring(0, Math.Min(str.Length, 0x10000)); // for simple string field, constrain maximum length 24 | 25 | return str; 26 | } 27 | 28 | return null; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/StringHandler.hbs: -------------------------------------------------------------------------------- 1 | {{#if TypeHandler.IsMultiline}} 2 |
3 | 4 |
5 | Edit 6 |
7 |
8 | 9 | 22 | {{else}} 23 | 24 | {{/if}} -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/StringHandler.js: -------------------------------------------------------------------------------- 1 | function init() { 2 | var 3 | $modal = $('.modal', this), 4 | $txtHiddenField = $('textarea[name]', this), 5 | $txtModalField = $('.modal textarea', this), 6 | $stats = $('input.stats', this); 7 | 8 | function calcStats() { 9 | var 10 | str = $txtHiddenField[0].value, 11 | lines = str.split(/\r?\n|\r/).length; 12 | if (str.length === 0) lines = 0; 13 | $stats.val(str.length + ' bytes, ' + lines + ' lines'); 14 | } 15 | 16 | if ($modal.length > 0) { // is multiline string 17 | // init 18 | $modal.modal({ 19 | onShow: function () { $txtModalField.val($txtHiddenField.val()); }, 20 | onApprove: function () { $txtHiddenField.val($txtModalField.val()); calcStats(); }, 21 | duration: 250 22 | }); 23 | 24 | calcStats(); 25 | 26 | $('.edit-button', this).click(function () { 27 | $modal.modal('show'); 28 | }); 29 | } 30 | } -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/TypeHandlerBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | 6 | namespace SilkierQuartz.TypeHandlers 7 | { 8 | public abstract class TypeHandlerBase 9 | { 10 | private string _name; 11 | 12 | /// 13 | /// Type Discriminator 14 | /// 15 | public string TypeId => GetTypeId(GetType()); 16 | 17 | internal static string GetTypeId(Type type) => type.FullName; 18 | 19 | /// 20 | /// Unique name across 21 | /// 22 | public string Name 23 | { 24 | get => _name; 25 | set 26 | { 27 | _name = value; 28 | DisplayName = DisplayName ?? _name; 29 | } 30 | } 31 | 32 | public string DisplayName { get; set; } 33 | 34 | public virtual string RenderView(Services services, object value) 35 | { 36 | return services.TypeHandlers.Render(this, new 37 | { 38 | Value = value, 39 | StringValue = ConvertToString(value), 40 | TypeHandler = this 41 | }); 42 | } 43 | 44 | public virtual object ConvertFrom(Dictionary formData) 45 | { 46 | return ConvertFrom(formData?.Values?.FirstOrDefault()); 47 | } 48 | 49 | public abstract bool CanHandle(object value); 50 | 51 | /// 52 | /// If the value is expected type, just return the value. Every implementation should support conversion from String. 53 | /// 54 | public abstract object ConvertFrom(object value); 55 | 56 | /// 57 | /// Most of TypeHandlers support conversion from invariant string. Implement this method such as another TypeHandler can easily convert from this string. 58 | /// 59 | public virtual string ConvertToString(object value) 60 | { 61 | return string.Format(CultureInfo.InvariantCulture, "{0}", value); 62 | } 63 | 64 | public override string ToString() 65 | { 66 | return DisplayName; 67 | } 68 | 69 | public virtual bool IsValid(object value) => value != null; 70 | 71 | public override bool Equals(object obj) 72 | { 73 | return Name.Equals((obj as TypeHandlerBase)?.Name); 74 | } 75 | 76 | public override int GetHashCode() 77 | { 78 | return Name.GetHashCode(); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/UnsupportedTypeHandler.cs: -------------------------------------------------------------------------------- 1 | namespace SilkierQuartz.TypeHandlers 2 | { 3 | [EmbeddedTypeHandlerResources(nameof(UnsupportedTypeHandler), Script = "")] 4 | public class UnsupportedTypeHandler : TypeHandlerBase 5 | { 6 | public string AssemblyQualifiedName { get; set; } 7 | 8 | public string StringValue { get; set; } 9 | 10 | public override bool CanHandle(object value) => true; 11 | 12 | public override object ConvertFrom(object value) 13 | { 14 | return StringValue; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SilkierQuartz/TypeHandlers/UnsupportedTypeHandler.hbs: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /src/SilkierQuartz/ViewEngine.cs: -------------------------------------------------------------------------------- 1 | using HandlebarsDotNet; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | 6 | namespace SilkierQuartz 7 | { 8 | public class ViewEngine 9 | { 10 | readonly Services _services; 11 | readonly Dictionary> _compiledViews = new Dictionary>(StringComparer.OrdinalIgnoreCase); 12 | 13 | public bool UseCache { get; set; } 14 | 15 | public ViewEngine(Services services) 16 | { 17 | _services = services; 18 | UseCache = string.IsNullOrEmpty(services.Options.ViewsRootDirectory); 19 | } 20 | 21 | HandlebarsTemplate GetRenderDelegate(string templatePath) 22 | { 23 | if (UseCache) 24 | { 25 | lock (_compiledViews) 26 | { 27 | if (!_compiledViews.ContainsKey(templatePath)) 28 | { 29 | _compiledViews[templatePath] = _services.Handlebars.CompileView(templatePath); 30 | } 31 | 32 | return _compiledViews[templatePath]; 33 | } 34 | } 35 | else 36 | { 37 | return _services.Handlebars.CompileView(templatePath); 38 | } 39 | } 40 | 41 | public string Render(string templatePath, object model) 42 | { 43 | return GetRenderDelegate(templatePath)(model); 44 | } 45 | 46 | public string Encode(object value) 47 | { 48 | return string.Format(CultureInfo.InvariantCulture, "{0}", value); 49 | } 50 | 51 | public string ErrorPage(Exception ex) 52 | { 53 | return Render("Error.hbs", new 54 | { 55 | ex.GetBaseException().GetType().FullName, 56 | Exception = ex, 57 | BaseException = ex.GetBaseException(), 58 | Dump = ex.ToString() 59 | }); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/SilkierQuartz/ViewFileSystemFactory.cs: -------------------------------------------------------------------------------- 1 | using HandlebarsDotNet; 2 | using Microsoft.Extensions.FileProviders; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Reflection; 6 | using System.Text; 7 | 8 | namespace SilkierQuartz 9 | { 10 | public static class ViewFileSystemFactory 11 | { 12 | public static ViewEngineFileSystem Create(SilkierQuartzOptions options) 13 | { 14 | ViewEngineFileSystem fs; 15 | if (string.IsNullOrEmpty(options.ViewsRootDirectory)) 16 | { 17 | fs = new EmbeddedFileSystem(); 18 | } 19 | else 20 | { 21 | fs = new DiskFileSystem(options.ViewsRootDirectory); 22 | } 23 | 24 | return fs; 25 | } 26 | 27 | private class DiskFileSystem : ViewEngineFileSystem 28 | { 29 | string root; 30 | 31 | public DiskFileSystem(string root) 32 | { 33 | this.root = root; 34 | } 35 | 36 | public override string GetFileContent(string filename) 37 | { 38 | return File.ReadAllText(GetFullPath(filename)); 39 | } 40 | 41 | protected override string CombinePath(string dir, string otherFileName) 42 | { 43 | return Path.Combine(dir, otherFileName); 44 | } 45 | 46 | public override bool FileExists(string filePath) 47 | { 48 | return File.Exists(GetFullPath(filePath)); 49 | } 50 | 51 | string GetFullPath(string filePath) 52 | { 53 | return Path.Combine(root, filePath.Replace("partials/", "Partials/").Replace('/', Path.DirectorySeparatorChar)); 54 | } 55 | } 56 | 57 | private class EmbeddedFileSystem : ViewEngineFileSystem 58 | { 59 | private EmbeddedFileProvider fs = new EmbeddedFileProvider(Assembly.GetExecutingAssembly(), "SilkierQuartz.Views"); 60 | public override string GetFileContent(string filename) 61 | { 62 | var result = string.Empty; 63 | var fi = fs.GetFileInfo(GetFullPath(filename)); 64 | using (var stream =fi.CreateReadStream()) 65 | { 66 | using (var reader = new StreamReader(stream)) 67 | { 68 | result = reader.ReadToEnd(); 69 | } 70 | } 71 | return result; 72 | } 73 | 74 | protected override string CombinePath(string dir, string otherFileName) 75 | { 76 | return Path.Combine(dir, otherFileName); 77 | } 78 | 79 | public override bool FileExists(string filePath) 80 | { 81 | return fs.GetFileInfo(GetFullPath(filePath)).Exists; 82 | } 83 | string GetFullPath(string filePath) 84 | { 85 | return filePath.Replace("partials/", "Partials/").Replace('/', Path.DirectorySeparatorChar); 86 | } 87 | 88 | } 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Authenticate/Login.hbs: -------------------------------------------------------------------------------- 1 | {{!
5 |
6 | 11 | {{#with Model}} 12 |
13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 |
22 | 23 |
24 |
25 | 26 | 27 | 28 |
29 |
30 |
31 |
32 |
33 | 34 |
35 |
36 |
37 | {{#if IsLoginError}} 38 |
39 |

User Name or Password is incorrect!

40 |
41 | {{/if}} 42 | {{/with}} 43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Calendars/Edit.hbs: -------------------------------------------------------------------------------- 1 | {{!
10 |
11 | 23 | 24 | 25 | 26 |
27 | 28 |
29 | 30 |
31 | 32 |
33 | 34 | 44 | 45 | 46 | 47 | 48 | 97 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Calendars/Index.hbs: -------------------------------------------------------------------------------- 1 | {{!
5 |
6 | 7 | 8 |
9 | {{#if ViewBag.EnableEdit}} 10 | 11 | New 12 | 13 | {{/if}} 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {{#each Model}} 27 | 28 | 29 | 30 | 33 | 34 | {{/each}} 35 | 36 |
NameDescriptionType
{{Name}}{{Description}} 31 |  {{Type.FullName}} 32 |
37 | {{#unless Model}} 38 | {{>EmptyList 'calendar'}} 39 | {{/unless}} 40 | 41 | 74 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Error.hbs: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Error 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 |
24 | 25 |

26 | 27 |
28 | An Error Has Occurred 29 |
{{FullName}}
30 |
31 |

32 | 33 |
34 |

{{BaseException.Message}}

35 |
36 | 37 |
{{Dump}}
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Executions/Index.hbs: -------------------------------------------------------------------------------- 1 | {{!
5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {{#each Model}} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {{/each}} 30 | 31 |
JobTriggerScheduled Fire TimeActual Fire TimeRun Time
{{JobGroup}}.{{JobName}}{{TriggerGroup}}.{{TriggerName}}{{ScheduledFireTime}}{{ActualFireTime}}{{RunTime}}{{#if EnableEdit}}{{/if}}
32 | {{#unless Model}} 33 | {{>EmptyList ''}} 34 | {{/unless}} 35 | 36 | 69 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Views/History/Index.hbs: -------------------------------------------------------------------------------- 1 | {{! 8 | 9 | History is periodicaly purged. Only recent job executions are shown. 10 | 11 | {{/if}} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {{#each Model}} 26 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {{#if Entity.ExceptionMessage}} 38 | 39 | 42 | 43 | {{/if}} 44 | {{/each}} 45 | 46 |
JobTriggerScheduled Fire TimeActual Fire TimeFinished TimeRun Time
28 | 29 | {{JobGroup}}.{{JobName}} 30 | {{TriggerGroup}}.{{TriggerName}}{{ScheduledFireTimeUtc}}{{ActualFireTimeUtc}}{{FinishedTimeUtc}}{{Duration}}
40 |

{{Entity.ExceptionMessage}}

41 |
47 | 48 | {{#unless Model}} 49 | {{>EmptyList ''}} 50 | {{/unless}} 51 | {{else}} 52 |
53 |
Job history is not available.
54 | Enable ExecutionHistoryPlugin in Quartz configuration section. 55 |
56 | {{/if}} -------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Jobs/AdditionalData.hbs: -------------------------------------------------------------------------------- 1 |  2 | 3 | {{#each Model}} 4 | 5 | 6 | {{#if History}} 7 | 8 | {{/if}} 9 | 10 | {{/each}} 11 | 12 |
{{NextFireTime}}{{>Histogram History}}
-------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Jobs/Trigger.hbs: -------------------------------------------------------------------------------- 1 | {{!
5 |
6 | 12 | 13 |
14 | 15 | 16 |

Job Data Map Overrides

17 | 18 | {{>JobDataMap Model}} 19 |
20 |
21 | 22 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Partials/DropdownOptions.hbs: -------------------------------------------------------------------------------- 1 | {{#isType items 'IEnumerable'}} 2 | {{#each items}} 3 | 4 | {{/each}} 5 | {{else}} 6 | {{#isType items 'IEnumerable>'}} 7 | {{#eachPair items}} 8 | 9 | {{/eachPair}} 10 | {{/isType}} 11 | {{/isType}} 12 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Partials/EmptyList.hbs: -------------------------------------------------------------------------------- 1 | 
2 |
3 |
Nothing to see here...
4 | {{#if .}} 5 |
You can add {{.}} by clicking the + button.
6 | {{/if}} 7 |
-------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Partials/GroupActions.hbs: -------------------------------------------------------------------------------- 1 | {{#if items}} 2 |
3 |

{{header}}

4 |
    5 | {{#each items}} 6 | {{#if IsPaused}} 7 |
  • {{Name}}
  • 8 | {{else}} 9 |
  • {{Name}}
  • 10 | {{/if}} 11 | {{/each}} 12 |
13 |
14 | {{/if}} -------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Partials/Histogram.hbs: -------------------------------------------------------------------------------- 1 | {{DoLayout}} 2 |
3 | {{#each Bars}} 4 |
5 | {{/each}} 6 |
-------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Partials/JobDataMap.hbs: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{>JobDataMapRow item=Template template=true}} 11 | {{#each Items}}{{>JobDataMapRow item=. template=false}}{{/each}} 12 | {{>JobDataMapRow item=Template template=false}} 13 | 14 |
NameValueType
15 | 16 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Partials/JobDataMapRow.hbs: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | {{RenderJobDataMapValue item}} 5 | 6 | 7 | 8 |   20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Partials/UnitDropdown.hbs: -------------------------------------------------------------------------------- 1 |  15 | -------------------------------------------------------------------------------- /src/SilkierQuartz/Views/Triggers/AdditionalData.hbs: -------------------------------------------------------------------------------- 1 |  2 | 3 | {{#each Model}} 4 | 5 | {{#if History}} 6 | 7 | {{/if}} 8 | 9 | {{/each}} 10 | 11 |
{{>Histogram History}}
-------------------------------------------------------------------------------- /test/ExpressionDescriptorUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CronExpressionDescriptor; 3 | using FluentAssertions; 4 | using Quartz; 5 | using Xunit; 6 | 7 | namespace SilkierQuartz.Test 8 | { 9 | //7 is used as Sunday on some systems, and considered valid. 10 | //Quartz uses this by default, and SilkierQuartz should also 11 | 12 | //Allowed value range in comments; 13 | //https://github.com/quartznet/quartznet/blob/a22915a9abac1568accb93eb24b4cce5331c8249/src/Quartz/CronExpression.cs#L91 14 | 15 | public class ExpressionDescriptorUnitTests 16 | { 17 | [Theory(DisplayName = "Parse Expressions")] 18 | [InlineData("0 0 2 ? * 7 *", "At 02:00, only on Saturday", false)] 19 | [InlineData("0 0 7 * * ?", "At 07:00", false)] 20 | [InlineData("0 0 20 * * ?", "At 20:00", false)] 21 | [InlineData("0 0 20 6 1/1 ? *", "At 20:00, on day 6 of the month", false)] 22 | [InlineData("0 0 19 20 11 ?", "At 19:00, on day 20 of the month, only in November", false)] 23 | [InlineData("0 10,15,20 12 ? * 6,7 *", "At 10, 15, and 20 minutes past the hour, at 12:00, only on Friday and Saturday", false)] 24 | [InlineData("0 30 10-13 ? * FRI#3", "At 30 minutes past the hour, between 10:00 and 13:59, on the third Friday of the month", false)] 25 | [InlineData("0 43 9 ? * 5L", "At 09:43, on the last Thursday of the month", false)] 26 | 27 | [InlineData("0 0 2 ? * 6 *", "At 02:00, only on Saturday", true)] 28 | [InlineData("0 0 7 * * ?", "At 07:00", true)] 29 | [InlineData("0 0 20 * * ?", "At 20:00", true)] 30 | [InlineData("0 0 20 6 1/1 ? *", "At 20:00, on day 6 of the month", true)] 31 | [InlineData("0 0 19 20 11 ?", "At 19:00, on day 20 of the month, only in November", true)] 32 | [InlineData("0 10,15,20 12 ? * 5,6 *", "At 10, 15, and 20 minutes past the hour, at 12:00, only on Friday and Saturday", true)] 33 | [InlineData("0 30 10-13 ? * FRI#3", "At 30 minutes past the hour, between 10:00 and 13:59, on the third Friday of the month", true)] 34 | [InlineData("0 43 9 ? * 4L", "At 09:43, on the last Thursday of the month", true)] 35 | public void ShouldParseExpressions(string cron, string expected, bool isZeroBased) 36 | { 37 | var options = new Options() {DayOfWeekStartIndexZero = isZeroBased}; 38 | CronExpression exp = null; 39 | //Ensure quartz properly parses the cron 40 | var ex = Record.Exception(() => exp = new CronExpression(cron)); 41 | 42 | ex.Should().BeNull("Quartz should correctly parse any expression before we can expect a valid description"); 43 | 44 | var result = ExpressionDescriptor.GetDescription(cron, options); 45 | result.Should() 46 | .NotBeNull(); 47 | 48 | result.Should().Be(expected); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /test/IServiceCollectionExtensionsUnitTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using Quartz; 5 | using Quartz.Impl; 6 | using Quartz.Spi; 7 | using SilkierQuartz; 8 | using SilkierQuartz.HostedService; 9 | using System; 10 | using Xunit; 11 | 12 | namespace SilkierQuartz.Test 13 | { 14 | public class IServiceCollectionExtensionsUnitTest 15 | { 16 | [Fact(DisplayName = "Registered HostedService")] 17 | public void IServiceCollectionExtensions_Register_HostedService() 18 | { 19 | IServiceCollection serviceCollection = new ServiceCollection(); 20 | IServiceCollectionExtensions.UseQuartzHostedService(serviceCollection, null); 21 | 22 | var testClass = serviceCollection.BuildServiceProvider().GetRequiredService(); 23 | testClass.Should() 24 | .NotBeNull() 25 | .And.BeOfType(); 26 | } 27 | 28 | [Fact(DisplayName = "IJobFactory(d - di in Job)")] 29 | public void IServiceCollectionExtensions_Register_IJobFactory() 30 | { 31 | IServiceCollection serviceCollection = new ServiceCollection(); 32 | IServiceCollectionExtensions.UseQuartzHostedService(serviceCollection, null); 33 | 34 | var testClass = serviceCollection.BuildServiceProvider().GetRequiredService(); 35 | testClass.Should() 36 | .NotBeNull() 37 | .And.BeOfType(); 38 | } 39 | 40 | [Fact(DisplayName = "ISchedulerFactory(did not pass the parameters for initialization)")] 41 | public void IServiceCollectionExtensions_Register_ISchedulerFactory() 42 | { 43 | IServiceCollection serviceCollection = new ServiceCollection(); 44 | IServiceCollectionExtensions.UseQuartzHostedService(serviceCollection, null); 45 | 46 | var testClass = serviceCollection.BuildServiceProvider().GetRequiredService(); 47 | testClass.Should() 48 | .NotBeNull() 49 | .And.BeOfType(); 50 | } 51 | 52 | [Fact(DisplayName = "ISchedulerFactory(transmitted parameters for initialization)")] 53 | public void IServiceCollectionExtensions_Register_ISchedulerFactory_WithParams() 54 | { 55 | IServiceCollection serviceCollection = new ServiceCollection(); 56 | IServiceCollectionExtensions.UseQuartzHostedService(serviceCollection, options => { options.Add("quartz.threadPool.threadCount", "1"); }); 57 | 58 | // TODO: ѕроверить что параметры передались в конструктор 59 | var testClass = serviceCollection.BuildServiceProvider().GetRequiredService(); 60 | testClass.Should() 61 | .NotBeNull() 62 | .And.BeOfType(); 63 | } 64 | 65 | [Fact(DisplayName = "IJobRegistrator registration")] 66 | public void IServiceCollectionExtensions_Return_IJobRegistrator() 67 | { 68 | IServiceCollection serviceCollection = new ServiceCollection(); 69 | var result = IServiceCollectionExtensions.UseQuartzHostedService(serviceCollection, null); 70 | 71 | result.Should() 72 | .NotBeNull() 73 | .And.BeAssignableTo() 74 | .Subject.Services.Should().Equal(serviceCollection); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/SilkierQuartz.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | --------------------------------------------------------------------------------