diff --git a/src/Ombi.Core/Ombi.Core.csproj b/src/Ombi.Core/Ombi.Core.csproj index 35688a6fe..f91cb053e 100644 --- a/src/Ombi.Core/Ombi.Core.csproj +++ b/src/Ombi.Core/Ombi.Core.csproj @@ -37,4 +37,10 @@ + + + ..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.signalr.core\1.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Core.dll + + + \ No newline at end of file diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 4d5ef6fd5..1bea848ff 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -2,6 +2,7 @@ using System.Security.Principal; using Hangfire; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.DependencyInjection; using Ombi.Api.Discord; diff --git a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj index ec905e718..8cb6122be 100644 --- a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj +++ b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj @@ -41,4 +41,10 @@ + + + ..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.signalr.core\1.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Core.dll + + + \ No newline at end of file diff --git a/src/Ombi.Hubs/Ombi.Hubs.csproj b/src/Ombi.Hubs/Ombi.Hubs.csproj new file mode 100644 index 000000000..3331096d0 --- /dev/null +++ b/src/Ombi.Hubs/Ombi.Hubs.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.2 + + + + + + + + + ..\..\..\..\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.signalr.core\1.1.0\lib\netstandard2.0\Microsoft.AspNetCore.SignalR.Core.dll + + + + diff --git a/src/Ombi.Hubs/ScheduledJobsHub.cs b/src/Ombi.Hubs/ScheduledJobsHub.cs new file mode 100644 index 000000000..4d5d4b901 --- /dev/null +++ b/src/Ombi.Hubs/ScheduledJobsHub.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; + +namespace Ombi.Hubs +{ + public class ScheduledJobsHub : Hub + { + public Task Send(string data) + { + return Clients.All.SendAsync("Send", data); + } + } +} diff --git a/src/Ombi.sln b/src/Ombi.sln index 2b9be2c42..70668294d 100644 --- a/src/Ombi.sln +++ b/src/Ombi.sln @@ -96,7 +96,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Notifications", "O EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Lidarr", "Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj", "{4FA21A20-92F4-462C-B929-2C517A88CC56}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Helpers.Tests", "Ombi.Helpers.Tests\Ombi.Helpers.Tests.csproj", "{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Helpers.Tests", "Ombi.Helpers.Tests\Ombi.Helpers.Tests.csproj", "{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Hubs", "Ombi.Hubs\Ombi.Hubs.csproj", "{67416CC5-13B2-44BB-98CE-39DA93D6F70E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -256,6 +258,10 @@ Global {CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Debug|Any CPU.Build.0 = Debug|Any CPU {CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Release|Any CPU.ActiveCfg = Release|Any CPU {CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Release|Any CPU.Build.0 = Release|Any CPU + {67416CC5-13B2-44BB-98CE-39DA93D6F70E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67416CC5-13B2-44BB-98CE-39DA93D6F70E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67416CC5-13B2-44BB-98CE-39DA93D6F70E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67416CC5-13B2-44BB-98CE-39DA93D6F70E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Ombi/ClientApp/angular.json b/src/Ombi/ClientApp/angular.json index 68d9b9cd8..a01be1215 100644 --- a/src/Ombi/ClientApp/angular.json +++ b/src/Ombi/ClientApp/angular.json @@ -27,7 +27,6 @@ "src/styles/_imports.scss", "node_modules/bootstrap/scss/bootstrap.scss", "node_modules/angular-bootstrap-md/scss/mdb-free.scss", - "node_modules/pace/themes/orange/pace-theme-flat-top.css", "node_modules/font-awesome/scss/font-awesome.scss" ], "scripts": [ diff --git a/src/Ombi/ClientApp/src/app/app.component.html b/src/Ombi/ClientApp/src/app/app.component.html index 0aa7d2612..b5229d2a4 100644 --- a/src/Ombi/ClientApp/src/app/app.component.html +++ b/src/Ombi/ClientApp/src/app/app.component.html @@ -164,7 +164,6 @@ --> - diff --git a/src/Ombi/ClientApp/src/app/app.component.ts b/src/Ombi/ClientApp/src/app/app.component.ts index ca4b4262c..cc0ae6542 100644 --- a/src/Ombi/ClientApp/src/app/app.component.ts +++ b/src/Ombi/ClientApp/src/app/app.component.ts @@ -6,9 +6,14 @@ import { AuthService } from "./auth/auth.service"; import { ILocalUser } from "./auth/IUserLogin"; import { IdentityService, NotificationService } from "./services"; import { JobService, SettingsService } from "./services"; +import { MatSnackBar } from '@angular/material'; import { ICustomizationSettings, ICustomPage } from "./interfaces"; +import { HubConnection } from '@aspnet/signalr'; +import * as signalR from '@aspnet/signalr'; + + @Component({ selector: "app-ombi", templateUrl: "./app.component.html", @@ -28,6 +33,8 @@ export class AppComponent implements OnInit { private checkedForUpdate: boolean; + private scheduleHubConnection: HubConnection | undefined; + constructor(public notificationService: NotificationService, public authService: AuthService, private readonly router: Router, @@ -35,7 +42,8 @@ export class AppComponent implements OnInit { private readonly jobService: JobService, public readonly translate: TranslateService, private readonly identityService: IdentityService, - private readonly platformLocation: PlatformLocation) { + private readonly platformLocation: PlatformLocation, + private readonly snackBar: MatSnackBar) { const base = this.platformLocation.getBaseHrefFromDOM(); if (base.length > 1) { @@ -85,6 +93,24 @@ export class AppComponent implements OnInit { } } }); + + this.scheduleHubConnection = new signalR.HubConnectionBuilder().withUrl("/hubs/schedules", { + accessTokenFactory: () => { + return this.authService.getToken(); + } + }) + .configureLogging(signalR.LogLevel.Information).build(); + + this.scheduleHubConnection + .start() + .then(() => console.info('Connection started!')) + .catch(err => console.error(err)); + this.scheduleHubConnection.on("Send", (data: any) => { + this.snackBar.open(data,"OK", { + duration: 3000 + }); + }); + } public roleClass() { diff --git a/src/Ombi/ClientApp/src/app/app.module.ts b/src/Ombi/ClientApp/src/app/app.module.ts index 7d33aa6fa..2949c833c 100644 --- a/src/Ombi/ClientApp/src/app/app.module.ts +++ b/src/Ombi/ClientApp/src/app/app.module.ts @@ -8,7 +8,6 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { RouterModule, Routes } from "@angular/router"; import { JwtModule } from "@auth0/angular-jwt"; - import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; import { TranslateLoader, TranslateModule } from "@ngx-translate/core"; import { TranslateHttpLoader } from "@ngx-translate/http-loader"; @@ -19,7 +18,7 @@ import { ButtonModule, CaptchaModule, ConfirmationService, ConfirmDialogModule, TooltipModule } from "primeng/primeng"; import { - MatButtonModule, MatNativeDateModule, MatIconModule, MatSidenavModule, MatListModule, MatToolbarModule, MatAutocompleteModule, MatCheckboxModule} from '@angular/material'; + MatButtonModule, MatNativeDateModule, MatIconModule, MatSidenavModule, MatListModule, MatToolbarModule, MatAutocompleteModule, MatCheckboxModule, MatSnackBarModule} from '@angular/material'; import { MatCardModule, MatInputModule, MatTabsModule } from "@angular/material"; import { MDBBootstrapModule, CardsFreeModule, NavbarModule } from "angular-bootstrap-md"; @@ -103,6 +102,7 @@ export function JwtTokenGetter() { DataTableModule, SharedModule, NgxEditorModule, + MatSnackBarModule, DialogModule, MatButtonModule, NavbarModule, diff --git a/src/Ombi/ClientApp/src/app/auth/auth.service.ts b/src/Ombi/ClientApp/src/app/auth/auth.service.ts index 2115b2516..3f9768c71 100644 --- a/src/Ombi/ClientApp/src/app/auth/auth.service.ts +++ b/src/Ombi/ClientApp/src/app/auth/auth.service.ts @@ -26,6 +26,10 @@ export class AuthService extends ServiceHelpers { return this.http.post(`${this.url}/requirePassword`, JSON.stringify(login), {headers: this.headers}); } + public getToken() { + return this.jwtHelperService.tokenGetter(); + } + public loggedIn() { const token: string = this.jwtHelperService.tokenGetter(); diff --git a/src/Ombi/ClientApp/src/app/shared/shared.module.ts b/src/Ombi/ClientApp/src/app/shared/shared.module.ts index 34678f4b7..985c95120 100644 --- a/src/Ombi/ClientApp/src/app/shared/shared.module.ts +++ b/src/Ombi/ClientApp/src/app/shared/shared.module.ts @@ -11,7 +11,7 @@ import { InputSwitchModule, SidebarModule } from "primeng/primeng"; import { MatButtonModule, MatNativeDateModule, MatIconModule, MatSidenavModule, MatListModule, MatToolbarModule, MatTooltipModule} from '@angular/material'; - import { MatCardModule, MatInputModule, MatTabsModule, MatAutocompleteModule, MatCheckboxModule, MatExpansionModule, MatDialogModule } from "@angular/material"; + import { MatCardModule, MatInputModule, MatTabsModule, MatAutocompleteModule, MatCheckboxModule, MatExpansionModule, MatDialogModule, MatSnackBarModule } from "@angular/material"; @NgModule({ declarations: [ @@ -37,6 +37,7 @@ import { MatCheckboxModule, MatExpansionModule, MatDialogModule, + MatSnackBarModule, ], exports: [ TranslateModule, @@ -52,6 +53,7 @@ import { MatButtonModule, MatNativeDateModule, MatIconModule, + MatSnackBarModule, MatSidenavModule, MatListModule, MatToolbarModule, diff --git a/src/Ombi/ClientApp/src/main.ts b/src/Ombi/ClientApp/src/main.ts index 738e001df..54b38fb28 100644 --- a/src/Ombi/ClientApp/src/main.ts +++ b/src/Ombi/ClientApp/src/main.ts @@ -1,8 +1,4 @@ // Main - -import * as Pace from "pace"; -Pace.start(); - import "jquery"; import "bootstrap/dist/js/bootstrap"; diff --git a/src/Ombi/ClientApp/src/typings/globals.d.ts b/src/Ombi/ClientApp/src/typings/globals.d.ts index a52b20fa0..ff6860d41 100644 --- a/src/Ombi/ClientApp/src/typings/globals.d.ts +++ b/src/Ombi/ClientApp/src/typings/globals.d.ts @@ -1,6 +1,4 @@ // Globals - -declare module "pace"; declare var __webpack_public_path__: any; declare var module: any; diff --git a/src/Ombi/Controllers/V2/HubController.cs b/src/Ombi/Controllers/V2/HubController.cs new file mode 100644 index 000000000..aa2bd604c --- /dev/null +++ b/src/Ombi/Controllers/V2/HubController.cs @@ -0,0 +1,40 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Ombi.Api.TheMovieDb.Models; +using Ombi.Core.Engine.V2; +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.SignalR; +using Ombi.Core; +using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Models.Search; +using Ombi.Core.Models.Search.V2; +using Ombi.Hubs; +using Ombi.Models; + +namespace Ombi.Controllers.V2 +{ + [ApiV2] + [Authorize] + [ApiController] + public class HubController : ControllerBase + { + public HubController(IHubContext hub) + { + _hub = hub; + } + + private readonly IHubContext _hub; + + /// + /// Returns search results for both TV and Movies + /// + /// + [HttpGet("{searchTerm}")] + public async Task MultiSearch(string searchTerm) + { + await _hub.Clients.All.SendAsync("Send", searchTerm); + } + } +} \ No newline at end of file diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index d02a2698a..5aab971c9 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -87,6 +87,7 @@ + diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index de8dbdc25..3d71dc643 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -21,6 +21,7 @@ using Ombi.Core.Authentication; using Ombi.Core.Settings; using Ombi.DependencyInjection; using Ombi.Helpers; +using Ombi.Hubs; using Ombi.Mapping; using Ombi.Schedule; using Ombi.Settings.Settings.Models; @@ -63,7 +64,6 @@ namespace Ombi } Log.Logger = config; - //} //if (env.IsProduction()) //{ @@ -135,10 +135,11 @@ namespace Ombi { builder.AllowAnyOrigin() .AllowAnyMethod() - .AllowAnyHeader(); + .AllowAnyHeader().AllowCredentials(); })); - services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + services.AddSignalR(); services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/dist"; @@ -241,6 +242,8 @@ namespace Ombi c.SwaggerEndpoint("/swagger/v2/swagger.json", "API V2"); }); + app.UseSignalR(routes => { routes.MapHub("/hubs/schedules"); }); + app.UseMvc(routes => { routes.MapRoute( diff --git a/src/Ombi/StartupExtensions.cs b/src/Ombi/StartupExtensions.cs index f0bec1383..cd26e8223 100644 --- a/src/Ombi/StartupExtensions.cs +++ b/src/Ombi/StartupExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.Extensions.Configuration; @@ -136,6 +137,23 @@ namespace Ombi { x.Audience = "Ombi"; x.TokenValidationParameters = tokenValidationParameters; + x.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + var accessToken = context.Request.Headers["id_token"]; + + // If the request is for our hub... + var path = context.HttpContext.Request.Path; + if (!string.IsNullOrEmpty(accessToken) && + (path.StartsWithSegments("/hubs/"))) + { + // Read the token out of the query string + context.Token = accessToken; + } + return Task.CompletedTask; + } + }; }); } }