Skip to content

Commit e151826

Browse files
committed
quality profile settings UI (working - need to add remove (wip)), add Observable return types, rename QualityProfile profile->quality
1 parent 2acf44f commit e151826

File tree

8 files changed

+108
-48
lines changed

8 files changed

+108
-48
lines changed

src/frontend/src/app/api.service.ts

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export class ApiService {
3939
API_URL_GENRES_MOVIE = '/api/genres/movie/';
4040
API_URL_GENRES_TV = '/api/genres/tv/';
4141
API_URL_MEDIA_CATEGORIES = '/api/media-categories/';
42+
API_URL_QUALITIES = '/api/qualities/';
4243
API_URL_QUALITY_PROFILES = '/api/quality-profile/';
4344
API_URL_GIT_COMMIT = '/api/git-commit/';
4445
API_URL_IMPORT_MEDIA_TV = '/api/import/media/tv/';
@@ -55,6 +56,7 @@ export class ApiService {
5556
public userToken: string;
5657
public users: any; // staff-only list of all users
5758
public settings: any;
59+
public qualities: string[] = [];
5860
public qualityProfiles: any[] = [];
5961
public mediaCategories: string[];
6062
public watchTVSeasons: any[] = [];
@@ -180,6 +182,7 @@ export class ApiService {
180182
}
181183
})
182184
),
185+
this.fetchQualities(),
183186
this.fetchQualityProfiles(),
184187
this.fetchMediaCategories(),
185188
]).pipe(
@@ -211,7 +214,7 @@ export class ApiService {
211214
);
212215
}
213216

