mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-19 04:49:33 -07:00
Merge branch 'feature/v4' of https://github.com/tidusjar/Ombi into feature/v4
This commit is contained in:
commit
abef3000ad
48 changed files with 3169 additions and 2974 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -248,3 +248,4 @@ _Pvt_Extensions
|
|||
*.vscode
|
||||
/src/Ombi/database.json
|
||||
/src/Ombi/healthchecksdb
|
||||
/src/Ombi/ClientApp/package-lock.json
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
language: csharp
|
||||
solution: src/Ombi.sln
|
||||
install:
|
||||
- mono Tools/nuget.exe restore Ombi.sln
|
||||
- nuget install NUnit.Runners -OutputDirectory testrunner
|
||||
script:
|
||||
- xbuild /p:Configuration=Release Ombi.sln /p:TargetFrameworkVersion="v4.5"
|
|
@ -2,16 +2,21 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoFixture" Version="4.5.0" />
|
||||
<PackageReference Include="Moq" Version="4.10.0" />
|
||||
<PackageReference Include="Nunit" Version="3.11.0" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
||||
<PackageReference Include="AutoFixture" Version="4.11.0" />
|
||||
<PackageReference Include="Moq" Version="4.14.1" />
|
||||
<PackageReference Include="Nunit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.11.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="16.0.1"></packagereference>
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="16.6.1"></packagereference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace Ombi.Core.Tests.Rule.Search
|
|||
var result = await Rule.Execute(search);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.That(search.EmbyUrl, Is.EqualTo("http://test.com/#!/item/item.html?id=1"));
|
||||
Assert.That(search.EmbyUrl, Is.EqualTo("http://test.com/#!/item.html?id=1"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -99,7 +99,7 @@ namespace Ombi.Core.Tests.Rule.Search
|
|||
var result = await Rule.Execute(search);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.That(search.EmbyUrl, Is.EqualTo("https://app.emby.media/#!/item/item.html?id=1"));
|
||||
Assert.That(search.EmbyUrl, Is.EqualTo("https://app.emby.media/#!/item.html?id=1"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
|
|
@ -113,7 +113,7 @@ namespace Ombi.Core.Tests.Rule.Search
|
|||
PercentOfTracks = 100
|
||||
}
|
||||
}.AsQueryable());
|
||||
var request = new SearchAlbumViewModel { ForeignAlbumId = "ABC" };
|
||||
var request = new SearchAlbumViewModel { ForeignAlbumId = "abc" };
|
||||
var result = await Rule.Execute(request);
|
||||
|
||||
Assert.True(result.Success);
|
||||
|
|
|
@ -63,7 +63,7 @@ namespace Ombi.Core.Tests.Rule.Search
|
|||
ForeignArtistId = "abc",
|
||||
}
|
||||
}.AsQueryable());
|
||||
var request = new SearchArtistViewModel { ForignArtistId = "ABC" };
|
||||
var request = new SearchArtistViewModel { ForignArtistId = "abc" };
|
||||
var result = await Rule.Execute(request);
|
||||
|
||||
Assert.True(result.Success);
|
||||
|
|
|
@ -267,10 +267,10 @@ namespace Ombi.Core.Engine
|
|||
allRequests = allRequests.Where(x => x.Approved && !x.Available && (!x.Denied.HasValue || !x.Denied.Value));
|
||||
break;
|
||||
case RequestStatus.Available:
|
||||
allRequests = allRequests.Where(x => x.Available && (!x.Denied.HasValue || !x.Denied.Value));
|
||||
allRequests = allRequests.Where(x => x.Available);
|
||||
break;
|
||||
case RequestStatus.Denied:
|
||||
allRequests = allRequests.Where(x => x.Denied.HasValue && x.Denied.Value);
|
||||
allRequests = allRequests.Where(x => x.Denied.HasValue && x.Denied.Value && !x.Available);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -332,12 +332,11 @@ namespace Ombi.Core.Engine
|
|||
//var secondProp = TypeDescriptor.GetProperties(propType).Find(properties[1], true);
|
||||
}
|
||||
|
||||
allRequests = sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
|
||||
? allRequests.OrderBy(x => prop.GetValue(x))
|
||||
: allRequests.OrderByDescending(x => prop.GetValue(x));
|
||||
var total = await allRequests.CountAsync();
|
||||
var requests = await allRequests.Skip(position).Take(count)
|
||||
.ToListAsync();
|
||||
var requests = (sortOrder.Equals("asc", StringComparison.InvariantCultureIgnoreCase)
|
||||
? allRequests.ToList().OrderBy(x => prop.GetValue(x))
|
||||
: allRequests.ToList().OrderByDescending(x => prop.GetValue(x))).ToList();
|
||||
var total = requests.Count();
|
||||
requests = requests.Skip(position).Take(count).ToList();
|
||||
|
||||
await CheckForSubscription(shouldHide, requests);
|
||||
return new RequestsViewModel<MovieRequests>
|
||||
|
|
|
@ -93,7 +93,7 @@ namespace Ombi.Store.Context
|
|||
}
|
||||
|
||||
needToSave = true;
|
||||
NotificationTemplates notificationToAdd;
|
||||
NotificationTemplates notificationToAdd = null;
|
||||
switch (notificationType)
|
||||
{
|
||||
case NotificationType.NewRequest:
|
||||
|
@ -159,6 +159,8 @@ namespace Ombi.Store.Context
|
|||
};
|
||||
break;
|
||||
case NotificationType.WelcomeEmail:
|
||||
if (agent == NotificationAgent.Email)
|
||||
{
|
||||
notificationToAdd = new NotificationTemplates
|
||||
{
|
||||
NotificationType = notificationType,
|
||||
|
@ -167,6 +169,7 @@ namespace Ombi.Store.Context
|
|||
Agent = agent,
|
||||
Enabled = true,
|
||||
};
|
||||
}
|
||||
break;
|
||||
case NotificationType.IssueResolved:
|
||||
notificationToAdd = new NotificationTemplates
|
||||
|
@ -204,9 +207,12 @@ namespace Ombi.Store.Context
|
|||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
if (notificationToAdd != null)
|
||||
{
|
||||
NotificationTemplates.Add(notificationToAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needToSave)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
|
||||
namespace Ombi.Store.Migrations.OmbiMySql
|
||||
{
|
||||
|
@ -8,14 +8,34 @@ namespace Ombi.Store.Migrations.OmbiMySql
|
|||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(@"CREATE TABLE `MobileDevices` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT,
|
||||
`Token` longtext CHARACTER SET utf8mb4 NULL,
|
||||
`UserId` varchar(255) COLLATE utf8mb4_bin NOT NULL,
|
||||
`AddedAt` datetime(6) NOT NULL,
|
||||
CONSTRAINT `PK_MobileDevices` PRIMARY KEY (`Id`),
|
||||
CONSTRAINT `FK_MobileDevices_AspNetUsers_UserId` FOREIGN KEY (`UserId`) REFERENCES `AspNetUsers` (`Id`) ON DELETE RESTRICT
|
||||
);");
|
||||
// migrationBuilder.Sql(@"CREATE TABLE `MobileDevices` (
|
||||
// `Id` int NOT NULL AUTO_INCREMENT,
|
||||
// `Token` longtext CHARACTER SET utf8mb4 NULL,
|
||||
// `UserId` varchar(255) COLLATE utf8mb4_bin NOT NULL,
|
||||
// `AddedAt` datetime(6) NOT NULL,
|
||||
// CONSTRAINT `PK_MobileDevices` PRIMARY KEY (`Id`),
|
||||
// CONSTRAINT `FK_MobileDevices_AspNetUsers_UserId` FOREIGN KEY (`UserId`) REFERENCES `AspNetUsers` (`Id`) ON DELETE RESTRICT
|
||||
//);");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MobileDevices",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(nullable: false).Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
Token = table.Column<string>(maxLength: 256, nullable: true),
|
||||
UserId = table.Column<string>(maxLength: 256, nullable: false),
|
||||
AddedAt = table.Column<DateTime>(maxLength: 256, nullable: false),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MobileDevices", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_MobileDevices_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
|
|
|
@ -18,7 +18,8 @@ import {
|
|||
} from "primeng/primeng";
|
||||
|
||||
import {
|
||||
MatButtonModule, MatNativeDateModule, MatIconModule, MatSidenavModule, MatListModule, MatToolbarModule, MatAutocompleteModule, MatCheckboxModule, MatSnackBarModule
|
||||
MatButtonModule, MatNativeDateModule, MatIconModule, MatSidenavModule, MatListModule, MatToolbarModule, MatAutocompleteModule, MatCheckboxModule, MatSnackBarModule,
|
||||
MatProgressSpinnerModule
|
||||
} from '@angular/material';
|
||||
import { MatCardModule, MatInputModule, MatTabsModule, MatSlideToggleModule } from "@angular/material";
|
||||
|
||||
|
@ -129,6 +130,7 @@ export function JwtTokenGetter() {
|
|||
CardsFreeModule,
|
||||
OverlayModule,
|
||||
MatCheckboxModule,
|
||||
MatProgressSpinnerModule,
|
||||
MDBBootstrapModule.forRoot(),
|
||||
JwtModule.forRoot({
|
||||
config: {
|
||||
|
|
|
@ -196,9 +196,6 @@ export interface IAbout {
|
|||
ombiDatabaseType: string;
|
||||
externalDatabaseType: string;
|
||||
settingsDatabaseType: string;
|
||||
ombiConnectionString: string;
|
||||
externalConnectionString: string;
|
||||
settingsConnectionString: string;
|
||||
storagePath: string;
|
||||
notSupported: boolean;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<!--Next to poster-->
|
||||
<div class="col-12 col-lg-3 col-xl-3 media-row">
|
||||
|
||||
<social-icons [homepage]="movie.homepage" [theMoviedbId]="movie.id" [hasTrailer]="movie.videos.results.length > 0" (openTrailer)="openDialog()" [imdbId]="movie.imdbId" [twitter]="movie.externalIds.twitterId" [facebook]="movie.externalIds.facebookId" [instagram]="movie.externalIds.instagramId"
|
||||
<social-icons [homepage]="movie.homepage" [theMoviedbId]="movie.id" [hasTrailer]="movie.videos?.results?.length > 0" (openTrailer)="openDialog()" [imdbId]="movie.imdbId" [twitter]="movie.externalIds.twitterId" [facebook]="movie.externalIds.facebookId" [instagram]="movie.externalIds.instagramId"
|
||||
[available]="movie.available" [plexUrl]="movie.plexUrl" [embyUrl]="movie.embyUrl"></social-icons>
|
||||
|
||||
</div>
|
||||
|
@ -117,9 +117,9 @@
|
|||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<div class="row card-spacer" *ngIf="movie.recommendations.results.length > 0">
|
||||
<div class="row card-spacer" *ngIf="movie.recommendations?.results?.length > 0">
|
||||
|
||||
<div class="col-md-2" *ngFor="let r of movie.recommendations.results">
|
||||
<div class="col-md-2" *ngFor="let r of movie.recommendations?.results">
|
||||
<div class="sidebar affixable affix-top preview-poster">
|
||||
<div class="poster">
|
||||
<a [routerLink]="'/details/movie/'+r.id">
|
||||
|
@ -138,7 +138,7 @@
|
|||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<div class="row card-spacer" *ngIf="movie.similar.results.length > 0">
|
||||
<div class="row card-spacer" *ngIf="movie.similar?.results?.length > 0">
|
||||
|
||||
<div class="col-md-2" *ngFor="let r of movie.similar.results">
|
||||
<div class="sidebar affixable affix-top preview-poster">
|
||||
|
@ -159,9 +159,9 @@
|
|||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<div class="row card-spacer" *ngIf="movie.videos.results.length > 0">
|
||||
<div class="row card-spacer" *ngIf="movie.videos?.results?.length > 0">
|
||||
|
||||
<div class="col-md-6" *ngFor="let video of movie.videos.results">
|
||||
<div class="col-md-6" *ngFor="let video of movie.videos?.results">
|
||||
<iframe width="100%" height="315px" [src]="'https://www.youtube.com/embed/' + video.key | safe" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
<div *ngIf="!movie.requested && !movie.available && !movie.approved">{{'Common.NotRequested' | translate}}
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="movie.quality">
|
||||
<strong>Quality:</strong>
|
||||
<div>{{movie.quality | quality}}</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="advancedOptions">
|
||||
<strong>Root Folder Override</strong>
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<mat-sidenav-container *ngIf="showNav" class="sidenav-container">
|
||||
<mat-sidenav #drawer class="sidenav" fixedInViewport="true"
|
||||
[attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'" [mode]="(isHandset$ | async) ? 'over' : 'side'"
|
||||
[opened]="!(isHandset$ | async)">
|
||||
<mat-sidenav #drawer class="sidenav" fixedInViewport="true" [attr.role]="(isHandset$ | async) ? 'dialog' : 'navigation'" [mode]="(isHandset$ | async) ? 'over' : 'side'" [opened]="!(isHandset$ | async)">
|
||||
<mat-toolbar>{{applicationName}}</mat-toolbar>
|
||||
<mat-nav-list>
|
||||
<span *ngFor="let nav of navItems">
|
||||
|
@ -23,15 +21,16 @@
|
|||
</mat-sidenav>
|
||||
<mat-sidenav-content>
|
||||
<mat-toolbar color="primary">
|
||||
<button type="button" aria-label="Toggle sidenav" mat-icon-button (click)="drawer.toggle()"
|
||||
*ngIf="isHandset$ | async">
|
||||
<button type="button" aria-label="Toggle sidenav" mat-icon-button (click)="drawer.toggle()" *ngIf="isHandset$ | async">
|
||||
<mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
|
||||
</button>
|
||||
|
||||
<div class="col-md-10 offset-md-1 col-10">
|
||||
<span class="middle justify-content-center align-items-center">
|
||||
<!-- Search Bar -->
|
||||
<div style="width: 50%;">
|
||||
<app-nav-search></app-nav-search>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
<input class="form-control quater-width search-bar" type="text" [(ngModel)]="selectedItem"
|
||||
placeholder="{{'NavigationBar.Search' | translate}}" aria-label="Search" [ngbTypeahead]="searchModel"
|
||||
[resultFormatter]="formatter" [inputFormatter]="formatter" [resultTemplate]="template" (selectItem)="selected($event)">
|
||||
|
||||
|
||||
<!-- <input class="form-control quater-width search-bar" type="text" [(ngModel)]="selectedItem" placeholder="{{'NavigationBar.Search' | translate}}"
|
||||
aria-label="Search" [ngbTypeahead]="searchModel" [resultFormatter]="formatter" [inputFormatter]="formatter" [resultTemplate]="template" (selectItem)="selected($event)">
|
||||
|
||||
<ng-template #template let-result="result">
|
||||
|
||||
</ng-template> -->
|
||||
|
||||
<form [formGroup]='searchForm'>
|
||||
<mat-form-field floatLabel="never" style="width: 100%;">
|
||||
<input matInput placeholder="{{'NavigationBar.Search' | translate}}" [matAutocomplete]="auto" formControlName='input'>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)" [displayWith]="displayFn">
|
||||
<mat-option *ngIf="searching" color="accent">
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
</mat-option>
|
||||
<ng-container *ngIf="!searching">
|
||||
<mat-option *ngFor="let result of results" [value]="result">
|
||||
<div *ngIf="result.mediaType === 'movie'">
|
||||
<i class="fa fa-film"></i>
|
||||
<span>{{result.title}}</span>
|
||||
|
@ -25,6 +36,7 @@
|
|||
<i class="fa fa-user"></i>
|
||||
<span>{{result.title}}</span>
|
||||
</div>
|
||||
<!-- Collection -->
|
||||
<!-- <i class="fa fa-file-video-o" aria-hidden="true"></i> -->
|
||||
</ng-template>
|
||||
</mat-option>
|
||||
</ng-container>
|
||||
</mat-autocomplete>
|
||||
</form>
|
|
@ -1,7 +1,6 @@
|
|||
$ombi-primary:#3f3f3f;
|
||||
$ombi-primary-darker:#2b2b2b;
|
||||
$ombi-accent: #258a6d;
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.quater-width {
|
||||
width: 15em !important;
|
||||
|
@ -23,22 +22,6 @@ $ombi-accent: #258a6d;
|
|||
padding: 0px 5px;
|
||||
}
|
||||
|
||||
::ng-deep ngb-typeahead-window.dropdown-menu {
|
||||
background-color: $ombi-primary;
|
||||
overflow: auto;
|
||||
height: 33em;
|
||||
}
|
||||
|
||||
::ng-deep ngb-typeahead-window button.dropdown-item {
|
||||
color: white;
|
||||
}
|
||||
|
||||
::ng-deep ngb-typeahead-window .dropdown-item.active,
|
||||
.dropdown-item:active {
|
||||
text-decoration: none;
|
||||
background-color: $ombi-accent;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
background-color: $ombi-primary-darker;
|
||||
border: solid 1px $ombi-primary-darker;
|
||||
|
|
|
@ -1,56 +1,78 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
import {
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
switchMap,
|
||||
tap,
|
||||
finalize,
|
||||
} from "rxjs/operators";
|
||||
|
||||
import { SearchV2Service } from '../services/searchV2.service';
|
||||
import { IMultiSearchResult } from '../interfaces';
|
||||
import { Router } from '@angular/router';
|
||||
import { NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { empty, of } from "rxjs";
|
||||
import { SearchV2Service } from "../services/searchV2.service";
|
||||
import { IMultiSearchResult } from "../interfaces";
|
||||
import { Router } from "@angular/router";
|
||||
import { NgbTypeaheadSelectItemEvent } from "@ng-bootstrap/ng-bootstrap";
|
||||
import { FormGroup, FormBuilder } from "@angular/forms";
|
||||
import { MatAutocompleteSelectedEvent } from "@angular/material";
|
||||
|
||||
@Component({
|
||||
selector: 'app-nav-search',
|
||||
templateUrl: './nav-search.component.html',
|
||||
styleUrls: ['./nav-search.component.scss']
|
||||
selector: "app-nav-search",
|
||||
templateUrl: "./nav-search.component.html",
|
||||
styleUrls: ["./nav-search.component.scss"],
|
||||
})
|
||||
export class NavSearchComponent {
|
||||
|
||||
export class NavSearchComponent implements OnInit {
|
||||
public selectedItem: string;
|
||||
|
||||
public results: IMultiSearchResult[];
|
||||
public searching = false;
|
||||
public searchFailed = false;
|
||||
|
||||
public formatter = (result: IMultiSearchResult) => {
|
||||
return result.title;
|
||||
}
|
||||
public searchForm: FormGroup;
|
||||
|
||||
public searchModel = (text$: Observable<string>) =>
|
||||
text$.pipe(
|
||||
constructor(
|
||||
private searchService: SearchV2Service,
|
||||
private router: Router,
|
||||
private fb: FormBuilder
|
||||
) {}
|
||||
|
||||
public async ngOnInit() {
|
||||
this.searchForm = this.fb.group({
|
||||
input: null,
|
||||
});
|
||||
|
||||
this.searchForm
|
||||
.get("input")
|
||||
.valueChanges.pipe(
|
||||
debounceTime(600),
|
||||
distinctUntilChanged(),
|
||||
switchMap(term => term.length < 2 ? []
|
||||
: this.searchService.multiSearch(term)
|
||||
tap(() => (this.searching = true)),
|
||||
switchMap((value: string) => {
|
||||
if (value) {
|
||||
return this.searchService
|
||||
.multiSearch(value)
|
||||
.pipe(finalize(() => (this.searching = false)));
|
||||
}
|
||||
return empty().pipe(finalize(() => (this.searching = false)));
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
constructor(private searchService: SearchV2Service, private router: Router) {
|
||||
|
||||
.subscribe((r) => (this.results = r));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public selected(event: NgbTypeaheadSelectItemEvent) {
|
||||
if (event.item.mediaType == "movie") {
|
||||
this.router.navigate([`details/movie/${event.item.id}`]);
|
||||
public selected(event: MatAutocompleteSelectedEvent) {
|
||||
const val = event.option.value as IMultiSearchResult;
|
||||
if (val.mediaType == "movie") {
|
||||
this.router.navigate([`details/movie/${val.id}`]);
|
||||
return;
|
||||
} else if (event.item.mediaType == "tv") {
|
||||
this.router.navigate([`details/tv/${event.item.id}/true`]);
|
||||
} else if (val.mediaType == "tv") {
|
||||
this.router.navigate([`details/tv/${val.id}/true`]);
|
||||
return;
|
||||
} else if (event.item.mediaType == "person") {
|
||||
this.router.navigate([`discover/actor/${event.item.id}`]);
|
||||
} else if (val.mediaType == "person") {
|
||||
this.router.navigate([`discover/actor/${val.id}`]);
|
||||
return;
|
||||
} else if (event.item.mediaType == "Artist") {
|
||||
this.router.navigate([`details/artist/${event.item.id}`]);
|
||||
} else if (val.mediaType == "Artist") {
|
||||
this.router.navigate([`details/artist/${val.id}`]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
displayFn(result: IMultiSearchResult) {
|
||||
if (result) { return result.title; }
|
||||
}
|
||||
}
|
||||
|
|
11
src/Ombi/ClientApp/src/app/pipes/QualityPipe.ts
Normal file
11
src/Ombi/ClientApp/src/app/pipes/QualityPipe.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({ name: 'quality' })
|
||||
export class QualityPipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (value.toUpperCase() === "4K" || value.toUpperCase() === "8K") {
|
||||
return value;
|
||||
}
|
||||
return value + "p";
|
||||
}
|
||||
}
|
|
@ -2,11 +2,12 @@
|
|||
import { HumanizePipe } from "./HumanizePipe";
|
||||
import { ThousandShortPipe } from "./ThousandShortPipe";
|
||||
import { SafePipe } from "./SafePipe";
|
||||
import { QualityPipe } from "./QualityPipe";
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
declarations: [HumanizePipe, ThousandShortPipe, SafePipe],
|
||||
exports: [HumanizePipe, ThousandShortPipe, SafePipe],
|
||||
declarations: [HumanizePipe, ThousandShortPipe, SafePipe, QualityPipe],
|
||||
exports: [HumanizePipe, ThousandShortPipe, SafePipe, QualityPipe],
|
||||
})
|
||||
export class PipeModule {
|
||||
|
||||
|
|
|
@ -1,51 +1,68 @@
|
|||
<div class="mat-elevation-z8">
|
||||
<grid-spinner [loading]="isLoadingResults"></grid-spinner>
|
||||
|
||||
|
||||
<!-- <div class="row"> -->
|
||||
<div class="row justify-content-md-center top-spacing">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" (click)="switchFilter(RequestFilter.All)" [attr.color]="currentFilter === RequestFilter.All ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.All ? 'mat-accent' : 'mat-primary'" mat-raised-button class="btn grow">{{'Requests.AllRequests' | translate}}</button>
|
||||
<button type="button" (click)="switchFilter(RequestFilter.Pending)" [attr.color]="currentFilter === RequestFilter.Pending ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Pending ? 'mat-accent' : 'mat-primary'" mat-raised-button class="btn grow">{{'Requests.PendingRequests' | translate}}</button>
|
||||
<button type="button" (click)="switchFilter(RequestFilter.Processing)" [attr.color]="currentFilter === RequestFilter.Processing ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Processing ? 'mat-accent' : 'mat-primary'" mat-raised-button
|
||||
class="btn grow">{{'Requests.ProcessingRequests' | translate}}</button>
|
||||
<button type="button" (click)="switchFilter(RequestFilter.Available)" [attr.color]="currentFilter === RequestFilter.Available ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Available ? 'mat-accent' : 'mat-primary'" mat-raised-button
|
||||
class="btn grow">{{'Requests.AvailableRequests' | translate}}</button>
|
||||
<button type="button" (click)="switchFilter(RequestFilter.Denied)" [attr.color]="currentFilter === RequestFilter.Denied ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Denied ? 'mat-accent' : 'mat-primary'" mat-raised-button class="btn grow">{{'Requests.DeniedRequests' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-2 offset-md-10">
|
||||
<mat-form-field>
|
||||
<mat-select placeholder="Requests to Display" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()">
|
||||
<mat-select placeholder="{{'Requests.RequestsToDisplay' | translate}}" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()">
|
||||
<mat-option value="10">10</mat-option>
|
||||
<mat-option value="15">15</mat-option>
|
||||
<mat-option value="30">30</mat-option>
|
||||
<mat-option value="100">100</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
<table mat-table [dataSource]="dataSource" class="table" matSort [matSortActive]="defaultSort" matSortDisableClear [matSortDirection]="defaultOrder">
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="table" matSort [matSortActive]="defaultSort"
|
||||
matSortDisableClear [matSortDirection]="defaultOrder">
|
||||
|
||||
<ng-container matColumnDef="title">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.RequestsTitle' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.title}} ({{element.releaseDate | amLocal | amDateFormat: 'YYYY'}}) </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="requestedUser.requestedBy">
|
||||
<th mat-header-cell *matHeaderCellDef > Requested By </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{'Requests.RequestedBy' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.requestedUser?.userAlias}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="title">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Title </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.title}} ({{element.releaseDate | amLocal | amDateFormat:
|
||||
'YYYY'}}) </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="requestedDate">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Request Date </th>
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.RequestDate' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.requestedDate | amLocal | amDateFormat: 'LL'}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="status">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Status </th>
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.Status' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.status}} </td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<ng-container matColumnDef="requestStatus">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Request Status </th>
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{ 'Requests.RequestStatus' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.requestStatus | translate}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef> </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button mat-raised-button color="accent" [routerLink]="'/details/movie/' + element.theMovieDbId">Details</button>
|
||||
<button mat-raised-button color="warn" (click)="openOptions(element)" *ngIf="isAdmin">Options</button>
|
||||
<button mat-raised-button color="accent" [routerLink]="'/details/movie/' + element.theMovieDbId">{{ 'Requests.Details' | translate}}</button>
|
||||
<button mat-raised-button color="warn" (click)="openOptions(element)" *ngIf="isAdmin"> {{ 'Requests.Options' | translate}}</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import { catchError, map, startWith, switchMap } from 'rxjs/operators';
|
|||
import { RequestServiceV2 } from "../../../services/requestV2.service";
|
||||
import { AuthService } from "../../../auth/auth.service";
|
||||
import { StorageService } from "../../../shared/storage/storage-service";
|
||||
import { RequestFilterType } from "../../models/RequestFilterType";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./movies-grid.component.html",
|
||||
|
@ -19,13 +20,18 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
|
|||
public isLoadingResults = true;
|
||||
public displayedColumns: string[] = ['requestedUser.requestedBy', 'title', 'requestedDate', 'status', 'requestStatus', 'actions'];
|
||||
public gridCount: string = "15";
|
||||
public showUnavailableRequests: boolean;
|
||||
public isAdmin: boolean;
|
||||
public defaultSort: string = "requestedDate";
|
||||
public defaultOrder: string = "desc";
|
||||
public currentFilter: RequestFilterType = RequestFilterType.All;
|
||||
|
||||
public RequestFilter = RequestFilterType;
|
||||
|
||||
|
||||
private storageKey = "Movie_DefaultRequestListSort";
|
||||
private storageKeyOrder = "Movie_DefaultRequestListSortOrder";
|
||||
private storageKeyGridCount = "Movie_DefaultGridCount";
|
||||
private storageKeyCurrentFilter = "Movie_DefaultFilter";
|
||||
|
||||
@Output() public onOpenOptions = new EventEmitter<{ request: any, filter: any, onChange: any }>();
|
||||
|
||||
|
@ -38,28 +44,35 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||
|
||||
const defaultCount = this.storageService.get(this.storageKeyGridCount);
|
||||
const defaultSort = this.storageService.get(this.storageKey);
|
||||
const defaultOrder = this.storageService.get(this.storageKeyOrder);
|
||||
const defaultFilter = +this.storageService.get(this.storageKeyCurrentFilter);
|
||||
if (defaultSort) {
|
||||
this.defaultSort = defaultSort;
|
||||
}
|
||||
if (defaultOrder) {
|
||||
this.defaultOrder = defaultOrder;
|
||||
}
|
||||
if (defaultCount) {
|
||||
this.gridCount = defaultCount;
|
||||
}
|
||||
if (defaultFilter) {
|
||||
this.currentFilter = defaultFilter;
|
||||
}
|
||||
}
|
||||
|
||||
public async ngAfterViewInit() {
|
||||
// const results = await this.requestService.getMovieRequests(this.gridCount, 0, OrderType.RequestedDateDesc,
|
||||
// { availabilityFilter: FilterType.None, statusFilter: FilterType.None }).toPromise();
|
||||
// this.dataSource = results.collection;
|
||||
// this.resultsLength = results.total;
|
||||
|
||||
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||
this.storageService.save(this.storageKeyGridCount, this.gridCount);
|
||||
this.storageService.save(this.storageKeyCurrentFilter, (+this.currentFilter).toString());
|
||||
|
||||
// If the user changes the sort order, reset back to the first page.
|
||||
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
|
||||
|
||||
merge(this.sort.sortChange, this.paginator.page)
|
||||
merge(this.sort.sortChange, this.paginator.page, this.currentFilter)
|
||||
.pipe(
|
||||
startWith({}),
|
||||
switchMap((value: any) => {
|
||||
|
@ -85,11 +98,19 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
|
||||
public loadData(): Observable<IRequestsViewModel<IMovieRequests>> {
|
||||
if (this.showUnavailableRequests) {
|
||||
return this.requestService.getMovieUnavailableRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
} else {
|
||||
switch(RequestFilterType[RequestFilterType[this.currentFilter]]) {
|
||||
case RequestFilterType.All:
|
||||
return this.requestService.getMovieRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
case RequestFilterType.Pending:
|
||||
return this.requestService.getMoviePendingRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
case RequestFilterType.Available:
|
||||
return this.requestService.getMovieAvailableRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
case RequestFilterType.Processing:
|
||||
return this.requestService.getMovieProcessingRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
case RequestFilterType.Denied:
|
||||
return this.requestService.getMovieDeniedRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public openOptions(request: IMovieRequests) {
|
||||
|
@ -105,4 +126,9 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
|
|||
|
||||
this.onOpenOptions.emit({ request: request, filter: filter, onChange: onChange });
|
||||
}
|
||||
|
||||
public switchFilter(type: RequestFilterType) {
|
||||
this.currentFilter = type;
|
||||
this.ngAfterViewInit();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,47 +2,54 @@
|
|||
|
||||
<grid-spinner [loading]="isLoadingResults"></grid-spinner>
|
||||
|
||||
|
||||
<div class="row justify-content-md-center top-spacing">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" (click)="switchFilter(RequestFilter.All)" [attr.color]="currentFilter === RequestFilter.All ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.All ? 'mat-accent' : 'mat-primary'" mat-raised-button class="btn grow">{{'Requests.AllRequests' | translate}}</button>
|
||||
<button type="button" (click)="switchFilter(RequestFilter.Pending)" [attr.color]="currentFilter === RequestFilter.Pending ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Pending ? 'mat-accent' : 'mat-primary'" mat-raised-button class="btn grow">{{'Requests.PendingRequests' | translate}}</button>
|
||||
<button type="button" (click)="switchFilter(RequestFilter.Processing)" [attr.color]="currentFilter === RequestFilter.Processing ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Processing ? 'mat-accent' : 'mat-primary'" mat-raised-button
|
||||
class="btn grow">{{'Requests.ProcessingRequests' | translate}}</button>
|
||||
<button type="button" (click)="switchFilter(RequestFilter.Available)" [attr.color]="currentFilter === RequestFilter.Available ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Available ? 'mat-accent' : 'mat-primary'" mat-raised-button
|
||||
class="btn grow">{{'Requests.AvailableRequests' | translate}}</button>
|
||||
<button type="button" (click)="switchFilter(RequestFilter.Denied)" [attr.color]="currentFilter === RequestFilter.Denied ? 'accent' : 'primary'" [ngClass]="currentFilter === RequestFilter.Denied ? 'mat-accent' : 'mat-primary'" mat-raised-button class="btn grow">{{'Requests.DeniedRequests' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-2 offset-md-10">
|
||||
<mat-form-field>
|
||||
<mat-select placeholder="Requests to Display" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()">
|
||||
<mat-select placeholder="{{'Requests.RequestsToDisplay' | translate}}" [(value)]="gridCount" (selectionChange)="ngAfterViewInit()">
|
||||
<mat-option value="10">10</mat-option>
|
||||
<mat-option value="15">15</mat-option>
|
||||
<mat-option value="30">30</mat-option>
|
||||
<mat-option value="100">100</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table mat-table [dataSource]="dataSource" class="table" matSort [matSortActive]="defaultSort" matSortDisableClear
|
||||
[matSortDirection]="defaultOrder">
|
||||
<table mat-table [dataSource]="dataSource" class="table" matSort [matSortActive]="defaultSort" matSortDisableClear [matSortDirection]="defaultOrder">
|
||||
|
||||
|
||||
<ng-container matColumnDef="series">
|
||||
<th mat-header-cell *matHeaderCellDef> Series </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{'Requests.RequestsTitle' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.parentRequest.title}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="requestedBy">
|
||||
<th mat-header-cell *matHeaderCellDef> Requested By </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{'Requests.RequestedBy' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element.requestedUser.userAlias}} </td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
|
||||
<ng-container matColumnDef="status">
|
||||
<th mat-header-cell *matHeaderCellDef> Status </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.parentRequest.status}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="requestedDate">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> Requested Date </th>
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header disableClear> {{'Requests.RequestDate' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.requestedDate | amLocal | amDateFormat: 'LL'}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="requestStatus">
|
||||
<th mat-header-cell *matHeaderCellDef> Request Status </th>
|
||||
<th mat-header-cell *matHeaderCellDef> {{'Requests.RequestStatus' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<div *ngIf="element.approved && !element.available">{{'Common.ProcessingRequest' | translate}}</div>
|
||||
<div *ngIf="!element.approved && !element.available">{{'Common.PendingApproval' |translate}}</div>
|
||||
|
@ -51,12 +58,18 @@
|
|||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="status">
|
||||
<th mat-header-cell *matHeaderCellDef> {{'Requests.Status' | translate}} </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.parentRequest.status}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef> </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button mat-raised-button color="accent"
|
||||
[routerLink]="'/details/tv/' + element.parentRequest.tvDbId">Details</button>
|
||||
<button mat-raised-button color="warn" (click)="openOptions(element)" *ngIf="isAdmin">Options</button>
|
||||
<button mat-raised-button color="accent" [routerLink]="'/details/tv/' + element.parentRequest.tvDbId">{{'Requests.Details' | translate}}</button>
|
||||
<button mat-raised-button color="warn" (click)="openOptions(element)" *ngIf="isAdmin">{{'Requests.Options' | translate}}</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import { catchError, map, startWith, switchMap } from 'rxjs/operators';
|
|||
import { RequestServiceV2 } from "../../../services/requestV2.service";
|
||||
import { AuthService } from "../../../auth/auth.service";
|
||||
import { StorageService } from "../../../shared/storage/storage-service";
|
||||
import { RequestFilterType } from "../../models/RequestFilterType";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./tv-grid.component.html",
|
||||
|
@ -19,13 +20,17 @@ export class TvGridComponent implements OnInit, AfterViewInit {
|
|||
public isLoadingResults = true;
|
||||
public displayedColumns: string[] = ['series', 'requestedBy', 'status', 'requestStatus', 'requestedDate','actions'];
|
||||
public gridCount: string = "15";
|
||||
public showUnavailableRequests: boolean;
|
||||
public isAdmin: boolean;
|
||||
public defaultSort: string = "requestedDate";
|
||||
public defaultOrder: string = "desc";
|
||||
public currentFilter: RequestFilterType = RequestFilterType.All;
|
||||
|
||||
public RequestFilter = RequestFilterType;
|
||||
|
||||
private storageKey = "Tv_DefaultRequestListSort";
|
||||
private storageKeyOrder = "Tv_DefaultRequestListSortOrder";
|
||||
private storageKeyGridCount = "Tv_DefaultGridCount";
|
||||
private storageKeyCurrentFilter = "Tv_DefaultFilter";
|
||||
|
||||
@Output() public onOpenOptions = new EventEmitter<{request: any, filter: any, onChange: any}>();
|
||||
|
||||
|
@ -38,19 +43,30 @@ export class TvGridComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||
const defaultCount = this.storageService.get(this.storageKeyGridCount);
|
||||
const defaultSort = this.storageService.get(this.storageKey);
|
||||
const defaultOrder = this.storageService.get(this.storageKeyOrder);
|
||||
const defaultFilter = +this.storageService.get(this.storageKeyCurrentFilter);
|
||||
if (defaultSort) {
|
||||
this.defaultSort = defaultSort;
|
||||
}
|
||||
if (defaultOrder) {
|
||||
this.defaultOrder = defaultOrder;
|
||||
}
|
||||
if (defaultCount) {
|
||||
this.gridCount = defaultCount;
|
||||
}
|
||||
if (defaultFilter) {
|
||||
this.currentFilter = defaultFilter;
|
||||
}
|
||||
}
|
||||
|
||||
public async ngAfterViewInit() {
|
||||
|
||||
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||
this.storageService.save(this.storageKeyGridCount, this.gridCount);
|
||||
this.storageService.save(this.storageKeyCurrentFilter, (+this.currentFilter).toString());
|
||||
|
||||
// If the user changes the sort order, reset back to the first page.
|
||||
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
|
||||
|
||||
|
@ -93,10 +109,22 @@ export class TvGridComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
|
||||
private loadData(): Observable<IRequestsViewModel<IChildRequests>> {
|
||||
if(this.showUnavailableRequests) {
|
||||
return this.requestService.getTvUnavailableRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
} else {
|
||||
switch(RequestFilterType[RequestFilterType[this.currentFilter]]) {
|
||||
case RequestFilterType.All:
|
||||
return this.requestService.getTvRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
case RequestFilterType.Pending:
|
||||
return this.requestService.getPendingTvRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
case RequestFilterType.Available:
|
||||
return this.requestService.getAvailableTvRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
case RequestFilterType.Processing:
|
||||
return this.requestService.getProcessingTvRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
case RequestFilterType.Denied:
|
||||
return this.requestService.getDeniedTvRequests(+this.gridCount, this.paginator.pageIndex * +this.gridCount, this.sort.active, this.sort.direction);
|
||||
}
|
||||
}
|
||||
|
||||
public switchFilter(type: RequestFilterType) {
|
||||
this.currentFilter = type;
|
||||
this.ngAfterViewInit();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export enum RequestFilterType {
|
||||
All,
|
||||
Pending,
|
||||
Processing,
|
||||
Available,
|
||||
Denied
|
||||
}
|
|
@ -17,10 +17,42 @@ export class RequestServiceV2 extends ServiceHelpers {
|
|||
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getMovieAvailableRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IMovieRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/available/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getMovieProcessingRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IMovieRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/processing/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getMoviePendingRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IMovieRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/pending/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getMovieDeniedRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IMovieRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}movie/denied/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getTvRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IChildRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IChildRequests>>(`${this.url}tv/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getPendingTvRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IChildRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IChildRequests>>(`${this.url}tv/pending/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getProcessingTvRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IChildRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IChildRequests>>(`${this.url}tv/processing/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getAvailableTvRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IChildRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IChildRequests>>(`${this.url}tv/available/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getDeniedTvRequests(count: number, position: number, sortProperty: string , order: string): Observable<IRequestsViewModel<IChildRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IChildRequests>>(`${this.url}tv/denied/${count}/${position}/${sortProperty}/${order}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public updateMovieAdvancedOptions(options: IMovieAdvancedOptions): Observable<IRequestEngineResult> {
|
||||
return this.http.post<IRequestEngineResult>(`${this.url}movie/advancedoptions`, options, {headers: this.headers});
|
||||
}
|
||||
|
|
|
@ -77,17 +77,17 @@
|
|||
|
||||
<div class="mat-row">
|
||||
<div class="mat-cell">Ombi Database</div>
|
||||
<div class="mat-cell">{{about.ombiDatabaseType}} - {{about.ombiConnectionString}}</div>
|
||||
<div class="mat-cell">{{about.ombiDatabaseType}}</div>
|
||||
</div>
|
||||
|
||||
<div class="mat-row">
|
||||
<div class="mat-cell">External Database</div>
|
||||
<div class="mat-cell">{{about.externalDatabaseType}} - {{about.externalConnectionString}}</div>
|
||||
<div class="mat-cell">{{about.externalDatabaseType}}</div>
|
||||
</div>
|
||||
|
||||
<div class="mat-row">
|
||||
<div class="mat-cell">Settings Database</div>
|
||||
<div class="mat-cell">{{about.settingsDatabaseType}} - {{about.settingsConnectionString}}</div>
|
||||
<div class="mat-cell">{{about.settingsDatabaseType}}</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -9,10 +9,11 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-3">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="enable" [(ngModel)]="settings.enable" [checked]="settings.enable">
|
||||
<label for="enable">Enable</label>
|
||||
<div>
|
||||
<mat-checkbox [(ngModel)]="settings.enable" [checked]="settings.enable">
|
||||
Enable</mat-checkbox>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -24,59 +25,52 @@
|
|||
<br />
|
||||
<br />
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="name" class="control-label">Server name</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="name" name="name" placeholder="Server" [(ngModel)]="server.name" value="{{server.name}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="Ip" class="control-label">Hostname or IP</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" [(ngModel)]="server.ip" value="{{server.ip}}">
|
||||
</div>
|
||||
</div>
|
||||
<mat-form-field class="full">
|
||||
<input matInput placeholder="Server Name" [(ngModel)]="server.name" value="{{server.name}}">
|
||||
</mat-form-field>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="portNumber" class="control-label">Port</label>
|
||||
<div>
|
||||
<input type="text" [(ngModel)]="server.port" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="{{server.port}}">
|
||||
</div>
|
||||
</div>
|
||||
<mat-form-field class="full">
|
||||
<input matInput placeholder="Hostname or IP" [(ngModel)]="server.ip" value="{{server.ip}}">
|
||||
</mat-form-field>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="ssl" [(ngModel)]="server.ssl" ng-checked="server.ssl">
|
||||
<label for="ssl">SSL</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="authToken" class="control-label">Emby Api Key</label>
|
||||
<div class="">
|
||||
<input type="text" class="form-control-custom form-control" id="authToken" [(ngModel)]="server.apiKey" placeholder="Emby Api Key" value="{{server.apiKey}}">
|
||||
</div>
|
||||
</div>
|
||||
<mat-form-field class="full">
|
||||
<input matInput placeholder="Port" [(ngModel)]="server.port" value="{{server.port}}">
|
||||
</mat-form-field>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="authToken" class="control-label">Externally Facing Hostname
|
||||
<mat-checkbox [(ngModel)]="server.ssl" [checked]="server.ssl">
|
||||
SSL</mat-checkbox>
|
||||
|
||||
|
||||
<mat-form-field class="full">
|
||||
<input matInput placeholder="Api Key" [(ngModel)]="server.apiKey" value="{{server.apiKey}}">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="full">
|
||||
<input matInput placeholder="Base Url" [(ngModel)]="server.subDir" value="{{server.subDir}}">
|
||||
</mat-form-field>
|
||||
|
||||
<label> Externally Facing Hostname
|
||||
<i class="fa fa-question-circle"
|
||||
pTooltip="This will be the external address that users will navigate to when they press the 'View On Emby' button"></i>
|
||||
matTooltip="This will be the external address that users will navigate to when they press the 'View On Emby' button"></i>
|
||||
</label>
|
||||
<mat-form-field class="full">
|
||||
<input matInput placeholder="e.g. https://jellyfin.server.com/" [(ngModel)]="server.serverHostname" value="{{server.serverHostname}}">
|
||||
</mat-form-field>
|
||||
<small>
|
||||
<span *ngIf="server.serverHostname">Current URL: "{{server.serverHostname}}/#!/{{settings.isJellyfin ? ("itemdetails"): ("item/item")}}.html?id=1"</span>
|
||||
<span *ngIf="!server.serverHostname">Current URL: "https://app.emby.media/#!/{{settings.isJellyfin ? ("itemdetails"): ("item/item")}}.html?id=1</span>
|
||||
</small>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<input type="text" class="form-control-custom form-control" id="authToken" [(ngModel)]="server.serverHostname" placeholder="e.g. https://jellyfin.server.com/" value="{{server.serverHostname}}">
|
||||
<small><span *ngIf="server.serverHostname">Current URL: "{{server.serverHostname}}/#!/{{settings.isJellyfin ? ("itemdetails"): ("item/item")}}.html?id=1"</span>
|
||||
<span *ngIf="!server.serverHostname">Current URL: "https://app.emby.media/#!/{{settings.isJellyfin ? ("itemdetails"): ("item/item")}}.html?id=1</span></small>
|
||||
<button mat-raised-button id="testEmby" type="button" (click)="test(server)" color="primary">Test Connectivity <div id="spinner"></div></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button id="testEmby" type="button" (click)="test(server)" class="btn btn-primary-outline">Test Connectivity <div id="spinner"></div></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button id="discover" type="button" (click)="discoverServerInfo(server)" class="btn btn-primary-outline">Discover Server Information <div id="spinner"></div></button>
|
||||
<button mat-raised-button id="discover" type="button" (click)="discoverServerInfo(server)" color="accent">Discover Server Information <div id="spinner"></div></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -88,14 +82,14 @@
|
|||
<div class="col-md-2">
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="!hasDiscovered" (click)="save()" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
<button mat-raised-button [disabled]="!hasDiscovered" (click)="save()" type="submit" id="save" color="accent">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button (click)="runCacher()" type="button" id="save" class="btn btn-primary-outline">Manually Run Cacher</button>
|
||||
<button mat-raised-button (click)="runCacher()" type="button" id="save" color="primary">Manually Run Cacher</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<mat-checkbox formControlName="ignoreCertificateErrors" matTooltip="Enable if you are having connectivity problems over SSL">
|
||||
Ignore any certificate errors
|
||||
Ignore any certificate errors (Please restart after changing)
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
<head>
|
||||
<script type='text/javascript'>
|
||||
|
||||
function configExists(url) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open('GET', url, false);
|
||||
|
@ -44,6 +43,13 @@ function configExists(url) {
|
|||
<script src="styles/please-wait.js"></script>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta property="og:image:height" content="375" />
|
||||
<meta property="og:image:width" content="991" />
|
||||
<meta property="og:image" content="~/images/logo.png" />
|
||||
<meta property="og:site_name" content="Ombi" />
|
||||
<meta property="og:title" content="Ombi" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:description" content="Ombi, media request tool">
|
||||
|
||||
<title>Ombi</title>
|
||||
|
||||
|
|
|
@ -123,11 +123,8 @@ namespace Ombi.Controllers.V1
|
|||
OsDescription = RuntimeInformation.OSDescription,
|
||||
ProcessArchitecture = RuntimeInformation.ProcessArchitecture.ToString(),
|
||||
ApplicationBasePath = Directory.GetCurrentDirectory(),
|
||||
ExternalConnectionString = dbConfiguration.ExternalDatabase.ConnectionString,
|
||||
ExternalDatabaseType = dbConfiguration.ExternalDatabase.Type,
|
||||
OmbiConnectionString = dbConfiguration.OmbiDatabase.ConnectionString,
|
||||
OmbiDatabaseType = dbConfiguration.OmbiDatabase.Type,
|
||||
SettingsConnectionString = dbConfiguration.SettingsDatabase.ConnectionString,
|
||||
SettingsDatabaseType = dbConfiguration.SettingsDatabase.Type,
|
||||
StoragePath = storage.StoragePath.HasValue() ? storage.StoragePath : "None Specified",
|
||||
NotSupported = Directory.GetCurrentDirectory().Contains("qpkg")
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace Ombi.Controllers.V2
|
|||
var model = new List<ConnectedUsersViewModel>();
|
||||
foreach (var user in users)
|
||||
{
|
||||
var ombiUser = await allUsers.FirstOrDefaultAsync(x => x.Id.Equals(user.UserId, StringComparison.InvariantCultureIgnoreCase));
|
||||
var ombiUser = await allUsers.FirstOrDefaultAsync(x => x.Id == user.UserId);
|
||||
|
||||
if (ombiUser == null)
|
||||
{
|
||||
|
|
|
@ -39,6 +39,10 @@ namespace Ombi.Controllers.V2
|
|||
|
||||
var username = User.Identity.Name.ToUpper();
|
||||
var user = await _userManager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username);
|
||||
if (user == null)
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
// Check if we already have this notification id
|
||||
var alreadyExists = await _mobileDevices.GetAll().AnyAsync(x => x.Token == body.Token && x.UserId == user.Id);
|
||||
|
||||
|
@ -46,8 +50,14 @@ namespace Ombi.Controllers.V2
|
|||
{
|
||||
return Ok();
|
||||
}
|
||||
// Ensure we don't have too many already for this user
|
||||
var tokens = await _mobileDevices.GetAll().Where(x => x.UserId == user.Id).OrderBy(x => x.AddedAt).ToListAsync();
|
||||
if (tokens.Count() > 5)
|
||||
{
|
||||
var toDelete = tokens.Take(tokens.Count() - 5);
|
||||
await _mobileDevices.DeleteRange(toDelete);
|
||||
}
|
||||
|
||||
// let's add it
|
||||
await _mobileDevices.Add(new MobileDevices
|
||||
{
|
||||
Token = body.Token,
|
||||
|
|
|
@ -37,6 +37,7 @@ namespace Ombi.Controllers.V2
|
|||
}
|
||||
|
||||
[HttpGet("movie/availble/{count:int}/{position:int}/{sort}/{sortOrder}")]
|
||||
[HttpGet("movie/available/{count:int}/{position:int}/{sort}/{sortOrder}")]
|
||||
public async Task<RequestsViewModel<MovieRequests>> GetAvailableRequests(int count, int position, string sort, string sortOrder)
|
||||
{
|
||||
return await _movieRequestEngine.GetRequestsByStatus(count, position, sort, sortOrder, RequestStatus.Available);
|
||||
|
|
|
@ -106,15 +106,15 @@
|
|||
"MoviesTab": "Movies",
|
||||
"TvTab": "TV Shows",
|
||||
"MusicTab": "Music",
|
||||
"RequestedBy": "Requested By:",
|
||||
"Status": "Status:",
|
||||
"RequestStatus": "Request status:",
|
||||
"RequestedBy": "Requested By",
|
||||
"Status": "Status",
|
||||
"RequestStatus": "Request status",
|
||||
"Denied": " Denied:",
|
||||
"TheatricalRelease": "Theatrical Release: {{date}}",
|
||||
"ReleaseDate": "Released: {{date}}",
|
||||
"TheatricalReleaseSort": "Theatrical Release",
|
||||
"DigitalRelease": "Digital Release: {{date}}",
|
||||
"RequestDate": "Request Date:",
|
||||
"RequestDate": "Request Date",
|
||||
"QualityOverride": "Quality Override:",
|
||||
"RootFolderOverride": "Root Folder Override:",
|
||||
"ChangeRootFolder": "Root Folder",
|
||||
|
|
|
@ -106,15 +106,15 @@
|
|||
"MoviesTab": "Film",
|
||||
"TvTab": "Tv-serier",
|
||||
"MusicTab": "Musik",
|
||||
"RequestedBy": "Anmodet af:",
|
||||
"Status": "Status:",
|
||||
"RequestStatus": "Status for anmodning:",
|
||||
"RequestedBy": "Anmodet af",
|
||||
"Status": "Status",
|
||||
"RequestStatus": "Status for anmodning",
|
||||
"Denied": " Afvist:",
|
||||
"TheatricalRelease": "Biografudgivelse: {{date}}",
|
||||
"ReleaseDate": "Udgivet: {{date}}",
|
||||
"TheatricalReleaseSort": "Biografudgivelse",
|
||||
"DigitalRelease": "Digital udgivelse: {{date}}",
|
||||
"RequestDate": "Dato for anmodning:",
|
||||
"RequestDate": "Dato for anmodning",
|
||||
"QualityOverride": "Tilsidesæt kvalitet:",
|
||||
"RootFolderOverride": "Tilsidesæt rodmappe:",
|
||||
"ChangeRootFolder": "Skift rodmappe",
|
||||
|
|
|
@ -106,15 +106,15 @@
|
|||
"MoviesTab": "Filme",
|
||||
"TvTab": "Serien",
|
||||
"MusicTab": "Musik",
|
||||
"RequestedBy": "Angefordert von:",
|
||||
"Status": "Status:",
|
||||
"RequestStatus": "Anfrage Status:",
|
||||
"RequestedBy": "Angefordert von",
|
||||
"Status": "Status",
|
||||
"RequestStatus": "Anfrage Status",
|
||||
"Denied": " Abgelehnt:",
|
||||
"TheatricalRelease": "Kinostart: {{date}}",
|
||||
"ReleaseDate": "Veröffentlicht: {{date}}",
|
||||
"TheatricalReleaseSort": "Kinostart",
|
||||
"DigitalRelease": "Veröffentlichung der digitalen Version: {{date}}",
|
||||
"RequestDate": "Datum der Anfrage:",
|
||||
"RequestDate": "Datum der Anfrage",
|
||||
"QualityOverride": "Qualitäts Überschreiben:",
|
||||
"RootFolderOverride": "Stammverzeichnis Überschreiben:",
|
||||
"ChangeRootFolder": "Stammordner ändern",
|
||||
|
|
|
@ -115,15 +115,15 @@
|
|||
"MoviesTab": "Movies",
|
||||
"TvTab": "TV Shows",
|
||||
"MusicTab": "Music",
|
||||
"RequestedBy": "Requested By:",
|
||||
"Status": "Status:",
|
||||
"RequestStatus": "Request status:",
|
||||
"RequestedBy": "Requested By",
|
||||
"Status": "Status",
|
||||
"RequestStatus": "Request status",
|
||||
"Denied": " Denied:",
|
||||
"TheatricalRelease": "Theatrical Release: {{date}}",
|
||||
"ReleaseDate": "Released: {{date}}",
|
||||
"TheatricalReleaseSort": "Theatrical Release",
|
||||
"DigitalRelease": "Digital Release: {{date}}",
|
||||
"RequestDate": "Request Date:",
|
||||
"RequestDate": "Request Date",
|
||||
"QualityOverride": "Quality Override:",
|
||||
"RootFolderOverride": "Root Folder Override:",
|
||||
"ChangeRootFolder": "Root Folder",
|
||||
|
@ -153,7 +153,16 @@
|
|||
"NextHours": "Another request will be added in {{time}} hours",
|
||||
"NextMinutes": "Another request will be added in {{time}} minutes",
|
||||
"NextMinute": "Another request will be added in {{time}} minute"
|
||||
}
|
||||
},
|
||||
"AllRequests": "All Requests",
|
||||
"PendingRequests": "Pending Requests",
|
||||
"ProcessingRequests": "Processing Requests",
|
||||
"AvailableRequests": "Available Requests",
|
||||
"DeniedRequests": "Denied Requests",
|
||||
"RequestsToDisplay": "Requests to display",
|
||||
"RequestsTitle": "Title",
|
||||
"Details": "Details",
|
||||
"Options": "Options"
|
||||
},
|
||||
"Issues": {
|
||||
"Title": "Issues",
|
||||
|
|
|
@ -106,15 +106,15 @@
|
|||
"MoviesTab": "Películas",
|
||||
"TvTab": "Series",
|
||||
"MusicTab": "Música",
|
||||
"RequestedBy": "Solicitado por:",
|
||||
"Status": "Estado:",
|
||||
"RequestStatus": "Estado de la solicitud:",
|
||||
"RequestedBy": "Solicitado por",
|
||||
"Status": "Estado",
|
||||
"RequestStatus": "Estado de la solicitud",
|
||||
"Denied": " Denegado:",
|
||||
"TheatricalRelease": "En cines: {{date}}",
|
||||
"ReleaseDate": "Publicado: {{date}}",
|
||||
"TheatricalReleaseSort": "En cines",
|
||||
"DigitalRelease": "Versión digital: {{date}}",
|
||||
"RequestDate": "Fecha de solicitud:",
|
||||
"RequestDate": "Fecha de solicitud",
|
||||
"QualityOverride": "Sobreescribir calidad:",
|
||||
"RootFolderOverride": "Sobreescribir carpeta raíz:",
|
||||
"ChangeRootFolder": "Carpeta raíz",
|
||||
|
|
|
@ -106,15 +106,15 @@
|
|||
"MoviesTab": "Films",
|
||||
"TvTab": "Séries",
|
||||
"MusicTab": "Musique",
|
||||
"RequestedBy": "Demandé par :",
|
||||
"Status": "Statut :",
|
||||
"RequestStatus": "Statut de la demande :",
|
||||
"RequestedBy": "Demandé par",
|
||||
"Status": "Statut",
|
||||
"RequestStatus": "Statut de la demande",
|
||||
"Denied": " Refusé :",
|
||||
"TheatricalRelease": "Sortie en salle: {{date}}",
|
||||
"ReleaseDate": "Sortie : {{date}}",
|
||||
"TheatricalReleaseSort": "Sortie en salle",
|
||||
"DigitalRelease": "Sortie numérique: {{date}}",
|
||||
"RequestDate": "Date de la demande :",
|
||||
"RequestDate": "Date de la demande",
|
||||
"QualityOverride": "Remplacement de la qualité :",
|
||||
"RootFolderOverride": "Remplacement du répertoire racine :",
|
||||
"ChangeRootFolder": "Modifier le répertoire racine",
|
||||
|
|
|
@ -106,15 +106,15 @@
|
|||
"MoviesTab": "Filmek",
|
||||
"TvTab": "Sorozatok",
|
||||
"MusicTab": "Zene",
|
||||
"RequestedBy": "Kérte:",
|
||||
"Status": "Állapot:",
|
||||
"RequestStatus": "Kérés állapota:",
|
||||
"RequestedBy": "Kérte",
|
||||
"Status": "Állapot",
|
||||
"RequestStatus": "Kérés állapota",
|
||||
"Denied": " Megtagadta:",
|
||||
"TheatricalRelease": "Mozis kiadás: {{date}}",
|
||||
"ReleaseDate": "Kiadva: {{date}}",
|
||||
"TheatricalReleaseSort": "Mozis kiadás",
|
||||
"DigitalRelease": "Digitális kiadás: {{date}}",
|
||||
"RequestDate": "Kérés ideje:",
|
||||
"RequestDate": "Kérés ideje",
|
||||
"QualityOverride": "Minőség felülírása:",
|
||||
"RootFolderOverride": "Gyökér mappa felülírása:",
|
||||
"ChangeRootFolder": "Gyökér mappa",
|
||||
|
|
|
@ -106,15 +106,15 @@
|
|||
"MoviesTab": "Film",
|
||||
"TvTab": "Serie TV",
|
||||
"MusicTab": "Music",
|
||||
"RequestedBy": "Richiesta da:",
|
||||
"Status": "Stato:",
|
||||
"RequestStatus": "Stato della richiesta:",
|
||||
"RequestedBy": "Richiesta da",
|
||||
"Status": "Stato",
|
||||
"RequestStatus": "Stato della richiesta",
|
||||
"Denied": " Rifiutato:",
|
||||
"TheatricalRelease": "Theatrical Release: {{date}}",
|
||||
"ReleaseDate": "Released: {{date}}",
|
||||
"TheatricalReleaseSort": "Theatrical Release",
|
||||
"DigitalRelease": "Digital Release: {{date}}",
|
||||
"RequestDate": "Data della richiesta:",
|
||||
"RequestDate": "Data della richiesta",
|
||||
"QualityOverride": "Sovrascrivi qualità:",
|
||||
"RootFolderOverride": "Sovrascrivi cartella principale:",
|
||||
"ChangeRootFolder": "Modifica cartella principale",
|
||||
|
|
|
@ -106,15 +106,15 @@
|
|||
"MoviesTab": "Films",
|
||||
"TvTab": "TV Series",
|
||||
"MusicTab": "Muziek",
|
||||
"RequestedBy": "Verzocht Door:",
|
||||
"Status": "Status:",
|
||||
"RequestStatus": "Aanvraagstatus:",
|
||||
"RequestedBy": "Verzocht Door",
|
||||
"Status": "Status",
|
||||
"RequestStatus": "Aanvraagstatus",
|
||||
"Denied": " Geweigerd:",
|
||||
"TheatricalRelease": "Cinema Uitgave: {{date}}",
|
||||
"ReleaseDate": "Uitgekomen: {{date}}",
|
||||
"TheatricalReleaseSort": "Bioscoop Uitgave",
|
||||
"DigitalRelease": "Digitale Uitgave: {{date}}",
|
||||
"RequestDate": "Aanvraag Datum:",
|
||||
"RequestDate": "Aanvraag Datum",
|
||||
"QualityOverride": "Kwaliteit overschrijven:",
|
||||
"RootFolderOverride": "Hoofdmap overschrijven:",
|
||||
"ChangeRootFolder": "Hoofdmap wijzigen",
|
||||
|
|
|
@ -106,15 +106,15 @@
|
|||
"MoviesTab": "Filmer",
|
||||
"TvTab": "TV serier",
|
||||
"MusicTab": "Musikk",
|
||||
"RequestedBy": "Etterspurt av:",
|
||||
"Status": "Status:",
|
||||
"RequestStatus": "Status for forespørsel:",
|
||||
"RequestedBy": "Etterspurt av",
|
||||
"Status": "Status",
|
||||
"RequestStatus": "Status for forespørsel",
|
||||
"Denied": " Avslått:",
|
||||
"TheatricalRelease": "Kinopremiere: {{date}}",
|
||||
"ReleaseDate": "Utgitt: {{date}}",
|
||||
"TheatricalReleaseSort": "Kinopremiere",
|
||||
"DigitalRelease": "Digital utgivelse: {{date}}",
|
||||
"RequestDate": "Dato for forespørsel:",
|
||||
"RequestDate": "Dato for forespørsel",
|
||||
"QualityOverride": "Overstyr kvalitet:",
|
||||
"RootFolderOverride": "Overstyring av rotmappe:",
|
||||
"ChangeRootFolder": "Endre rotmappe",
|
||||
|
|
|
@ -106,15 +106,15 @@
|
|||
"MoviesTab": "Filmy",
|
||||
"TvTab": "Seriale",
|
||||
"MusicTab": "Muzyka",
|
||||
"RequestedBy": "Zgłoszone przez:",
|
||||
"Status": "Status:",
|
||||
"RequestStatus": "Status zgłoszenia:",
|
||||
"RequestedBy": "Zgłoszone przez",
|
||||
"Status": "Status",
|
||||
"RequestStatus": "Status zgłoszenia",
|
||||
"Denied": "Odrzucono:",
|
||||
"TheatricalRelease": "Premiera kinowa: {{date}}",
|
||||
"ReleaseDate": "Wydany: {{date}}",
|
||||
"TheatricalReleaseSort": "Premiera kinowa",
|
||||
"DigitalRelease": "Wydanie cyfrowe: {{date}}",
|
||||
"RequestDate": "Data zgłoszenia:",
|
||||
"RequestDate": "Data zgłoszenia",
|
||||
"QualityOverride": "Wymuszenie jakości:",
|
||||
"RootFolderOverride": "Wymuszenie folderu głównego:",
|
||||
"ChangeRootFolder": "Folder główny",
|
||||
|
|
|
@ -106,15 +106,15 @@
|
|||
"MoviesTab": "Filmes",
|
||||
"TvTab": "Séries",
|
||||
"MusicTab": "Músicas",
|
||||
"RequestedBy": "Solicitado por:",
|
||||
"Status": "Status:",
|
||||
"RequestStatus": "Status da solicitação:",
|
||||
"RequestedBy": "Solicitado por",
|
||||
"Status": "Status",
|
||||
"RequestStatus": "Status da solicitação",
|
||||
"Denied": " Negados:",
|
||||
"TheatricalRelease": "Lançamento nos Cinemas: {{date}}",
|
||||
"ReleaseDate": "Lançado: {{date}}",
|
||||
"TheatricalReleaseSort": "Lançamento nos Cinemas",
|
||||
"DigitalRelease": "Lançamento digital: {{date}}",
|
||||
"RequestDate": "Data da Solicitação:",
|
||||
"RequestDate": "Data da Solicitação",
|
||||
"QualityOverride": "Substituição de qualidade:",
|
||||
"RootFolderOverride": "Substituição da pasta raiz:",
|
||||
"ChangeRootFolder": "Pasta raiz",
|
||||
|
|
|
@ -106,15 +106,15 @@
|
|||
"MoviesTab": "Фильмы",
|
||||
"TvTab": "Сериалы",
|
||||
"MusicTab": "Музыка",
|
||||
"RequestedBy": "Автор запроса:",
|
||||
"Status": "Статус:",
|
||||
"RequestStatus": "Статус запроса:",
|
||||
"RequestedBy": "Автор запроса",
|
||||
"Status": "Статус",
|
||||
"RequestStatus": "Статус запроса",
|
||||
"Denied": " Отказано:",
|
||||
"TheatricalRelease": "Релиз в кинотеатрах: {{date}}",
|
||||
"ReleaseDate": "Дата выхода: {{date}}",
|
||||
"TheatricalReleaseSort": "Релиз в кинотеатрах",
|
||||
"DigitalRelease": "Дигитальный релиз: {{date}}",
|
||||
"RequestDate": "Дата запроса:",
|
||||
"RequestDate": "Дата запроса",
|
||||
"QualityOverride": "Переопределение качества:",
|
||||
"RootFolderOverride": "Переопределение корневой папки:",
|
||||
"ChangeRootFolder": "Корневая папка",
|
||||
|
|
|
@ -106,15 +106,15 @@
|
|||
"MoviesTab": "Filmy",
|
||||
"TvTab": "Seriály",
|
||||
"MusicTab": "Hudba",
|
||||
"RequestedBy": "Vyžiadané od:",
|
||||
"Status": "Stav:",
|
||||
"RequestStatus": "Stav požiadavky:",
|
||||
"RequestedBy": "Vyžiadané od",
|
||||
"Status": "Stav",
|
||||
"RequestStatus": "Stav požiadavky",
|
||||
"Denied": " Zamietnuté:",
|
||||
"TheatricalRelease": "Kino vydanie: {{date}}",
|
||||
"ReleaseDate": "Vydané: {{date}}",
|
||||
"TheatricalReleaseSort": "Kino vydanie",
|
||||
"DigitalRelease": "Online vydanie: {{date}}",
|
||||
"RequestDate": "Dátum požiadavky:",
|
||||
"RequestDate": "Dátum požiadavky",
|
||||
"QualityOverride": "Prepísanie kvality:",
|
||||
"RootFolderOverride": "Prepísanie Root priečinku:",
|
||||
"ChangeRootFolder": "Koreňový priečinok",
|
||||
|
|
|
@ -106,15 +106,15 @@
|
|||
"MoviesTab": "Filmer",
|
||||
"TvTab": "TV-serier",
|
||||
"MusicTab": "Musik",
|
||||
"RequestedBy": "Efterfrågats av:",
|
||||
"Status": "Status:",
|
||||
"RequestStatus": "Status för begäran:",
|
||||
"RequestedBy": "Efterfrågats av",
|
||||
"Status": "Status",
|
||||
"RequestStatus": "Status för begäran",
|
||||
"Denied": " Nekad:",
|
||||
"TheatricalRelease": "Biopremiär: {{date}}",
|
||||
"ReleaseDate": "Releasedatum: {{date}}",
|
||||
"TheatricalReleaseSort": "Biopremiär",
|
||||
"DigitalRelease": "Digitalt Releasedatum: {{date}}",
|
||||
"RequestDate": "Datum för begäran:",
|
||||
"RequestDate": "Datum för begäran",
|
||||
"QualityOverride": "Kvalitétsöverskridande:",
|
||||
"RootFolderOverride": "Rotmappsöverskridande:",
|
||||
"ChangeRootFolder": "Byt rotmapp",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue