ref(frontend): Replace Bootstrap with Tailwind CSS
Swap the Bootstrap CDN for a self-hosted Tailwind CSS build, mirroring the myice setup: standalone Tailwind CLI v3.4.17, a one-off build-css.sh script, and a gitignored generated style.css. - Add tailwind.config.js, style-input.css, build-css.sh at repo root - Rewrite templates/index.html: drop Bootstrap link/script, link /style.css, convert all classes to Tailwind utilities; showAlert now uses a class lookup so the JIT scanner detects dynamic classes - Add /style.css FileResponse route in main.py; drop cdn.jsdelivr.net from script-src/style-src in the CSP - Flip test assertion to assert cdn.jsdelivr.net is absent from CSP The generated style.css and tailwindcss binary are gitignored; run ./build-css.sh to regenerate (or --watch for dev).
This commit is contained in:
+35
-30
@@ -4,54 +4,53 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OIDC Login</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h1 class="text-center h3">OIDC Login</h1>
|
||||
<div class="container mx-auto mt-5 px-4">
|
||||
<div class="flex justify-center">
|
||||
<div class="w-full md:w-1/2">
|
||||
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
||||
<div class="px-4 py-3 border-b border-gray-200 bg-gray-50">
|
||||
<h1 class="text-center text-2xl font-bold">OIDC Login</h1>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="p-4">
|
||||
<!-- Login Section -->
|
||||
<section id="login-section" class="text-center">
|
||||
<p>Login with your Infomaniak account</p>
|
||||
<button id="login-btn" class="btn btn-primary w-100" aria-label="Login with Infomaniak">Login with Infomaniak</button>
|
||||
<button id="login-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded transition" aria-label="Login with Infomaniak">Login with Infomaniak</button>
|
||||
</section>
|
||||
|
||||
<!-- User Info Section -->
|
||||
<section id="user-info" class="d-none">
|
||||
<section id="user-info" class="hidden">
|
||||
<div class="mb-4">
|
||||
<h2 class="h5">Welcome, <span id="user-name">User</span>!</h2>
|
||||
<h2 class="text-xl font-bold">Welcome, <span id="user-name">User</span>!</h2>
|
||||
<p class="mb-0">Email: <span id="user-email"></span></p>
|
||||
</div>
|
||||
|
||||
<!-- Secret Phrase Card -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h3 class="h5 mb-0">Secret Phrase</h3>
|
||||
<button id="refresh-secret-btn" class="btn btn-secondary btn-sm" aria-label="Refresh secret phrase">Refresh</button>
|
||||
<div class="bg-white rounded-lg shadow-md mb-4 overflow-hidden">
|
||||
<div class="px-4 py-3 border-b border-gray-200 bg-gray-50 flex justify-between items-center">
|
||||
<h3 class="text-xl font-bold mb-0">Secret Phrase</h3>
|
||||
<button id="refresh-secret-btn" class="bg-gray-500 hover:bg-gray-600 text-white text-sm py-1 px-2 rounded transition" aria-label="Refresh secret phrase">Refresh</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="p-4">
|
||||
<div id="secret-result" class="mb-0">Checking for secret phrase...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bitcoin Price Card -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h3 class="h5 mb-0">Bitcoin Price in CHF</h3>
|
||||
<div class="bg-white rounded-lg shadow-md mb-4 overflow-hidden">
|
||||
<div class="px-4 py-3 border-b border-gray-200 bg-gray-50">
|
||||
<h3 class="text-xl font-bold mb-0">Bitcoin Price in CHF</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<button id="fetch-btc-btn" class="btn btn-success mb-3" aria-label="Fetch Bitcoin Price">Fetch Bitcoin Price</button>
|
||||
<div class="p-4">
|
||||
<button id="fetch-btc-btn" class="bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded mb-3 transition" aria-label="Fetch Bitcoin Price">Fetch Bitcoin Price</button>
|
||||
<div id="btc-price-result" class="mb-0"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="logout-btn" class="btn btn-danger w-100" aria-label="Logout">Logout</button>
|
||||
<button id="logout-btn" class="w-full bg-red-600 hover:bg-red-700 text-white font-medium py-2 px-4 rounded transition" aria-label="Logout">Logout</button>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -157,12 +156,18 @@
|
||||
* Show an alert message in the specified element
|
||||
* @param {HTMLElement} element - Element to show the message in
|
||||
* @param {string} message - Message to display
|
||||
* @param {string} type - Bootstrap alert type (success, danger, warning, info)
|
||||
* @param {string} type - Alert type (success, danger, warning, info)
|
||||
*/
|
||||
showAlert(element, message, type = 'info') {
|
||||
const alertClasses = {
|
||||
success: 'bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded',
|
||||
danger: 'bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded',
|
||||
warning: 'bg-yellow-100 border border-yellow-400 text-yellow-700 px-4 py-3 rounded',
|
||||
info: 'bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded'
|
||||
};
|
||||
element.replaceChildren();
|
||||
const alert = document.createElement('div');
|
||||
alert.className = `alert alert-${type}`;
|
||||
alert.className = alertClasses[type] || alertClasses.info;
|
||||
alert.setAttribute('role', 'alert');
|
||||
if (typeof message === 'string') {
|
||||
alert.textContent = message;
|
||||
@@ -427,8 +432,8 @@
|
||||
* @param {Object} user - User object with email, first_name, last_name
|
||||
*/
|
||||
showUserInfo(user) {
|
||||
elements.loginSection.classList.add('d-none');
|
||||
elements.userInfo.classList.remove('d-none');
|
||||
elements.loginSection.classList.add('hidden');
|
||||
elements.userInfo.classList.remove('hidden');
|
||||
|
||||
// Display user's full name
|
||||
elements.userName.textContent = utils.getUserDisplayName(user);
|
||||
@@ -441,8 +446,8 @@
|
||||
* Hide user information section
|
||||
*/
|
||||
hideUserInfo() {
|
||||
elements.loginSection.classList.remove('d-none');
|
||||
elements.userInfo.classList.add('d-none');
|
||||
elements.loginSection.classList.remove('hidden');
|
||||
elements.userInfo.classList.add('hidden');
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -699,4 +704,4 @@
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user