214-
public fetchSettings() {
217+
public fetchSettings(): Observable<any> {
215218
return this.http.get(this.API_URL_SETTINGS, {headers: this._requestHeaders()}).pipe(
216219
map((data: any) => {
217220
if (data.length) {
@@ -224,7 +227,7 @@ export class ApiService {
224227
);
225228
}
226229

227-
public fetchMediaCategories() {
230+
public fetchMediaCategories(): Observable<string[]> {
228231
return this.http.get(this.API_URL_MEDIA_CATEGORIES, {headers: this._requestHeaders()}).pipe(
229232
map((data: any) => {
230233
if (data.mediaCategories) {
@@ -237,7 +240,20 @@ export class ApiService {
237240
);
238241
}
239242

240-
public fetchQualityProfiles() {
243+
public fetchQualities(): Observable<string[]> {
244+
return this.http.get(this.API_URL_QUALITIES, {headers: this._requestHeaders()}).pipe(
245+
map((data: any) => {
246+
if (data.length) {
247+
this.qualities = data;
248+
} else {
249+
console.error('no qualities');
250+
}
251+
return this.qualities;
252+
}),
253+
);
254+
}
255+
256+
public fetchQualityProfiles(): Observable<any[]> {
241257
return this.http.get(this.API_URL_QUALITY_PROFILES, {headers: this._requestHeaders()}).pipe(
242258
map((data: any) => {
243259
if (data.length) {
@@ -303,7 +319,7 @@ export class ApiService {
303319
);
304320
}
305321

306-
public login(user: string, pass: string) {
322+
public login(user: string, pass: string): Observable<any> {
307323
const params = {
308324
username: user,
309325
password: pass,
@@ -334,15 +350,27 @@ export class ApiService {
334350
);
335351
}
336352

337-
public searchTorrents(query: string, mediaType: string) {
353+
public updateQualityProfile(id: number, params: any): Observable<any> {
354+
return this.http.patch(`${this.API_URL_QUALITY_PROFILES}${id}/`, params, {headers: this._requestHeaders()}).pipe(
355+
map((data: any) => {
356+
this.qualityProfiles.forEach((profile, index) => {
357+
if (profile.id === id) {
358+
this.qualityProfiles[index] = params;
359+
}
360+
})
361+
}),
362+
);
363+
}
364+
365+
public searchTorrents(query: string, mediaType: string): Observable<any> {
338366
return this.http.get(`${this.API_URL_SEARCH_TORRENTS}?q=${query}&media_type=${mediaType}`, {headers: this._requestHeaders()}).pipe(
339367
map((data: any) => {
340368
return data;
341369
}),
342370
);
343371
}
344372

345-
public download(torrentResult: any, mediaType: string, tmdbMedia: any, params?: any) {
373+
public download(torrentResult: any, mediaType: string, tmdbMedia: any, params?: any): Observable<any> {
346374
// add extra params
347375
Object.assign(params || {}, {
348376
torrent: torrentResult,
@@ -377,7 +405,7 @@ export class ApiService {
377405
);
378406
}
379407

380-
public searchMedia(query: string, mediaType: string, page = 1) {
408+
public searchMedia(query: string, mediaType: string, page = 1): Observable<any> {
381409
let params = {
382410
q: query,
383411
media_type: mediaType,
@@ -392,7 +420,7 @@ export class ApiService {
392420
);
393421
}
394422

395-
public searchSimilarMedia(tmdbMediaId: string, mediaType: string) {
423+
public searchSimilarMedia(tmdbMediaId: string, mediaType: string): Observable<any> {
396424
let params = {
397425
tmdb_media_id: tmdbMediaId,
398426
media_type: mediaType,
@@ -407,7 +435,7 @@ export class ApiService {
407435
);
408436
}
409437

410-
public searchRecommendedMedia(tmdbMediaId: string, mediaType: string) {
438+
public searchRecommendedMedia(tmdbMediaId: string, mediaType: string): Observable<any> {
411439
let params = {
412440
tmdb_media_id: tmdbMediaId,
413441
media_type: mediaType,
@@ -422,7 +450,7 @@ export class ApiService {
422450
);
423451
}
424452

425-
public searchMediaDetail(mediaType: string, id: string) {
453+
public searchMediaDetail(mediaType: string, id: string): Observable<any> {
426454
const options = {headers: this._requestHeaders(), params: this._defaultParams()};
427455
return this.http.get(`${this.API_URL_SEARCH_MEDIA}${mediaType}/${id}/`, options).pipe(
428456
map((data: any) => {
@@ -431,7 +459,7 @@ export class ApiService {
431459
);
432460
}
433461

434-
public fetchMediaVideos(mediaType: string, id: string) {
462+
public fetchMediaVideos(mediaType: string, id: string): Observable<any> {
435463
const options = {headers: this._requestHeaders()};
436464
return this.http.get(`${this.API_URL_SEARCH_MEDIA}${mediaType}/${id}/videos/`, options).pipe(
437465
map((data: any) => {
@@ -440,7 +468,7 @@ export class ApiService {
440468
);
441469
}
442470

443-
public fetchWatchTVShows(params?: any) {
471+
public fetchWatchTVShows(params?: any): Observable<any[]> {
444472
params = params || {};
445473
const httpParams = new HttpParams({fromObject: params});
446474
return this.http.get(this.API_URL_WATCH_TV_SHOW, {params: httpParams, headers: this._requestHeaders()}).pipe(
@@ -451,7 +479,7 @@ export class ApiService {
451479
);
452480
}
453481

454-
public fetchWatchTVSeasons(params?: any) {
482+
public fetchWatchTVSeasons(params?: any): Observable<any[]> {
455483
params = params || {};
456484
const httpParams = new HttpParams({fromObject: params});
457485
return this.http.get(this.API_URL_WATCH_TV_SEASON, {params: httpParams, headers: this._requestHeaders()}).pipe(
@@ -467,7 +495,7 @@ export class ApiService {
467495
);
468496
}
469497

470-
public fetchWatchTVSeasonRequests(params?: any) {
498+
public fetchWatchTVSeasonRequests(params?: any): Observable<any[]> {
471499
params = params || {};
472500
const httpParams = new HttpParams({fromObject: params});
473501
return this.http.get(this.API_URL_WATCH_TV_SEASON_REQUEST, {params: httpParams, headers: this._requestHeaders()}).pipe(
@@ -483,7 +511,7 @@ export class ApiService {
483511
);
484512
}
485513

486-
public fetchWatchMovies(params?: any) {
514+
public fetchWatchMovies(params?: any): Observable<any[]> {
487515
params = params || {};
488516
const httpParams = new HttpParams({fromObject: params});
489517

@@ -500,7 +528,7 @@ export class ApiService {
500528
);
501529
}
502530

503-
public fetchWatchTVEpisodes(params: any) {
531+
public fetchWatchTVEpisodes(params: any): Observable<any[]> {
504532
const httpParams = new HttpParams({fromObject: params});
505533
return this.http.get(this.API_URL_WATCH_TV_EPISODE, {headers: this._requestHeaders(), params: httpParams}).pipe(
506534
map((records: any) => {
@@ -515,7 +543,7 @@ export class ApiService {
515543
);
516544
}
517545

518-
public fetchCurrentTorrents(params: any) {
546+
public fetchCurrentTorrents(params: any): Observable<any[]> {
519547
const httpParams = new HttpParams({fromObject: params});
520548
return this.http.get(this.API_URL_CURRENT_TORRENTS, {headers: this._requestHeaders(), params: httpParams}).pipe(
521549
map((data: any) => {
@@ -734,7 +762,7 @@ export class ApiService {
734762
);
735763
}
736764

737-
public verifySettings() {
765+
public verifySettings(): Observable<any> {
738766
return this.http.get(`${this.API_URL_SETTINGS}${this.settings.id}/verify/`, {headers: this._requestHeaders()}).pipe(
739767
map((data: any) => {
740768
return data;
@@ -801,15 +829,15 @@ export class ApiService {
801829
return this._discoverMedia(this.SEARCH_MEDIA_TYPE_TV, params);
802830
}
803831

804-
public fetchMovieGenres() {
832+
public fetchMovieGenres(): Observable<any> {
805833
return this._fetchGenres(this.SEARCH_MEDIA_TYPE_MOVIE);
806834
}
807835

808-
public fetchTVGenres() {
836+
public fetchTVGenres(): Observable<any> {
809837
return this._fetchGenres(this.SEARCH_MEDIA_TYPE_TV);
810838
}
811839

812-
public verifyJackettIndexers() {
840+
public verifyJackettIndexers(): Observable<any> {
813841
return this.http.get(`${this.API_URL_SETTINGS}${this.settings.id}/verify-jackett-indexers/`, {headers: this._requestHeaders()});
814842
}
815843

@@ -837,7 +865,7 @@ export class ApiService {
837865
return this.http.get(url, {params: httpParams, headers: this._requestHeaders()});
838866
}
839867

840-
public openSubtitlesAuth() {
868+
public openSubtitlesAuth(): Observable<any> {
841869
const url = this.API_URL_OPEN_SUBTITLES_AUTH;
842870
return this.http.post(url, null, {headers: this._requestHeaders()});
843871
}
@@ -961,13 +989,13 @@ export class ApiService {
961989
this._updateStorage().subscribe();
962990
}
963991

964-
protected _fetchGenres(mediaType: string) {
992+
protected _fetchGenres(mediaType: string): Observable<any> {
965993
const url = mediaType === this.SEARCH_MEDIA_TYPE_MOVIE ? this.API_URL_GENRES_MOVIE : this.API_URL_GENRES_TV;
966994
const params = this._defaultParams();
967995
return this.http.get(url, {headers: this._requestHeaders(), params: params});
968996
}
969997

970-
protected _discoverMedia(mediaType: string, params: any) {
998+
protected _discoverMedia(mediaType: string, params: any): Observable<any> {
971999
params = Object.assign(params, this._defaultParams());
9721000
const httpParams = new HttpParams({fromObject: params});
9731001
const url = mediaType === this.SEARCH_MEDIA_TYPE_MOVIE ? this.API_URL_DISCOVER_MOVIES : this.API_URL_DISCOVER_TV;

src/frontend/src/app/settings/quality-profiles.component.html

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ <h4 class="modal-title">Quality Profiles</h4>
33
<button type="button" class="btn-close" aria-label="Close" (click)="activeModal.dismiss('Cross click')"></button>
44
</div>
55
<div class="modal-body" [formGroup]="form">
6+
<ngx-loading [show]="isLoading" [config]="{fullScreenBackdrop: true}"></ngx-loading>
67
<form class="was-validated" [formArrayName]="'profiles'">
78
<div class="card my-2" *ngFor="let profile of form.controls.profiles.controls; let i = index" [formGroupName]="i">
89
<div class="card-body">
@@ -15,10 +16,10 @@ <h4 class="modal-title">Quality Profiles</h4>
1516
</div>
1617
<!-- quality -->
1718
<div class="mb-3">
18-
<label [for]="i + '_profile'" class="form-label">Quality</label>
19-
<select class="form-select" [id]="i + '_profile'" [formControlName]="'profile'" required>
19+
<label [for]="i + '_quality'" class="form-label">Quality</label>
20+
<select class="form-select" [id]="i + '_quality'" [formControlName]="'quality'" required>
2021
<option></option>
21-
<option *ngFor="let profile of apiService.qualityProfiles" [value]="profile.profile">{{ profile.profile }}</option>
22+
<option *ngFor="let quality of apiService.qualities" [value]="quality">{{ quality }}</option>
2223
</select>
2324
</div>
2425
<!-- min size -->
@@ -45,10 +46,10 @@ <h4 class="modal-title">Quality Profiles</h4>
4546
Require 5.1 Surround Sound
4647
</label>
4748
</div>
49+
<div *ngIf="{'valid': form.controls.profiles.controls[i].valid} as validity" class="text-end">
50+
<button type="button" class="btn" [ngClass]="{'btn-outline-success': validity.valid, 'btn-outline-danger': !validity.valid}" (click)="save(form.controls.profiles.controls[i])" [disabled]="!validity.valid">Save</button>
51+
</div>
4852
</div>
4953
</div>
5054
</form>
5155
</div>
52-
<div class="modal-footer">
53-
<button type="button" class="btn btn-outline-success" (click)="save()">Save</button>
54-
</div>
Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import {Component, OnInit} from '@angular/core';
22
import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap";
33
import {ApiService} from "../api.service";
4-
import {FormArray, FormBuilder, FormGroup, Validators} from "@angular/forms";
4+
import {FormArray, FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms";
55
import {ToastrService} from 'ngx-toastr';
66

77
// TODO - add/remove profiles
8-
// TODO - implement UI (html & angular) validation
98

109
@Component({
1110
selector: 'app-quality-profiles',
1211
templateUrl: './quality-profiles.component.html',
1312
styleUrl: './quality-profiles.component.css'
1413
})
1514
export class QualityProfilesComponent implements OnInit {
16-
17-
public form: FormGroup<{ profiles: FormArray }>;
15+
public isLoading = false;
16+
public form: FormGroup<{ profiles: FormArray<FormGroup> }>;
1817

1918
constructor(
2019
public apiService: ApiService,
@@ -29,24 +28,28 @@ export class QualityProfilesComponent implements OnInit {
2928
profiles: this.fb.array(this.apiService.qualityProfiles.map(p => this.fb.group({
3029
id: p.id,
3130
name: this.fb.control(p.name, [Validators.required, Validators.minLength(2)]),
32-
profile: this.fb.control(p.profile, [Validators.required]),
31+
quality: this.fb.control(p.quality, [Validators.required]),
3332
min_size_gb: this.fb.control(p.min_size_gb, [Validators.min(0)]),
3433
max_size_gb: this.fb.control(p.max_size_gb, [Validators.min(0)]),
3534
require_hdr: p.require_hdr,
3635
require_five_point_one: p.require_five_point_one,
3736
}))),
3837
});
38+
}
3939

40-
this.form.valueChanges.subscribe({
41-
next: (data) => {
42-
console.log(data);
40+
public save(profileFormGroup: FormGroup) {
41+
this.isLoading = true;
42+
const data = profileFormGroup.value;
43+
this.apiService.updateQualityProfile(data.id, data).subscribe({
44+
next: () => {
45+
this.toastr.success('Successfully updated quality profile');
46+
this.isLoading = false;
47+
},
48+
error: (error) => {
49+
console.error(error);
50+
this.toastr.error('An unknown error occurred updating the quality profile');
51+
this.isLoading = false;
4352
}
4453
})
4554
}
46-
47-
public save() {
48-
// TODO - implement save
49-
this.toastr.error('SAVE TODO')
50-
this.activeModal.close()
51-
}
5255
}

src/nefarious/api/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
path('import/media/<str:media_type>/', views.ImportMediaLibraryView.as_view()),
3535
path('genres/<str:media_type>/', views.GenresView.as_view()),
3636
path('media-categories/', views.MediaCategoriesView.as_view()),
37+
path('qualities/', views.QualitiesView.as_view()),
3738
path('auth/', views.ObtainAuthTokenView.as_view()), # authenticates user and returns token
3839
path('git-commit/', views.GitCommitView.as_view()), # returns this app's git commit
3940
path('open-subtitles/auth/', views.OpenSubtitlesAuthView.as_view()), # auths against open subtitles

src/nefarious/api/views.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,3 +570,10 @@ def post(self, request):
570570
assert 'message' in request.data, 'missing notification message'
571571
return Response({'success': send_message(request.data['message'])})
572572

573+
574+
@method_decorator(gzip_page, name='dispatch')
575+
class QualitiesView(views.APIView):
576+
577+
def get(self, request):
578+
return Response([p.name for p in PROFILES])
579+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 3.0.2 on 2024-08-12 22:09
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('nefarious', '0089_qualityprofile_require_five_point_one'),
10+
]
11+
12+
operations = [
13+
migrations.RenameField(
14+
model_name='qualityprofile',
15+
old_name='profile',
16+
new_name='quality',
17+
),
18+
]

0 commit comments

Comments
 (0)