Add MeTube UI controls for hasharr integration settings.
Expose hasharr enabled/url/service-id/timeout controls in Advanced Options and wire them to hasharr-settings backend endpoints so integration can be configured directly from the MeTube web UI. Made-with: Cursor
This commit is contained in:
parent
e49f11c67b
commit
c6b86690ae
3 changed files with 131 additions and 1 deletions
|
|
@ -428,6 +428,60 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<hr class="my-3">
|
<hr class="my-3">
|
||||||
|
<div class="row g-3 mb-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="action-group-label">Hasharr Integration</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="form-check form-switch mt-2">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="hasharrEnabled"
|
||||||
|
name="hasharrEnabled" [(ngModel)]="hasharrEnabled"
|
||||||
|
[disabled]="addInProgress || downloads.loading">
|
||||||
|
<label class="form-check-label" for="hasharrEnabled">Enabled</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">Hasharr URL</span>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="http://hasharr:9995"
|
||||||
|
name="hasharrUrl"
|
||||||
|
[(ngModel)]="hasharrUrl"
|
||||||
|
[disabled]="addInProgress || downloads.loading">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">Service ID</span>
|
||||||
|
<input type="number"
|
||||||
|
min="1"
|
||||||
|
class="form-control"
|
||||||
|
name="hasharrServiceID"
|
||||||
|
[(ngModel)]="hasharrServiceID"
|
||||||
|
[disabled]="addInProgress || downloads.loading">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">Timeout</span>
|
||||||
|
<input type="number"
|
||||||
|
min="1"
|
||||||
|
class="form-control"
|
||||||
|
name="hasharrTimeoutSec"
|
||||||
|
[(ngModel)]="hasharrTimeoutSec"
|
||||||
|
[disabled]="addInProgress || downloads.loading">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<button type="button" class="btn btn-secondary" (click)="saveHasharrSettings()">
|
||||||
|
Save Hasharr Settings
|
||||||
|
</button>
|
||||||
|
@if (hasharrSettingsStatus) {
|
||||||
|
<span class="ms-3 text-muted">{{ hasharrSettingsStatus }}</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="action-group-label">Cookies</div>
|
<div class="action-group-label">Cookies</div>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { NgSelectModule } from '@ng-select/ng-select';
|
||||||
import { faTrashAlt, faCheckCircle, faTimesCircle, faRedoAlt, faSun, faMoon, faCheck, faCircleHalfStroke, faDownload, faExternalLinkAlt, faFileImport, faFileExport, faCopy, faClock, faTachometerAlt, faSortAmountDown, faSortAmountUp, faChevronRight, faChevronDown, faUpload } from '@fortawesome/free-solid-svg-icons';
|
import { faTrashAlt, faCheckCircle, faTimesCircle, faRedoAlt, faSun, faMoon, faCheck, faCircleHalfStroke, faDownload, faExternalLinkAlt, faFileImport, faFileExport, faCopy, faClock, faTachometerAlt, faSortAmountDown, faSortAmountUp, faChevronRight, faChevronDown, faUpload } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { faGithub } from '@fortawesome/free-brands-svg-icons';
|
import { faGithub } from '@fortawesome/free-brands-svg-icons';
|
||||||
import { CookieService } from 'ngx-cookie-service';
|
import { CookieService } from 'ngx-cookie-service';
|
||||||
import { AddDownloadPayload, DownloadsService } from './services/downloads.service';
|
import { AddDownloadPayload, DownloadsService, HasharrSettings } from './services/downloads.service';
|
||||||
import { Themes } from './theme';
|
import { Themes } from './theme';
|
||||||
import {
|
import {
|
||||||
Download,
|
Download,
|
||||||
|
|
@ -96,6 +96,11 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
||||||
ytDlpVersion: string | null = null;
|
ytDlpVersion: string | null = null;
|
||||||
metubeVersion: string | null = null;
|
metubeVersion: string | null = null;
|
||||||
isAdvancedOpen = false;
|
isAdvancedOpen = false;
|
||||||
|
hasharrEnabled = false;
|
||||||
|
hasharrUrl = 'http://hasharr:9995';
|
||||||
|
hasharrServiceID = 1;
|
||||||
|
hasharrTimeoutSec = 20;
|
||||||
|
hasharrSettingsStatus = '';
|
||||||
sortAscending = false;
|
sortAscending = false;
|
||||||
expandedErrors: Set<string> = new Set<string>();
|
expandedErrors: Set<string> = new Set<string>();
|
||||||
cachedSortedDone: [string, Download][] = [];
|
cachedSortedDone: [string, Download][] = [];
|
||||||
|
|
@ -268,6 +273,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
||||||
this.cdr.markForCheck();
|
this.cdr.markForCheck();
|
||||||
});
|
});
|
||||||
this.getConfiguration();
|
this.getConfiguration();
|
||||||
|
this.loadHasharrSettings();
|
||||||
this.getYtdlOptionsUpdateTime();
|
this.getYtdlOptionsUpdateTime();
|
||||||
this.customDirs$ = this.getMatchingCustomDir();
|
this.customDirs$ = this.getMatchingCustomDir();
|
||||||
this.setTheme(this.activeTheme!);
|
this.setTheme(this.activeTheme!);
|
||||||
|
|
@ -275,6 +281,57 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
|
||||||
this.colorSchemeMediaQuery.addEventListener('change', this.onColorSchemeChanged);
|
this.colorSchemeMediaQuery.addEventListener('change', this.onColorSchemeChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadHasharrSettings() {
|
||||||
|
this.downloads.getHasharrSettings().pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
if (!data || typeof data !== 'object' || ('status' in data && data.status === 'error')) {
|
||||||
|
this.hasharrSettingsStatus = 'Unable to load hasharr settings.';
|
||||||
|
this.cdr.markForCheck();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const settings = data as HasharrSettings;
|
||||||
|
this.hasharrEnabled = !!settings.enabled;
|
||||||
|
this.hasharrUrl = String(settings.url || 'http://hasharr:9995');
|
||||||
|
this.hasharrServiceID = Math.max(1, Number(settings.service_id || 1));
|
||||||
|
this.hasharrTimeoutSec = Math.max(1, Number(settings.timeout_sec || 20));
|
||||||
|
this.hasharrSettingsStatus = '';
|
||||||
|
this.cdr.markForCheck();
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.hasharrSettingsStatus = 'Unable to load hasharr settings.';
|
||||||
|
this.cdr.markForCheck();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveHasharrSettings() {
|
||||||
|
const payload: HasharrSettings = {
|
||||||
|
enabled: !!this.hasharrEnabled,
|
||||||
|
url: String(this.hasharrUrl || '').trim(),
|
||||||
|
service_id: Math.max(1, Number(this.hasharrServiceID || 1)),
|
||||||
|
timeout_sec: Math.max(1, Number(this.hasharrTimeoutSec || 20)),
|
||||||
|
};
|
||||||
|
if (!payload.url) {
|
||||||
|
this.hasharrSettingsStatus = 'Hasharr URL is required.';
|
||||||
|
this.cdr.markForCheck();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.downloads.saveHasharrSettings(payload).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
||||||
|
next: (out) => {
|
||||||
|
if (out && typeof out === 'object' && 'status' in out && out.status === 'ok') {
|
||||||
|
this.hasharrSettingsStatus = 'Hasharr settings saved.';
|
||||||
|
} else {
|
||||||
|
this.hasharrSettingsStatus = 'Failed to save hasharr settings.';
|
||||||
|
}
|
||||||
|
this.cdr.markForCheck();
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.hasharrSettingsStatus = 'Failed to save hasharr settings.';
|
||||||
|
this.cdr.markForCheck();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
this.downloads.queueChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
this.downloads.queueChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
||||||
this.queueMasterCheckbox()?.selectionChanged();
|
this.queueMasterCheckbox()?.selectionChanged();
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,13 @@ import { MeTubeSocket } from './metube-socket.service';
|
||||||
import { Download, Status, State } from '../interfaces';
|
import { Download, Status, State } from '../interfaces';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
|
export interface HasharrSettings {
|
||||||
|
enabled: boolean;
|
||||||
|
url: string;
|
||||||
|
service_id: number;
|
||||||
|
timeout_sec: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AddDownloadPayload {
|
export interface AddDownloadPayload {
|
||||||
url: string;
|
url: string;
|
||||||
downloadType: string;
|
downloadType: string;
|
||||||
|
|
@ -224,4 +231,16 @@ export class DownloadsService {
|
||||||
catchError(this.handleHTTPError)
|
catchError(this.handleHTTPError)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHasharrSettings() {
|
||||||
|
return this.http.get<HasharrSettings>('hasharr-settings').pipe(
|
||||||
|
catchError(this.handleHTTPError)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveHasharrSettings(settings: HasharrSettings) {
|
||||||
|
return this.http.post<{ status: string; msg?: string }>('hasharr-settings', settings).pipe(
|
||||||
|
catchError(this.handleHTTPError)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue