mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Base Url implementation (#1824)
* Base Url implementation * PR requested changes
This commit is contained in:
parent
74f62fd5e2
commit
2cff1bcebe
@ -191,6 +191,7 @@ public class SettingsController : BaseApiController
|
||||
? $"{path}/"
|
||||
: path;
|
||||
setting.Value = path;
|
||||
Configuration.BaseUrl = updateSettingsDto.BaseUrl;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"TokenKey": "super secret unguessable key",
|
||||
"Port": 5000,
|
||||
"IpAddresses": ""
|
||||
"IpAddresses": "",
|
||||
"BaseUrl": "/"
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"TokenKey": "super secret unguessable key",
|
||||
"Port": 5000,
|
||||
"IpAddresses": ""
|
||||
"IpAddresses": "",
|
||||
"BaseUrl": "/"
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -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> <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> <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> <i class="fa fa-info-circle" placement="right" [ngbTooltip]="ipAddressesTooltip" role="button" tabindex="0"></i>
|
||||
|
@ -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)]));
|
||||
|
3
UI/Web/src/app/base-url.provider.ts
Normal file
3
UI/Web/src/app/base-url.provider.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function getBaseUrl() : string {
|
||||
return document.getElementsByTagName('base')[0]?.getAttribute('href') || '/';
|
||||
}
|
@ -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}`;
|
||||
}
|
||||
|
||||
|
@ -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/`
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user