diff --git a/client/src/components/ChatInput.vue b/client/src/components/ChatInput.vue index f606301..b450f95 100644 --- a/client/src/components/ChatInput.vue +++ b/client/src/components/ChatInput.vue @@ -2,7 +2,7 @@
@@ -47,11 +47,22 @@ const chatStore = useChatStore(); const message = ref(''); const showSmileys = ref(false); const hasConversation = computed(() => !!chatStore.currentConversation); -const inputPlaceholder = computed(() => - hasConversation.value +const isAwaitingUsername = computed(() => chatStore.awaitingLoginUsername); +const isAwaitingPassword = computed(() => chatStore.awaitingLoginPassword); + +const inputPlaceholder = computed(() => { + if (isAwaitingUsername.value) { + return 'Admin-Username eingeben'; + } + if (isAwaitingPassword.value) { + return 'Admin-Passwort eingeben'; + } + return hasConversation.value ? 'Nachricht senden oder /Befehl eingeben' - : 'Nur /Befehle eingeben (z.B. /login, /stat help)' -); + : 'Nur /Befehle eingeben (z.B. /login, /stat help)'; +}); + +const inputType = computed(() => (isAwaitingPassword.value ? 'password' : 'text')); // Smiley-Definitionen (wie im Original) const smileys = { @@ -81,7 +92,10 @@ function sendMessage() { const trimmed = message.value.trim(); if (!trimmed) return; const isCommand = trimmed.startsWith('/'); - if (!isCommand && !hasConversation.value) { + const canSendPlain = + hasConversation.value || isAwaitingUsername.value || isAwaitingPassword.value; + + if (!isCommand && !canSendPlain) { chatStore.errorMessage = 'Ohne aktive Konversation sind nur /Befehle erlaubt.'; setTimeout(() => { if (chatStore.errorMessage === 'Ohne aktive Konversation sind nur /Befehle erlaubt.') { @@ -90,8 +104,9 @@ function sendMessage() { }, 4000); return; } - - chatStore.sendMessage(chatStore.currentConversation, trimmed); + + const suppressLocal = isCommand || isAwaitingUsername.value || isAwaitingPassword.value; + chatStore.sendMessage(chatStore.currentConversation, trimmed, { suppressLocal }); message.value = ''; } diff --git a/client/src/stores/chat.js b/client/src/stores/chat.js index fa720be..36f4fb6 100644 --- a/client/src/stores/chat.js +++ b/client/src/stores/chat.js @@ -22,6 +22,8 @@ export const useChatStore = defineStore('chat', () => { const unreadChatsCount = ref(0); const errorMessage = ref(null); const remainingSecondsToTimeout = ref(1800); + const awaitingLoginUsername = ref(false); + const awaitingLoginPassword = ref(false); const searchData = ref({ nameIncludes: '', minAge: null, @@ -293,7 +295,20 @@ export const useChatStore = defineStore('chat', () => { break; case 'commandResult': { const lines = Array.isArray(data.lines) ? data.lines : []; - // Command output is global and must not pollute per-conversation history. + const kind = data.kind || 'info'; + + if (kind === 'loginPromptUsername') { + awaitingLoginUsername.value = true; + awaitingLoginPassword.value = false; + } else if (kind === 'loginPromptPassword') { + awaitingLoginUsername.value = false; + awaitingLoginPassword.value = true; + } else if (kind === 'loginSuccess' || kind === 'loginError' || kind === 'loginAbort' || kind === 'loginLogout') { + awaitingLoginUsername.value = false; + awaitingLoginPassword.value = false; + } + + // Command-Ausgaben immer global anzeigen, nicht im Chatverlauf. errorMessage.value = lines.join(' | '); setTimeout(() => { errorMessage.value = null; @@ -360,7 +375,7 @@ export const useChatStore = defineStore('chat', () => { }); } - function sendMessage(toUserName, message) { + function sendMessage(toUserName, message, options = {}) { if (!socket.value || !socket.value.connected) { console.error('Socket.IO nicht verbunden'); return; @@ -375,8 +390,10 @@ export const useChatStore = defineStore('chat', () => { messageId }); - // Lokal hinzufügen (außer bei Commands, die serverseitig beantwortet werden) - if (!isCommand) { + const suppressLocal = !!options.suppressLocal || isCommand || awaitingLoginUsername.value || awaitingLoginPassword.value; + + // Lokal hinzufügen (außer bei Commands oder sensiblen Eingaben wie Login) + if (!isCommand && !suppressLocal) { messages.value.push({ from: userName.value, message: trimmed, @@ -673,6 +690,8 @@ export const useChatStore = defineStore('chat', () => { remainingSecondsToTimeout, errorMessage, searchData, + awaitingLoginUsername, + awaitingLoginPassword, // Computed currentConversationWith, // Actions diff --git a/server/broadcast.js b/server/broadcast.js index a0b6847..5758e66 100644 --- a/server/broadcast.js +++ b/server/broadcast.js @@ -320,9 +320,9 @@ export function setupBroadcast(io, __dirname) { downloadCountries(); setInterval(downloadCountries, 24 * 60 * 60 * 1000); // Täglich aktualisieren - function sendCommandResult(socket, lines) { + function sendCommandResult(socket, lines, kind = 'info') { const payload = Array.isArray(lines) ? lines : [String(lines)]; - socket.emit('commandResult', { lines: payload }); + socket.emit('commandResult', { lines: payload, kind }); } function hasRight(client, right) { @@ -536,15 +536,21 @@ export function setupBroadcast(io, __dirname) { function executeCommand(socket, client, rawInput) { const input = rawInput.trim(); - if (client.pendingChatLogin) { + if (!input.startsWith('/')) return false; + + const parts = input.split(/\s+/); + const command = parts[0].toLowerCase(); + + // Laufender Login-Dialog: Nur Eingaben ohne Slash als Username/Passwort behandeln. + if (client.pendingChatLogin && !input.startsWith('/')) { if (client.pendingChatLogin.step === 'username') { const enteredUser = input; if (!enteredUser) { - sendCommandResult(socket, 'Username darf nicht leer sein. Bitte Username eingeben:'); + sendCommandResult(socket, 'Username darf nicht leer sein. Bitte Username eingeben:', 'loginPromptUsername'); return true; } client.pendingChatLogin = { step: 'password', username: enteredUser }; - sendCommandResult(socket, 'Passwort eingeben:'); + sendCommandResult(socket, 'Passwort eingeben:', 'loginPromptPassword'); return true; } @@ -553,32 +559,33 @@ export function setupBroadcast(io, __dirname) { const auth = verifyChatUser(username, input); client.pendingChatLogin = null; if (!auth) { - sendCommandResult(socket, 'Login fehlgeschlagen. Benutzername oder Passwort falsch.'); + sendCommandResult(socket, 'Login fehlgeschlagen. Benutzername oder Passwort falsch.', 'loginError'); return true; } client.chatAuth = auth; sendCommandResult( socket, - `Login erfolgreich als ${auth.username}. Rechte: ${Array.from(auth.rights).join(', ') || 'keine'}` + `Login erfolgreich als ${auth.username}. Rechte: ${Array.from(auth.rights).join(', ') || 'keine'}`, + 'loginSuccess' ); return true; } + } else if (client.pendingChatLogin && input.startsWith('/')) { + // Ein neuer /Befehl bricht den Login-Vorgang ab. + client.pendingChatLogin = null; + sendCommandResult(socket, 'Login-Vorgang abgebrochen.', 'loginAbort'); + // und läuft unten als normaler Befehl weiter } - if (!input.startsWith('/')) return false; - - const parts = input.split(/\s+/); - const command = parts[0].toLowerCase(); - if (command === '/login') { const username = (parts[1] || '').trim(); if (username) { client.pendingChatLogin = { step: 'password', username }; - sendCommandResult(socket, 'Passwort eingeben:'); + sendCommandResult(socket, 'Passwort eingeben:', 'loginPromptPassword'); } else { client.pendingChatLogin = { step: 'username', username: '' }; - sendCommandResult(socket, 'Username eingeben:'); + sendCommandResult(socket, 'Username eingeben:', 'loginPromptUsername'); } return true; } @@ -591,20 +598,35 @@ export function setupBroadcast(io, __dirname) { socket, wasLoggedIn ? 'Admin/Command-Login wurde abgemeldet.' - : 'Es war kein Admin/Command-Login aktiv.' + : 'Es war kein Admin/Command-Login aktiv.', + 'loginLogout' ); return true; } if (command === '/whoami-rights') { if (!client.chatAuth) { - sendCommandResult(socket, 'Nicht per Command-Login angemeldet.'); + sendCommandResult(socket, 'Nicht per Command-Login angemeldet.', 'whoami'); return true; } sendCommandResult(socket, [ `Angemeldet als: ${client.chatAuth.username}`, `Rechte: ${Array.from(client.chatAuth.rights).join(', ') || 'keine'}` - ]); + ], 'whoami'); + return true; + } + + if (command === '/help' || command === '/?') { + sendCommandResult(socket, [ + 'Verfügbare Befehle:', + '/login [username] - Admin-/Command-Login starten', + '/logout-admin - Admin-/Command-Login beenden', + '/whoami-rights - Aktuelle Admin-Rechte anzeigen', + '/stat help - Hilfe zu Statistikbefehlen', + '/all-stats - Zusammenfassung wichtiger Statistiken', + '/kick - Benutzer aus dem Chat werfen', + '/help oder /? - Diese Hilfe' + ], 'help'); return true; } @@ -623,7 +645,7 @@ export function setupBroadcast(io, __dirname) { return true; } - sendCommandResult(socket, `Unbekannter Befehl: ${command}`); + sendCommandResult(socket, `Unbekannter Befehl: ${command}`, 'unknown'); return true; }