changed poll and some UI bugs.

This commit is contained in:
Philipp
2026-06-04 14:31:53 +02:00
parent 0b059aec1d
commit 2102481ed0
7 changed files with 77 additions and 14 deletions
+18 -9
View File
@@ -36,7 +36,6 @@ export default function App() {
const [loading, setLoading] = useState(true);
async function refresh() {
setError('');
try {
const [healthResult, vmsResult, jobsResult, schedulesResult, nodesResult] = await Promise.allSettled([
api.get('/health'),
@@ -47,20 +46,20 @@ export default function App() {
]);
if (healthResult.status === 'fulfilled') {
setHealth(healthResult.value.data);
setStableState(setHealth, healthResult.value.data);
} else {
setHealth(healthResult.reason.response?.data || { ok: false, checks: {} });
setStableState(setHealth, healthResult.reason.response?.data || { ok: false, checks: {} });
}
if (vmsResult.status === 'fulfilled') setVms(vmsResult.value.data);
if (jobsResult.status === 'fulfilled') setJobs(jobsResult.value.data);
if (schedulesResult.status === 'fulfilled') setSchedules(schedulesResult.value.data);
if (nodesResult.status === 'fulfilled') setNodes(nodesResult.value.data);
if (vmsResult.status === 'fulfilled') setStableState(setVms, vmsResult.value.data);
if (jobsResult.status === 'fulfilled') setStableState(setJobs, jobsResult.value.data);
if (schedulesResult.status === 'fulfilled') setStableState(setSchedules, schedulesResult.value.data);
if (nodesResult.status === 'fulfilled') setStableState(setNodes, nodesResult.value.data);
const firstFailure = [healthResult, vmsResult, jobsResult, schedulesResult, nodesResult].find((result) => result.status === 'rejected');
if (firstFailure) setError(errorMessage(firstFailure.reason));
setStableState(setError, firstFailure ? errorMessage(firstFailure.reason) : '');
} catch (requestError) {
setError(errorMessage(requestError));
setStableState(setError, errorMessage(requestError));
} finally {
setLoading(false);
}
@@ -227,6 +226,16 @@ export default function App() {
}
}
function setStableState(setter, nextValue) {
setter((currentValue) => (
stableStringify(currentValue) === stableStringify(nextValue) ? currentValue : nextValue
));
}
function stableStringify(value) {
return JSON.stringify(value);
}
function NavButton({ active, icon: Icon, label, onClick }) {
return (
<button
+36 -3
View File
@@ -4,6 +4,7 @@ import { api, errorMessage } from '../api.js';
export function Nodes({ nodes, onChanged }) {
const [drafts, setDrafts] = useState(() => nodes);
const [dirtyIds, setDirtyIds] = useState(() => new Set());
const [newNode, setNewNode] = useState({ name: '', baseUrl: '', token: '', enabled: true });
const [message, setMessage] = useState('');
const [error, setError] = useState('');
@@ -29,6 +30,7 @@ export function Nodes({ nodes, onChanged }) {
const body = { ...node };
if (!body.token) delete body.token;
await api.put(`/nodes/${encodeURIComponent(node.id)}`, body);
setDirtyIds((current) => withoutId(current, node.id));
setMessage('Node saved.');
await onChanged();
} catch (requestError) {
@@ -53,6 +55,7 @@ export function Nodes({ nodes, onChanged }) {
setMessage('');
try {
await api.delete(`/nodes/${encodeURIComponent(node.id)}`);
setDirtyIds((current) => withoutId(current, node.id));
setMessage('Node deleted.');
await onChanged();
} catch (requestError) {
@@ -61,12 +64,30 @@ export function Nodes({ nodes, onChanged }) {
}
function updateDraft(id, values) {
setDirtyIds((current) => withId(current, id));
setDrafts((current) => current.map((node) => node.id === id ? { ...node, ...values } : node));
}
async function toggleNodeEnabled(node, enabled) {
setError('');
setMessage('');
setDrafts((current) => current.map((draft) => draft.id === node.id ? { ...draft, enabled } : draft));
try {
await api.put(`/nodes/${encodeURIComponent(node.id)}`, { enabled });
setMessage(enabled ? 'Node enabled.' : 'Node disabled.');
await onChanged();
} catch (requestError) {
setError(errorMessage(requestError));
await onChanged();
}
}
useEffect(() => {
setDrafts(nodes);
}, [nodes]);
setDrafts((current) => {
const currentById = new Map(current.map((node) => [node.id, node]));
return nodes.map((node) => dirtyIds.has(node.id) ? { ...node, ...currentById.get(node.id) } : node);
});
}, [dirtyIds, nodes]);
return (
<section className="space-y-5">
@@ -107,7 +128,7 @@ export function Nodes({ nodes, onChanged }) {
{drafts.map((node) => (
<tr key={node.id}>
<td className="px-4 py-3">
<input checked={node.enabled} className="h-4 w-4 accent-cyan-500" onChange={(event) => updateDraft(node.id, { enabled: event.target.checked })} type="checkbox" />
<input checked={node.enabled} className="h-4 w-4 accent-cyan-500" onChange={(event) => toggleNodeEnabled(node, event.target.checked)} type="checkbox" />
</td>
<td className="px-4 py-3"><input className="h-9 w-full rounded-md border border-zinc-700 bg-zinc-950 px-2 text-zinc-100" onChange={(event) => updateDraft(node.id, { name: event.target.value })} value={node.name} /></td>
<td className="px-4 py-3"><input className="h-9 w-full rounded-md border border-zinc-700 bg-zinc-950 px-2 text-zinc-100" onChange={(event) => updateDraft(node.id, { baseUrl: event.target.value })} value={node.baseUrl} /></td>
@@ -131,6 +152,18 @@ export function Nodes({ nodes, onChanged }) {
);
}
function withId(set, id) {
const next = new Set(set);
next.add(id);
return next;
}
function withoutId(set, id) {
const next = new Set(set);
next.delete(id);
return next;
}
function Input({ label, onChange, placeholder = '', type = 'text', value }) {
return (
<label className="block text-sm text-zinc-300">