From 3dcc6479a898b1a891948b862a6c629f09241992 Mon Sep 17 00:00:00 2001 From: Remi <73385395+itsrubberduck@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:56:48 +0200 Subject: [PATCH] Finalize admin tools and transmission fault flow --- app/pages/pm.vue | 206 +++++++++++++++++++++++++++++++++++++++---- app/stores/auth.ts | 4 +- server/utils/auth.ts | 12 +++ 3 files changed, 205 insertions(+), 17 deletions(-) diff --git a/app/pages/pm.vue b/app/pages/pm.vue index 28f29bf..88d5f52 100644 --- a/app/pages/pm.vue +++ b/app/pages/pm.vue @@ -581,16 +581,133 @@ - - -
- - Letzte Übertragung + + +
+
+ + + Letzte Übertragung + +
+
+ + Fehlerhaft + + + + {{ lastTransmissionFaulty ? 'Fehler bearbeiten' : 'Fehler markieren' }} + + + + Zurücksetzen + + + + Löschen + +
+
+

{{ lastTransmission }}

+
+

Fehlerbeschreibung

+

+ {{ lastTransmissionFaultNote }} +

+

+ Als fehlerhaft markiert. +

-

{{ lastTransmission }}

+ + + + + Übertragung als fehlerhaft markieren + + +

+ Hinterlasse optional eine kurze Beschreibung, damit das Dev Team die + Übertragung nachvollziehen kann. +

