update
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
import { useRef, useEffect } from 'react';
|
import { useRef, useEffect, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
Box, Button, Card, CardContent, Chip, CircularProgress, Typography,
|
Box, Button, Card, CardContent, Chip, CircularProgress, IconButton, Tooltip, Typography,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import SyncIcon from '@mui/icons-material/Sync';
|
import SyncIcon from '@mui/icons-material/Sync';
|
||||||
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { adminApi } from '../../services/admin';
|
import { adminApi } from '../../services/admin';
|
||||||
import { useNotification } from '../../contexts/NotificationContext';
|
import { useNotification } from '../../contexts/NotificationContext';
|
||||||
@@ -41,6 +42,14 @@ function FdiskSyncTab() {
|
|||||||
|
|
||||||
const running = data?.running ?? false;
|
const running = data?.running ?? false;
|
||||||
|
|
||||||
|
const copyLogs = useCallback(() => {
|
||||||
|
const text = (data?.logs ?? []).map((e) => e.line).join('\n');
|
||||||
|
navigator.clipboard.writeText(text).then(
|
||||||
|
() => showSuccess('Logs kopiert'),
|
||||||
|
() => showError('Kopieren fehlgeschlagen'),
|
||||||
|
);
|
||||||
|
}, [data?.logs, showSuccess, showError]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
<Card>
|
<Card>
|
||||||
@@ -73,7 +82,20 @@ function FdiskSyncTab() {
|
|||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="subtitle2" gutterBottom>Protokoll (letzte 500 Zeilen)</Typography>
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
|
||||||
|
<Typography variant="subtitle2">Protokoll (letzte 500 Zeilen)</Typography>
|
||||||
|
<Tooltip title="Logs kopieren">
|
||||||
|
<span>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={copyLogs}
|
||||||
|
disabled={!data?.logs?.length}
|
||||||
|
>
|
||||||
|
<ContentCopyIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
||||||
<CircularProgress size={28} />
|
<CircularProgress size={28} />
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ const ID_FEUERWEHREN = process.env.FDISK_ID_FEUERWEHREN ?? '164';
|
|||||||
const ID_INSTANZEN = process.env.FDISK_ID_INSTANZEN ?? '2853';
|
const ID_INSTANZEN = process.env.FDISK_ID_INSTANZEN ?? '2853';
|
||||||
|
|
||||||
const LOGIN_URL = `${BASE_URL}/fdisk/module/vws/logins/logins.aspx`;
|
const LOGIN_URL = `${BASE_URL}/fdisk/module/vws/logins/logins.aspx`;
|
||||||
const MEMBERS_URL = `${BASE_URL}/fdisk/module/vws/vws/MitgliedschaftenList.aspx`
|
const MEMBERS_URL = `${BASE_URL}/fdisk/module/mgvw/mitgliedschaften/meine_Mitglieder.aspx`;
|
||||||
+ `?id_instanzen=${ID_INSTANZEN}`
|
|
||||||
+ `&id_feuerwehren=${ID_FEUERWEHREN}`;
|
|
||||||
|
|
||||||
function log(msg: string) {
|
function log(msg: string) {
|
||||||
console.log(`[scraper] ${new Date().toISOString()} ${msg}`);
|
console.log(`[scraper] ${new Date().toISOString()} ${msg}`);
|
||||||
@@ -117,36 +115,21 @@ async function login(page: Page, username: string, password: string): Promise<vo
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function navigateToMemberList(page: Page): Promise<Frame> {
|
async function navigateToMemberList(page: Page): Promise<Frame> {
|
||||||
const menuFrame = page.frame({ name: 'menu' });
|
const mainFrame = page.frame({ name: 'mainFrame' });
|
||||||
if (!menuFrame) throw new Error('Menu frame (left.aspx) not found in Start.aspx frameset');
|
if (!mainFrame) throw new Error('mainFrame not found in Start.aspx frameset');
|
||||||
|
|
||||||
await menuFrame.waitForLoadState('networkidle');
|
log(`Navigating mainFrame to: ${MEMBERS_URL}`);
|
||||||
|
await mainFrame.goto(MEMBERS_URL, { waitUntil: 'domcontentloaded' });
|
||||||
|
await mainFrame.waitForLoadState('networkidle');
|
||||||
|
|
||||||
// Log all menu links for diagnostics
|
const url = mainFrame.url();
|
||||||
const menuLinks = await menuFrame.$$eval('a', (as) =>
|
const title = await mainFrame.title();
|
||||||
as.map((a) => ({ text: (a.textContent ?? '').trim(), href: a.href })).filter((l) => l.href),
|
log(`mainFrame loaded: ${url} — title: "${title}"`);
|
||||||
);
|
|
||||||
log(`Menu links (${menuLinks.length}): ${JSON.stringify(menuLinks.slice(0, 20))}`);
|
|
||||||
|
|
||||||
// Find the Mitgliedschaften link and click it — this sets the server-side session
|
if (url.includes('BLError') || url.includes('support.aspx') || url.includes('Error')) {
|
||||||
// context and navigates mainFrame to the correct URL
|
throw new Error(`Member list returned error page: ${url}`);
|
||||||
const mitgliedLink = menuFrame.locator('a[href*="MitgliedschaftenList"], a[href*="mitglied" i]').first();
|
|
||||||
const found = await mitgliedLink.count() > 0;
|
|
||||||
if (!found) {
|
|
||||||
throw new Error('Could not find Mitgliedschaften link in menu frame — check menu link log above');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const linkHref = await mitgliedLink.getAttribute('href');
|
|
||||||
log(`Clicking menu link: ${linkHref}`);
|
|
||||||
await mitgliedLink.click();
|
|
||||||
|
|
||||||
// Wait for mainFrame to load the member list
|
|
||||||
await page.waitForLoadState('networkidle');
|
|
||||||
|
|
||||||
const mainFrame = page.frame({ name: 'mainFrame' });
|
|
||||||
if (!mainFrame) throw new Error('mainFrame not found after menu navigation');
|
|
||||||
|
|
||||||
log(`mainFrame after menu click: ${mainFrame.url()}`);
|
|
||||||
return mainFrame;
|
return mainFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,6 +146,12 @@ async function scrapeMembers(frame: Frame): Promise<FdiskMember[]> {
|
|||||||
log(`After form submit: ${frame.url()}`);
|
log(`After form submit: ${frame.url()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log tables found for diagnostics
|
||||||
|
const tableInfo = await frame.$$eval('table', (ts) =>
|
||||||
|
ts.map((t) => `${t.className || '(no-class)'}[${t.querySelectorAll('tr').length}rows]`),
|
||||||
|
);
|
||||||
|
log(`Tables: ${tableInfo.join(', ') || 'none'}`);
|
||||||
|
|
||||||
// The member table uses class FdcLayList
|
// The member table uses class FdcLayList
|
||||||
await frame.waitForSelector('table.FdcLayList', { timeout: 20000 });
|
await frame.waitForSelector('table.FdcLayList', { timeout: 20000 });
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user