Files
mcphub/core/templates/dashboard/partials/head_assets.html
airano 9e6f06d933 feat: v3.1.0 — Live Platform Foundation (Track E.1-E.3)
Major release introducing the Live Platform architecture:

- SQLite database backend with async operations and migrations (E.1)
- AES-256-GCM credential encryption with HKDF key derivation (E.1)
- OAuth Social Login with GitHub and Google (E.2)
- Site management API with encrypted credential storage (E.3)
- Per-user MCP endpoints at /u/{user_id}/{alias}/mcp (E.3)
- User API keys with bcrypt hashing (E.3)
- Auto-generated config snippets for 5 MCP clients (E.3)
- Dashboard: dark/light mode, RBAC, My Sites, Connect page
- Active background health checks
- 452 tests (up from 303), all passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 23:20:03 +03:30

107 lines
3.6 KiB
HTML

<!-- Vazirmatn Font for Persian -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100;200;300;400;500;600;700;800;900&display=swap"
rel="stylesheet">
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- HTMX for dynamic updates -->
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<!-- Alpine.js for simple interactions -->
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
<!-- Custom Tailwind Config -->
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
'vazirmatn': ['Vazirmatn', 'system-ui', 'sans-serif'],
},
colors: {
primary: {
50: '#f5f3ff',
100: '#ede9fe',
200: '#ddd6fe',
300: '#c4b5fd',
400: '#a78bfa',
500: '#8b5cf6',
600: '#7c3aed',
700: '#6d28d9',
800: '#5b21b6',
900: '#4c1d95',
}
}
}
}
}
</script>
<!-- Theme Initialization -->
<script>
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
</script>
<meta name="csrf-token" content="{{ request.state.csrf_token }}">
<!-- Global CSRF Interceptor -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const getCsrfToken = () => document.querySelector('meta[name="csrf-token"]')?.content;
// Intercept HTMX requests
document.body.addEventListener('htmx:configRequest', (event) => {
const token = getCsrfToken();
if (token && event.detail.verb !== 'get') {
event.detail.headers['X-CSRF-Token'] = token;
}
});
// Intercept standard fetch requests
const originalFetch = window.fetch;
window.fetch = async function (resource, init) {
const token = getCsrfToken();
if (token) {
const url = typeof resource === 'string' ? new URL(resource, window.location.origin) : resource;
if (url.origin === window.location.origin) {
init = init || {};
init.headers = init.headers || {};
const method = (init.method || 'GET').toUpperCase();
if (method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS') {
if (init.headers instanceof Headers) {
init.headers.append('X-CSRF-Token', token);
} else if (Array.isArray(init.headers)) {
init.headers.push(['X-CSRF-Token', token]);
} else {
init.headers['X-CSRF-Token'] = token;
}
}
}
}
return originalFetch.call(this, resource, init);
};
});
</script>
<style>
[x-cloak] {
display: none !important;
}
/* Vazirmatn font for Persian */
html[lang="fa"],
html[lang="fa"] *,
[lang="fa"],
[lang="fa"] * {
font-family: 'Vazirmatn', system-ui, sans-serif !important;
}
</style>