+ +
+ + + Abbrechen + + + Markierung entfernen + + + Speichern + + +
+
+ @@ -676,9 +793,61 @@ const { getStateDetails } = engine +const lastTransmission = ref('') +const lastTransmissionFaulty = ref(false) +const lastTransmissionFaultNote = ref('') +const showTransmissionIssueDialog = ref(false) +const transmissionIssueNote = ref('') + +function setLastTransmission(text: string) { + lastTransmission.value = text + lastTransmissionFaulty.value = false + lastTransmissionFaultNote.value = '' +} + +function clearLastTransmission() { + setLastTransmission('') +} + +function markLastTransmissionFault(note: string) { + if (!lastTransmission.value) return + lastTransmissionFaulty.value = true + lastTransmissionFaultNote.value = note +} + +function resetLastTransmissionFault() { + lastTransmissionFaulty.value = false + lastTransmissionFaultNote.value = '' +} + +function startTransmissionIssueFlow() { + if (!lastTransmission.value) return + transmissionIssueNote.value = lastTransmissionFaultNote.value + showTransmissionIssueDialog.value = true +} + +function confirmTransmissionIssue() { + if (!lastTransmission.value) { + showTransmissionIssueDialog.value = false + return + } + + markLastTransmissionFault(transmissionIssueNote.value.trim()) + showTransmissionIssueDialog.value = false +} + +function removeTransmissionIssue() { + resetLastTransmissionFault() + showTransmissionIssueDialog.value = false +} + +function cancelTransmissionIssue() { + showTransmissionIssueDialog.value = false +} + const clearLog = () => { clearCommunicationLog() - lastTransmission.value = '' + clearLastTransmission() } // UI State @@ -686,7 +855,6 @@ const currentScreen = ref<'login' | 'flightselect' | 'monitor'>('login') const loading = ref(false) const error = ref('') const pilotInput = ref('') -const lastTransmission = ref('') const radioMode = ref<'atc' | 'intercom'>('atc') const isRecording = ref(false) const micPermission = ref(false) @@ -1071,7 +1239,7 @@ const speakPrepared = async (prepared: PreparedSpeech, options: SpeechOptions = if (response.success && response.audio) { if (options.updateLastTransmission !== false) { - lastTransmission.value = options.lastTransmissionLabel || `ATC: ${prepared.plain}` + setLastTransmission(options.lastTransmissionLabel || `ATC: ${prepared.plain}`) } await playAudioWithEffects(response.audio.base64) } @@ -1117,7 +1285,7 @@ const handlePilotTransmission = async (message: string, source: 'text' | 'ptt' = if (!transcript) return const prefix = source === 'ptt' ? 'Pilot (PTT)' : 'Pilot' - lastTransmission.value = `${prefix}: ${transcript}` + setLastTransmission(`${prefix}: ${transcript}`) const quickResponse = processPilotTransmission(transcript) @@ -1142,7 +1310,7 @@ const handlePilotTransmission = async (message: string, source: 'text' | 'ptt' = } } catch (e) { console.error('LLM decision failed', e) - lastTransmission.value = `${prefix}: ${transcript} (LLM failed)` + setLastTransmission(`${prefix}: ${transcript} (LLM failed)`) } } @@ -1199,7 +1367,7 @@ const startDemoFlight = () => { const backToSetup = () => { currentScreen.value = 'login' selectedPlan.value = null - lastTransmission.value = '' + clearLastTransmission() } // Audio/PTT Functions @@ -1289,7 +1457,7 @@ const processTransmission = async (audioBlob: Blob, isIntercom: boolean) => { }) if (result.success) { - lastTransmission.value = `INTERCOM: ${result.transcription}` + setLastTransmission(`INTERCOM: ${result.transcription}`) const transcription = result.transcription.toLowerCase() if (transcription.includes('checklist') || transcription.includes('check list')) { speakWithRadioEffects('Checklist functionality available in advanced mode', { @@ -1318,7 +1486,7 @@ const processTransmission = async (audioBlob: Blob, isIntercom: boolean) => { } } catch (err) { console.error('Error processing transmission:', err) - lastTransmission.value = 'Error processing audio' + setLastTransmission('Error processing audio') } } @@ -1423,7 +1591,7 @@ const recordCurrentAtcMessage = () => { payload: { text: plain, normalized } }) - lastTransmission.value = `ATC: ${plain}` + setLastTransmission(`ATC: ${plain}`) recordedAtcStates.add(state.id) } @@ -1515,7 +1683,7 @@ const runFullSimulation = async () => { payload: { text: pilotText, normalized: pilotNormalized } }) - lastTransmission.value = `Pilot: ${pilotText}` + setLastTransmission(`Pilot: ${pilotText}`) const quickResponse = processPilotTransmission(pilotText) if (quickResponse) { @@ -1597,6 +1765,12 @@ onMounted(async () => { await requestMicAccess() }) +watch(showTransmissionIssueDialog, (open) => { + if (!open) { + transmissionIssueNote.value = '' + } +}) + // Watch for frequency changes from engine watch(() => activeFrequency.value, (newFreq) => { if (newFreq && newFreq !== frequencies.value.active) { diff --git a/app/stores/auth.ts b/app/stores/auth.ts index 6f52556..a022033 100644 --- a/app/stores/auth.ts +++ b/app/stores/auth.ts @@ -12,11 +12,13 @@ interface RegisterPayload extends Credentials { acceptPrivacy: boolean } +type UserRole = 'user' | 'admin' | 'dev' + interface AuthUser { id: string email: string name?: string - role: string + role: UserRole createdAt: string } diff --git a/server/utils/auth.ts b/server/utils/auth.ts index 6e3783b..600bd08 100644 --- a/server/utils/auth.ts +++ b/server/utils/auth.ts @@ -170,6 +170,18 @@ export async function getUserFromEvent(event: H3Event) { return user } +export function hasAdminRole(user: UserDocument | null | undefined) { + return user ? user.role === 'admin' || user.role === 'dev' : false +} + +export async function requireAdmin(event: H3Event) { + const user = await requireUserSession(event) + if (!hasAdminRole(user)) { + throw createError({ statusCode: 403, statusMessage: 'Administratorrechte erforderlich' }) + } + return user +} + export async function issueAuthTokens(event: H3Event, user: UserDocument) { const accessToken = createAccessToken(user) const refreshToken = createRefreshToken(user)