- Move index.html → templates/index.html (Jinja2, uses url_for) - Extract CSS → static/css/app.css - Split JS into static/js/: utils.js, dashboard.js, shares.js, quotas.js, users.js, main.js - Switch app.py from send_file to render_template Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
29 lines
1.2 KiB
JavaScript
29 lines
1.2 KiB
JavaScript
function esc(s) {
|
|
return String(s ?? '')
|
|
.replace(/&/g, '&').replace(/</g, '<')
|
|
.replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
function showToast(msg, type = 'success') {
|
|
const colors = { success: '#3fb950', danger: '#f85149', warning: '#d29922' };
|
|
const id = 'toast-' + Date.now();
|
|
document.getElementById('toastContainer').insertAdjacentHTML('beforeend', `
|
|
<div id="${id}" class="toast align-items-center border-0 mb-2" role="alert"
|
|
style="background:#161b22; border-left:3px solid ${colors[type] ?? colors.success} !important; color:#e6edf3;">
|
|
<div class="d-flex">
|
|
<div class="toast-body">${esc(msg)}</div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
|
</div>
|
|
</div>`);
|
|
const el = document.getElementById(id);
|
|
const t = new bootstrap.Toast(el, { delay: 3500 });
|
|
t.show();
|
|
el.addEventListener('hidden.bs.toast', () => el.remove());
|
|
}
|
|
|
|
async function apiFetch(url, opts) {
|
|
const res = await fetch(url, opts);
|
|
if (res.status === 401) { location.reload(); return null; }
|
|
return res;
|
|
}
|