175 lines
6.1 KiB
TypeScript
175 lines
6.1 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { fetchClientsWithoutDescription, setClientDescription } from './apiClients';
|
|
import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
|
|
import { TextBoxComponent } from '@syncfusion/ej2-react-inputs';
|
|
import { GridComponent, ColumnsDirective, ColumnDirective } from '@syncfusion/ej2-react-grids';
|
|
import { DialogComponent } from '@syncfusion/ej2-react-popups';
|
|
import { useClientDelete } from './hooks/useClientDelete';
|
|
|
|
type Client = {
|
|
uuid: string;
|
|
hostname?: string;
|
|
ip_address?: string;
|
|
last_alive?: string;
|
|
};
|
|
|
|
const SetupMode: React.FC = () => {
|
|
const [clients, setClients] = useState<Client[]>([]);
|
|
const [descriptions, setDescriptions] = useState<Record<string, string>>({});
|
|
const [loading /* setLoading */] = useState(false);
|
|
const [inputActive, setInputActive] = useState(false);
|
|
|
|
// Lösch-Logik aus Hook (analog zu clients.tsx)
|
|
const { showDialog, deleteClientId, handleDelete, confirmDelete, cancelDelete } = useClientDelete(
|
|
async uuid => {
|
|
// Nach dem Löschen neu laden!
|
|
const updated = await fetchClientsWithoutDescription();
|
|
setClients(updated);
|
|
setDescriptions(prev => {
|
|
const copy = { ...prev };
|
|
delete copy[uuid];
|
|
return copy;
|
|
});
|
|
}
|
|
);
|
|
|
|
// Hilfsfunktion zum Vergleich der Clients
|
|
const isEqual = (a: Client[], b: Client[]) => {
|
|
if (a.length !== b.length) return false;
|
|
const aSorted = [...a].sort((x, y) => x.uuid.localeCompare(y.uuid));
|
|
const bSorted = [...b].sort((x, y) => x.uuid.localeCompare(y.uuid));
|
|
for (let i = 0; i < aSorted.length; i++) {
|
|
if (aSorted[i].uuid !== bSorted[i].uuid) return false;
|
|
if (aSorted[i].hostname !== bSorted[i].hostname) return false;
|
|
if (aSorted[i].ip_address !== bSorted[i].ip_address) return false;
|
|
if (aSorted[i].last_alive !== bSorted[i].last_alive) return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
useEffect(() => {
|
|
let polling: ReturnType<typeof setInterval> | null = null;
|
|
|
|
const fetchClients = () => {
|
|
if (inputActive) return;
|
|
fetchClientsWithoutDescription().then(list => {
|
|
setClients(prev => (isEqual(prev, list) ? prev : list));
|
|
});
|
|
};
|
|
|
|
fetchClients();
|
|
polling = setInterval(fetchClients, 5000);
|
|
|
|
return () => {
|
|
if (polling) clearInterval(polling);
|
|
};
|
|
}, [inputActive]);
|
|
|
|
const handleDescriptionChange = (uuid: string, value: string) => {
|
|
setDescriptions(prev => ({ ...prev, [uuid]: value }));
|
|
};
|
|
|
|
const handleSave = (uuid: string) => {
|
|
setClientDescription(uuid, descriptions[uuid] || '')
|
|
.then(() => {
|
|
setClients(prev => prev.filter(c => c.uuid !== uuid));
|
|
})
|
|
.catch(err => {
|
|
console.error('Fehler beim Speichern der Beschreibung:', err);
|
|
});
|
|
};
|
|
|
|
if (loading) return <div>Lade neue Clients ...</div>;
|
|
|
|
return (
|
|
<div>
|
|
<h2>Erweiterungsmodus: Neue Clients zuordnen</h2>
|
|
<GridComponent
|
|
dataSource={clients}
|
|
allowPaging={true}
|
|
pageSettings={{ pageSize: 10 }}
|
|
rowHeight={50}
|
|
width="100%"
|
|
allowTextWrap={false}
|
|
>
|
|
<ColumnsDirective>
|
|
<ColumnDirective field="uuid" headerText="UUID" width="180" />
|
|
<ColumnDirective field="hostname" headerText="Hostname" width="90" />
|
|
<ColumnDirective field="ip_address" headerText="IP" width="80" />
|
|
<ColumnDirective
|
|
headerText="Letzter Kontakt"
|
|
width="120"
|
|
template={(props: Client) => {
|
|
if (!props.last_alive) return '';
|
|
let iso = props.last_alive;
|
|
if (!iso.endsWith('Z')) iso += 'Z';
|
|
const date = new Date(iso);
|
|
const pad = (n: number) => n.toString().padStart(2, '0');
|
|
return `${pad(date.getDate())}.${pad(date.getMonth() + 1)}.${date.getFullYear()} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
}}
|
|
/>
|
|
<ColumnDirective
|
|
headerText="Beschreibung"
|
|
width="220"
|
|
template={(props: Client) => (
|
|
<TextBoxComponent
|
|
value={descriptions[props.uuid] || ''}
|
|
placeholder="Beschreibung eingeben"
|
|
change={e => handleDescriptionChange(props.uuid, e.value as string)}
|
|
focus={() => setInputActive(true)}
|
|
blur={() => setInputActive(false)}
|
|
/>
|
|
)}
|
|
/>
|
|
<ColumnDirective
|
|
headerText="Aktion"
|
|
width="180"
|
|
template={(props: Client) => (
|
|
<div style={{ display: 'flex', gap: '8px' }}>
|
|
<ButtonComponent
|
|
content="Speichern"
|
|
disabled={!descriptions[props.uuid]}
|
|
onClick={() => handleSave(props.uuid)}
|
|
/>
|
|
<ButtonComponent
|
|
content="Entfernen"
|
|
cssClass="e-danger"
|
|
onClick={e => {
|
|
e.stopPropagation();
|
|
handleDelete(props.uuid);
|
|
}}
|
|
/>
|
|
</div>
|
|
)}
|
|
/>
|
|
</ColumnsDirective>
|
|
</GridComponent>
|
|
{clients.length === 0 && <div>Keine neuen Clients ohne Beschreibung.</div>}
|
|
|
|
{/* Syncfusion Dialog für Sicherheitsabfrage */}
|
|
{showDialog && deleteClientId && (
|
|
<DialogComponent
|
|
visible={showDialog}
|
|
header="Bestätigung"
|
|
content={(() => {
|
|
const client = clients.find(c => c.uuid === deleteClientId);
|
|
const hostname = client?.hostname ? ` (${client.hostname})` : '';
|
|
return client
|
|
? `Möchten Sie diesen Client${hostname} wirklich entfernen?`
|
|
: 'Client nicht gefunden.';
|
|
})()}
|
|
showCloseIcon={true}
|
|
width="400px"
|
|
buttons={[
|
|
{ click: confirmDelete, buttonModel: { content: 'Ja', isPrimary: true } },
|
|
{ click: cancelDelete, buttonModel: { content: 'Abbrechen' } },
|
|
]}
|
|
close={cancelDelete}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SetupMode;
|