mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-14 02:26:55 -07:00
Merge branch 'develop' into feature/reverse-proxy-chnage
This commit is contained in:
commit
4035bbeb89
45 changed files with 345 additions and 186 deletions
|
@ -111,7 +111,8 @@ stages:
|
|||
|
||||
$header = @{
|
||||
"Accept"="application/vnd.github.v3+json"
|
||||
"Authorization"="Bearer ${env:APTPAT}"
|
||||
"Authorization"="Bearer $(APTPAT)"
|
||||
"User-Agent"="Ombi"
|
||||
}
|
||||
|
||||
Invoke-RestMethod -Uri "https://api.github.com/repos/Ombi-app/Ombi.Apt/actions/workflows/build-deb.yml/dispatches" -Method 'Post' -Body $body -Headers $header
|
|
@ -5,6 +5,7 @@ using Ombi.Store.Entities;
|
|||
using Ombi.Store.Entities.Requests;
|
||||
using Ombi.Store.Repository;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -93,18 +94,27 @@ namespace Ombi.Core.Engine
|
|||
var issues = _issuesRepository.GetAll().Where(x => x.UserReportedId == userId);
|
||||
var issueComments = _issueCommentsRepository.GetAll().Where(x => x.UserId == userId);
|
||||
var requestLog = _requestLogRepository.GetAll().Where(x => x.UserId == userId);
|
||||
|
||||
if (issueComments.Any())
|
||||
{
|
||||
await _issueCommentsRepository.DeleteRange(issueComments);
|
||||
}
|
||||
if (issues.Any())
|
||||
{
|
||||
var extraComments = new List<IssueComments>();
|
||||
var issueIds = issues.Select(x => x.Id).Distinct();
|
||||
foreach (var issue in issueIds)
|
||||
{
|
||||
// Get all the comments for this issue and delete them, since the issue will be deleted
|
||||
var extra = _issueCommentsRepository.GetAll().Where(x => x.IssuesId == issue);
|
||||
extraComments.AddRange(extra.ToList());
|
||||
}
|
||||
await _issuesRepository.DeleteRange(issues);
|
||||
}
|
||||
if (requestLog.Any())
|
||||
{
|
||||
await _requestLogRepository.DeleteRange(requestLog);
|
||||
}
|
||||
if (issueComments.Any())
|
||||
{
|
||||
await _issueCommentsRepository.DeleteRange(issueComments);
|
||||
}
|
||||
|
||||
// Delete the Subscriptions and mobile notification ids
|
||||
var subs = _requestSubscriptionRepository.GetAll().Where(x => x.UserId == userId);
|
||||
|
|
|
@ -36,5 +36,7 @@ namespace Ombi.Core.Models.UI
|
|||
{
|
||||
public string Id { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Email { get; set; }
|
||||
|
||||
}
|
||||
}
|
|
@ -124,5 +124,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "ombi"
|
||||
"defaultProject": "ombi",
|
||||
"cli": {
|
||||
"analytics": false
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@ import { SettingsService } from "./services";
|
|||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
|
||||
import { ICustomizationSettings, ICustomPage } from "./interfaces";
|
||||
import { StorageService } from './shared/storage/storage-service';
|
||||
|
||||
import { SignalRNotificationService } from './services/signlarnotification.service';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
|
@ -46,7 +45,6 @@ export class AppComponent implements OnInit {
|
|||
public readonly translate: TranslateService,
|
||||
private readonly customPageService: CustomPageService,
|
||||
public overlayContainer: OverlayContainer,
|
||||
private storage: StorageService,
|
||||
private signalrNotification: SignalRNotificationService,
|
||||
private readonly snackBar: MatSnackBar,
|
||||
private readonly identity: IdentityService,
|
||||
|
|
|
@ -28,6 +28,7 @@ export interface IUser {
|
|||
export interface IUserDropdown {
|
||||
username: string;
|
||||
id: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface IUserQualityProfiles {
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
<div *ngIf="form && customizationSettings && authenticationSettings">
|
||||
|
||||
<mat-card class="mat-elevation-z8 top-margin login-card">
|
||||
<H1 *ngIf="!customizationSettings.logo" class="login_logo">OMBI</H1>
|
||||
<H1 *ngIf="!customizationSettings.logo && !customizationSettings.applicationName" class="login_logo">OMBI</H1>
|
||||
<H1 *ngIf="customizationSettings.applicationName && !customizationSettings.logo" class="login_logo custom">{{customizationSettings.applicationName}}</H1>
|
||||
<img mat-card-image *ngIf="customizationSettings.logo" [src]="customizationSettings.logo">
|
||||
<mat-card-content id="login-box" *ngIf="!authenticationSettings.enableOAuth || loginWithOmbi">
|
||||
<form *ngIf="authenticationSettings" class="form-signin" novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
|
||||
|
|
|
@ -203,6 +203,9 @@ div.bg {
|
|||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 10em;
|
||||
&.custom {
|
||||
font-size: 5em;
|
||||
}
|
||||
width:100%;
|
||||
margin-bottom:70px;
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
</button>
|
||||
</span>
|
||||
|
||||
<button mat-raised-button class="btn-spacing" color="danger" (click)="issue()">
|
||||
<button mat-raised-button class="btn-spacing" color="danger" (click)="issue()" *ngIf="issuesEnabled">
|
||||
<i class="fas fa-exclamation"></i> {{'Requests.ReportIssue' | translate }}
|
||||
</button>
|
||||
<button *ngIf="movie.belongsToCollection" [routerLink]="'/discover/collection/' + movie.belongsToCollection.id" mat-raised-button class="btn-spacing">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { AfterViewInit, Component, ViewChild, ViewEncapsulation } from "@angular/core";
|
||||
import { ImageService, SearchV2Service, RequestService, MessageService, RadarrService } from "../../../services";
|
||||
import { ImageService, SearchV2Service, RequestService, MessageService, RadarrService, SettingsStateService } from "../../../services";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2";
|
||||
|
@ -26,6 +26,7 @@ export class MovieDetailsComponent {
|
|||
public isAdmin: boolean;
|
||||
public advancedOptions: IAdvancedData;
|
||||
public showAdvanced: boolean; // Set on the UI
|
||||
public issuesEnabled: boolean;
|
||||
|
||||
public requestType = RequestType.movie;
|
||||
|
||||
|
@ -37,7 +38,7 @@ export class MovieDetailsComponent {
|
|||
private sanitizer: DomSanitizer, private imageService: ImageService,
|
||||
public dialog: MatDialog, private requestService: RequestService,
|
||||
private requestService2: RequestServiceV2, private radarrService: RadarrService,
|
||||
public messageService: MessageService, private auth: AuthService) {
|
||||
public messageService: MessageService, private auth: AuthService, private settingsState: SettingsStateService) {
|
||||
this.route.params.subscribe(async (params: any) => {
|
||||
if (typeof params.movieDbId === 'string' || params.movieDbId instanceof String) {
|
||||
if (params.movieDbId.startsWith("tt")) {
|
||||
|
@ -51,6 +52,7 @@ export class MovieDetailsComponent {
|
|||
|
||||
public async load() {
|
||||
|
||||
this.issuesEnabled = this.settingsState.getIssue();
|
||||
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||
|
||||
if (this.isAdmin) {
|
||||
|
@ -188,7 +190,6 @@ export class MovieDetailsComponent {
|
|||
const folders = this.radarrService.getRootFoldersFromSettings();
|
||||
|
||||
forkJoin([profile, folders]).subscribe(x => {
|
||||
debugger;
|
||||
const radarrProfiles = x[0];
|
||||
const radarrRootFolders = x[1];
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Advanced Options</h1>
|
||||
<div mat-dialog-content>
|
||||
<mat-form-field>
|
||||
<mat-form-field appearance="outline" floatLabel=auto>
|
||||
<mat-label>{{'MediaDetails.QualityProfilesSelect' | translate }}</mat-label>
|
||||
<mat-select [(value)]="data.profileId">
|
||||
<mat-option *ngFor="let profile of radarrProfiles" value="{{profile.id}}">{{profile.name}}</mat-option>
|
||||
|
@ -10,7 +10,7 @@
|
|||
</mat-form-field>
|
||||
</div>
|
||||
<div mat-dialog-content>
|
||||
<mat-form-field>
|
||||
<mat-form-field appearance="outline" floatLabel=auto>
|
||||
<mat-label>{{'MediaDetails.RootFolderSelect' | translate }}</mat-label>
|
||||
<mat-select [(value)]="data.rootFolderId">
|
||||
<mat-option *ngFor="let profile of radarrRootFolders" value="{{profile.id}}">{{profile.path}}</mat-option>
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
<h1 mat-dialog-title>{{ 'MediaDetails.RequestOnBehalf' | translate}}</h1>
|
||||
<div mat-dialog-content>
|
||||
<form class="example-form">
|
||||
<mat-form-field class="example-full-width">
|
||||
<mat-form-field class="example-full-width" appearance="outline" floatLabel=auto>
|
||||
<mat-label>{{ 'MediaDetails.PleaseSelectUser' | translate}}</mat-label>
|
||||
<input type="text"
|
||||
placeholder="{{ 'MediaDetails.PleaseSelectUser' | translate}}"
|
||||
aria-label="Number"
|
||||
matInput
|
||||
[formControl]="myControl"
|
||||
[matAutocomplete]="auto">
|
||||
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
|
||||
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
|
||||
{{option.username}}
|
||||
{{displayFn(option)}}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button mat-raised-button (click)="onNoClick()"> Cancel</button>
|
||||
<button mat-raised-button (click)="onNoClick()">{{'Common.Cancel' | translate}}</button>
|
||||
<button mat-raised-button (click)="request()" color="accent" [mat-dialog-close]="userId" cdkFocusInitial>{{'Common.Request' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -41,7 +41,9 @@ export class RequestBehalfComponent implements OnInit {
|
|||
}
|
||||
|
||||
public displayFn(user: IUserDropdown): string {
|
||||
return user?.username ? user.username : '';
|
||||
const username = user?.username ? user.username : '';
|
||||
const email = user?.email ? `(${user.email})` : '';
|
||||
return `${username} ${email}`;
|
||||
}
|
||||
|
||||
private _filter(value: string|IUserDropdown): IUserDropdown[] {
|
||||
|
|
|
@ -1,63 +1,68 @@
|
|||
<div>
|
||||
<span *ngIf="tv.rating">
|
||||
<img style="width: 4em;" src="{{baseUrl}}/images/tvm-logo.png"> {{tv.rating}}/10
|
||||
</span>
|
||||
<span *ngIf="ratings?.score && ratings?.class">
|
||||
<img class="rating-small" src="{{baseUrl}}/images/{{ratings.class === 'rotten' ? 'rotten-rotten.svg' : 'rotten-fresh.svg'}}"> {{ratings.score}}%
|
||||
</span>
|
||||
|
||||
<div *ngIf="streams?.length > 0">
|
||||
<div class="left-panel-details">
|
||||
<div>
|
||||
<div class="rating medium-font">
|
||||
<span *ngIf="tv.rating">
|
||||
<img class="rating-small" src="{{baseUrl}}/images/tvm-logo.png"> {{tv.rating}}/10
|
||||
</span>
|
||||
<span *ngIf="ratings?.score && ratings?.class">
|
||||
<img class="rating-small" src="{{baseUrl}}/images/{{ratings.class === 'rotten' ? 'rotten-rotten.svg' : 'rotten-fresh.svg'}}"> {{ratings.score}}%
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="streams?.length > 0" class="streaming-on-container">
|
||||
<hr>
|
||||
<div class="streaming-on-content">
|
||||
<span class="label">{{'MediaDetails.StreamingOn' | translate }}:</span>
|
||||
<div>
|
||||
<span *ngFor="let stream of streams">
|
||||
<img class="stream-small" [matTooltip]="stream.streamingProvider" src="https://image.tmdb.org/t/p/original{{stream.logo}}">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<strong>{{'MediaDetails.StreamingOn' | translate }}:</strong>
|
||||
<div *ngIf="tv.status">
|
||||
<span class="label">{{'MediaDetails.Status' | translate }}:</span>
|
||||
{{tv.status}}
|
||||
</div>
|
||||
<span class="label">First Aired:</span>
|
||||
{{tv.firstAired | date: 'mediumDate'}}
|
||||
</div>
|
||||
|
||||
<div *ngIf="seasonCount">
|
||||
<span class="label">Seasons:</span>
|
||||
{{seasonCount}}
|
||||
</div>
|
||||
<div *ngIf="totalEpisodes">
|
||||
<span class="label">Episodes:</span>
|
||||
{{totalEpisodes}}
|
||||
</div>
|
||||
|
||||
<div *ngIf="advancedOptions && request?.rootPathOverrideTitle">
|
||||
<span class="label">{{'MediaDetails.RootFolderOverride' | translate }}:</span>
|
||||
<div>{{request.rootPathOverrideTitle}}</div>
|
||||
</div>
|
||||
<div *ngIf="advancedOptions && request?.qualityOverrideTitle">
|
||||
<span class="label">{{'MediaDetails.QualityOverride' | translate }}:</span>
|
||||
<div>{{request.qualityOverrideTitle}}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="label">{{'MediaDetails.Runtime' | translate }}:</span>
|
||||
{{'MediaDetails.Minutes' | translate:{ runtime: tv.runtime} }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="tv.network">
|
||||
<span class="label">Network:</span>
|
||||
{{tv.network.name}}
|
||||
</div>
|
||||
|
||||
<div *ngIf="tv.genre">
|
||||
<span class="label">{{'MediaDetails.Genres' | translate }}:</span>
|
||||
<div>
|
||||
<span *ngFor="let stream of streams">
|
||||
<img class="stream-small" [matTooltip]="stream.streamingProvider" src="https://image.tmdb.org/t/p/original{{stream.logo}}">
|
||||
</span>
|
||||
<span *ngFor="let genre of tv.genre">
|
||||
{{genre}} |
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div *ngIf="tv.status">
|
||||
<strong>{{'MediaDetails.Status' | translate }}:</strong>
|
||||
{{tv.status}}
|
||||
</div>
|
||||
<strong>First Aired:</strong>
|
||||
{{tv.firstAired | date: 'mediumDate'}}
|
||||
</div>
|
||||
|
||||
<div *ngIf="seasonCount">
|
||||
<strong>Seasons:</strong>
|
||||
{{seasonCount}}
|
||||
</div>
|
||||
<div *ngIf="totalEpisodes">
|
||||
<strong>Episodes:</strong>
|
||||
{{totalEpisodes}}
|
||||
</div>
|
||||
|
||||
<div *ngIf="advancedOptions && request?.rootPathOverrideTitle">
|
||||
<strong>{{'MediaDetails.RootFolderOverride' | translate }}:</strong>
|
||||
<div>{{request.rootPathOverrideTitle}}</div>
|
||||
</div>
|
||||
<div *ngIf="advancedOptions && request?.qualityOverrideTitle">
|
||||
<strong>{{'MediaDetails.QualityOverride' | translate }}:</strong>
|
||||
<div>{{request.qualityOverrideTitle}}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>{{'MediaDetails.Runtime' | translate }}:</strong>
|
||||
{{'MediaDetails.Minutes' | translate:{ runtime: tv.runtime} }}
|
||||
</div>
|
||||
|
||||
<div *ngIf="tv.network">
|
||||
<strong>Network:</strong>
|
||||
{{tv.network.name}}
|
||||
</div>
|
||||
|
||||
<div *ngIf="tv.genre">
|
||||
<strong>{{'MediaDetails.Genres' | translate }}:</strong>
|
||||
<div>
|
||||
<span *ngFor="let genre of tv.genre">
|
||||
{{genre}} |
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
class="btn-spacing" color="accent" [disabled]>
|
||||
<i class="fas fa-check"></i> {{'Common.PartiallyAvailable' | translate }}</button>
|
||||
|
||||
<button mat-raised-button class="btn-spacing" color="danger" (click)="issue()">
|
||||
<button mat-raised-button class="btn-spacing" color="danger" *ngIf="issuesEnabled" (click)="issue()">
|
||||
<i class="fas fa-exclamation"></i> {{
|
||||
'Requests.ReportIssue' | translate }}</button>
|
||||
|
||||
|
@ -67,8 +67,8 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-2">
|
||||
<mat-card class="mat-elevation-z8">
|
||||
<mat-card-content class="medium-font">
|
||||
<mat-card class="mat-elevation-z8 spacing-below">
|
||||
<mat-card-content>
|
||||
<tv-information-panel [tv]="tv" [request]="showRequest"
|
||||
[advancedOptions]="showAdvanced"></tv-information-panel>
|
||||
</mat-card-content>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, ViewEncapsulation, OnInit } from "@angular/core";
|
||||
import { ImageService, SearchV2Service, MessageService, RequestService, SonarrService } from "../../../services";
|
||||
import { ImageService, SearchV2Service, MessageService, RequestService, SonarrService, SettingsStateService } from "../../../services";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { ISearchTvResultV2 } from "../../../interfaces/ISearchTvResultV2";
|
||||
|
@ -29,6 +29,7 @@ export class TvDetailsComponent implements OnInit {
|
|||
public advancedOptions: IAdvancedData;
|
||||
public showAdvanced: boolean; // Set on the UI
|
||||
public requestType = RequestType.tvShow;
|
||||
public issuesEnabled: boolean;
|
||||
|
||||
private tvdbId: number;
|
||||
|
||||
|
@ -36,7 +37,7 @@ export class TvDetailsComponent implements OnInit {
|
|||
private sanitizer: DomSanitizer, private imageService: ImageService,
|
||||
public dialog: MatDialog, public messageService: MessageService, private requestService: RequestService,
|
||||
private requestService2: RequestServiceV2,
|
||||
private auth: AuthService, private sonarrService: SonarrService) {
|
||||
private auth: AuthService, private sonarrService: SonarrService, private settingsState: SettingsStateService) {
|
||||
this.route.params.subscribe((params: any) => {
|
||||
this.tvdbId = params.tvdbId;
|
||||
this.fromSearch = params.search;
|
||||
|
@ -49,6 +50,7 @@ export class TvDetailsComponent implements OnInit {
|
|||
|
||||
public async load() {
|
||||
|
||||
this.issuesEnabled = this.settingsState.getIssue();
|
||||
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||
|
||||
if (this.isAdmin) {
|
||||
|
|
|
@ -288,6 +288,18 @@
|
|||
font-weight:500;
|
||||
}
|
||||
|
||||
.left-panel-details .streaming-on-content{
|
||||
display:flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-flow:row wrap;
|
||||
}
|
||||
|
||||
.left-panel-details .streaming-on-content .label{
|
||||
white-space:nowrap;
|
||||
padding-right:10px;
|
||||
}
|
||||
|
||||
.left-panel-details{
|
||||
font-weight:100;
|
||||
}
|
||||
|
@ -314,6 +326,7 @@
|
|||
padding:2px 1.5em;;
|
||||
width:170px;
|
||||
margin-top:10px;
|
||||
margin-left:10px;
|
||||
}
|
||||
|
||||
@media (max-width:500px){
|
||||
|
@ -330,6 +343,7 @@
|
|||
|
||||
.media-row .mat-raised-button{
|
||||
width:100%;
|
||||
margin-left:0px;
|
||||
}
|
||||
|
||||
.media-row .btn-spacing{
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Observable } from 'rxjs';
|
|||
import { map } from 'rxjs/operators';
|
||||
import { INavBar } from '../interfaces/ICommon';
|
||||
import { StorageService } from '../shared/storage/storage-service';
|
||||
import { SettingsService } from '../services';
|
||||
import { SettingsService, SettingsStateService } from '../services';
|
||||
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
|
||||
import { SearchFilter } from './SearchFilter';
|
||||
import { Md5 } from 'ts-md5/dist/md5';
|
||||
|
@ -49,7 +49,8 @@ export class MyNavComponent implements OnInit {
|
|||
constructor(private breakpointObserver: BreakpointObserver,
|
||||
private settingsService: SettingsService,
|
||||
private store: StorageService,
|
||||
private filterService: FilterService) {
|
||||
private filterService: FilterService,
|
||||
private readonly settingState: SettingsStateService) {
|
||||
}
|
||||
|
||||
public async ngOnInit() {
|
||||
|
@ -68,6 +69,8 @@ export class MyNavComponent implements OnInit {
|
|||
}
|
||||
|
||||
this.issuesEnabled = await this.settingsService.issueEnabled().toPromise();
|
||||
this.settingState.setIssue(this.issuesEnabled);
|
||||
|
||||
const customizationSettings = await this.settingsService.getCustomization().toPromise();
|
||||
console.log("issues enabled: " + this.issuesEnabled);
|
||||
this.theme = this.store.get("theme");
|
||||
|
|
|
@ -14,18 +14,18 @@ export class FileDownloadService extends ServiceHelpers {
|
|||
|
||||
downloadFile(url: string, contentType: string): void {
|
||||
this.http.get(url).subscribe((response: any) => {
|
||||
|
||||
|
||||
// It is necessary to create a new blob object with mime-type explicitly set
|
||||
// otherwise only Chrome works like it should
|
||||
const newBlob = new Blob([(response)], { type: contentType });
|
||||
|
||||
|
||||
// IE doesn't allow using a blob object directly as link href
|
||||
// instead it is necessary to use msSaveOrOpenBlob
|
||||
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
|
||||
window.navigator.msSaveOrOpenBlob(newBlob);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// For other browsers:
|
||||
// Create a link pointing to the ObjectURL containing the blob.
|
||||
const downloadURL = URL.createObjectURL(response);
|
||||
|
|
|
@ -22,3 +22,4 @@ export * from "./message.service";
|
|||
export * from "./hub.service";
|
||||
export * from "./system.service";
|
||||
export * from "./filedownload.service";
|
||||
export * from "./settingsState.service";
|
|
@ -16,6 +16,6 @@ export class RequestRetryService extends ServiceHelpers {
|
|||
return this.http.get<IFailedRequestsViewModel[]>(this.url, {headers: this.headers});
|
||||
}
|
||||
public deleteFailedRequest(failedId: number): Observable<boolean> {
|
||||
return this.http.delete<boolean>(`${this.url}/${failedId}`, {headers: this.headers});
|
||||
return this.http.delete<boolean>(`${this.url}${failedId}`, {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
|
17
src/Ombi/ClientApp/src/app/services/settingsState.service.ts
Normal file
17
src/Ombi/ClientApp/src/app/services/settingsState.service.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { Injectable } from "@angular/core";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class SettingsStateService {
|
||||
|
||||
private issuesEnabled: boolean;
|
||||
|
||||
public getIssue(): boolean {
|
||||
return this.issuesEnabled;
|
||||
}
|
||||
|
||||
public setIssue(settings: boolean): void {
|
||||
this.issuesEnabled = settings;
|
||||
}
|
||||
}
|
|
@ -109,7 +109,7 @@ export class LidarrComponent implements OnInit {
|
|||
this.testerService.lidarrTest(settings).subscribe(result => {
|
||||
if (result.isValid) {
|
||||
this.notificationService.success("Successfully connected to Lidarr!");
|
||||
} else if (result.expectedSubDir !== null) {
|
||||
} else if (result.expectedSubDir) {
|
||||
this.notificationService.error("Your Lidarr Base URL must be set to " + result.expectedSubDir);
|
||||
} else {
|
||||
this.notificationService.error("We could not connect to Lidarr!");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<settings-menu>
|
||||
</settings-menu>
|
||||
<div *ngIf="form" class="container">
|
||||
<div *ngIf="form" class="small-middle-container">
|
||||
<fieldset>
|
||||
<legend>Mobile Notifications</legend>
|
||||
|
||||
|
@ -47,7 +47,7 @@
|
|||
|
||||
<div class="md-form-field ">
|
||||
<div>
|
||||
<button mat-raised-button type="submit " color="primary" [disabled]="form.invalid ">Submit</button>
|
||||
<button mat-raised-button type="submit " color="accent" [disabled]="form.invalid ">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -11,6 +11,7 @@ import { MatTableDataSource } from "@angular/material/table";
|
|||
|
||||
@Component({
|
||||
templateUrl: "./cloudmobile.component.html",
|
||||
styleUrls: ["./notificationtemplate.component.scss"]
|
||||
})
|
||||
export class CloudMobileComponent implements OnInit {
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
<button [disabled]="form.invalid" mat-raised-button type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
Test
|
||||
<div id="spinner"></div>
|
||||
</button>
|
||||
|
@ -53,7 +53,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
<button [disabled]="form.invalid" mat-raised-button type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
<button [disabled]="form.invalid" mat-raised-button type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
Test
|
||||
<div id="spinner"></div>
|
||||
</button>
|
||||
|
@ -58,7 +58,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
<button [disabled]="form.invalid" mat-raised-button type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -3,71 +3,73 @@
|
|||
<div *ngIf="form" class="small-middle-container">
|
||||
<fieldset>
|
||||
<legend>Legacy Mobile Notifications</legend>
|
||||
<div class="col-md-6">
|
||||
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
|
||||
<div class="row">
|
||||
<div *ngIf="userList" class="col-md-8">
|
||||
<table class="table table-striped table-hover table-responsive table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a>Username/Alias</a>
|
||||
</th>
|
||||
<th>
|
||||
<a>Mobile Devices Registered</a>
|
||||
</th>
|
||||
<div class="lmobile-container">
|
||||
<div class="col-md-6">
|
||||
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
|
||||
<div class="row">
|
||||
<div *ngIf="userList" class="col-md-8">
|
||||
<table class="table table-striped table-hover table-responsive table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a>Username/Alias</a>
|
||||
</th>
|
||||
<th>
|
||||
<a>Mobile Devices Registered</a>
|
||||
</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let user of userList">
|
||||
<td>
|
||||
{{user.username}}
|
||||
</td>
|
||||
<td>
|
||||
{{user.devices}}
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let user of userList">
|
||||
<td>
|
||||
{{user.username}}
|
||||
</td>
|
||||
<td>
|
||||
{{user.devices}}
|
||||
</td>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label for="select" class="control-label">Users</label>
|
||||
<div>
|
||||
<select class="form-control form-control-custom" id="select" [(ngModel)]="testUserId" [ngModelOptions]="{standalone: true}">
|
||||
<option value="">Please select</option>
|
||||
<option *ngFor="let x of userList" [value]="x.userId">{{x.username}}</option>
|
||||
</select>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-danger-outline">Send Test Notification</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="button" (click)="remove(form)" class="btn btn-danger-outline">Remove User</button>
|
||||
<div class="row lmobile-actions">
|
||||
<div class="form-group">
|
||||
<label for="select" class="control-label">Users</label>
|
||||
<div>
|
||||
<select class="form-control form-control-custom" id="select" [(ngModel)]="testUserId" [ngModelOptions]="{standalone: true}">
|
||||
<option value="">Please select</option>
|
||||
<option *ngFor="let x of userList" [value]="x.userId">{{x.username}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" mat-raised-button type="button" (click)="test(form)" class="btn btn-danger-outline">Send Test Notification</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" mat-raised-button type="button" (click)="remove(form)" class="btn btn-danger-outline">Remove User</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" mat-raised-button type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-6">
|
||||
<notification-templates [templates]="templates" [showSubject]="false"></notification-templates>
|
||||
<div class="col-md-6 issue-content">
|
||||
<notification-templates [templates]="templates" [showSubject]="false"></notification-templates>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
|
@ -1,15 +1,30 @@
|
|||
@import "~styles/shared.scss";
|
||||
::ng-deep ngb-accordion > div.card {
|
||||
color:white;
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
::ng-deep ngb-accordion > div.card > div.card-header {
|
||||
padding:0px;
|
||||
}
|
||||
|
||||
.small-middle-container{
|
||||
margin: auto;
|
||||
width: 95%;
|
||||
margin-top:10px;
|
||||
}
|
||||
|
||||
.lmobile-actions{
|
||||
display:flex;
|
||||
justify-content: left;
|
||||
align-items:flex-end;
|
||||
}
|
||||
|
||||
.lmobile-actions .form-group{
|
||||
margin-right:10px;
|
||||
}
|
||||
|
||||
.lmobile-container{
|
||||
display:flex;
|
||||
margin-top:10px;
|
||||
}
|
||||
|
||||
.issue-content{
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.mat-raised-button{
|
||||
margin-right:10px;
|
||||
}
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
<button [disabled]="form.invalid" mat-raised-button type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
Test
|
||||
<div id="spinner"></div>
|
||||
</button>
|
||||
|
@ -43,7 +43,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
<button [disabled]="form.invalid" mat-raised-button type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
<button [disabled]="form.invalid" mat-raised-button type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
Test
|
||||
<div id="spinner"></div>
|
||||
</button>
|
||||
|
@ -85,7 +85,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
<button [disabled]="form.invalid" mat-raised-button type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
<button [disabled]="form.invalid" mat-raised-button type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
Test
|
||||
<div id="spinner"></div>
|
||||
</button>
|
||||
|
@ -68,7 +68,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
<button [disabled]="form.invalid" mat-raised-button type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
<button [disabled]="form.invalid" mat-raised-button type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
Test
|
||||
<div id="spinner"></div>
|
||||
</button>
|
||||
|
@ -52,7 +52,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
<button [disabled]="form.invalid" mat-raised-button type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
<button [disabled]="form.invalid" mat-raised-button type="button" (click)="test(form)" class="btn btn-primary-outline">
|
||||
Test
|
||||
<div id="spinner"></div>
|
||||
</button>
|
||||
|
@ -40,7 +40,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
<button [disabled]="form.invalid" mat-raised-button type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -99,7 +99,7 @@ export class RadarrComponent implements OnInit {
|
|||
this.testerService.radarrTest(settings).subscribe(result => {
|
||||
if (result.isValid) {
|
||||
this.notificationService.success("Successfully connected to Radarr!");
|
||||
} else if (result.expectedSubDir !== null) {
|
||||
} else if (result.expectedSubDir) {
|
||||
this.notificationService.error("Your Radarr Base URL must be set to " + result.expectedSubDir);
|
||||
} else {
|
||||
this.notificationService.error("We could not connect to Radarr!");
|
||||
|
|
|
@ -153,7 +153,7 @@ export class SonarrComponent implements OnInit {
|
|||
this.testerService.sonarrTest(settings).subscribe(result => {
|
||||
if (result.isValid) {
|
||||
this.notificationService.success("Successfully connected to Sonarr!");
|
||||
} else if (result.expectedSubDir !== null) {
|
||||
} else if (result.expectedSubDir) {
|
||||
this.notificationService.error("Your Sonarr Base URL must be set to " + result.expectedSubDir);
|
||||
} else {
|
||||
this.notificationService.error("We could not connect to Sonarr!");
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -145,3 +145,15 @@
|
|||
hr{
|
||||
border-top: 1px solid $ombi-background-primary;
|
||||
}
|
||||
|
||||
.form-control{
|
||||
background-color: $ombi-background-accent;
|
||||
color:#FFF;
|
||||
border: 1px solid $ombi-background-accent;
|
||||
}
|
||||
|
||||
.form-control:focus{
|
||||
background-color: $ombi-background-accent;
|
||||
color:#FFF;
|
||||
border: 1px solid $ombi-active;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@ td.mat-cell {
|
|||
.mat-dialog-container,
|
||||
.mat-menu-content,
|
||||
.mat-table,
|
||||
.mat-paginator {
|
||||
.mat-paginator,
|
||||
.mat-select-panel {
|
||||
background: $ombi-background-accent;
|
||||
}
|
||||
|
||||
|
|
|
@ -374,7 +374,7 @@ namespace Ombi.Controllers.V1.External
|
|||
var result = await RadarrApi.SystemStatus(settings.ApiKey, settings.FullUri);
|
||||
return new TesterResultModel
|
||||
{
|
||||
IsValid = result.urlBase == settings.SubDir,
|
||||
IsValid = result.urlBase == settings.SubDir || string.IsNullOrEmpty(result.urlBase) && string.IsNullOrEmpty(settings.SubDir),
|
||||
ExpectedSubDir = result.urlBase
|
||||
};
|
||||
}
|
||||
|
@ -399,7 +399,7 @@ namespace Ombi.Controllers.V1.External
|
|||
var result = await SonarrApi.SystemStatus(settings.ApiKey, settings.FullUri);
|
||||
return new TesterResultModel
|
||||
{
|
||||
IsValid = result.urlBase == settings.SubDir,
|
||||
IsValid = result.urlBase == settings.SubDir || string.IsNullOrEmpty(result.urlBase) && string.IsNullOrEmpty(settings.SubDir),
|
||||
ExpectedSubDir = result.urlBase
|
||||
};
|
||||
}
|
||||
|
@ -513,7 +513,7 @@ namespace Ombi.Controllers.V1.External
|
|||
var status = await LidarrApi.Status(settings.ApiKey, settings.FullUri);
|
||||
return new TesterResultModel
|
||||
{
|
||||
IsValid = status?.urlBase == settings.SubDir,
|
||||
IsValid = status?.urlBase == settings.SubDir || string.IsNullOrEmpty(status.urlBase) && string.IsNullOrEmpty(settings.SubDir),
|
||||
ExpectedSubDir = status?.urlBase
|
||||
};
|
||||
}
|
||||
|
|
|
@ -299,7 +299,8 @@ namespace Ombi.Controllers.V1
|
|||
model.Add(new UserViewModelDropdown
|
||||
{
|
||||
Id = user.Id,
|
||||
Username = user.UserName
|
||||
Username = user.UserName,
|
||||
Email = user.Email
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -203,7 +203,7 @@ namespace Ombi.Controllers.V1
|
|||
var item = rand.Next(moviesArray.Length);
|
||||
var result = await _cache.GetOrAdd($"{CacheKeys.FanartTv}movie{moviesArray[item]}", async () => await FanartTvApi.GetMovieImages(moviesArray[item].ToString(), key.Value), DateTime.Now.AddDays(1));
|
||||
|
||||
while (!result.moviebackground.Any())
|
||||
while (!result.moviebackground?.Any() ?? true)
|
||||
{
|
||||
item = rand.Next(moviesArray.Length);
|
||||
result = await _cache.GetOrAdd($"{CacheKeys.FanartTv}movie{moviesArray[item]}", async () => await FanartTvApi.GetMovieImages(moviesArray[item].ToString(), key.Value), DateTime.Now.AddDays(1));
|
||||
|
@ -220,7 +220,7 @@ namespace Ombi.Controllers.V1
|
|||
var item = rand.Next(tvArray.Length);
|
||||
var result = await _cache.GetOrAdd($"{CacheKeys.FanartTv}tv{tvArray[item]}", async () => await FanartTvApi.GetTvImages(tvArray[item], key.Value), DateTime.Now.AddDays(1));
|
||||
|
||||
while (!result.showbackground.Any())
|
||||
while (!result.showbackground?.Any() ?? true)
|
||||
{
|
||||
item = rand.Next(tvArray.Length);
|
||||
result = await _cache.GetOrAdd($"{CacheKeys.FanartTv}tv{tvArray[item]}", async () => await FanartTvApi.GetTvImages(tvArray[item], key.Value), DateTime.Now.AddDays(1));
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Ombi.Api.Emby;
|
||||
using Ombi.Api.Jellyfin;
|
||||
using Ombi.Api.Plex;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
|
@ -18,19 +19,22 @@ namespace Ombi.Controllers.V1
|
|||
public class LandingPageController : ControllerBase
|
||||
{
|
||||
public LandingPageController(ISettingsService<PlexSettings> plex, ISettingsService<EmbySettings> emby,
|
||||
IPlexApi plexApi, IEmbyApiFactory embyApi)
|
||||
IPlexApi plexApi, IEmbyApiFactory embyApi, ISettingsService<JellyfinSettings> jellyfin, IJellyfinApi jellyfinApi)
|
||||
{
|
||||
_plexSettings = plex;
|
||||
_embySettings = emby;
|
||||
_plexApi = plexApi;
|
||||
_embyApi = embyApi;
|
||||
_jellyfin = jellyfin;
|
||||
_jellyfinApi = jellyfinApi;
|
||||
}
|
||||
|
||||
private readonly IPlexApi _plexApi;
|
||||
private readonly IEmbyApiFactory _embyApi;
|
||||
private readonly ISettingsService<PlexSettings> _plexSettings;
|
||||
private readonly ISettingsService<EmbySettings> _embySettings;
|
||||
|
||||
private readonly ISettingsService<JellyfinSettings> _jellyfin;
|
||||
private readonly IJellyfinApi _jellyfinApi;
|
||||
|
||||
[HttpGet]
|
||||
public async Task<MediaSeverAvailibilityViewModel> GetMediaServerStatus()
|
||||
|
@ -86,6 +90,31 @@ namespace Ombi.Controllers.V1
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var jellyfin = await _jellyfin.GetSettingsAsync();
|
||||
if (jellyfin.Enable)
|
||||
{
|
||||
foreach (var server in jellyfin.Servers)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _jellyfinApi.GetUsers(server.FullUri, server.ApiKey);
|
||||
if (result.Any())
|
||||
{
|
||||
model.ServersAvailable++;
|
||||
}
|
||||
else
|
||||
{
|
||||
model.ServersUnavailable++;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
model.ServersUnavailable++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,7 +146,7 @@ namespace Ombi.Controllers.V1
|
|||
|
||||
var token = new JwtSecurityToken(
|
||||
claims: claims,
|
||||
expires: rememberMe ? DateTime.Now.AddDays(7) : DateTime.Now.AddDays(1),
|
||||
expires: rememberMe ? DateTime.Now.AddYears(1) : DateTime.Now.AddDays(7),
|
||||
signingCredentials: creds,
|
||||
audience: "Ombi", issuer: "Ombi"
|
||||
);
|
||||
|
|
|
@ -25,24 +25,40 @@
|
|||
399106,
|
||||
351286,
|
||||
348350,
|
||||
539885,
|
||||
508442,
|
||||
664767,
|
||||
260513,
|
||||
372058,
|
||||
299536,
|
||||
581389,
|
||||
577922,
|
||||
383498,
|
||||
330457,
|
||||
755812,
|
||||
495764,
|
||||
14160,
|
||||
429617,
|
||||
475557,
|
||||
420818,
|
||||
775996,
|
||||
283995
|
||||
],
|
||||
"TvShows": [
|
||||
121361,
|
||||
361753,
|
||||
295685,
|
||||
74205,
|
||||
362392,
|
||||
81189,
|
||||
79126,
|
||||
332858,
|
||||
73762,
|
||||
79349,
|
||||
349309,
|
||||
275274,
|
||||
305288,
|
||||
260449,
|
||||
296762,
|
||||
280619,
|
||||
305074,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue