feat(teamsbot): show per-variant logs in auth test UI, single direct /v2/ test

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ValueOn AG 2026-02-16 23:06:55 +01:00
parent ae18912e1e
commit 2f9de759eb
3 changed files with 149 additions and 36 deletions

View file

@ -131,6 +131,7 @@ export interface AuthTestResult {
durationMs: number;
error?: string;
detectedSignals: string[];
logs: string[];
}
export interface AuthTestResults {

View file

@ -732,6 +732,69 @@
max-width: 250px;
}
/* Variant cell with expand toggle */
.testVariantCell {
display: flex;
align-items: center;
gap: 0.35rem;
}
.testLogToggle {
display: inline-flex;
align-items: center;
justify-content: center;
background: none;
border: none;
cursor: pointer;
padding: 0.15rem;
font-size: 0.7rem;
color: var(--text-secondary, #666);
border-radius: 3px;
flex-shrink: 0;
}
.testLogToggle:hover {
background: var(--surface-alt, #f5f5f5);
color: var(--primary-color, #4A90D9);
}
/* Log row */
.testLogRow td {
padding: 0 !important;
border-top: none !important;
}
.testLogContainer {
background: var(--surface-dark, #1e1e1e);
color: var(--text-light, #d4d4d4);
font-family: 'Fira Code', 'Consolas', 'Courier New', monospace;
font-size: 0.72rem;
line-height: 1.5;
padding: 0.5rem 0.75rem;
max-height: 200px;
overflow-y: auto;
border-radius: 0 0 4px 4px;
margin: 0 0.5rem 0.5rem;
}
.testLogLine {
white-space: pre-wrap;
word-break: break-all;
padding: 0.05rem 0;
}
.testLogInfo {
color: var(--text-light, #d4d4d4);
}
.testLogWarn {
color: #e5a100;
}
.testLogError {
color: #f14c4c;
}
@keyframes spin {
to { transform: rotate(360deg); }
}

View file

@ -2,7 +2,7 @@ import React, { useState, useEffect, useCallback, useRef } from 'react';
import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
import * as teamsbotApi from '../../../api/teamsbotApi';
import type { TeamsbotConfig, ConfigUpdateRequest, VoiceOption, AuthTestResults, AuthTestResult } from '../../../api/teamsbotApi';
import { FaPlay, FaSpinner, FaFlask, FaImage } from 'react-icons/fa';
import { FaPlay, FaSpinner, FaFlask, FaImage, FaChevronDown, FaChevronRight } from 'react-icons/fa';
import styles from './Teamsbot.module.css';
/** Format voice name for display: "de-DE-Wavenet-A" -> "Wavenet A" + gender */
@ -43,6 +43,7 @@ export const TeamsbotSettingsView: React.FC = () => {
const [testResults, setTestResults] = useState<AuthTestResults | null>(null);
const [testError, setTestError] = useState<string | null>(null);
const [screenshotPreview, setScreenshotPreview] = useState<{ src: string; caption: string } | null>(null);
const [expandedLogs, setExpandedLogs] = useState<Set<string>>(new Set());
const _loadConfig = useCallback(async () => {
if (!instanceId) return;
@ -189,6 +190,28 @@ export const TeamsbotSettingsView: React.FC = () => {
return `${(ms / 1000).toFixed(1)}s`;
};
const _toggleLogs = (variantId: string) => {
setExpandedLogs(prev => {
const next = new Set(prev);
if (next.has(variantId)) {
next.delete(variantId);
} else {
next.add(variantId);
}
return next;
});
};
const _getLogLevelClass = (log: string): string => {
if (log.startsWith('[ERROR]') || log.startsWith('[CONSOLE:error]') || log.startsWith('[PAGE_ERROR]')) {
return styles.testLogError;
}
if (log.startsWith('[WARN]') || log.startsWith('[CONSOLE:warning]')) {
return styles.testLogWarn;
}
return styles.testLogInfo;
};
if (loading) return <div className={styles.loading}>Lade Konfiguration...</div>;
return (
@ -206,7 +229,7 @@ export const TeamsbotSettingsView: React.FC = () => {
<h4 className={styles.testSectionTitle}>Auth-Erkennung testen</h4>
</div>
<span className={styles.hint}>
Testet 5 Browser-Konfigurationen gegen einen aktiven Teams-Call.
Testet Direct-/v2/-Navigation gegen einen aktiven Teams-Call.
Prueft ob Teams /v2/ (Auth moeglich) oder light-meetings (anonym erzwungen) liefert.
Der Bot tritt dem Meeting NICHT bei.
</span>
@ -233,7 +256,7 @@ export const TeamsbotSettingsView: React.FC = () => {
{testRunning && (
<div className={styles.testProgress}>
<FaSpinner className={styles.spinner} />
5 Varianten werden sequentiell getestet (~2-3 Minuten)...
Test laeuft (~30-45 Sekunden)...
</div>
)}
@ -256,12 +279,24 @@ export const TeamsbotSettingsView: React.FC = () => {
</thead>
<tbody>
{testResults.variants.map((result) => (
<tr key={result.variantId}>
<React.Fragment key={result.variantId}>
<tr>
<td>
<div className={styles.testVariantCell}>
{result.logs && result.logs.length > 0 && (
<button
className={styles.testLogToggle}
onClick={() => _toggleLogs(result.variantId)}
title="Logs anzeigen/ausblenden"
>
{expandedLogs.has(result.variantId) ? <FaChevronDown /> : <FaChevronRight />}
</button>
)}
<span className={styles.testVariantName}>{result.variantName}</span>
</div>
{result.error && (
<div className={styles.testErrorText} title={result.error}>
{result.error}
{result.error.length > 120 ? result.error.substring(0, 120) + '...' : result.error}
</div>
)}
</td>
@ -290,6 +325,20 @@ export const TeamsbotSettingsView: React.FC = () => {
)}
</td>
</tr>
{expandedLogs.has(result.variantId) && result.logs && result.logs.length > 0 && (
<tr className={styles.testLogRow}>
<td colSpan={8}>
<div className={styles.testLogContainer}>
{result.logs.map((log, idx) => (
<div key={idx} className={`${styles.testLogLine} ${_getLogLevelClass(log)}`}>
{log}
</div>
))}
</div>
</td>
</tr>
)}
</React.Fragment>
))}
</tbody>
</table>