mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-04-21 02:02:31 -04:00
v1.4.3: fixed .env restarting from Admin UI, re-release of prev ver
This commit is contained in:
@@ -65,13 +65,13 @@ Allstarr includes a web UI for easy configuration and playlist management, acces
|
||||
- `37i9dQZF1DXcBWIGoYBM5M` (just the ID)
|
||||
- `spotify:playlist:37i9dQZF1DXcBWIGoYBM5M` (Spotify URI)
|
||||
- `https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M` (full URL)
|
||||
4. **Restart** to apply changes (should be a banner)
|
||||
4. **Restart Allstarr** to apply changes (should be a banner)
|
||||
|
||||
Then, proceeed to **Active Playlists**, which shows you which Spotify playlists are currently being monitored and filled with tracks, and lets you do a bunch of useful operations on them.
|
||||
|
||||
### Configuration Persistence
|
||||
|
||||
The web UI updates your `.env` file directly. Changes persist across container restarts, but require a restart to take effect. In development mode, the `.env` file is in your project root. In Docker, it's at `/app/.env`.
|
||||
The web UI updates your `.env` file directly. Allstarr reloads that file on startup, so a normal container restart is enough for UI changes to take effect. In development mode, the `.env` file is in your project root. In Docker, it's at `/app/.env`.
|
||||
|
||||
There's an environment variable to modify this.
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
using allstarr.Services.Common;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace allstarr.Tests;
|
||||
|
||||
public sealed class RuntimeEnvConfigurationTests : IDisposable
|
||||
{
|
||||
private readonly string _envFilePath = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
$"allstarr-runtime-{Guid.NewGuid():N}.env");
|
||||
|
||||
[Fact]
|
||||
public void MapEnvVarToConfiguration_MapsFlatKeyToNestedConfigKey()
|
||||
{
|
||||
var mappings = RuntimeEnvConfiguration
|
||||
.MapEnvVarToConfiguration("SPOTIFY_IMPORT_MATCHING_INTERVAL_HOURS", "7")
|
||||
.ToList();
|
||||
|
||||
var mapping = Assert.Single(mappings);
|
||||
Assert.Equal("SpotifyImport:MatchingIntervalHours", mapping.Key);
|
||||
Assert.Equal("7", mapping.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapEnvVarToConfiguration_MapsSharedBackendKeysToBothSections()
|
||||
{
|
||||
var mappings = RuntimeEnvConfiguration
|
||||
.MapEnvVarToConfiguration("MUSIC_SERVICE", "Qobuz")
|
||||
.OrderBy(x => x.Key, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
Assert.Equal(2, mappings.Count);
|
||||
Assert.Equal("Jellyfin:MusicService", mappings[0].Key);
|
||||
Assert.Equal("Qobuz", mappings[0].Value);
|
||||
Assert.Equal("Subsonic:MusicService", mappings[1].Key);
|
||||
Assert.Equal("Qobuz", mappings[1].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapEnvVarToConfiguration_IgnoresComposeOnlyMountKeys()
|
||||
{
|
||||
var mappings = RuntimeEnvConfiguration
|
||||
.MapEnvVarToConfiguration("DOWNLOAD_PATH", "./downloads")
|
||||
.ToList();
|
||||
|
||||
Assert.Empty(mappings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadDotEnvOverrides_StripsQuotesAndSupportsDoubleUnderscoreKeys()
|
||||
{
|
||||
File.WriteAllText(
|
||||
_envFilePath,
|
||||
"""
|
||||
SPOTIFY_API_SESSION_COOKIE="secret-cookie"
|
||||
Admin__EnableEnvExport=true
|
||||
""");
|
||||
|
||||
var overrides = RuntimeEnvConfiguration.LoadDotEnvOverrides(_envFilePath);
|
||||
|
||||
Assert.Equal("secret-cookie", overrides["SpotifyApi:SessionCookie"]);
|
||||
Assert.Equal("true", overrides["Admin:EnableEnvExport"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddDotEnvOverrides_OverridesEarlierConfigurationValues()
|
||||
{
|
||||
File.WriteAllText(_envFilePath, "SPOTIFY_IMPORT_MATCHING_INTERVAL_HOURS=7\n");
|
||||
|
||||
var configuration = new ConfigurationManager();
|
||||
configuration.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["SpotifyImport:MatchingIntervalHours"] = "24"
|
||||
});
|
||||
|
||||
RuntimeEnvConfiguration.AddDotEnvOverrides(configuration, _envFilePath);
|
||||
|
||||
Assert.Equal(7, configuration.GetValue<int>("SpotifyImport:MatchingIntervalHours"));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (File.Exists(_envFilePath))
|
||||
{
|
||||
File.Delete(_envFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,5 +9,5 @@ public static class AppVersion
|
||||
/// <summary>
|
||||
/// Current application version.
|
||||
/// </summary>
|
||||
public const string Version = "1.4.1";
|
||||
public const string Version = "1.4.3";
|
||||
}
|
||||
|
||||
@@ -580,7 +580,7 @@ public class ConfigController : ControllerBase
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
message = "Configuration updated. Restart container to apply changes.",
|
||||
message = "Configuration updated. Restart Allstarr to apply changes.",
|
||||
updatedKeys = appliedUpdates,
|
||||
requiresRestart = true,
|
||||
envFilePath = _helperService.GetEnvFilePath()
|
||||
@@ -696,7 +696,7 @@ public class ConfigController : ControllerBase
|
||||
_logger.LogWarning("Docker socket not available at {Path}", socketPath);
|
||||
return StatusCode(503, new {
|
||||
error = "Docker socket not available",
|
||||
message = "Please restart manually: docker-compose restart allstarr"
|
||||
message = "Please restart manually: docker restart allstarr"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -749,7 +749,7 @@ public class ConfigController : ControllerBase
|
||||
_logger.LogError("Failed to restart container: {StatusCode} - {Body}", response.StatusCode, errorBody);
|
||||
return StatusCode((int)response.StatusCode, new {
|
||||
error = "Failed to restart container",
|
||||
message = "Please restart manually: docker-compose restart allstarr"
|
||||
message = "Please restart manually: docker restart allstarr"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -758,7 +758,7 @@ public class ConfigController : ControllerBase
|
||||
_logger.LogError(ex, "Error restarting container");
|
||||
return StatusCode(500, new {
|
||||
error = "Failed to restart container",
|
||||
message = "Please restart manually: docker-compose restart allstarr"
|
||||
message = "Please restart manually: docker restart allstarr"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -890,7 +890,7 @@ public class ConfigController : ControllerBase
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
message = ".env file imported successfully. Restart the application for changes to take effect."
|
||||
message = ".env file imported successfully. Restart Allstarr for changes to take effect."
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -16,6 +16,7 @@ using Microsoft.Extensions.Http;
|
||||
using System.Net;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
RuntimeEnvConfiguration.AddDotEnvOverrides(builder.Configuration, builder.Environment, Console.Out);
|
||||
|
||||
// Discover SquidWTF API and streaming endpoints from uptime feeds.
|
||||
var squidWtfEndpointCatalog = await SquidWtfEndpointDiscovery.DiscoverAsync();
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using allstarr.Models.Settings;
|
||||
using allstarr.Models.Spotify;
|
||||
using allstarr.Services.Common;
|
||||
|
||||
namespace allstarr.Services.Admin;
|
||||
|
||||
@@ -20,9 +21,7 @@ public class AdminHelperService
|
||||
{
|
||||
_logger = logger;
|
||||
_jellyfinSettings = jellyfinSettings.Value;
|
||||
_envFilePath = environment.IsDevelopment()
|
||||
? Path.Combine(environment.ContentRootPath, "..", ".env")
|
||||
: "/app/.env";
|
||||
_envFilePath = RuntimeEnvConfiguration.ResolveEnvFilePath(environment);
|
||||
}
|
||||
|
||||
public string GetJellyfinAuthHeader()
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace allstarr.Services.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Loads supported flat .env keys into ASP.NET configuration so Docker/admin UI
|
||||
/// updates stored in /app/.env take effect on the next application startup.
|
||||
/// </summary>
|
||||
public static class RuntimeEnvConfiguration
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<string, string[]> ExactKeyMappings =
|
||||
new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["BACKEND_TYPE"] = ["Backend:Type"],
|
||||
["ADMIN_BIND_ANY_IP"] = ["Admin:BindAnyIp"],
|
||||
["ADMIN_TRUSTED_SUBNETS"] = ["Admin:TrustedSubnets"],
|
||||
["ADMIN_ENABLE_ENV_EXPORT"] = ["Admin:EnableEnvExport"],
|
||||
|
||||
["CORS_ALLOWED_ORIGINS"] = ["Cors:AllowedOrigins"],
|
||||
["CORS_ALLOWED_METHODS"] = ["Cors:AllowedMethods"],
|
||||
["CORS_ALLOWED_HEADERS"] = ["Cors:AllowedHeaders"],
|
||||
["CORS_ALLOW_CREDENTIALS"] = ["Cors:AllowCredentials"],
|
||||
|
||||
["SUBSONIC_URL"] = ["Subsonic:Url"],
|
||||
["JELLYFIN_URL"] = ["Jellyfin:Url"],
|
||||
["JELLYFIN_API_KEY"] = ["Jellyfin:ApiKey"],
|
||||
["JELLYFIN_USER_ID"] = ["Jellyfin:UserId"],
|
||||
["JELLYFIN_CLIENT_USERNAME"] = ["Jellyfin:ClientUsername"],
|
||||
["JELLYFIN_LIBRARY_ID"] = ["Jellyfin:LibraryId"],
|
||||
|
||||
["LIBRARY_DOWNLOAD_PATH"] = ["Library:DownloadPath"],
|
||||
["LIBRARY_KEPT_PATH"] = ["Library:KeptPath"],
|
||||
|
||||
["REDIS_ENABLED"] = ["Redis:Enabled"],
|
||||
["REDIS_CONNECTION_STRING"] = ["Redis:ConnectionString"],
|
||||
|
||||
["SPOTIFY_IMPORT_ENABLED"] = ["SpotifyImport:Enabled"],
|
||||
["SPOTIFY_IMPORT_SYNC_START_HOUR"] = ["SpotifyImport:SyncStartHour"],
|
||||
["SPOTIFY_IMPORT_SYNC_START_MINUTE"] = ["SpotifyImport:SyncStartMinute"],
|
||||
["SPOTIFY_IMPORT_SYNC_WINDOW_HOURS"] = ["SpotifyImport:SyncWindowHours"],
|
||||
["SPOTIFY_IMPORT_MATCHING_INTERVAL_HOURS"] = ["SpotifyImport:MatchingIntervalHours"],
|
||||
["SPOTIFY_IMPORT_PLAYLISTS"] = ["SpotifyImport:Playlists"],
|
||||
["SPOTIFY_IMPORT_PLAYLIST_IDS"] = ["SpotifyImport:PlaylistIds"],
|
||||
["SPOTIFY_IMPORT_PLAYLIST_NAMES"] = ["SpotifyImport:PlaylistNames"],
|
||||
["SPOTIFY_IMPORT_PLAYLIST_LOCAL_TRACKS_POSITIONS"] = ["SpotifyImport:PlaylistLocalTracksPositions"],
|
||||
|
||||
["SPOTIFY_API_ENABLED"] = ["SpotifyApi:Enabled"],
|
||||
["SPOTIFY_API_SESSION_COOKIE"] = ["SpotifyApi:SessionCookie"],
|
||||
["SPOTIFY_API_SESSION_COOKIE_SET_DATE"] = ["SpotifyApi:SessionCookieSetDate"],
|
||||
["SPOTIFY_API_CACHE_DURATION_MINUTES"] = ["SpotifyApi:CacheDurationMinutes"],
|
||||
["SPOTIFY_API_RATE_LIMIT_DELAY_MS"] = ["SpotifyApi:RateLimitDelayMs"],
|
||||
["SPOTIFY_API_PREFER_ISRC_MATCHING"] = ["SpotifyApi:PreferIsrcMatching"],
|
||||
["SPOTIFY_LYRICS_API_URL"] = ["SpotifyApi:LyricsApiUrl"],
|
||||
|
||||
["SCROBBLING_ENABLED"] = ["Scrobbling:Enabled"],
|
||||
["SCROBBLING_LOCAL_TRACKS_ENABLED"] = ["Scrobbling:LocalTracksEnabled"],
|
||||
["SCROBBLING_SYNTHETIC_LOCAL_PLAYED_SIGNAL_ENABLED"] = ["Scrobbling:SyntheticLocalPlayedSignalEnabled"],
|
||||
["SCROBBLING_LASTFM_ENABLED"] = ["Scrobbling:LastFm:Enabled"],
|
||||
["SCROBBLING_LASTFM_API_KEY"] = ["Scrobbling:LastFm:ApiKey"],
|
||||
["SCROBBLING_LASTFM_SHARED_SECRET"] = ["Scrobbling:LastFm:SharedSecret"],
|
||||
["SCROBBLING_LASTFM_SESSION_KEY"] = ["Scrobbling:LastFm:SessionKey"],
|
||||
["SCROBBLING_LASTFM_USERNAME"] = ["Scrobbling:LastFm:Username"],
|
||||
["SCROBBLING_LASTFM_PASSWORD"] = ["Scrobbling:LastFm:Password"],
|
||||
["SCROBBLING_LISTENBRAINZ_ENABLED"] = ["Scrobbling:ListenBrainz:Enabled"],
|
||||
["SCROBBLING_LISTENBRAINZ_USER_TOKEN"] = ["Scrobbling:ListenBrainz:UserToken"],
|
||||
|
||||
["DEBUG_LOG_ALL_REQUESTS"] = ["Debug:LogAllRequests"],
|
||||
["DEBUG_REDACT_SENSITIVE_REQUEST_VALUES"] = ["Debug:RedactSensitiveRequestValues"],
|
||||
|
||||
["DEEZER_ARL"] = ["Deezer:Arl"],
|
||||
["DEEZER_ARL_FALLBACK"] = ["Deezer:ArlFallback"],
|
||||
["DEEZER_QUALITY"] = ["Deezer:Quality"],
|
||||
["DEEZER_MIN_REQUEST_INTERVAL_MS"] = ["Deezer:MinRequestIntervalMs"],
|
||||
|
||||
["QOBUZ_USER_AUTH_TOKEN"] = ["Qobuz:UserAuthToken"],
|
||||
["QOBUZ_USER_ID"] = ["Qobuz:UserId"],
|
||||
["QOBUZ_QUALITY"] = ["Qobuz:Quality"],
|
||||
["QOBUZ_MIN_REQUEST_INTERVAL_MS"] = ["Qobuz:MinRequestIntervalMs"],
|
||||
|
||||
["SQUIDWTF_QUALITY"] = ["SquidWTF:Quality"],
|
||||
["SQUIDWTF_MIN_REQUEST_INTERVAL_MS"] = ["SquidWTF:MinRequestIntervalMs"],
|
||||
|
||||
["MUSICBRAINZ_ENABLED"] = ["MusicBrainz:Enabled"],
|
||||
["MUSICBRAINZ_USERNAME"] = ["MusicBrainz:Username"],
|
||||
["MUSICBRAINZ_PASSWORD"] = ["MusicBrainz:Password"],
|
||||
|
||||
["CACHE_SEARCH_RESULTS_MINUTES"] = ["Cache:SearchResultsMinutes"],
|
||||
["CACHE_PLAYLIST_IMAGES_HOURS"] = ["Cache:PlaylistImagesHours"],
|
||||
["CACHE_SPOTIFY_PLAYLIST_ITEMS_HOURS"] = ["Cache:SpotifyPlaylistItemsHours"],
|
||||
["CACHE_SPOTIFY_MATCHED_TRACKS_DAYS"] = ["Cache:SpotifyMatchedTracksDays"],
|
||||
["CACHE_LYRICS_DAYS"] = ["Cache:LyricsDays"],
|
||||
["CACHE_GENRE_DAYS"] = ["Cache:GenreDays"],
|
||||
["CACHE_METADATA_DAYS"] = ["Cache:MetadataDays"],
|
||||
["CACHE_ODESLI_LOOKUP_DAYS"] = ["Cache:OdesliLookupDays"],
|
||||
["CACHE_PROXY_IMAGES_DAYS"] = ["Cache:ProxyImagesDays"],
|
||||
["CACHE_TRANSCODE_MINUTES"] = ["Cache:TranscodeCacheMinutes"]
|
||||
};
|
||||
|
||||
private static readonly IReadOnlyDictionary<string, string[]> SharedBackendKeyMappings =
|
||||
new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["MUSIC_SERVICE"] = ["Subsonic:MusicService", "Jellyfin:MusicService"],
|
||||
["EXPLICIT_FILTER"] = ["Subsonic:ExplicitFilter", "Jellyfin:ExplicitFilter"],
|
||||
["DOWNLOAD_MODE"] = ["Subsonic:DownloadMode", "Jellyfin:DownloadMode"],
|
||||
["STORAGE_MODE"] = ["Subsonic:StorageMode", "Jellyfin:StorageMode"],
|
||||
["CACHE_DURATION_HOURS"] = ["Subsonic:CacheDurationHours", "Jellyfin:CacheDurationHours"],
|
||||
["ENABLE_EXTERNAL_PLAYLISTS"] = ["Subsonic:EnableExternalPlaylists", "Jellyfin:EnableExternalPlaylists"],
|
||||
["PLAYLISTS_DIRECTORY"] = ["Subsonic:PlaylistsDirectory", "Jellyfin:PlaylistsDirectory"]
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> IgnoredComposeOnlyKeys = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"DOWNLOAD_PATH",
|
||||
"KEPT_PATH",
|
||||
"CACHE_PATH",
|
||||
"REDIS_DATA_PATH"
|
||||
};
|
||||
|
||||
public static string ResolveEnvFilePath(IHostEnvironment environment)
|
||||
{
|
||||
return environment.IsDevelopment()
|
||||
? Path.GetFullPath(Path.Combine(environment.ContentRootPath, "..", ".env"))
|
||||
: "/app/.env";
|
||||
}
|
||||
|
||||
public static void AddDotEnvOverrides(
|
||||
ConfigurationManager configuration,
|
||||
IHostEnvironment environment,
|
||||
TextWriter? logWriter = null)
|
||||
{
|
||||
AddDotEnvOverrides(configuration, ResolveEnvFilePath(environment), logWriter);
|
||||
}
|
||||
|
||||
public static void AddDotEnvOverrides(
|
||||
ConfigurationManager configuration,
|
||||
string envFilePath,
|
||||
TextWriter? logWriter = null)
|
||||
{
|
||||
var overrides = LoadDotEnvOverrides(envFilePath);
|
||||
if (overrides.Count == 0)
|
||||
{
|
||||
if (File.Exists(envFilePath))
|
||||
{
|
||||
logWriter?.WriteLine($"No supported runtime overrides found in {envFilePath}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
configuration.AddInMemoryCollection(overrides);
|
||||
logWriter?.WriteLine($"Loaded {overrides.Count} runtime override(s) from {envFilePath}");
|
||||
}
|
||||
|
||||
public static Dictionary<string, string?> LoadDotEnvOverrides(string envFilePath)
|
||||
{
|
||||
var overrides = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (!File.Exists(envFilePath))
|
||||
{
|
||||
return overrides;
|
||||
}
|
||||
|
||||
foreach (var line in File.ReadLines(envFilePath))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line) || line.TrimStart().StartsWith('#'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var separatorIndex = line.IndexOf('=');
|
||||
if (separatorIndex <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var envKey = line[..separatorIndex].Trim();
|
||||
var envValue = StripQuotes(line[(separatorIndex + 1)..].Trim());
|
||||
|
||||
foreach (var mapping in MapEnvVarToConfiguration(envKey, envValue))
|
||||
{
|
||||
overrides[mapping.Key] = mapping.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return overrides;
|
||||
}
|
||||
|
||||
public static IEnumerable<KeyValuePair<string, string?>> MapEnvVarToConfiguration(string envKey, string? envValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(envKey) || IgnoredComposeOnlyKeys.Contains(envKey))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (envKey.Contains("__", StringComparison.Ordinal))
|
||||
{
|
||||
yield return new KeyValuePair<string, string?>(envKey.Replace("__", ":"), envValue);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (SharedBackendKeyMappings.TryGetValue(envKey, out var sharedKeys))
|
||||
{
|
||||
foreach (var sharedKey in sharedKeys)
|
||||
{
|
||||
yield return new KeyValuePair<string, string?>(sharedKey, envValue);
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (ExactKeyMappings.TryGetValue(envKey, out var configKeys))
|
||||
{
|
||||
foreach (var configKey in configKeys)
|
||||
{
|
||||
yield return new KeyValuePair<string, string?>(configKey, envValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string StripQuotes(string? value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return value ?? string.Empty;
|
||||
}
|
||||
|
||||
if (value.StartsWith('"') && value.EndsWith('"') && value.Length >= 2)
|
||||
{
|
||||
return value[1..^1];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
<!-- Restart Required Banner -->
|
||||
<div class="restart-banner" id="restart-banner">
|
||||
⚠️ Configuration changed. Restart required to apply changes.
|
||||
<button onclick="restartContainer()">Restart Now</button>
|
||||
<button onclick="restartContainer()">Restart Allstarr</button>
|
||||
<button onclick="dismissRestartBanner()"
|
||||
style="background: transparent; border: 1px solid var(--bg-primary);">Dismiss</button>
|
||||
</div>
|
||||
@@ -858,7 +858,7 @@
|
||||
</p>
|
||||
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<button class="danger" onclick="clearCache()">Clear All Cache</button>
|
||||
<button class="danger" onclick="restartContainer()">Restart Container</button>
|
||||
<button class="danger" onclick="restartContainer()">Restart Allstarr</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -274,7 +274,7 @@ export async function restartContainer() {
|
||||
return requestJson(
|
||||
"/api/admin/restart",
|
||||
{ method: "POST" },
|
||||
"Failed to restart container",
|
||||
"Failed to restart Allstarr",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ async function importEnv(event) {
|
||||
|
||||
const result = await runAction({
|
||||
confirmMessage:
|
||||
"Import this .env file? This will replace your current configuration.\n\nA backup will be created automatically.\n\nYou will need to restart the container for changes to take effect.",
|
||||
"Import this .env file? This will replace your current configuration.\n\nA backup will be created automatically.\n\nYou will need to restart Allstarr for changes to take effect.",
|
||||
task: () => API.importEnv(file),
|
||||
success: (data) => data.message,
|
||||
error: (err) => err.message || "Failed to import .env file",
|
||||
@@ -283,7 +283,7 @@ async function importEnv(event) {
|
||||
async function restartContainer() {
|
||||
if (
|
||||
!confirm(
|
||||
"Restart the container to apply configuration changes?\n\nThe dashboard will be temporarily unavailable.",
|
||||
"Restart Allstarr to reload /app/.env and apply configuration changes?\n\nThe dashboard will be temporarily unavailable.",
|
||||
)
|
||||
) {
|
||||
return;
|
||||
@@ -291,7 +291,7 @@ async function restartContainer() {
|
||||
|
||||
const result = await runAction({
|
||||
task: () => API.restartContainer(),
|
||||
error: "Failed to restart container",
|
||||
error: "Failed to restart Allstarr",
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
@@ -301,7 +301,7 @@ async function restartContainer() {
|
||||
document.getElementById("restart-overlay")?.classList.add("active");
|
||||
const statusEl = document.getElementById("restart-status");
|
||||
if (statusEl) {
|
||||
statusEl.textContent = "Stopping container...";
|
||||
statusEl.textContent = "Restarting Allstarr...";
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
Reference in New Issue
Block a user