mirror of
https://github.com/grafana/grafana.git
synced 2025-07-29 05:22:32 +08:00

* add dev mechanism for making backend unavailable * handle unavailable backend in html * fix ordering of /-/ routes * Add new loader to index.html * tweak light colours * fix readme * add error handling and error state * use setTimeout for the retry loop * easier on the comments:
295 lines
8.7 KiB
HTML
295 lines
8.7 KiB
HTML
<!DOCTYPE html>
|
|
<html class="fs-loading">
|
|
<head>
|
|
[[ if and .CSPEnabled .IsDevelopmentEnv ]]
|
|
<!-- Cypress overwrites CSP headers in HTTP requests, so this is required for e2e tests-->
|
|
<meta http-equiv="Content-Security-Policy" content="[[.CSPContent]]"/>
|
|
[[ end ]]
|
|
<meta charset="utf-8" />
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
|
<meta name="viewport" content="width=device-width" />
|
|
<meta name="theme-color" content="#000" />
|
|
|
|
<title>[[.AppTitle]]</title>
|
|
|
|
<base href="[[.AppSubUrl]]/" />
|
|
|
|
<link rel="mask-icon" href="[[.Assets.ContentDeliveryURL]]public/img/grafana_mask_icon.svg" color="#F05A28" />
|
|
|
|
[[range $asset := .Assets.CSSFiles]]
|
|
<link rel="stylesheet" href="[[$asset.FilePath]]" />
|
|
[[end]]
|
|
|
|
<script nonce="[[.Nonce]]">
|
|
performance.mark('frontend_boot_css_time_seconds');
|
|
</script>
|
|
|
|
|
|
</head>
|
|
|
|
<body>
|
|
<div class="preloader">
|
|
<style>
|
|
/**
|
|
* This style tag is purposefully inside the fs-loader div so
|
|
* when AppWrapper mounts and removes the div
|
|
* the styles are taken away with it as well.
|
|
*/
|
|
|
|
/* Light theme */
|
|
:root {
|
|
--fs-loader-bg: #f4f5f5;
|
|
--fs-loader-text-color: rgb(36, 41, 46);
|
|
--fs-spinner-arc-color: #F55F3E;
|
|
--fs-spinner-track-color: rgba(36, 41, 46, 0.12);
|
|
--fs-color-error: #e0226e;
|
|
}
|
|
|
|
/* Dark theme */
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--fs-loader-bg: #111217;
|
|
--fs-loader-text-color: rgb(204, 204, 220);
|
|
--fs-spinner-arc-color: #F55F3E;
|
|
--fs-spinner-track-color: rgba(204, 204, 220, 0.12);
|
|
--fs-color-error: #d10e5c;
|
|
}
|
|
}
|
|
|
|
@keyframes spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
body {
|
|
background-color: var(--fs-loader-bg);
|
|
color: var(--fs-loader-text-color);
|
|
margin: 0;
|
|
}
|
|
|
|
.preloader {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
height: 100dvh;
|
|
justify-content: center;
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
|
|
"Segoe UI Symbol";
|
|
}
|
|
|
|
.fs-variant-loader, .fs-variant-error {
|
|
display: contents;
|
|
}
|
|
|
|
.fs-hidden {
|
|
display: none;
|
|
}
|
|
|
|
.fs-spinner {
|
|
animation: spin 1500ms linear infinite;
|
|
width: 32px;
|
|
height: 32px;
|
|
}
|
|
|
|
.fs-spinner-track {
|
|
stroke: rgba(255,255,255,.15);
|
|
}
|
|
|
|
.fs-spinner-arc {
|
|
stroke: #F55F3E;
|
|
}
|
|
|
|
.fs-loader-text {
|
|
opacity: 0;
|
|
font-size: 16px;
|
|
margin-bottom: 0;
|
|
transition: opacity 300ms ease-in-out;
|
|
}
|
|
|
|
.fs-loader-starting-up .fs-loader-text {
|
|
opacity: 1;
|
|
}
|
|
|
|
.fs-variant-error .fs-loader-text {
|
|
opacity: 1;
|
|
}
|
|
|
|
.fs-error-icon {
|
|
fill: var(--fs-color-error);
|
|
}
|
|
</style>
|
|
|
|
<div class="fs-variant-loader">
|
|
<svg
|
|
width="32"
|
|
height="32"
|
|
class="fs-spinner"
|
|
viewBox="0 0 100 100"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
>
|
|
<circle class="fs-spinner-track" cx="50" cy="50" r="45" fill="none" stroke-width="10" />
|
|
<circle class="fs-spinner-arc" cx="50" cy="50" r="45" fill="none" stroke-width="10" stroke-linecap="round" stroke-dasharray="70.7 212.3" stroke-dashoffset="0" />
|
|
</svg>
|
|
|
|
<p class="fs-loader-text">Grafana is starting up...</p>
|
|
</div>
|
|
|
|
<div class="fs-variant-error fs-hidden">
|
|
<svg
|
|
width="32"
|
|
height="32"
|
|
class="fs-error-icon"
|
|
viewBox="0 0 24 24"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
>
|
|
<path d="M12,14a1.25,1.25,0,1,0,1.25,1.25A1.25,1.25,0,0,0,12,14Zm0-1.5a1,1,0,0,0,1-1v-3a1,1,0,0,0-2,0v3A1,1,0,0,0,12,12.5ZM12,2A10,10,0,1,0,22,12,10.01114,10.01114,0,0,0,12,2Zm0,18a8,8,0,1,1,8-8A8.00917,8.00917,0,0,1,12,20Z"/>
|
|
</svg>
|
|
|
|
<p class="fs-loader-text">Error loading Grafana</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="reactRoot"></div>
|
|
|
|
<script nonce="[[.Nonce]]">
|
|
[[if .Nonce]]
|
|
window.nonce = '[[.Nonce]]';
|
|
[[end]]
|
|
|
|
[[if .Assets.ContentDeliveryURL]]
|
|
window.public_cdn_path = '[[.Assets.ContentDeliveryURL]]public/build/';
|
|
[[end]]
|
|
</script>
|
|
|
|
<script nonce="[[.Nonce]]">
|
|
// Wrap in an IIFE to avoid polluting the global scope. Intentionally global-scope properties
|
|
// are explicitly assigned to the `window` object.
|
|
(() => {
|
|
window.__grafana_load_failed = function(...args) {
|
|
console.error('Failed to load Grafana', ...args);
|
|
document.querySelector('.fs-variant-loader').classList.add('fs-hidden');
|
|
document.querySelector('.fs-variant-error').classList.remove('fs-hidden');
|
|
};
|
|
|
|
window.onload = function() {
|
|
if (window.__grafana_app_bundle_loaded) {
|
|
return;
|
|
}
|
|
window.__grafana_load_failed();
|
|
};
|
|
|
|
let hasSetLoading = false;
|
|
function setLoading() {
|
|
if (hasSetLoading) {
|
|
return;
|
|
}
|
|
|
|
document.querySelector('.preloader').classList.add('fs-loader-starting-up');
|
|
hasSetLoading = true;
|
|
}
|
|
|
|
const CHECK_INTERVAL = 1 * 1000;
|
|
|
|
/**
|
|
* Fetches boot data from the server. If it returns undefined, it should be retried later.
|
|
* Will return a rejected promise on unrecoverable errors.
|
|
**/
|
|
async function fetchBootData() {
|
|
const resp = await fetch("/bootdata");
|
|
const textResponse = await resp.text();
|
|
|
|
let rawBootData;
|
|
try {
|
|
rawBootData = JSON.parse(textResponse);
|
|
} catch {
|
|
throw new Error("Unexpected response type: " + textResponse);
|
|
}
|
|
|
|
// If the response is 503, instruct the caller to retry again later.
|
|
if (resp.status === 503 && rawBootData.code === 'Loading') {
|
|
return;
|
|
}
|
|
|
|
if (!resp.ok) {
|
|
throw new Error("Unexpected response body: " + textResponse);
|
|
}
|
|
|
|
return rawBootData;
|
|
}
|
|
|
|
/**
|
|
* Loads the boot data from the server, retrying if it's unavailable.
|
|
**/
|
|
function loadBootData() {
|
|
return new Promise((resolve, reject) => {
|
|
const attemptFetch = async () => {
|
|
try {
|
|
const bootData = await fetchBootData();
|
|
|
|
// If the boot data is undefined, retry after a delay
|
|
if (!bootData) {
|
|
setLoading();
|
|
setTimeout(attemptFetch, CHECK_INTERVAL);
|
|
return;
|
|
}
|
|
|
|
resolve(bootData);
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
};
|
|
|
|
// Start the first attempt immediately
|
|
attemptFetch();
|
|
});
|
|
}
|
|
|
|
async function initGrafana() {
|
|
const rawBootData = await loadBootData();
|
|
|
|
window.grafanaBootData = {
|
|
_femt: true,
|
|
...rawBootData,
|
|
}
|
|
|
|
// The per-theme CSS still contains some global styles needed
|
|
// to render the page correctly.
|
|
const cssLink = document.createElement("link");
|
|
cssLink.rel = 'stylesheet';
|
|
|
|
let theme = window.grafanaBootData.user.theme;
|
|
if (theme === "system") {
|
|
const darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
theme = darkQuery.matches ? 'dark' : 'light';
|
|
}
|
|
|
|
if (theme === "light") {
|
|
document.body.classList.add("theme-light");
|
|
cssLink.href = window.grafanaBootData.assets.light;
|
|
window.grafanaBootData.user.lightTheme = true;
|
|
} else if (theme === "dark") {
|
|
document.body.classList.add("theme-dark");
|
|
cssLink.href = window.grafanaBootData.assets.dark;
|
|
window.grafanaBootData.user.lightTheme = false;
|
|
}
|
|
|
|
document.head.appendChild(cssLink);
|
|
}
|
|
|
|
|
|
window.__grafana_boot_data_promise = initGrafana()
|
|
window.__grafana_boot_data_promise.catch((err) => {
|
|
console.error("__grafana_boot_data_promise rejected", err);
|
|
window.__grafana_load_failed(err);
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
[[range $asset := .Assets.JSFiles]]
|
|
<script nonce="[[$.Nonce]]" src="[[$asset.FilePath]]" type="text/javascript" defer></script>
|
|
[[end]]
|
|
</body>
|
|
</html>
|