Initial commit - copied workspace after database cleanup
This commit is contained in:
174
dashboard/src/SetupMode.tsx
Normal file
174
dashboard/src/SetupMode.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user