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:
KennyG 2026-04-01 14:20:10 -04:00
parent e49f11c67b
commit c6b86690ae
3 changed files with 131 additions and 1 deletions

View file

@ -428,6 +428,60 @@
<div class="row">
<div class="col-12">
<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="col-md-4">
<div class="action-group-label">Cookies</div>

View file

@ -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 { faGithub } from '@fortawesome/free-brands-svg-icons';
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 {
Download,
@ -96,6 +96,11 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
ytDlpVersion: string | null = null;
metubeVersion: string | null = null;
isAdvancedOpen = false;
hasharrEnabled = false;
hasharrUrl = 'http://hasharr:9995';
hasharrServiceID = 1;
hasharrTimeoutSec = 20;
hasharrSettingsStatus = '';
sortAscending = false;
expandedErrors: Set<string> = new Set<string>();
cachedSortedDone: [string, Download][] = [];
@ -268,6 +273,7 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
this.cdr.markForCheck();
});
this.getConfiguration();
this.loadHasharrSettings();
this.getYtdlOptionsUpdateTime();
this.customDirs$ = this.getMatchingCustomDir();
this.setTheme(this.activeTheme!);
@ -275,6 +281,57 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
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() {
this.downloads.queueChanged.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.queueMasterCheckbox()?.selectionChanged();

View file

@ -6,6 +6,13 @@ import { MeTubeSocket } from './metube-socket.service';
import { Download, Status, State } from '../interfaces';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export interface HasharrSettings {
enabled: boolean;
url: string;
service_id: number;
timeout_sec: number;
}
export interface AddDownloadPayload {
url: string;
downloadType: string;
@ -224,4 +231,16 @@ export class DownloadsService {
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)
);
}
}