Files
aliasvault/apps/server/AliasVault.Client/wwwroot/index.template.html
2026-01-26 14:48:07 +00:00

326 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<!--
==========================================================================================================
db 88 88 8b d8 88
d88b 88 "" `8b d8' 88 ,d
d8'`8b 88 `8b d8' 88 88
d8' `8b 88 88 ,adPPYYba, ,adPPYba, `8b d8' ,adPPYYba, 88 88 88 MM88MMM
d8YaaaaY8b 88 88 "" `Y8 I8[ "" `8b d8' "" `Y8 88 88 88 88
d8""""""""8b 88 88 ,adPPPPP88 `"Y8ba, `8b d8' ,adPPPPP88 88 88 88 88
d8' `8b 88 88 88, ,88 aa ]8I `888' 88, ,88 "8a, ,a88 88 88,
d8' `8b 88 88 `"8bbdP"Y8 `"YbbdP"' `8' `"8bbdP"Y8 `"YbbdP'Y8 88 "Y888
==========================================================================================================
AliasVault - Privacy-first password manager with built-in email aliasing. Fully encrypted and self-hostable.
Build (UTC): @BuildVersion
Source code: https://github.com/aliasvault/aliasvault
License: AGPLv3
-->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"/>
<title>AliasVault</title>
<base href="/" />
<link rel="stylesheet" href="css/app.css?v=@CacheBuster" />
<link rel="icon" type="image/png" href="favicon.png" />
<link href="css/tailwind.css?v=@CacheBuster" rel="stylesheet">
<link rel="manifest" href="manifest.json" />
<link rel="apple-touch-icon" sizes="500x500" href="img/icon-500.png" />
<link rel="apple-touch-icon" sizes="192x192" href="img/icon-192.png" />
<link rel="preload" href="/wasm/aliasvault_core_bg.wasm?v=@CacheBuster" as="fetch" type="application/wasm" crossorigin />
<link rel="modulepreload" href="/wasm/aliasvault_core.js?v=@CacheBuster" />
</head>
<body class="bg-gray-100 dark:bg-gray-900" av-disable="true">
<div id="loading-screen">
<div class="fixed inset-0 flex items-center justify-center px-6 pt-8 pb-8">
<div class="w-full max-w-md space-y-4">
<div class="p-6 sm:p-8 bg-white rounded-lg shadow dark:bg-gray-800">
<div class="text-center">
<div class="inner">
<div class="index-aliasvault-inline-spinner mx-auto">
<div class="index-cloud-shape-inline">
<div class="index-dot-inline index-delay-1"></div>
<div class="index-dot-inline index-delay-2"></div>
<div class="index-dot-inline index-delay-3"></div>
<div class="index-dot-inline index-delay-4"></div>
</div>
</div>
<h2 id="loading-title" class="mt-4 text-xl font-semibold text-gray-900 dark:text-white">AliasVault is loading</h2>
<p id="loading-message" class="mt-2 text-sm text-gray-500 dark:text-gray-400">
Initializing secure environment. AliasVault prioritizes your privacy by running entirely in your browser. The first load might take a short while.
</p>
<div class="loading-progress-text text-sm font-medium text-gray-700 dark:text-gray-300 mt-4"></div>
<div class="mt-4 text-center">
<p id="security-quote" class="text-sm text-primary-600 italic"></p>
</div>
<div id="error-message" class="hidden text-red-600 dark:text-red-400 mt-4"></div>
</div>
</div>
<style>
.index-aliasvault-inline-spinner {
height: 51px;
width: 112px;
display: flex;
justify-content: center;
align-items: center;
}
.index-cloud-shape-inline {
border: 6px solid #eabf69;
border-radius: 9999px;
padding: 13px 26px;
display: flex;
gap: 10px;
align-items: center;
background-color: transparent;
box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.1);
}
.index-dot-inline {
width: 10px;
height: 10px;
border-radius: 9999px;
background-color: #eabf69;
animation: index-pulse-inverted 1.4s infinite ease-in-out;
}
.index-delay-1 { animation-delay: 0s; }
.index-delay-2 { animation-delay: 0.2s; }
.index-delay-3 { animation-delay: 0.4s; }
.index-delay-4 { animation-delay: 0.6s; }
@keyframes index-pulse-inverted {
0%, 100% { opacity: 0.3; transform: scale(1); }
50% { opacity: 1; transform: scale(1.3); }
}
</style>
</div>
<div id="refresh-button" class="text-center w-full mt-4 p-6 sm:p-8 bg-white rounded-lg shadow dark:bg-gray-800 hidden">
<p id="refresh-text" class="text-sm text-gray-600 dark:text-gray-400 mb-2">If loading seems stuck, you can click the button below to refresh the page.</p>
<a href="javascript:window.location.reload(true)" class="inline-block px-4 py-2 bg-primary-600 text-white text-sm font-medium rounded-md hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 transition duration-150 ease-in-out">
<span id="refresh-button-text">Refresh Page</span>
</a>
</div>
</div>
</div>
</div>
<div id="app">
</div>
<div id="blazor-error-ui" class="text-white bg-red-700 dark:bg-red-900 p-6 border-t-2 border-red-500 dark:border-red-800">
<div class="container mx-auto max-w-screen-xl px-4">
<div class="flex flex-col sm:flex-row items-center justify-between gap-4">
<div class="flex items-center">
<svg class="w-8 h-8 text-white mr-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
<span id="unhandled-error-text">An unhandled error has occurred. Please try reloading the page. If the issue persists, please contact support.</span>
</div>
<div class="flex items-center gap-4">
<a href="" class="reload flex items-center px-4 py-2 bg-red-600 hover:bg-red-700 rounded-md transition-colors duration-150">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
<span id="reload-page-text">Reload Page</span>
</a>
<a class="dismiss hover:text-red-200">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</a>
</div>
</div>
</div>
</div>
<script>
let translations = {};
function getCurrentLanguage() {
return localStorage.getItem('blazorCulture') ||
localStorage.getItem('AppLanguage') ||
'en';
}
async function loadTranslations() {
const lang = getCurrentLanguage();
try {
const response = await fetch(`/locales/${lang}.json`);
if (response.ok) {
translations = await response.json();
} else {
// Fallback to English if the language file is not found
const fallbackResponse = await fetch('/locales/en.json');
translations = await fallbackResponse.json();
}
} catch (error) {
console.warn('Failed to load localization files, using fallback strings:', error);
// Fallback translations
translations = {
loading: {
title: 'AliasVault is loading',
message: 'Initializing secure environment. AliasVault prioritizes your privacy by running entirely in your browser. The first load might take a short while.',
refreshText: 'If loading seems stuck, you can click the button below to refresh the page.',
refreshButtonText: 'Refresh Page'
},
errors: {
unhandledError: 'An unhandled error has occurred. Please try reloading the page. If the issue persists, please contact support.',
webAssemblyError: 'AliasVault requires WebAssembly, which this browser does not support. Try using a more modern browser that supports WebAssembly.',
reloadPageText: 'Reload Page'
},
quotes: {
security: [
"Your identity is your most valuable asset. Protect it like one.",
"In the digital world, a strong password is your first line of defense.",
"Security is not a product, but a process."
]
}
};
}
}
function getTranslation(path) {
const keys = path.split('.');
let value = translations;
for (const key of keys) {
value = value?.[key];
if (!value) break;
}
return value || '';
}
function localizeContent() {
// Localize loading screen
const localizations = [
{ id: 'loading-title', key: 'loading.title' },
{ id: 'loading-message', key: 'loading.message' },
{ id: 'refresh-text', key: 'loading.refreshText' },
{ id: 'refresh-button-text', key: 'loading.refreshButtonText' },
{ id: 'unhandled-error-text', key: 'errors.unhandledError' },
{ id: 'reload-page-text', key: 'errors.reloadPageText' }
];
localizations.forEach(({ id, key }) => {
const element = document.getElementById(id);
if (element) {
element.textContent = getTranslation(key);
}
});
// Set random security quote
const securityQuotes = getTranslation('quotes.security');
if (securityQuotes && securityQuotes.length > 0) {
const quoteElement = document.getElementById('security-quote');
const randomIndex = Math.floor(Math.random() * securityQuotes.length);
if (quoteElement) {
quoteElement.textContent = `"${securityQuotes[randomIndex]}"`;
}
}
}
async function initializeLocalization() {
await loadTranslations();
localizeContent();
}
document.addEventListener('DOMContentLoaded', initializeLocalization);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeLocalization);
} else {
initializeLocalization();
}
function manageLoadingScreen() {
const startTime = new Date().getTime();
const minDisplayTime = 1000;
const checkInterval = 500;
const refreshButtonTimeout = 300000;
const scriptsCheckTimeout = 5000; // Check for blocked scripts after 5 seconds
const appElement = document.getElementById('app');
const refreshButton = document.getElementById('refresh-button');
appElement.style.visibility = 'hidden';
loadingScreen.style.display = 'flex';
// Show refresh button after 30 seconds
setTimeout(() => {
refreshButton.classList.remove('hidden');
}, refreshButtonTimeout);
// Add click event listener to refresh button
refreshButton.addEventListener('click', () => {
window.location.reload();
});
const checkContentAndTime = () => {
const elapsedTime = new Date().getTime() - startTime;
const hasContent = appElement.innerHTML.trim() !== '';
if (elapsedTime >= minDisplayTime && hasContent) {
loadingScreen.style.display = 'none';
appElement.style.removeProperty('visibility');
clearInterval(intervalId);
} else if (elapsedTime % 1000 < checkInterval) {
if (!('WebAssembly' in window)) {
showError(getTranslation('errors.webAssemblyError'));
clearInterval(intervalId);
}
}
};
const intervalId = setInterval(checkContentAndTime, checkInterval);
}
const loadingScreen = document.getElementById('loading-screen');
const errorMessageElement = document.getElementById('error-message');
const showError = (message) => {
errorMessageElement.textContent = message;
errorMessageElement.classList.remove('hidden');
document.querySelector('.loading-progress-text').classList.add('hidden');
document.querySelector('svg.animate-spin').classList.add('hidden');
};
window.addEventListener('error', function(event) {
if (event.error && event.error.message && event.error.message.includes('WebAssembly')) {
showError(getTranslation('errors.webAssemblyError'));
}
});
window.addEventListener('unhandledrejection', function(event) {
if (event.reason && event.reason.message && event.reason.message.includes('WebAssembly')) {
showError(getTranslation('errors.webAssemblyError'));
}
});
window.addEventListener('load', manageLoadingScreen);
window.blazorCulture = {
get: function() {
return localStorage.getItem('blazorCulture') ||
localStorage.getItem('AppLanguage') ||
'en';
},
set: function(culture) {
localStorage.setItem('blazorCulture', culture);
}
};
window.__CACHE_BUSTER__ = '@CacheBuster';
if (typeof window.rustCoreIsAvailable === 'function') {
window.rustCoreIsAvailable().catch(() => {});
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js');
}
</script>
<script src="js/bundle.min.js?v=@CacheBuster"></script>
<script src="_framework/blazor.webassembly.js?v=@CacheBuster" async></script>
</body>
</html>