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:
parent
ae18912e1e
commit
2f9de759eb
3 changed files with 149 additions and 36 deletions
|
|
@ -131,6 +131,7 @@ export interface AuthTestResult {
|
||||||
durationMs: number;
|
durationMs: number;
|
||||||
error?: string;
|
error?: string;
|
||||||
detectedSignals: string[];
|
detectedSignals: string[];
|
||||||
|
logs: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthTestResults {
|
export interface AuthTestResults {
|
||||||
|
|
|
||||||
|
|
@ -732,6 +732,69 @@
|
||||||
max-width: 250px;
|
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 {
|
@keyframes spin {
|
||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
|
import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
|
||||||
import * as teamsbotApi from '../../../api/teamsbotApi';
|
import * as teamsbotApi from '../../../api/teamsbotApi';
|
||||||
import type { TeamsbotConfig, ConfigUpdateRequest, VoiceOption, AuthTestResults, AuthTestResult } 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';
|
import styles from './Teamsbot.module.css';
|
||||||
|
|
||||||
/** Format voice name for display: "de-DE-Wavenet-A" -> "Wavenet A" + gender */
|
/** 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 [testResults, setTestResults] = useState<AuthTestResults | null>(null);
|
||||||
const [testError, setTestError] = useState<string | null>(null);
|
const [testError, setTestError] = useState<string | null>(null);
|
||||||
const [screenshotPreview, setScreenshotPreview] = useState<{ src: string; caption: 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 () => {
|
const _loadConfig = useCallback(async () => {
|
||||||
if (!instanceId) return;
|
if (!instanceId) return;
|
||||||
|
|
@ -189,6 +190,28 @@ export const TeamsbotSettingsView: React.FC = () => {
|
||||||
return `${(ms / 1000).toFixed(1)}s`;
|
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>;
|
if (loading) return <div className={styles.loading}>Lade Konfiguration...</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -206,7 +229,7 @@ export const TeamsbotSettingsView: React.FC = () => {
|
||||||
<h4 className={styles.testSectionTitle}>Auth-Erkennung testen</h4>
|
<h4 className={styles.testSectionTitle}>Auth-Erkennung testen</h4>
|
||||||
</div>
|
</div>
|
||||||
<span className={styles.hint}>
|
<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.
|
Prueft ob Teams /v2/ (Auth moeglich) oder light-meetings (anonym erzwungen) liefert.
|
||||||
Der Bot tritt dem Meeting NICHT bei.
|
Der Bot tritt dem Meeting NICHT bei.
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -233,7 +256,7 @@ export const TeamsbotSettingsView: React.FC = () => {
|
||||||
{testRunning && (
|
{testRunning && (
|
||||||
<div className={styles.testProgress}>
|
<div className={styles.testProgress}>
|
||||||
<FaSpinner className={styles.spinner} />
|
<FaSpinner className={styles.spinner} />
|
||||||
5 Varianten werden sequentiell getestet (~2-3 Minuten)...
|
Test laeuft (~30-45 Sekunden)...
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -256,40 +279,66 @@ export const TeamsbotSettingsView: React.FC = () => {
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{testResults.variants.map((result) => (
|
{testResults.variants.map((result) => (
|
||||||
<tr key={result.variantId}>
|
<React.Fragment key={result.variantId}>
|
||||||
<td>
|
<tr>
|
||||||
<span className={styles.testVariantName}>{result.variantName}</span>
|
<td>
|
||||||
{result.error && (
|
<div className={styles.testVariantCell}>
|
||||||
<div className={styles.testErrorText} title={result.error}>
|
{result.logs && result.logs.length > 0 && (
|
||||||
{result.error}
|
<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>
|
</div>
|
||||||
)}
|
{result.error && (
|
||||||
</td>
|
<div className={styles.testErrorText} title={result.error}>
|
||||||
<td>{_getPageTypeBadge(result)}</td>
|
{result.error.length > 120 ? result.error.substring(0, 120) + '...' : result.error}
|
||||||
<td>{result.success ? _getCheckmark(result.hasSignInLink) : <span className={styles.testDash}>—</span>}</td>
|
</div>
|
||||||
<td>{result.success ? _getCheckmark(result.hasNameInput) : <span className={styles.testDash}>—</span>}</td>
|
)}
|
||||||
<td>{result.success ? _getCheckmark(result.hasJoinButton) : <span className={styles.testDash}>—</span>}</td>
|
</td>
|
||||||
<td>
|
<td>{_getPageTypeBadge(result)}</td>
|
||||||
{result.authAttempted
|
<td>{result.success ? _getCheckmark(result.hasSignInLink) : <span className={styles.testDash}>—</span>}</td>
|
||||||
? (result.authSuccess ? <span className={styles.testCheckmark}>Erfolgreich</span> : <span className={styles.testCross}>Fehlgeschlagen</span>)
|
<td>{result.success ? _getCheckmark(result.hasNameInput) : <span className={styles.testDash}>—</span>}</td>
|
||||||
: <span className={styles.testDash}>—</span>
|
<td>{result.success ? _getCheckmark(result.hasJoinButton) : <span className={styles.testDash}>—</span>}</td>
|
||||||
}
|
<td>
|
||||||
</td>
|
{result.authAttempted
|
||||||
<td><span className={styles.testDuration}>{_formatDuration(result.durationMs)}</span></td>
|
? (result.authSuccess ? <span className={styles.testCheckmark}>Erfolgreich</span> : <span className={styles.testCross}>Fehlgeschlagen</span>)
|
||||||
<td>
|
: <span className={styles.testDash}>—</span>
|
||||||
{result.screenshot && (
|
}
|
||||||
<button
|
</td>
|
||||||
className={styles.testScreenshotButton}
|
<td><span className={styles.testDuration}>{_formatDuration(result.durationMs)}</span></td>
|
||||||
onClick={() => setScreenshotPreview({
|
<td>
|
||||||
src: `data:image/jpeg;base64,${result.screenshot}`,
|
{result.screenshot && (
|
||||||
caption: result.variantName,
|
<button
|
||||||
})}
|
className={styles.testScreenshotButton}
|
||||||
>
|
onClick={() => setScreenshotPreview({
|
||||||
<FaImage /> Bild
|
src: `data:image/jpeg;base64,${result.screenshot}`,
|
||||||
</button>
|
caption: result.variantName,
|
||||||
)}
|
})}
|
||||||
</td>
|
>
|
||||||
</tr>
|
<FaImage /> Bild
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue