Base Url implementation (#1824)

* Base Url implementation

* PR requested changes
This commit is contained in:
Gazy Mahomar 2023-03-11 14:47:40 +01:00 committed by GitHub
parent 74f62fd5e2
commit 2cff1bcebe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 134 additions and 9 deletions

View File

@ -191,6 +191,7 @@ public class SettingsController : BaseApiController
? $"{path}/"
: path;
setting.Value = path;
Configuration.BaseUrl = updateSettingsDto.BaseUrl;
_unitOfWork.SettingsRepository.Update(setting);
}

View File

@ -19,6 +19,7 @@ using API.Services.HostedServices;
using API.Services.Tasks;
using API.SignalR;
using Hangfire;
using HtmlAgilityPack;
using Kavita.Common;
using Kavita.Common.EnvironmentInfo;
using Microsoft.AspNetCore.Builder;
@ -277,6 +278,11 @@ public class Startup
app.UseForwardedHeaders();
var basePath = Configuration.BaseUrl;
app.UsePathBase(basePath);
UpdateBaseUrlInIndex(basePath);
app.UseRouting();
// Ordering is important. Cors, authentication, authorization
@ -351,6 +357,20 @@ public class Startup
}
Console.WriteLine($"Kavita - v{BuildInfo.Version}");
});
var _logger = serviceProvider.GetRequiredService<ILogger<Startup>>();
_logger.LogInformation("Starting with base url as {baseUrl}", basePath);
}
private static void UpdateBaseUrlInIndex(string baseUrl)
{
var htmlDoc = new HtmlDocument();
var indexHtmlPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "index.html");
htmlDoc.Load(indexHtmlPath);
var baseNode = htmlDoc.DocumentNode.SelectSingleNode("/html/head/base");
baseNode.SetAttributeValue("href", baseUrl);
htmlDoc.Save(indexHtmlPath);
}
private static void OnShutdown()

View File

@ -1,5 +1,6 @@
{
"TokenKey": "super secret unguessable key",
"Port": 5000,
"IpAddresses": ""
"IpAddresses": "",
"BaseUrl": "/"
}

View File

@ -1,5 +1,6 @@
{
"TokenKey": "super secret unguessable key",
"Port": 5000,
"IpAddresses": ""
"IpAddresses": "",
"BaseUrl": "/"
}

View File

@ -9,6 +9,7 @@ namespace Kavita.Common;
public static class Configuration
{
public const string DefaultIpAddresses = "0.0.0.0,::";
public const string DefaultBaseUrl = "/";
private static readonly string AppSettingsFilename = Path.Join("config", GetAppSettingFilename());
public static int Port
@ -29,6 +30,12 @@ public static class Configuration
set => SetJwtToken(GetAppSettingFilename(), value);
}
public static string BaseUrl
{
get => GetBaseUrl(GetAppSettingFilename());
set => SetBaseUrl(GetAppSettingFilename(), value);
}
private static string GetAppSettingFilename()
{
if (!string.IsNullOrEmpty(AppSettingsFilename))
@ -200,10 +207,81 @@ public static class Configuration
}
#endregion
#region BaseUrl
private static string GetBaseUrl(string filePath)
{
if (new OsInfo(Array.Empty<IOsVersionAdapter>()).IsDocker)
{
return DefaultBaseUrl;
}
try
{
var json = File.ReadAllText(filePath);
var jsonObj = JsonSerializer.Deserialize<dynamic>(json);
const string key = "BaseUrl";
if (jsonObj.TryGetProperty(key, out JsonElement tokenElement))
{
var baseUrl = tokenElement.GetString();
if (!String.IsNullOrEmpty(baseUrl))
{
baseUrl = !baseUrl.StartsWith("/")
? $"/{baseUrl}"
: baseUrl;
baseUrl = !baseUrl.EndsWith("/")
? $"{baseUrl}/"
: baseUrl;
return baseUrl;
}
return DefaultBaseUrl;
}
}
catch (Exception ex)
{
Console.WriteLine("Error reading app settings: " + ex.Message);
}
return DefaultBaseUrl;
}
private static void SetBaseUrl(string filePath, string value)
{
if (new OsInfo(Array.Empty<IOsVersionAdapter>()).IsDocker)
{
return;
}
var baseUrl = !value.StartsWith("/")
? $"/{value}"
: value;
baseUrl = !baseUrl.EndsWith("/")
? $"{baseUrl}/"
: baseUrl;
try
{
var json = File.ReadAllText(filePath);
var jsonObj = JsonSerializer.Deserialize<AppSettings>(json);
jsonObj.BaseUrl = baseUrl;
json = JsonSerializer.Serialize(jsonObj, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(filePath, json);
}
catch (Exception)
{
/* Swallow exception */
}
}
#endregion
private class AppSettings
{
public string TokenKey { get; set; }
public int Port { get; set; }
public string IpAddresses { get; set; }
public string BaseUrl { get; set; }
}
}

