- Web UI: login, dashboard, links CRUD, collections, API keys, admin pages - Query engine: AND/OR/XOR with field filters, tag search, preview endpoint - Session management: token expiry detection, 401 interceptor, expiry banner - Links search: tags included, multi-word AND, query mode with set operations - Collections: static/dynamic, query builder with preview, public tree view - Save as Collection: convert search results (static) or query (dynamic) - Dashboard stats: resilient loading with allSettled pattern - Login page: redesigned with public collections tree view - Bug fix: query executor None fields crash (notes/description/url/title) - E2E tests: 20 Playwright tests covering all critical user flows - All 104 tests passing (84 unit/integration + 20 E2E)
46 lines
1.8 KiB
JavaScript
46 lines
1.8 KiB
JavaScript
document.addEventListener('DOMContentLoaded', async function() {
|
|
const user = JSON.parse(localStorage.getItem('user') || 'null');
|
|
if (!user) {
|
|
window.location.href = '/login';
|
|
return;
|
|
}
|
|
|
|
const token = localStorage.getItem('token');
|
|
if (token && LinkSync.isTokenExpired(token)) {
|
|
LinkSync.logout();
|
|
return;
|
|
}
|
|
|
|
document.getElementById('current-user').textContent = user.username;
|
|
|
|
if (user.role === 'admin') {
|
|
document.getElementById('admin-section').style.display = '';
|
|
}
|
|
|
|
try {
|
|
const [linksResult, collectionsResult, keysResult] = await Promise.allSettled([
|
|
LinkSync.getLinks({ limit: 1 }),
|
|
LinkSync.getCollections(),
|
|
LinkSync.getApiKeys(),
|
|
]);
|
|
|
|
const links = linksResult.status === 'fulfilled' ? linksResult.value : [];
|
|
const collections = collectionsResult.status === 'fulfilled' ? collectionsResult.value : [];
|
|
const keys = keysResult.status === 'fulfilled' ? keysResult.value : [];
|
|
|
|
document.getElementById('link-count').textContent = Array.isArray(links) ? links.length : 0;
|
|
document.getElementById('collection-count').textContent = Array.isArray(collections) ? collections.length : 0;
|
|
document.getElementById('api-key-count').textContent = Array.isArray(keys) ? keys.length : 0;
|
|
} catch (err) {
|
|
console.error('Failed to load stats:', err);
|
|
}
|
|
|
|
const expirySeconds = LinkSync.getTokenExpirySeconds(token);
|
|
if (expirySeconds > 0 && expirySeconds < 120) {
|
|
const warning = document.createElement('div');
|
|
warning.className = 'info-message';
|
|
warning.textContent = `Session expires in ${Math.ceil(expirySeconds / 60)} minute${expirySeconds > 60 ? 's' : ''}. Save your work.`;
|
|
document.querySelector('.dashboard-header').prepend(warning);
|
|
}
|
|
});
|