Initial commit: Crumbforest Architecture Refinement v1 (Clean)
This commit is contained in:
141
app/templates/pages/admin.html
Normal file
141
app/templates/pages/admin.html
Normal file
@@ -0,0 +1,141 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Admin Header -->
|
||||
<section class="mb-2">
|
||||
<h1>Admin Dashboard</h1>
|
||||
<p class="text-muted">
|
||||
Welcome back, <strong>{{ user.email }}</strong>
|
||||
<span class="badge badge-admin">{{ user.role }}</span>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Admin Actions Grid -->
|
||||
<section class="grid-2" style="margin: 3rem 0;">
|
||||
<!-- Content Management -->
|
||||
<article class="card">
|
||||
<header>
|
||||
<h3>📝 Content Management</h3>
|
||||
</header>
|
||||
<p>
|
||||
Manage blog posts, pages, and other content.
|
||||
Create, edit, and publish multilingual content.
|
||||
</p>
|
||||
<footer>
|
||||
<a href="/admin/posts" role="button">
|
||||
Manage Posts →
|
||||
</a>
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
<!-- RAG System -->
|
||||
<article class="card">
|
||||
<header>
|
||||
<h3>🤖 RAG System</h3>
|
||||
</header>
|
||||
<p>
|
||||
Index content, search semantically, and manage AI providers.
|
||||
OpenAI, Claude, and OpenRouter support.
|
||||
</p>
|
||||
<footer>
|
||||
<a href="/admin/rag" role="button" class="secondary">
|
||||
RAG Dashboard →
|
||||
</a>
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
<!-- API Management -->
|
||||
<article class="card">
|
||||
<header>
|
||||
<h3>⚙️ API & Settings</h3>
|
||||
</header>
|
||||
<p>
|
||||
View API documentation, check system health, and configure settings.
|
||||
</p>
|
||||
<footer>
|
||||
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
||||
<a href="/docs" role="button" class="outline" target="_blank">API Docs</a>
|
||||
<a href="/__routes" role="button" class="outline" target="_blank">Routes</a>
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
<!-- System Status -->
|
||||
<article class="card">
|
||||
<header>
|
||||
<h3>📊 System Status</h3>
|
||||
</header>
|
||||
<p>
|
||||
Monitor system health, view logs, and check database status.
|
||||
</p>
|
||||
<footer>
|
||||
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
||||
<a href="/health" role="button" class="outline" target="_blank">Health</a>
|
||||
<a href="http://localhost:6333/dashboard" role="button" class="outline" target="_blank">Qdrant</a>
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<!-- Quick Links -->
|
||||
<section style="margin: 3rem 0;">
|
||||
<details>
|
||||
<summary><strong>Developer Tools</strong></summary>
|
||||
<div style="padding: 1rem;">
|
||||
<h4>Debug Endpoints</h4>
|
||||
<ul>
|
||||
<li><a href="/__routes" target="_blank">All Routes</a> - View all available API routes</li>
|
||||
<li><a href="/__whoami" target="_blank">Whoami</a> - Current user and session info</li>
|
||||
<li><a href="/health" target="_blank">Health Check</a> - System health status</li>
|
||||
<li><a href="/admin/rag/providers" target="_blank">Provider Status</a> - AI provider availability</li>
|
||||
<li><a href="/admin/rag/status" target="_blank">RAG Status</a> - Indexing status</li>
|
||||
</ul>
|
||||
|
||||
<h4>External Services</h4>
|
||||
<ul>
|
||||
<li><a href="http://localhost:6333/dashboard" target="_blank">Qdrant Dashboard</a> - Vector database UI</li>
|
||||
<li><a href="/docs" target="_blank">Swagger UI</a> - Interactive API documentation</li>
|
||||
</ul>
|
||||
|
||||
<h4>Shell Commands</h4>
|
||||
<pre><code>./logs.sh app # View FastAPI logs
|
||||
./test.sh # Run tests
|
||||
./stop.sh # Stop system</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<!-- System Info Table -->
|
||||
<section style="margin: 3rem 0;">
|
||||
<article>
|
||||
<header>
|
||||
<h3>Session Information</h3>
|
||||
</header>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">User ID</th>
|
||||
<td>{{ user.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Email</th>
|
||||
<td>{{ user.email }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Role</th>
|
||||
<td><span class="badge badge-admin">{{ user.role }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Locale</th>
|
||||
<td>{{ user.locale }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Current Language</th>
|
||||
<td>{{ lang }} ({{ 'Deutsch' if lang == 'de' else 'English' }})</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
290
app/templates/pages/chat.html
Normal file
290
app/templates/pages/chat.html
Normal file
@@ -0,0 +1,290 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<header>
|
||||
<h1>🌲 Crumbforest Chat</h1>
|
||||
<p>Wähle deinen Gesprächspartner und stelle deine Fragen!</p>
|
||||
</header>
|
||||
|
||||
<!-- Character Selection -->
|
||||
<section id="character-selection" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; margin-bottom: 2rem;">
|
||||
<article class="character-card" data-character="eule" style="cursor: pointer; border: 2px solid transparent; transition: all 0.3s;">
|
||||
<header style="text-align: center;">
|
||||
<h2 style="font-size: 3rem; margin: 0;">🦉</h2>
|
||||
<h3 style="margin: 0.5rem 0;">Krümeleule</h3>
|
||||
</header>
|
||||
<p style="text-align: center; color: var(--pico-muted-color);">
|
||||
Weise und geduldig. Erklärt die Welt mit Ruhe und Respekt.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="character-card" data-character="fox" style="cursor: pointer; border: 2px solid transparent; transition: all 0.3s;">
|
||||
<header style="text-align: center;">
|
||||
<h2 style="font-size: 3rem; margin: 0;">🦊</h2>
|
||||
<h3 style="margin: 0.5rem 0;">FunkFox</h3>
|
||||
</header>
|
||||
<p style="text-align: center; color: var(--pico-muted-color);">
|
||||
Der rappende Bit! Erklärt Tech mit Beats und Reimen.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="character-card" data-character="bugsy" style="cursor: pointer; border: 2px solid transparent; transition: all 0.3s;">
|
||||
<header style="text-align: center;">
|
||||
<h2 style="font-size: 3rem; margin: 0;">🐞</h2>
|
||||
<h3 style="margin: 0.5rem 0;">Bugsy</h3>
|
||||
</header>
|
||||
<p style="text-align: center; color: var(--pico-muted-color);">
|
||||
Fehler ohne Scham. Macht Debugging zu einer Lernchance.
|
||||
</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<!-- Chat Interface -->
|
||||
<section id="chat-interface" style="display: none;">
|
||||
<header style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
||||
<div>
|
||||
<h2 id="current-character-name" style="margin: 0;">Character</h2>
|
||||
<p id="current-character-emoji" style="font-size: 2rem; margin: 0;">🦉</p>
|
||||
</div>
|
||||
<button id="change-character-btn" class="outline secondary">Character wechseln</button>
|
||||
</header>
|
||||
|
||||
<!-- Chat Messages -->
|
||||
<div id="chat-messages" style="min-height: 300px; max-height: 500px; overflow-y: auto; padding: 1rem; background: var(--pico-card-background-color); border-radius: var(--pico-border-radius); margin-bottom: 1rem;">
|
||||
<p class="text-muted text-center">Stelle deine erste Frage! 🌱</p>
|
||||
</div>
|
||||
|
||||
<!-- Input Form -->
|
||||
<form id="chat-form">
|
||||
<fieldset role="group">
|
||||
<input
|
||||
type="text"
|
||||
id="question-input"
|
||||
placeholder="Deine Frage..."
|
||||
required
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button type="submit" id="send-btn">Senden</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<!-- Loading Indicator -->
|
||||
<div id="loading" style="display: none; text-align: center; margin-top: 1rem;">
|
||||
<progress></progress>
|
||||
<p class="text-muted">Dein Character denkt nach...</p>
|
||||
</div>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
<style>
|
||||
.character-card:hover {
|
||||
border-color: var(--brand-color) !important;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.character-card.selected {
|
||||
border-color: var(--brand-color) !important;
|
||||
background: rgba(16, 185, 129, 0.05);
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1rem;
|
||||
border-radius: var(--pico-border-radius);
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(0.5rem);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.message-user {
|
||||
background: var(--pico-primary-background);
|
||||
border-left: 4px solid var(--pico-primary);
|
||||
}
|
||||
|
||||
.message-character {
|
||||
background: var(--pico-card-background-color);
|
||||
border-left: 4px solid var(--brand-color);
|
||||
}
|
||||
|
||||
.message-header {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.sources {
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid var(--pico-muted-border-color);
|
||||
font-size: 0.875rem;
|
||||
color: var(--pico-muted-color);
|
||||
}
|
||||
|
||||
.sources ul {
|
||||
margin: 0.5rem 0 0 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.sources li {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const API_URL = '/api/chat';
|
||||
let currentCharacter = null;
|
||||
|
||||
// Character Selection
|
||||
document.querySelectorAll('.character-card').forEach(card => {
|
||||
card.addEventListener('click', () => {
|
||||
currentCharacter = card.dataset.character;
|
||||
startChat();
|
||||
});
|
||||
});
|
||||
|
||||
// Change Character Button
|
||||
document.getElementById('change-character-btn').addEventListener('click', () => {
|
||||
document.getElementById('chat-interface').style.display = 'none';
|
||||
document.getElementById('character-selection').style.display = 'grid';
|
||||
document.getElementById('chat-messages').innerHTML = '<p class="text-muted text-center">Stelle deine erste Frage! 🌱</p>';
|
||||
currentCharacter = null;
|
||||
});
|
||||
|
||||
function startChat() {
|
||||
// Update UI
|
||||
document.getElementById('character-selection').style.display = 'none';
|
||||
document.getElementById('chat-interface').style.display = 'block';
|
||||
|
||||
// Update character info
|
||||
const characterInfo = {
|
||||
eule: { name: 'Krümeleule', emoji: '🦉' },
|
||||
fox: { name: 'FunkFox', emoji: '🦊' },
|
||||
bugsy: { name: 'Bugsy', emoji: '🐞' }
|
||||
};
|
||||
|
||||
const info = characterInfo[currentCharacter];
|
||||
document.getElementById('current-character-name').textContent = info.name;
|
||||
document.getElementById('current-character-emoji').textContent = info.emoji;
|
||||
|
||||
// Focus input
|
||||
document.getElementById('question-input').focus();
|
||||
}
|
||||
|
||||
// Chat Form Submission
|
||||
document.getElementById('chat-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const input = document.getElementById('question-input');
|
||||
const question = input.value.trim();
|
||||
|
||||
if (!question) return;
|
||||
|
||||
// Add user message to chat
|
||||
addMessage('user', 'Du', question);
|
||||
|
||||
// Clear input
|
||||
input.value = '';
|
||||
|
||||
// Show loading
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
document.getElementById('send-btn').disabled = true;
|
||||
|
||||
try {
|
||||
// Send request to API
|
||||
const response = await fetch(API_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
character_id: currentCharacter,
|
||||
question: question,
|
||||
lang: '{{ lang }}'
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Add character response
|
||||
const characterInfo = {
|
||||
eule: { name: 'Krümeleule', emoji: '🦉' },
|
||||
fox: { name: 'FunkFox', emoji: '🦊' },
|
||||
bugsy: { name: 'Bugsy', emoji: '🐞' }
|
||||
};
|
||||
const info = characterInfo[currentCharacter];
|
||||
|
||||
addMessage('character', `${info.emoji} ${info.name}`, data.answer, data.sources);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Chat error:', error);
|
||||
addMessage('error', '❌ Fehler', 'Entschuldigung, es gab einen Fehler. Bitte versuche es erneut.');
|
||||
} finally {
|
||||
// Hide loading
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
document.getElementById('send-btn').disabled = false;
|
||||
input.focus();
|
||||
}
|
||||
});
|
||||
|
||||
function addMessage(type, sender, content, sources = null) {
|
||||
const messagesDiv = document.getElementById('chat-messages');
|
||||
|
||||
// Remove placeholder if exists
|
||||
if (messagesDiv.querySelector('.text-muted')) {
|
||||
messagesDiv.innerHTML = '';
|
||||
}
|
||||
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `message message-${type}`;
|
||||
|
||||
let html = `
|
||||
<div class="message-header">${sender}</div>
|
||||
<div class="message-content">${escapeHtml(content)}</div>
|
||||
`;
|
||||
|
||||
if (sources && sources.length > 0) {
|
||||
html += `
|
||||
<div class="sources">
|
||||
📚 Quellen (${sources.length}):
|
||||
<ul>
|
||||
${sources.map(s => `<li>${escapeHtml(s.file_path)} (${s.score.toFixed(3)})</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
messageDiv.innerHTML = html;
|
||||
messagesDiv.appendChild(messageDiv);
|
||||
|
||||
// Scroll to bottom
|
||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
169
app/templates/pages/home.html
Normal file
169
app/templates/pages/home.html
Normal file
@@ -0,0 +1,169 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Hero Section -->
|
||||
<section class="text-center mb-2">
|
||||
<h1>Wuuuuhuuu! 🦉</h1>
|
||||
<p style="font-size: 1.25rem; color: var(--pico-muted-color);">
|
||||
Knowledge Management & Diary System with AI-powered RAG
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Feature Cards -->
|
||||
<section class="grid-2" style="margin: 3rem 0;">
|
||||
<article class="card">
|
||||
<header>
|
||||
<h3>🤖 RAG System</h3>
|
||||
</header>
|
||||
<p>
|
||||
Semantic search and Q&A powered by OpenAI, Claude, or OpenRouter.
|
||||
Index your content and ask questions in natural language.
|
||||
</p>
|
||||
<footer>
|
||||
{% if user and user.role == 'admin' %}
|
||||
<a href="/admin/rag" role="button">Manage RAG →</a>
|
||||
{% else %}
|
||||
<small class="text-muted">Admin access required</small>
|
||||
{% endif %}
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<header>
|
||||
<h3>📔 Diary System</h3>
|
||||
</header>
|
||||
<p>
|
||||
Personal diary entries with full-text search and AI insights.
|
||||
Each child has their own secure collection.
|
||||
</p>
|
||||
<footer>
|
||||
{% if user %}
|
||||
<a href="/api/hello?lang={{ lang }}" role="button" class="outline">Try API →</a>
|
||||
{% else %}
|
||||
<a href="/{{ lang }}/login" role="button" class="outline">Login to Access →</a>
|
||||
{% endif %}
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<header>
|
||||
<h3>🌍 Multilingual</h3>
|
||||
</header>
|
||||
<p>
|
||||
Full internationalization support with German and English.
|
||||
Server-rendered templates with language switching.
|
||||
</p>
|
||||
<footer>
|
||||
<small class="text-muted">Current: <strong>{{ 'Deutsch' if lang == 'de' else 'English' }}</strong></small>
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<header>
|
||||
<h3>🔐 Secure & GDPR</h3>
|
||||
</header>
|
||||
<p>
|
||||
Role-based access control, audit logging, and GDPR-compliant
|
||||
data handling for all operations.
|
||||
</p>
|
||||
<footer>
|
||||
<small class="text-muted">Audit log & immutable records</small>
|
||||
</footer>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<!-- Quick Start / Status -->
|
||||
<section style="margin: 3rem 0;">
|
||||
{% if user %}
|
||||
<article>
|
||||
<header>
|
||||
<h3>Welcome back, {{ user.email }}! 👋</h3>
|
||||
</header>
|
||||
<p>You're logged in as <strong>{{ user.role }}</strong></p>
|
||||
|
||||
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
|
||||
{% if user.role == 'admin' %}
|
||||
<a href="/admin" role="button">Admin Dashboard</a>
|
||||
<a href="/admin/rag" role="button" class="secondary">RAG Management</a>
|
||||
{% endif %}
|
||||
<a href="/docs" role="button" class="outline" target="_blank">API Docs</a>
|
||||
</div>
|
||||
</article>
|
||||
{% else %}
|
||||
<article>
|
||||
<header>
|
||||
<h3>Get Started</h3>
|
||||
</header>
|
||||
<p>
|
||||
Login to access the admin dashboard, manage content, and use AI-powered features.
|
||||
</p>
|
||||
<div style="display: flex; gap: 1rem; flex-wrap: wrap; align-items: center;">
|
||||
<a href="/{{ lang }}/login" role="button">Login</a>
|
||||
<small class="text-muted">
|
||||
Demo: <code>demo@crumb.local</code> / <code>demo123</code>
|
||||
</small>
|
||||
</div>
|
||||
</article>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
<!-- Tech Stack -->
|
||||
<section style="margin: 3rem 0;">
|
||||
<details>
|
||||
<summary><strong>Tech Stack & APIs</strong></summary>
|
||||
<div style="padding: 1rem;">
|
||||
<h4>Backend</h4>
|
||||
<ul>
|
||||
<li><strong>FastAPI</strong> - Modern Python web framework</li>
|
||||
<li><strong>MariaDB</strong> - Relational database</li>
|
||||
<li><strong>Qdrant</strong> - Vector database for RAG</li>
|
||||
<li><strong>Jinja2</strong> - Template engine</li>
|
||||
</ul>
|
||||
|
||||
<h4>API Endpoints</h4>
|
||||
<pre><code>GET /api/hello - Hello World API
|
||||
POST /api/diary/index - Index diary entry
|
||||
POST /api/diary/search - Semantic search
|
||||
POST /api/diary/ask - RAG Q&A
|
||||
GET /api/diary/{child_id}/status - Status check</code></pre>
|
||||
|
||||
<h4>Quick Test</h4>
|
||||
<pre><code>curl http://localhost:8000/api/hello?lang={{ lang }}
|
||||
curl http://localhost:8000/health
|
||||
curl http://localhost:8000/__routes</code></pre>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<!-- System Info (Debug) -->
|
||||
{% if user and user.role == 'admin' %}
|
||||
<section style="margin: 3rem 0;">
|
||||
<details>
|
||||
<summary><strong>System Information (Admin Only)</strong></summary>
|
||||
<div style="padding: 1rem;">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">User</th>
|
||||
<td>{{ user.email }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Role</th>
|
||||
<td><span class="badge badge-admin">{{ user.role }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Locale</th>
|
||||
<td>{{ user.locale or lang }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Language</th>
|
||||
<td>{{ lang }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
73
app/templates/pages/login.html
Normal file
73
app/templates/pages/login.html
Normal file
@@ -0,0 +1,73 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div style="max-width: 480px; margin: 4rem auto;">
|
||||
<article>
|
||||
<header class="text-center">
|
||||
<h1 style="margin-bottom: 0.5rem;">🦉 Login</h1>
|
||||
<p class="text-muted">Welcome back to Crumbforest</p>
|
||||
</header>
|
||||
|
||||
<form method="post" action="/{{ lang }}/login">
|
||||
<label for="email">
|
||||
Email Address
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="admin@crumb.local"
|
||||
value="{{ form.email if form else '' }}"
|
||||
required
|
||||
autofocus
|
||||
>
|
||||
</label>
|
||||
|
||||
<label for="password">
|
||||
Password
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Enter your password"
|
||||
required
|
||||
>
|
||||
</label>
|
||||
|
||||
<button type="submit" style="width: 100%;">
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<footer>
|
||||
<details>
|
||||
<summary>Demo Accounts</summary>
|
||||
<div style="padding: 1rem 0;">
|
||||
<table role="grid">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Admin</th>
|
||||
<td><code>admin@crumb.local</code></td>
|
||||
<td><code>admin123</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Demo User</th>
|
||||
<td><code>demo@crumb.local</code></td>
|
||||
<td><code>demo123</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</details>
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
<!-- Quick Links -->
|
||||
<div class="text-center" style="margin-top: 2rem;">
|
||||
<small class="text-muted">
|
||||
<a href="/{{ lang }}/">← Back to Home</a>
|
||||
·
|
||||
<a href="/docs" target="_blank">API Docs</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
87
app/templates/pages/settings.html
Normal file
87
app/templates/pages/settings.html
Normal file
@@ -0,0 +1,87 @@
|
||||
{% extends group_config.template_base or "base_demo.html" %}
|
||||
|
||||
{% block title %}Settings{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main class="container">
|
||||
<hgroup>
|
||||
<h1>⚙️ Settings</h1>
|
||||
<p>Customize your Crumbforest experience</p>
|
||||
</hgroup>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Theme Selection -->
|
||||
<article>
|
||||
<header>
|
||||
<h3>🎨 Appearance</h3>
|
||||
</header>
|
||||
|
||||
<form action="/settings/theme" method="post">
|
||||
<label for="theme">Theme</label>
|
||||
<select name="theme" id="theme" onchange="this.form.submit()">
|
||||
{% for variant_id, variant in theme_variants.items() %}
|
||||
<option value="{{ variant_id }}" {% if user.theme==variant_id %}selected{% endif %}>
|
||||
{{ variant.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<small>
|
||||
Current: <strong>{{ theme_variants[user.theme].name if user.theme in theme_variants else user.theme
|
||||
}}</strong>
|
||||
</small>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<!-- Accessibility Settings -->
|
||||
<article>
|
||||
<header>
|
||||
<h3>♿ Accessibility</h3>
|
||||
</header>
|
||||
|
||||
<form action="/settings/accessibility" method="post">
|
||||
<label>
|
||||
<input type="checkbox" name="high_contrast" {% if user.accessibility and
|
||||
user.accessibility.high_contrast %}checked{% endif %}>
|
||||
High Contrast Mode
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" name="animation_reduced" {% if user.accessibility and
|
||||
user.accessibility.animation_reduced %}checked{% endif %}>
|
||||
Reduce Animations
|
||||
</label>
|
||||
|
||||
<label for="font_size">Font Size</label>
|
||||
<select name="font_size" id="font_size">
|
||||
<option value="normal" {% if not user.accessibility or user.accessibility.font_size !='large'
|
||||
%}selected{% endif %}>Normal</option>
|
||||
<option value="large" {% if user.accessibility and user.accessibility.font_size=='large'
|
||||
%}selected{% endif %}>Large</option>
|
||||
</select>
|
||||
|
||||
<button type="submit" class="contrast">Save Accessibility Settings</button>
|
||||
</form>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- Account Info (Read Only) -->
|
||||
<article>
|
||||
<header>
|
||||
<h3>👤 Account Info</h3>
|
||||
</header>
|
||||
<dl>
|
||||
<dt>Email</dt>
|
||||
<dd>{{ user.email }}</dd>
|
||||
|
||||
<dt>Group</dt>
|
||||
<dd>
|
||||
<span class="badge">{{ group_config.name }}</span>
|
||||
</dd>
|
||||
|
||||
<dt>Role</dt>
|
||||
<dd>{{ user.role }}</dd>
|
||||
</dl>
|
||||
</article>
|
||||
</main>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user