View File

@ -1,6 +1,6 @@
<div class="container-fluid">
<form [formGroup]="settingsForm" *ngIf="serverSettings !== undefined">
<p class="text-warning pt-2">Changing Port requires a manual restart of Kavita to take effect.</p>
<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">
<label for="settings-cachedir" class="form-label">Cache Directory</label>&nbsp;<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>
@ -33,6 +33,19 @@
</div>
</div>
<div class="mb-3">
<label for="settings-baseurl" class="form-label">Base Url</label>&nbsp;<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</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</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 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 /
</div>
</div>
</div>
<div class="row g-0 mb-2">
<div class="col-md-8 col-sm-12 pe-2">
<label for="settings-ipaddresses" class="form-label">IP Addresses</label>&nbsp;<i class="fa fa-info-circle" placement="right" [ngbTooltip]="ipAddressesTooltip" role="button" tabindex="0"></i>

View File

@ -47,7 +47,7 @@ export class ManageSettingsComponent implements OnInit {
this.settingsForm.addControl('loggingLevel', new FormControl(this.serverSettings.loggingLevel, [Validators.required]));
this.settingsForm.addControl('allowStatCollection', new FormControl(this.serverSettings.allowStatCollection, [Validators.required]));
this.settingsForm.addControl('enableOpds', new FormControl(this.serverSettings.enableOpds, [Validators.required]));
this.settingsForm.addControl('baseUrl', new FormControl(this.serverSettings.baseUrl, [Validators.required]));
this.settingsForm.addControl('baseUrl', new FormControl(this.serverSettings.baseUrl, [Validators.pattern(/^(\/[\w-]+)*\/$/)]));
this.settingsForm.addControl('emailServiceUrl', new FormControl(this.serverSettings.emailServiceUrl, [Validators.required]));
this.settingsForm.addControl('totalBackups', new FormControl(this.serverSettings.totalBackups, [Validators.required, Validators.min(1), Validators.max(30)]));
this.settingsForm.addControl('totalLogs', new FormControl(this.serverSettings.totalLogs, [Validators.required, Validators.min(1), Validators.max(30)]));

View File

@ -0,0 +1,3 @@
export function getBaseUrl() : string {
return document.getElementsByTagName('base')[0]?.getAttribute('href') || '/';
}

View File

@ -22,6 +22,7 @@ import { BookPageLayoutMode } from 'src/app/_models/readers/book-page-layout-mod
import { forkJoin, Subject } from 'rxjs';
import { bookColorThemes } from 'src/app/book-reader/_components/reader-settings/reader-settings.component';
import { BookService } from 'src/app/book-reader/_services/book.service';
import { environment } from 'src/environments/environment';
enum AccordionPanelID {
ImageReader = 'image-reader',
@ -252,6 +253,10 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
transformKeyToOpdsUrl(key: string) {
if (environment.production) {
return `${location.origin}${environment.apiUrl}opds/${key}`;
}
return `${location.origin}/api/opds/${key}`;
}

View File

@ -1,5 +1,8 @@
import { getBaseUrl } from "src/app/base-url.provider";
const BASE_URL = getBaseUrl();
export const environment = {
production: true,
apiUrl: '/api/',
hubUrl: '/hubs/'
apiUrl: `${BASE_URL}api/`,
hubUrl:`${BASE_URL}hubs/`
};