Random Cleanup + OPDS Base Url Support (#1926)
* Updated a ton of dependencies. PDFs reader got a big update from PDF.js 2.6 -> 3.x * Rolled back fontawesome update * Updated to latest angular patch. Fixed search being too long instead of just to the end of the browser screen. * Fixed alignment on download icon for download indicator in cards * Include progress information on Want To Read API and when marking something as Read, perform cleanup service on want to read. * Removed mark-read updating want to read. As there are series restrictions and it could be misleading. * Tweaked login page spacing when form is dirty * Replaced an object instantiation * Commented out a few tests that always break when updating NetVips (but always work) * Updated ngx-toastr * Added styles for alerts to Kavita. They were somehow missing. Fixed an issue where when OPDS was disabled, user preferences wouldn't tell them. * Wired up a reset base url button to match Ip Addresses * Disable ipAddress and port for docker users * Removed cache dir since it's kinda pointless currently * Started the update for OPDS BaseUrl support * Fixed OPDS url not reflecting base url on localhost * Added extra plumbing to allow sending a real email when testing a custom service. * Implemented OPDS support under Base Url. Added pagination to all APIs where applicable. * Added a swallowing of permission denied on Updating baseurl in index.html for inapplicable users. * Fixed a bad test
This commit is contained in:
parent
1bf4fde58f
commit
21a9f28923
39 changed files with 2519 additions and 1219 deletions
|
|
@ -23,6 +23,19 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="settings-hostname" class="form-label">Host Name</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="hostNameTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #hostNameTooltip>Domain Name (of Reverse Proxy). If set, email generation will always use this.</ng-template>
|
||||
<span class="visually-hidden" id="settings-hostname-help">Domain Name (of Reverse Proxy). If set, email generation will always use this.</span>
|
||||
<input id="settings-hostname" aria-describedby="settings-hostname-help" class="form-control" formControlName="hostName" type="text"
|
||||
[class.is-invalid]="settingsForm.get('hostName')?.invalid && settingsForm.get('hostName')?.touched">
|
||||
<div id="hostname-validations" class="invalid-feedback" *ngIf="settingsForm.dirty || settingsForm.touched">
|
||||
<div *ngIf="settingsForm.get('hostName')?.errors?.pattern">
|
||||
Host name must start with http(s) and not end in /
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto d-flex d-md-block justify-content-sm-center text-md-end">
|
||||
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetToDefaults()">Reset to Default</button>
|
||||
<button type="button" class="flex-fill btn btn-secondary me-2" (click)="resetForm()">Reset</button>
|
||||
|
|
|
|||
|
|
@ -21,19 +21,23 @@ export class ManageEmailSettingsComponent implements OnInit {
|
|||
this.settingsService.getServerSettings().pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings = settings;
|
||||
this.settingsForm.addControl('emailServiceUrl', new FormControl(this.serverSettings.emailServiceUrl, [Validators.required]));
|
||||
this.settingsForm.addControl('hostName', new FormControl(this.serverSettings.hostName, []));
|
||||
});
|
||||
}
|
||||
|
||||
resetForm() {
|
||||
this.settingsForm.get('emailServiceUrl')?.setValue(this.serverSettings.emailServiceUrl);
|
||||
this.settingsForm.get('hostName')?.setValue(this.serverSettings.hostName);
|
||||
this.settingsForm.markAsPristine();
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
const modelSettings = Object.assign({}, this.serverSettings);
|
||||
modelSettings.emailServiceUrl = this.settingsForm.get('emailServiceUrl')?.value;
|
||||
|
||||
this.settingsService.updateServerSettings(modelSettings).pipe(take(1)).subscribe(async (settings: ServerSettings) => {
|
||||
modelSettings.hostName = this.settingsForm.get('hostName')?.value;
|
||||
|
||||
|
||||
this.settingsService.updateServerSettings(modelSettings).pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings = settings;
|
||||
this.resetForm();
|
||||
this.toastr.success('Server settings updated');
|
||||
|
|
@ -43,7 +47,7 @@ export class ManageEmailSettingsComponent implements OnInit {
|
|||
}
|
||||
|
||||
resetToDefaults() {
|
||||
this.settingsService.resetServerSettings().pipe(take(1)).subscribe(async (settings: ServerSettings) => {
|
||||
this.settingsService.resetServerSettings().pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings = settings;
|
||||
this.resetForm();
|
||||
this.toastr.success('Server settings updated');
|
||||
|
|
@ -53,7 +57,7 @@ export class ManageEmailSettingsComponent implements OnInit {
|
|||
}
|
||||
|
||||
resetEmailServiceUrl() {
|
||||
this.settingsService.resetEmailServerSettings().pipe(take(1)).subscribe(async (settings: ServerSettings) => {
|
||||
this.settingsService.resetEmailServerSettings().pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings.emailServiceUrl = settings.emailServiceUrl;
|
||||
this.resetForm();
|
||||
this.toastr.success('Email Service Reset');
|
||||
|
|
@ -65,7 +69,7 @@ export class ManageEmailSettingsComponent implements OnInit {
|
|||
testEmailServiceUrl() {
|
||||
this.settingsService.testEmailServerSettings(this.settingsForm.get('emailServiceUrl')?.value || '').pipe(take(1)).subscribe(async (result: EmailTestResult) => {
|
||||
if (result.successful) {
|
||||
this.toastr.success('Email Service Url validated');
|
||||
this.toastr.success('Email Service was reachable');
|
||||
} else {
|
||||
this.toastr.error('Email Service Url did not respond. ' + result.errorMessage);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
<div class="container-fluid">
|
||||
<form [formGroup]="settingsForm" *ngIf="serverSettings !== undefined">
|
||||
<p class="text-warning pt-2">Changing Port or Base Url requires a manual restart of Kavita to take effect.</p>
|
||||
<div class="mb-3">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<strong>Notice:</strong> Changing Port or Base Url requires a manual restart of Kavita to take effect.
|
||||
</div>
|
||||
<!-- <div class="mb-3">
|
||||
<label for="settings-cachedir" class="form-label">Cache Directory</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="cacheDirectoryTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #cacheDirectoryTooltip>Where the server places temporary files when reading. This will be cleaned up on a regular basis.</ng-template>
|
||||
<span class="visually-hidden" id="settings-cachedir-help">Where the server places temporary files when reading. This will be cleaned up on a regular basis.</span>
|
||||
<input readonly id="settings-cachedir" aria-describedby="settings-cachedir-help" class="form-control" formControlName="cacheDirectory" type="text">
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="settings-bookmarksdir" class="form-label">Bookmarks Directory</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="bookmarksDirectoryTooltip" role="button" tabindex="0"></i>
|
||||
|
|
@ -20,25 +22,16 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="settings-hostname" class="form-label">Host Name</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="hostNameTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #hostNameTooltip>Domain Name (of Reverse Proxy). If set, email generation will always use this.</ng-template>
|
||||
<span class="visually-hidden" id="settings-hostname-help">Domain Name (of Reverse Proxy). If set, email generation will always use this.</span>
|
||||
<input id="settings-hostname" aria-describedby="settings-hostname-help" class="form-control" formControlName="hostName" type="text"
|
||||
[class.is-invalid]="settingsForm.get('hostName')?.invalid && settingsForm.get('hostName')?.touched">
|
||||
<div id="hostname-validations" class="invalid-feedback" *ngIf="settingsForm.dirty || settingsForm.touched">
|
||||
<div *ngIf="settingsForm.get('hostName')?.errors?.pattern">
|
||||
Host name must start with http(s) and not end in /
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="settings-baseurl" class="form-label">Base Url</label> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="baseUrlTooltip" role="button" tabindex="0"></i>
|
||||
<ng-template #baseUrlTooltip>Use this if you want to host Kavita on a base url ie) yourdomain.com/kavita. Not supported on Docker using non-root user.</ng-template>
|
||||
<span class="visually-hidden" id="settings-cachedir-help">Use this if you want to host Kavita on a base url ie) yourdomain.com/kavita. Not supported on Docker using non-root user.</span>
|
||||
<input id="settings-baseurl" aria-describedby="settings-baseurl-help" class="form-control" formControlName="baseUrl" type="text"
|
||||
[class.is-invalid]="settingsForm.get('baseUrl')?.invalid && settingsForm.get('baseUrl')?.touched">
|
||||
<div class="input-group">
|
||||
<input id="settings-baseurl" aria-describedby="settings-baseurl-help" class="form-control" formControlName="baseUrl" type="text"
|
||||
[class.is-invalid]="settingsForm.get('baseUrl')?.invalid && settingsForm.get('baseUrl')?.touched">
|
||||
<button class="btn btn-outline-secondary" (click)="resetBaseUrl()">Reset</button>
|
||||
</div>
|
||||
<div id="baseurl-validations" class="invalid-feedback" *ngIf="settingsForm.dirty || settingsForm.touched">
|
||||
<div *ngIf="settingsForm.get('baseUrl')?.errors?.pattern">
|
||||
Base URL must start and end with /
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
|||
import { ToastrService } from 'ngx-toastr';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { TagBadgeCursor } from 'src/app/shared/tag-badge/tag-badge.component';
|
||||
import { ServerService } from 'src/app/_services/server.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { DirectoryPickerComponent, DirectoryPickerResult } from '../_modals/directory-picker/directory-picker.component';
|
||||
import { ServerSettings } from '../_models/server-settings';
|
||||
|
|
@ -27,7 +28,7 @@ export class ManageSettingsComponent implements OnInit {
|
|||
}
|
||||
|
||||
constructor(private settingsService: SettingsService, private toastr: ToastrService,
|
||||
private modalService: NgbModal) { }
|
||||
private modalService: NgbModal, private serverService: ServerService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.settingsService.getTaskFrequencies().pipe(take(1)).subscribe(frequencies => {
|
||||
|
|
@ -54,6 +55,13 @@ export class ManageSettingsComponent implements OnInit {
|
|||
this.settingsForm.addControl('enableFolderWatching', new FormControl(this.serverSettings.enableFolderWatching, [Validators.required]));
|
||||
this.settingsForm.addControl('convertBookmarkToWebP', new FormControl(this.serverSettings.convertBookmarkToWebP, []));
|
||||
this.settingsForm.addControl('hostName', new FormControl(this.serverSettings.hostName, [Validators.pattern(/^(http:|https:)+[^\s]+[\w]$/)]));
|
||||
|
||||
this.serverService.getServerInfo().subscribe(info => {
|
||||
if (info.isDocker) {
|
||||
this.settingsForm.get('ipAddresses')?.disable();
|
||||
this.settingsForm.get('port')?.disable();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +88,7 @@ export class ManageSettingsComponent implements OnInit {
|
|||
async saveSettings() {
|
||||
const modelSettings = this.settingsForm.value;
|
||||
|
||||
this.settingsService.updateServerSettings(modelSettings).pipe(take(1)).subscribe(async (settings: ServerSettings) => {
|
||||
this.settingsService.updateServerSettings(modelSettings).pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings = settings;
|
||||
this.resetForm();
|
||||
this.toastr.success('Server settings updated');
|
||||
|
|
@ -90,7 +98,7 @@ export class ManageSettingsComponent implements OnInit {
|
|||
}
|
||||
|
||||
resetToDefaults() {
|
||||
this.settingsService.resetServerSettings().pipe(take(1)).subscribe(async (settings: ServerSettings) => {
|
||||
this.settingsService.resetServerSettings().pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings = settings;
|
||||
this.resetForm();
|
||||
this.toastr.success('Server settings updated');
|
||||
|
|
@ -100,15 +108,25 @@ export class ManageSettingsComponent implements OnInit {
|
|||
}
|
||||
|
||||
resetIPAddresses() {
|
||||
this.settingsService.resetIPAddressesSettings().pipe(take(1)).subscribe(async (settings: ServerSettings) => {
|
||||
this.settingsService.resetIPAddressesSettings().pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings.ipAddresses = settings.ipAddresses;
|
||||
this.settingsForm.get("ipAddresses")?.setValue(this.serverSettings.ipAddresses);
|
||||
this.settingsForm.get('ipAddresses')?.setValue(this.serverSettings.ipAddresses);
|
||||
this.toastr.success('IP Addresses Reset');
|
||||
}, (err: any) => {
|
||||
console.error('error: ', err);
|
||||
});
|
||||
}
|
||||
|
||||
resetBaseUrl() {
|
||||
this.settingsService.resetBaseUrl().pipe(take(1)).subscribe((settings: ServerSettings) => {
|
||||
this.serverSettings.baseUrl = settings.baseUrl;
|
||||
this.settingsForm.get('baseUrl')?.setValue(this.serverSettings.baseUrl);
|
||||
this.toastr.success('Base Url Reset');
|
||||
}, (err: any) => {
|
||||
console.error('error: ', err);
|
||||
});
|
||||
}
|
||||
|
||||
openDirectoryChooser(existingDirectory: string, formControl: string) {
|
||||
const modalRef = this.modalService.open(DirectoryPickerComponent, { scrollable: true, size: 'lg' });
|
||||
modalRef.componentInstance.startingFolder = existingDirectory || '';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map } from 'rxjs';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { TextResonse } from '../_types/text-response';
|
||||
import { ServerSettings } from './_models/server-settings';
|
||||
|
|
@ -37,6 +38,10 @@ export class SettingsService {
|
|||
return this.http.post<ServerSettings>(this.baseUrl + 'settings/reset-ip-addresses', {});
|
||||
}
|
||||
|
||||
resetBaseUrl() {
|
||||
return this.http.post<ServerSettings>(this.baseUrl + 'settings/reset-base-url', {});
|
||||
}
|
||||
|
||||
resetEmailServerSettings() {
|
||||
return this.http.post<ServerSettings>(this.baseUrl + 'settings/reset-email-url', {});
|
||||
}
|
||||
|
|
@ -58,6 +63,6 @@ export class SettingsService {
|
|||
}
|
||||
|
||||
getOpdsEnabled() {
|
||||
return this.http.get<boolean>(this.baseUrl + 'settings/opds-enabled', TextResonse);
|
||||
return this.http.get<string>(this.baseUrl + 'settings/opds-enabled', TextResonse).pipe(map(d => d === 'true'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ form {
|
|||
border: none;
|
||||
|
||||
&:focus-visible {
|
||||
width: calc(100vw - 175px);
|
||||
width: calc(100vw - 180px);
|
||||
}
|
||||
|
||||
&:empty {
|
||||
|
|
|
|||
|
|
@ -22,13 +22,11 @@
|
|||
[showHandToolButton]="true"
|
||||
[showOpenFileButton]="false"
|
||||
[showPrintButton]="false"
|
||||
[showBookmarkButton]="false"
|
||||
[showRotateButton]="false"
|
||||
[showDownloadButton]="false"
|
||||
[showPropertiesButton]="false"
|
||||
[(zoom)]="zoomSetting"
|
||||
[showSecondaryToolbarButton]="true"
|
||||
|
||||
[showBorders]="true"
|
||||
[theme]="theme"
|
||||
[formTheme]="theme"
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@
|
|||
<input class="form-control custom-input" formControlName="username" id="username" type="text" autofocus placeholder="Username">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="mb-2">
|
||||
<label for="password" class="form-label visually-hidden">Password</label>
|
||||
<input class="form-control custom-input" formControlName="password" name="password" id="password" type="password" ngModel pattern="^.{6,32}$" placeholder="Password">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="loginForm.dirty || loginForm.touched">
|
||||
<input class="form-control custom-input" formControlName="password" name="password"
|
||||
id="password" type="password" ngModel pattern="^.{6,32}$" placeholder="Password">
|
||||
<div id="inviteForm-validations" class="invalid-feedback" *ngIf="loginForm.get('password')?.errors?.pattern" >
|
||||
<div class="" *ngIf="loginForm.get('password')?.errors?.pattern">
|
||||
Password must be between 6 and 32 characters in length
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
.number {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
left: 46%;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export class CircularLoaderComponent {
|
|||
/**
|
||||
* The height in pixels of the loader
|
||||
*/
|
||||
@Input() height: string = '100px';
|
||||
@Input() height: string = '100px';
|
||||
/**
|
||||
* Centers the icon in the middle of the loader. Best for card use.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -340,8 +340,8 @@
|
|||
|
||||
|
||||
<ng-container *ngIf="tab.fragment === FragmentID.Clients">
|
||||
<div class="alert alert-warning" role="alert" *ngIf="!opdsEnabled">OPDS is not enabled on this server. This will not affect Tachiyomi users.</div>
|
||||
<p>All 3rd Party clients will either use the API key or the Connection Url below. These are like passwords, keep it private.</p>
|
||||
<p class="alert alert-warning" role="alert" *ngIf="!opdsEnabled">OPDS is not enabled on this server.</p>
|
||||
<app-api-key tooltipText="The API key is like a password. Keep it secret, Keep it safe."></app-api-key>
|
||||
<app-api-key title="OPDS URL" [showRefresh]="false" [transform]="makeUrl"></app-api-key>
|
||||
</ng-container>
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||
];
|
||||
active = this.tabs[1];
|
||||
opdsEnabled: boolean = false;
|
||||
baseUrl: string = '';
|
||||
makeUrl: (val: string) => string = (val: string) => {return this.transformKeyToOpdsUrl(val)};
|
||||
|
||||
private onDestroy = new Subject<void>();
|
||||
|
|
@ -106,6 +107,8 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
this.settingsService.getServerSettings().subscribe(settings => this.baseUrl = settings.baseUrl);
|
||||
|
||||
this.settingsService.getOpdsEnabled().subscribe(res => {
|
||||
this.opdsEnabled = res;
|
||||
this.cdRef.markForCheck();
|
||||
|
|
@ -257,7 +260,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||
return `${location.origin}${environment.apiUrl}opds/${key}`;
|
||||
}
|
||||
|
||||
return `${location.origin}/api/opds/${key}`;
|
||||
return `${location.origin}${this.baseUrl}api/opds/${key}`;
|
||||
}
|
||||
|
||||
handleBackgroundColorChange() {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
@import './theme/components/carousel';
|
||||
@import './theme/components/offcanvas';
|
||||
@import './theme/components/table';
|
||||
@import './theme/components/alerts';
|
||||
|
||||
|
||||
@import './theme/utilities/utilities';
|
||||
|
|
|
|||
5
UI/Web/src/theme/components/_alerts.scss
Normal file
5
UI/Web/src/theme/components/_alerts.scss
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
.alert-warning {
|
||||
--bs-alert-color: black;
|
||||
--bs-alert-bg: #fff3cd;
|
||||
--bs-alert-border-color: #ffecb5
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue