Feature user and admin panels (#30)
This commit is contained in:
165
src/js/app.js
165
src/js/app.js
@@ -10,10 +10,11 @@ async function init() {
|
||||
Backend.init();
|
||||
|
||||
// Auto-detect backend at same origin
|
||||
let health = null;
|
||||
if (!Backend.baseUrl) {
|
||||
const health = await Backend.checkHealth('');
|
||||
health = await Backend.checkHealth('');
|
||||
if (health && health.database) {
|
||||
Backend.baseUrl = window.location.origin; // same origin
|
||||
Backend.baseUrl = window.location.origin;
|
||||
Backend.save();
|
||||
console.log('🔗 Backend detected at same origin');
|
||||
}
|
||||
@@ -21,27 +22,68 @@ async function init() {
|
||||
|
||||
// If we have saved auth, validate it
|
||||
if (Backend.accessToken) {
|
||||
const health = await Backend.checkHealth(Backend.baseUrl);
|
||||
health = health || await Backend.checkHealth(Backend.baseUrl);
|
||||
if (!health) {
|
||||
console.log('⚠️ Backend unreachable, falling back to unmanaged');
|
||||
Backend.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Decide: splash gate or straight to app
|
||||
if (Backend.baseUrl && !Backend.accessToken) {
|
||||
// Online but not authenticated → show splash, hide app
|
||||
showSplashGate(health);
|
||||
initAuthListeners();
|
||||
return; // Don't init app yet — authSuccess() will do it
|
||||
}
|
||||
|
||||
// Already authed or no backend → go straight to app
|
||||
hideSplashGate();
|
||||
await initApp();
|
||||
}
|
||||
|
||||
async function initApp() {
|
||||
await loadChats();
|
||||
initializeEventListeners();
|
||||
updateQuickModelSelector();
|
||||
renderChatHistory();
|
||||
updateConnectionStatus();
|
||||
|
||||
// If backend available but not logged in, show auth
|
||||
if (Backend.baseUrl && !Backend.accessToken) {
|
||||
showAuthModal();
|
||||
}
|
||||
|
||||
initAdmin();
|
||||
console.log(`✅ Chat Switchboard ready (${Backend.isManaged ? 'managed' : 'unmanaged'} mode)`);
|
||||
}
|
||||
|
||||
function showSplashGate(health) {
|
||||
const splash = document.getElementById('splashGate');
|
||||
const app = document.getElementById('appContainer');
|
||||
splash.classList.add('visible');
|
||||
splash.style.display = 'flex'; // Fallback for cached CSS
|
||||
app.classList.add('app-hidden');
|
||||
app.style.display = 'none'; // Fallback for cached CSS
|
||||
|
||||
// Hide register tab if registration disabled
|
||||
const registerTab = document.getElementById('authTabRegister');
|
||||
if (health && health.registration_enabled === false) {
|
||||
registerTab.style.display = 'none';
|
||||
} else {
|
||||
registerTab.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
function hideSplashGate() {
|
||||
const splash = document.getElementById('splashGate');
|
||||
const app = document.getElementById('appContainer');
|
||||
splash.classList.remove('visible');
|
||||
splash.style.display = 'none'; // Hide splash
|
||||
app.classList.remove('app-hidden');
|
||||
app.style.display = ''; // Show app
|
||||
}
|
||||
|
||||
async function authSuccess() {
|
||||
hideSplashGate();
|
||||
await initApp();
|
||||
showToast(`✅ Welcome, ${Backend.user?.username || 'user'}!`, 'success');
|
||||
}
|
||||
|
||||
function initializeEventListeners() {
|
||||
// Settings modal
|
||||
document.getElementById('settingsBtn').addEventListener('click', openSettings);
|
||||
@@ -51,6 +93,12 @@ function initializeEventListeners() {
|
||||
document.getElementById('saveBtn').addEventListener('click', handleSaveSettings);
|
||||
document.getElementById('fetchModelsBtn').addEventListener('click', () => fetchModels(true));
|
||||
|
||||
// Profile
|
||||
document.getElementById('profileChangePwBtn').addEventListener('click', () => {
|
||||
document.getElementById('profileChangePwForm').style.display = '';
|
||||
});
|
||||
document.getElementById('profileSavePwBtn').addEventListener('click', handleChangePassword);
|
||||
|
||||
// Chat controls
|
||||
document.getElementById('newChatBtn').addEventListener('click', newChat);
|
||||
document.getElementById('sendBtn').addEventListener('click', sendMessage);
|
||||
@@ -97,26 +145,21 @@ function initializeEventListeners() {
|
||||
document.getElementById('settingsModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) closeSettings();
|
||||
});
|
||||
document.getElementById('authModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
// Only closeable if user has option to skip (unmanaged available)
|
||||
if (!Backend.baseUrl || Backend.accessToken) {
|
||||
closeAuthModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Auth modal
|
||||
document.getElementById('authLoginBtn').addEventListener('click', handleLogin);
|
||||
document.getElementById('authRegisterBtn').addEventListener('click', handleRegister);
|
||||
document.getElementById('authTabLogin').addEventListener('click', () => switchAuthTab('login'));
|
||||
document.getElementById('authTabRegister').addEventListener('click', () => switchAuthTab('register'));
|
||||
document.getElementById('authCloseBtn').addEventListener('click', closeAuthModal);
|
||||
document.getElementById('authSkipBtn').addEventListener('click', skipAuth);
|
||||
|
||||
// Connection status click
|
||||
document.getElementById('connectionStatus').addEventListener('click', handleConnectionClick);
|
||||
|
||||
// Admin panel
|
||||
document.getElementById('adminBtn').addEventListener('click', openAdmin);
|
||||
document.getElementById('adminCloseBtn').addEventListener('click', closeAdmin);
|
||||
document.querySelectorAll('.admin-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => switchAdminTab(tab.dataset.tab));
|
||||
});
|
||||
document.getElementById('adminSaveSettings').addEventListener('click', saveAdminSettings);
|
||||
document.getElementById('adminModal').addEventListener('click', function(e) {
|
||||
if (e.target === this) closeAdmin();
|
||||
});
|
||||
|
||||
// Global keyboard shortcuts
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.ctrlKey && e.key === ',') {
|
||||
@@ -132,20 +175,6 @@ function initializeEventListeners() {
|
||||
stopGeneration();
|
||||
} else if (document.getElementById('settingsModal').classList.contains('active')) {
|
||||
closeSettings();
|
||||
} else if (document.getElementById('authModal').classList.contains('active')) {
|
||||
closeAuthModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Enter key in auth form
|
||||
document.getElementById('authPassword').addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
const activeTab = document.querySelector('.auth-tab.active');
|
||||
if (activeTab && activeTab.id === 'authTabRegister') {
|
||||
// Focus email field first if registering
|
||||
} else {
|
||||
handleLogin();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -176,6 +205,7 @@ function handleSaveSettings() {
|
||||
State.settings.presencePenalty = parseFloat(document.getElementById('presencePenalty').value) || 0;
|
||||
|
||||
saveSettings();
|
||||
saveProfile(); // async, fire-and-forget
|
||||
updateQuickModelSelector();
|
||||
closeSettings();
|
||||
showToast('✅ Settings saved', 'success');
|
||||
@@ -307,18 +337,24 @@ async function regenerateResponse() {
|
||||
|
||||
// ── Auth Flow ───────────────────────────────
|
||||
|
||||
function showAuthModal() {
|
||||
document.getElementById('authModal').classList.add('active');
|
||||
document.getElementById('authLogin').value = '';
|
||||
document.getElementById('authPassword').value = '';
|
||||
document.getElementById('authUsername').value = '';
|
||||
document.getElementById('authEmail').value = '';
|
||||
document.getElementById('authError').textContent = '';
|
||||
switchAuthTab('login');
|
||||
}
|
||||
let _authListenersInit = false;
|
||||
function initAuthListeners() {
|
||||
if (_authListenersInit) return;
|
||||
_authListenersInit = true;
|
||||
|
||||
function closeAuthModal() {
|
||||
document.getElementById('authModal').classList.remove('active');
|
||||
document.getElementById('authLoginBtn').addEventListener('click', handleLogin);
|
||||
document.getElementById('authRegisterBtn').addEventListener('click', handleRegister);
|
||||
document.getElementById('authTabLogin').addEventListener('click', () => switchAuthTab('login'));
|
||||
document.getElementById('authTabRegister').addEventListener('click', () => switchAuthTab('register'));
|
||||
document.getElementById('authSkipBtn').addEventListener('click', skipAuth);
|
||||
|
||||
// Enter key in auth forms
|
||||
document.getElementById('authPassword').addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') handleLogin();
|
||||
});
|
||||
document.getElementById('authRegPassword').addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') handleRegister();
|
||||
});
|
||||
}
|
||||
|
||||
function switchAuthTab(tab) {
|
||||
@@ -343,11 +379,7 @@ async function handleLogin() {
|
||||
setAuthLoading(true);
|
||||
try {
|
||||
await Backend.login(login, password);
|
||||
closeAuthModal();
|
||||
await loadChats();
|
||||
renderChatHistory();
|
||||
updateConnectionStatus();
|
||||
showToast(`✅ Welcome back, ${Backend.user.username}`, 'success');
|
||||
await authSuccess();
|
||||
} catch (e) {
|
||||
document.getElementById('authError').textContent = e.message;
|
||||
} finally {
|
||||
@@ -372,11 +404,7 @@ async function handleRegister() {
|
||||
setAuthLoading(true);
|
||||
try {
|
||||
await Backend.register(username, email, password);
|
||||
closeAuthModal();
|
||||
await loadChats();
|
||||
renderChatHistory();
|
||||
updateConnectionStatus();
|
||||
showToast(`✅ Welcome, ${Backend.user.username}!`, 'success');
|
||||
await authSuccess();
|
||||
} catch (e) {
|
||||
document.getElementById('authError').textContent = e.message;
|
||||
} finally {
|
||||
@@ -393,10 +421,10 @@ function setAuthLoading(loading) {
|
||||
});
|
||||
}
|
||||
|
||||
function skipAuth() {
|
||||
closeAuthModal();
|
||||
async function skipAuth() {
|
||||
Backend.clear();
|
||||
updateConnectionStatus();
|
||||
hideSplashGate();
|
||||
await initApp();
|
||||
showToast('📴 Running in offline mode', 'success');
|
||||
}
|
||||
|
||||
@@ -406,7 +434,7 @@ function updateConnectionStatus() {
|
||||
const el = document.getElementById('connectionStatus');
|
||||
if (Backend.isManaged) {
|
||||
el.className = 'connection-status managed';
|
||||
el.innerHTML = `<span class="status-dot"></span> ${Backend.user?.username || 'Connected'}`;
|
||||
el.innerHTML = `<span class="status-dot"></span> ${Backend.user?.display_name || Backend.user?.username || 'Connected'}`;
|
||||
el.title = 'Managed mode – click to sign out';
|
||||
} else if (Backend.baseUrl) {
|
||||
el.className = 'connection-status available';
|
||||
@@ -425,14 +453,13 @@ async function handleConnectionClick() {
|
||||
await Backend.logout();
|
||||
State.chats = [];
|
||||
State.currentChatId = null;
|
||||
loadChats();
|
||||
newChat();
|
||||
renderChatHistory();
|
||||
updateConnectionStatus();
|
||||
showSplashGate(null);
|
||||
initAuthListeners();
|
||||
showToast('👋 Signed out', 'success');
|
||||
}
|
||||
} else if (Backend.baseUrl) {
|
||||
showAuthModal();
|
||||
showSplashGate(null);
|
||||
initAuthListeners();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user