Files
jellyfin/Jellyfin.Server/ServerSetupApp/index.mstemplate.html
Joshua M. Boniface 58e9e3423a Remove server version from page title
This leaks additional information publicly, and is not really
necessary/useful.
2026-06-22 02:32:59 -04:00

458 lines
19 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="dark" />
<title>
{{#IF isInReportingMode}}
{{/IF}}
Jellyfin Startup
</title>
<style>
:root {
--jf-bg: #101010;
--jf-bg-accent: #181818;
--jf-surface: #202020;
--jf-border: rgba(255, 255, 255, 0.09);
--jf-text: #ffffff;
--jf-text-muted: rgba(255, 255, 255, 0.7);
--jf-text-dim: rgba(255, 255, 255, 0.45);
--jf-accent-start: #aa5cc3;
--jf-accent-end: #00a4dc;
--jf-accent: #00a4dc;
}
* {
box-sizing: border-box;
font-family: "Noto Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
html,
body {
margin: 0;
padding: 0;
min-height: 100%;
}
body {
background-color: var(--jf-bg);
background-image: radial-gradient(circle at 50% -10%, var(--jf-bg-accent), var(--jf-bg) 60%);
color: var(--jf-text);
/* Bottom padding doubles as the minimum gap the log viewport keeps from the window bottom. */
padding: 2.5rem 1rem 50px;
}
.flex-row {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
align-content: normal;
}
.flex-col {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
align-content: normal;
}
.container {
max-width: 52rem;
margin: 0 auto;
}
header {
width: 100%;
margin-bottom: 2rem;
}
header .logo {
height: 3.25rem;
width: auto;
margin-bottom: 1.75rem;
}
.status-card {
width: 100%;
background-color: var(--jf-surface);
border: 1px solid var(--jf-border);
border-radius: 0.6rem;
padding: 1.25rem 1.5rem;
display: flex;
flex-direction: row;
align-items: center;
gap: 1rem;
}
.status-card .status-text {
margin: 0;
font-size: 1.05rem;
font-weight: 500;
line-height: 1.5;
}
.status-card.is-error {
border-color: rgba(229, 72, 77, 0.5);
background-color: rgba(229, 72, 77, 0.08);
}
.spinner {
flex: 0 0 auto;
width: 1.4rem;
height: 1.4rem;
border-radius: 50%;
border: 3px solid rgba(255, 255, 255, 0.14);
border-top-color: var(--jf-accent);
animation: spin 0.9s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.error-mark {
flex: 0 0 auto;
font-size: 1.4rem;
line-height: 1;
}
.logs-panel {
width: 100%;
margin-top: 2rem;
background-color: var(--jf-surface);
border: 1px solid var(--jf-border);
border-radius: 0.6rem;
padding: 1rem 1.5rem 1.25rem;
}
.logs-panel .logs-header {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
margin: 0.25rem 0 0.75rem;
}
.logs-panel h2 {
font-size: 0.8rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--jf-text-dim);
margin: 0;
}
.logs-panel .download-logs {
flex: 0 0 auto;
color: var(--jf-accent);
text-decoration: none;
font-size: 0.8rem;
font-weight: 600;
}
.logs-panel .download-logs:hover {
text-decoration: underline;
}
/* Bounded, scrollable log viewport that scales with the window height. */
.logs-panel .logs-scroll {
max-height: 60vh;
overflow-y: auto;
overscroll-behavior: contain;
padding-right: 0.25rem;
scrollbar-width: thin;
scrollbar-color: var(--jf-border) transparent;
}
.logs-panel .logs-scroll::-webkit-scrollbar {
width: 8px;
}
.logs-panel .logs-scroll::-webkit-scrollbar-thumb {
background-color: var(--jf-border);
border-radius: 4px;
}
ol.action-list * {
font-family: ui-monospace, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", Menlo, Consolas, monospace;
font-weight: 400;
font-size: clamp(13px, 100vw / var(--width), 15px);
font-feature-settings: 'onum', 'pnum';
line-height: 1.9;
-webkit-text-size-adjust: none;
}
/* Attribution as heavily inspired by: https://iamkate.com/code/tree-views/ */
.action-list {
--spacing: 1.4rem;
--radius: 12px;
margin: 0;
padding-left: 0.5rem;
width: 100%;
}
.action-list li {
display: block;
position: relative;
padding-left: calc(2 * var(--spacing) - var(--radius) - 1px);
}
.action-list ul {
margin-left: calc(var(--radius) - var(--spacing));
padding-left: 0;
}
.action-list ul li {
border-left: 2px solid var(--jf-border);
}
.action-list ul li:last-child {
border-color: transparent;
}
.action-list ul li::before {
content: '';
display: block;
position: absolute;
top: calc(var(--spacing) / -2);
left: -2px;
width: calc(var(--spacing) + 2px);
height: calc(var(--spacing) + 1px);
border: solid var(--jf-border);
border-width: 0 0 2px 2px;
}
.action-list summary {
display: block;
cursor: pointer;
color: var(--jf-text);
}
.action-list summary::marker,
.action-list summary::-webkit-details-marker {
display: none;
}
.action-list summary:focus {
outline: none;
}
.action-list summary:focus-visible {
outline: 1px dotted var(--jf-accent);
}
/* Status icon, placed in the left gutter and aligned to the first line of the entry. */
.action-list li::after {
display: block;
position: absolute;
top: 0;
left: 0;
width: calc(2 * var(--spacing) - var(--radius) - 1px - 0.5rem);
text-align: right;
line-height: 1.9;
font-size: 0.95em;
}
.action-list li.danger-item::after,
.action-list li.danger-strong-item::after {
content: '❌';
}
ol.action-list li span.danger-strong-item {
text-decoration-style: solid;
text-decoration-color: #e5484d;
text-decoration-line: underline;
}
ol.action-list li.warn-item::after {
content: '⚠️';
}
ol.action-list li.success-item::after {
content: '✅';
}
ol.action-list li.info-item::after {
content: '🔹';
}
/* End Attribution */
</style>
</head>
<body>
<div class="container">
<header class="flex-col">
<svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 251 72" fill="none" role="img" aria-label="Jellyfin">
<g clip-path="url(#a)">
<path fill="url(#b)" d="M24.212 49.158C22.66 46.042 32.838 27.588 36 27.588c3.167.002 13.323 18.488 11.788 21.57-1.534 3.082-22.025 3.116-23.576 0" />
<path fill="url(#c)" fill-rule="evenodd" d="M.482 64.995C-4.195 55.605 26.477 0 36 0c9.533 0 40.153 55.713 35.527 64.995s-66.368 9.39-71.045 0m12.254-8.148c3.064 6.152 43.518 6.084 46.548 0 3.03-6.086-17.032-42.586-23.275-42.586S9.671 50.694 12.736 56.847" clip-rule="evenodd" />
<path fill="#fff" d="M225.22 56c-.28 0-.42 0-.527-.055a.5.5 0 0 1-.219-.218c-.054-.107-.054-.247-.054-.527V26.8c0-.28 0-.42.054-.527a.5.5 0 0 1 .219-.219c.107-.054.247-.054.527-.054h5.183c.28 0 .42 0 .527.054a.5.5 0 0 1 .218.219c.055.107.055.247.055.527v2.895a7.9 7.9 0 0 1 3.419-3.254q2.261-1.103 5.074-1.103 3.308 0 5.845 1.434a10.1 10.1 0 0 1 4.026 4.026q1.434 2.536 1.434 5.9V55.2c0 .28 0 .42-.055.527a.5.5 0 0 1-.218.218c-.107.055-.247.055-.527.055h-5.625c-.28 0-.42 0-.527-.055a.5.5 0 0 1-.218-.218c-.055-.107-.055-.247-.055-.527V38.408q0-2.978-1.709-4.688-1.654-1.764-4.357-1.764-2.702 0-4.412 1.764-1.654 1.766-1.654 4.688V55.2c0 .28 0 .42-.054.527a.5.5 0 0 1-.219.218c-.107.055-.247.055-.527.055zm-11.54-33.363c-.28 0-.42 0-.527-.055a.5.5 0 0 1-.218-.218c-.055-.107-.055-.247-.055-.527v-6.121c0-.28 0-.42.055-.527a.5.5 0 0 1 .218-.219c.107-.054.247-.054.527-.054h5.624c.28 0 .42 0 .527.054a.5.5 0 0 1 .219.219c.054.107.054.247.054.527v6.12c0 .28 0 .42-.054.528a.5.5 0 0 1-.219.218c-.107.055-.247.055-.527.055zm0 33.363c-.28 0-.42 0-.527-.054a.5.5 0 0 1-.218-.219c-.055-.107-.055-.247-.055-.527V26.8c0-.28 0-.42.055-.527a.5.5 0 0 1 .218-.218c.107-.055.247-.055.527-.055h5.624c.28 0 .42 0 .527.055a.5.5 0 0 1 .219.218c.054.107.054.247.054.527v28.4c0 .28 0 .42-.054.527a.5.5 0 0 1-.219.219c-.107.054-.247.054-.527.054zm-16.712-.054c.107.054.247.054.527.054h5.625c.28 0 .42 0 .526-.054a.5.5 0 0 0 .219-.219c.055-.107.055-.247.055-.527V32.452h5.872c.28 0 .42 0 .527-.054a.5.5 0 0 0 .219-.219c.054-.107.054-.247.054-.527V26.8c0-.28 0-.42-.054-.527a.5.5 0 0 0-.219-.218c-.107-.055-.247-.055-.527-.055h-5.872v-.992q0-2.261 1.323-3.31 1.379-1.102 3.75-1.102.454 0 .939.044c.345.031.518.047.634-.004a.48.48 0 0 0 .241-.22c.061-.111.061-.274.061-.6V15.39c0-.304 0-.457-.061-.589a.7.7 0 0 0-.248-.284c-.122-.078-.261-.097-.537-.136a14.5 14.5 0 0 0-1.966-.126q-5.184 0-8.273 2.812t-3.088 7.942V26H186.53c-.3 0-.451 0-.58.05a.75.75 0 0 0-.296.205c-.091.104-.143.244-.248.526l-7.43 19.9-7.483-19.903c-.105-.28-.158-.42-.249-.524a.75.75 0 0 0-.296-.205c-.129-.049-.279-.049-.578-.049h-5.769c-.394 0-.591 0-.717.083a.5.5 0 0 0-.213.314c-.031.147.041.33.186.697L174.281 56l-.661 1.6q-.883 1.874-2.041 3.033-1.103 1.158-3.584 1.158-.883 0-1.875-.166a13 13 0 0 1-.73-.1c-.389-.066-.584-.099-.709-.053a.47.47 0 0 0-.26.22c-.066.116-.066.298-.066.663v4.329c0 .243 0 .365.045.481a.7.7 0 0 0 .189.266c.095.081.194.116.392.185q.684.24 1.47.351 1.158.22 2.371.22 4.246 0 7.059-2.426 2.867-2.37 4.577-6.728l10.517-26.58h5.72V55.2c0 .28 0 .42.055.527a.5.5 0 0 0 .218.219M154.363 56c-.28 0-.42 0-.527-.054a.5.5 0 0 1-.219-.219c-.054-.107-.054-.247-.054-.527V15.054c0-.28 0-.42.054-.527a.5.5 0 0 1 .219-.219c.107-.054.247-.054.527-.054h5.624c.28 0 .42 0 .527.054a.5.5 0 0 1 .218.219c.055.107.055.247.055.527V55.2c0 .28 0 .42-.055.527a.5.5 0 0 1-.218.219c-.107.054-.247.054-.527.054zm-11.621 0c-.28 0-.42 0-.527-.054a.5.5 0 0 1-.219-.219c-.054-.107-.054-.247-.054-.527V15.054c0-.28 0-.42.054-.527a.5.5 0 0 1 .219-.219c.107-.054.247-.054.527-.054h5.624c.28 0 .42 0 .527.054a.5.5 0 0 1 .219.219c.054.107.054.247.054.527V55.2c0 .28 0 .42-.054.527a.5.5 0 0 1-.219.219c-.107.054-.247.054-.527.054zm-18.132.662q-4.632-.001-8.107-2.096a14.6 14.6 0 0 1-5.404-5.68q-1.93-3.585-1.93-7.942 0-4.522 1.93-7.996 1.985-3.53 5.349-5.57 3.42-2.04 7.61-2.04 4.688 0 7.942 2.04 3.253 1.986 4.963 5.294 1.71 3.309 1.709 7.335 0 .828-.11 1.654-.031.45-.12.841c-.037.165-.055.247-.115.33a.55.55 0 0 1-.208.168c-.095.04-.194.04-.393.04h-21.057q.33 3.309 2.537 5.294 2.205 1.986 5.459 1.985 2.482 0 4.191-1.047a8.2 8.2 0 0 0 2.206-1.986c.241-.316.362-.474.484-.542a.6.6 0 0 1 .352-.083c.139.006.296.083.608.236l4.269 2.094c.239.118.359.176.431.275a.52.52 0 0 1 .098.298c0 .122-.058.231-.172.45q-1.432 2.742-4.526 4.607-3.419 2.04-7.996 2.04m-.552-25.368q-2.702 0-4.687 1.654-1.93 1.6-2.537 4.577h14.118q-.22-2.757-2.151-4.466-1.875-1.765-4.743-1.765M90.801 56c-.28 0-.42 0-.527-.054a.5.5 0 0 1-.218-.218C90 55.62 90 55.48 90 55.2v-5.294c0-.28 0-.42.055-.527a.5.5 0 0 1 .218-.218c.107-.055.247-.055.527-.055h1.572q2.646 0 4.19-1.489 1.6-1.545 1.6-4.08V15.715c0-.28 0-.42.055-.527a.5.5 0 0 1 .218-.219c.107-.054.247-.054.527-.054h5.956c.28 0 .42 0 .527.054a.5.5 0 0 1 .218.219c.055.107.055.247.055.527v27.546q0 3.804-1.655 6.672-1.599 2.868-4.632 4.467-2.979 1.6-7.06 1.6z" />
</g>
<defs>
<linearGradient id="b" x1="12" x2="71.999" y1="30.001" y2="63.002" gradientUnits="userSpaceOnUse">
<stop stop-color="#aa5cc3" />
<stop offset="1" stop-color="#00a4dc" />
</linearGradient>
<linearGradient id="c" x1="12" x2="71.999" y1="29.999" y2="63.001" gradientUnits="userSpaceOnUse">
<stop stop-color="#aa5cc3" />
<stop offset="1" stop-color="#00a4dc" />
</linearGradient>
<clipPath id="a">
<path fill="#fff" d="M0 0h251v72H0z" />
</clipPath>
</defs>
</svg>
{{^IF isInReportingMode}}
<div class="status-card">
<div class="spinner" aria-hidden="true"></div>
<p class="status-text">Jellyfin is still starting. Please wait&hellip; {{currentActivity}}</p>
</div>
{{#ELSE}}
<div class="status-card is-error">
<span class="error-mark" aria-hidden="true"></span>
<p class="status-text">Jellyfin has encountered an error and was not able to start.</p>
</div>
{{/ELSE}}
{{/IF}}
</header>
{{#DECLARE LogEntry |--}}
{{#LET children = Children}}
<li class="{{FormatLogLevel(children).ToString()}}-item">
{{--| #IF children.Count > 0}}
<details open>
<summary>{{DateOfCreation}} - {{Content}}</summary>
<ul class="action-list">
{{--| #EACH children |-}}
{{#IMPORT 'LogEntry'}}
{{--| /EACH |-}}
</ul>
</details>
{{--| #ELSE |-}}
<span class="{{FormatLogLevel(children).ToString()}}-item">{{DateOfCreation}} - {{Content}}</span>
{{--| /ELSE |--}}
{{--| /IF |-}}
</li>
{{--| /DECLARE}}
{{#IF localNetworkRequest}}
<div class="logs-panel">
<div class="logs-header">
<h2>Startup log</h2>
<a class="download-logs" href='/startup/logger' target="_blank">Download full logs</a>
</div>
<div class="logs-scroll" id="logs-scroll">
<ol class="action-list">
{{#FOREACH log IN logs}}
{{#IMPORT 'LogEntry' #WITH log}}
{{/FOREACH}}
</ol>
</div>
</div>
{{/IF}}
</div>
<script>
(function () {
var reporting = false;
{{#IF isInReportingMode}}
reporting = true;
{{/IF}}
var intervalMs = {{ retryValue.TotalMilliseconds }};
var box = document.getElementById('logs-scroll');
var panel = box ? box.closest('.logs-panel') : null;
function px(el, prop) {
return el ? (parseFloat(getComputedStyle(el)[prop]) || 0) : 0;
}
// Size the log viewport so the panel never runs past the bottom of the window
// (keeping the body's bottom padding as the margin). Recomputed on resize.
function fit() {
if (!box) {
return;
}
var topFromDoc = box.getBoundingClientRect().top + window.scrollY;
var below = px(panel, 'paddingBottom') + px(document.body, 'paddingBottom');
box.style.maxHeight = Math.max(window.innerHeight - topFromDoc - below, 120) + 'px';
}
function nearBottom(el) {
return el.scrollHeight - el.scrollTop - el.clientHeight < 24;
}
fit();
window.addEventListener('resize', fit);
window.addEventListener('load', fit);
if (box) {
box.scrollTop = box.scrollHeight; // start pinned to the newest entry
}
// In the terminal error state the page is static, so stop here.
if (reporting) {
return;
}
// Soft refresh: pull the page in the background and swap the log list + activity line in
// place, so polling never disturbs where the user has scrolled. Only follow to the bottom
// when the user is already there. A real reload happens only on the final transition.
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();
return null;
}
return resp.text();
}).then(function (html) {
if (!html) {
return;
}
var doc = new DOMParser().parseFromString(html, 'text/html');
// Startup failed and the page switched to the error view -> reload to render it.
if (doc.querySelector('.status-card.is-error')) {
window.location.reload();
return;
}
var newStatus = doc.querySelector('.status-text');
var curStatus = document.querySelector('.status-text');
if (newStatus && curStatus) {
curStatus.innerHTML = newStatus.innerHTML;
}
if (box) {
var newList = doc.querySelector('#logs-scroll .action-list');
var curList = box.querySelector('.action-list');
if (newList && curList) {
var stick = nearBottom(box);
var prevTop = box.scrollTop;
curList.replaceWith(document.importNode(newList, true));
fit();
box.scrollTop = stick ? box.scrollHeight : prevTop;
}
}
}).catch(function () {
// Server is mid-transition (port rebinding); just try again on the next tick.
});
}
setInterval(poll, intervalMs);
})();
</script>
</body>
</html>