diff options
| -rw-r--r-- | Jellyfin.Server/Program.cs | 3 | ||||
| -rw-r--r-- | Jellyfin.Server/ServerSetupApp/SetupServer.cs | 2 | ||||
| -rw-r--r-- | Jellyfin.Server/ServerSetupApp/StartupActivity.cs | 3 | ||||
| -rw-r--r-- | Jellyfin.Server/ServerSetupApp/index.mstemplate.html | 127 |
4 files changed, 127 insertions, 8 deletions
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 2b20ee4314..12f92efb35 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -207,7 +207,8 @@ namespace Jellyfin.Server var jellyfinMigrationService = ActivatorUtilities.CreateInstance<JellyfinMigrationService>(appHost.ServiceProvider); SetupServer.ReportActivity(StartupActivity.PreparingMigrations); await jellyfinMigrationService.PrepareSystemForMigration(_logger).ConfigureAwait(false); - SetupServer.ReportActivity(StartupActivity.ApplyingMigrations); + // "Preparing migrations" carries through the DB read; per-migration progress is reported + // as "Running migration X of Y" from inside the step once the pending set is known. await jellyfinMigrationService.MigrateStepAsync(JellyfinMigrationStageTypes.CoreInitialisation, appHost.ServiceProvider).ConfigureAwait(false); SetupServer.ReportActivity(StartupActivity.InitializingServices); diff --git a/Jellyfin.Server/ServerSetupApp/SetupServer.cs b/Jellyfin.Server/ServerSetupApp/SetupServer.cs index 893272590e..598de5aa5f 100644 --- a/Jellyfin.Server/ServerSetupApp/SetupServer.cs +++ b/Jellyfin.Server/ServerSetupApp/SetupServer.cs @@ -262,7 +262,7 @@ public sealed class SetupServer : IDisposable /// Reports the current startup activity shown to all clients in the startup UI header. /// Only pass generic, non-identifying text from <see cref="StartupActivity"/>. /// </summary> - /// <param name="activity">A generic description such as <see cref="StartupActivity.ApplyingMigrations"/>.</param> + /// <param name="activity">A generic description such as <see cref="StartupActivity.PreparingMigrations"/>.</param> internal static void ReportActivity(string activity) { _currentActivity = activity; diff --git a/Jellyfin.Server/ServerSetupApp/StartupActivity.cs b/Jellyfin.Server/ServerSetupApp/StartupActivity.cs index 5baaf1d40a..888cc617d4 100644 --- a/Jellyfin.Server/ServerSetupApp/StartupActivity.cs +++ b/Jellyfin.Server/ServerSetupApp/StartupActivity.cs @@ -21,9 +21,6 @@ public static class StartupActivity /// <summary>Preparing the system for migrations (e.g. taking safety backups).</summary> public const string PreparingMigrations = "Preparing migrations"; - /// <summary>Applying database/system migrations without a known count.</summary> - public const string ApplyingMigrations = "Applying migrations"; - /// <summary>Restoring from a backup.</summary> public const string RestoringBackup = "Restoring backup"; diff --git a/Jellyfin.Server/ServerSetupApp/index.mstemplate.html b/Jellyfin.Server/ServerSetupApp/index.mstemplate.html index cc37a8b4dd..9c12762c31 100644 --- a/Jellyfin.Server/ServerSetupApp/index.mstemplate.html +++ b/Jellyfin.Server/ServerSetupApp/index.mstemplate.html @@ -126,6 +126,63 @@ color: var(--jf-error); } + /* Buttons — matching the web client's emby-button styles. */ + .jf-button { + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + margin: 0; + padding: 0.9em 1em; + border: 0; + border-radius: 0.2em; + font-family: inherit; + font-size: inherit; + font-weight: 600; + line-height: 1.35; + cursor: pointer; + outline: none; + text-decoration: none; + transition: 0.2s; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + } + + .jf-button-primary { + background: var(--jf-primary); + color: rgba(0, 0, 0, 0.87); + } + + .jf-button-primary:hover, + .jf-button-primary:focus { + background: var(--jf-primary-dark); + } + + .jf-button-secondary { + background: #424242; + color: var(--jf-text-secondary); + } + + .jf-button-secondary:hover, + .jf-button-secondary:focus { + background: #616161; + } + + /* Redirect countdown shown once the server is ready. */ + .redirect-bar { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + flex-wrap: wrap; + gap: 1em; + margin-bottom: 1.5em; + text-align: center; + } + + .redirect-countdown { + color: var(--jf-text-secondary); + } + /* Material (MDL) spinner — the same one the web client uses while loading. */ .mdl-spinner { position: relative; @@ -491,8 +548,8 @@ function poll() { fetch(window.location.href, { cache: 'no-store' }).then(function (resp) { if (resp.ok) { - // The real server is now answering (HTTP 200) -> load the actual app. - window.location.reload(); + // The real server is now answering (HTTP 200) -> offer to continue to the app. + onServerReady(); return null; } return resp.text(); @@ -530,7 +587,71 @@ }); } - setInterval(poll, intervalMs); + // The server finished starting. Stop polling and present a cancelable countdown so the + // user can either ride the redirect into the app or stay to review the startup output. + function onServerReady() { + clearInterval(pollTimer); + + var status = document.querySelector('.status'); + var statusText = document.querySelector('.status-text'); + var spinner = document.querySelector('.mdl-spinner'); + if (spinner) { + spinner.style.display = 'none'; + } + if (status) { + status.classList.add('is-success'); + } + if (statusText) { + statusText.textContent = 'Jellyfin started successfully.'; + } + if (!status) { + window.location.reload(); + return; + } + + var bar = document.createElement('div'); + bar.className = 'redirect-bar'; + var countdownText = document.createElement('span'); + countdownText.className = 'redirect-countdown'; + var cancelButton = document.createElement('button'); + cancelButton.type = 'button'; + cancelButton.className = 'jf-button jf-button-secondary'; + cancelButton.textContent = 'Cancel'; + bar.appendChild(countdownText); + bar.appendChild(cancelButton); + status.insertAdjacentElement('afterend', bar); + + var remaining = 5; + function renderCountdown() { + countdownText.textContent = 'Redirecting in ' + remaining + '…'; + } + renderCountdown(); + var countdown = setInterval(function () { + remaining -= 1; + if (remaining <= 0) { + clearInterval(countdown); + window.location.reload(); + return; + } + renderCountdown(); + }, 1000); + + // Cancel stops both the redirect and the refreshing, and offers a manual continue. + cancelButton.addEventListener('click', function () { + clearInterval(countdown); + bar.innerHTML = ''; + var continueButton = document.createElement('button'); + continueButton.type = 'button'; + continueButton.className = 'jf-button jf-button-primary'; + continueButton.textContent = 'Continue to Jellyfin'; + continueButton.addEventListener('click', function () { + window.location.reload(); + }); + bar.appendChild(continueButton); + }); + } + + var pollTimer = setInterval(poll, intervalMs); })(); </script> </body> |
