Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update macro events real-time #294

Open
wants to merge 15 commits into
base: dev
Choose a base branch
from
96 changes: 76 additions & 20 deletions dashboard/src/data/EventStream.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useUid, useUserInfo } from 'data/UserInfo';
import { useUid } from 'data/UserInfo';
import { addInstance, deleteInstance, updateInstance } from 'data/InstanceList';
import { LodestoneContext } from 'data/LodestoneContext';
import { useQueryClient } from '@tanstack/react-query';
Expand All @@ -14,6 +14,9 @@ import { UserPermission } from 'bindings/UserPermission';
import { PublicUser } from 'bindings/PublicUser';
import { toast } from 'react-toastify';
import { Player } from 'bindings/Player';
import { TaskEntry } from '../bindings/TaskEntry';
hanmindev marked this conversation as resolved.
Show resolved Hide resolved
import { getTasks } from '../utils/apis';
import { HistoryEntry } from '../bindings/HistoryEntry';

/**
* does not return anything, call this for the side effect of subscribing to the event stream
Expand Down Expand Up @@ -248,7 +251,10 @@ export const useEventStream = () => {
['user', 'list'],
(oldList: { [uid: string]: PublicUser } | undefined) => {
if (!oldList) return oldList;
const newUser = {...oldList[uid], permissions: new_permissions};
const newUser = {
...oldList[uid],
permissions: new_permissions,
};
const newList = { ...oldList };
newList[uid] = newUser;
return newList;
Expand All @@ -270,6 +276,7 @@ export const useEventStream = () => {
match(event_inner, {
Started: () => {
console.log(`Macro ${macro_pid} started on ${uuid}`);
queryClient.invalidateQueries(['instance', uuid, 'taskList']); // need to invalidate the query to get the new task list
hanmindev marked this conversation as resolved.
Show resolved Hide resolved
dispatch({
title: `Macro ${macro_pid} started on ${uuid}`,
event,
Expand All @@ -278,16 +285,57 @@ export const useEventStream = () => {
});
},
Detach: () => {
console.log(`Macro ${macro_pid} detached on ${uuid}`);
dispatch({
title: `Macro ${macro_pid} detached on ${uuid}`,
event,
type: 'add',
fresh,
});
console.log(`Macro ${macro_pid} detached on ${uuid}`);
dispatch({
title: `Macro ${macro_pid} detached on ${uuid}`,
event,
type: 'add',
fresh,
});
},
Stopped: ({ exit_status }) => {
console.log(`Macro ${macro_pid} stopped on ${uuid} with status ${exit_status.type}`);
console.log(
`Macro ${macro_pid} stopped on ${uuid} with status ${exit_status.type}`
);

let oldTask: TaskEntry | undefined;
queryClient.setQueryData(
['instance', uuid, 'taskList'],
(oldData: TaskEntry[] | undefined): TaskEntry[] | undefined => {
if (oldData === undefined) {
return undefined;
}
return oldData.filter((task) => {
const shouldKeep = task.pid !== macro_pid;
if (!shouldKeep) {
oldTask = task;
}

return shouldKeep;
});
}
);
hanmindev marked this conversation as resolved.
Show resolved Hide resolved

queryClient.setQueryData(
['instance', uuid, 'historyList'],
(
oldData: HistoryEntry[] | undefined
hanmindev marked this conversation as resolved.
Show resolved Hide resolved
): HistoryEntry[] | undefined => {
if (oldTask === undefined) {
hanmindev marked this conversation as resolved.
Show resolved Hide resolved
return oldData;
}
const newHistory: HistoryEntry = {
task: oldTask,
exit_status,
};

if (oldData === undefined) {
return [newHistory];
}

return [newHistory, ...oldData];
hanmindev marked this conversation as resolved.
Show resolved Hide resolved
}
);
dispatch({
title: `Macro ${macro_pid} stopped on ${uuid} with status ${exit_status.type}`,
event,
Expand Down Expand Up @@ -320,14 +368,22 @@ export const useEventStream = () => {
addInstance(instance_info, queryClient),
InstanceDelete: ({ instance_uuid: uuid }) =>
deleteInstance(uuid, queryClient),
FSOperationCompleted: ({ instance_uuid, success, message }) => {
FSOperationCompleted: ({
instance_uuid,
success,
message,
}) => {
if (success) {
toast.success(message)
toast.success(message);
} else {
toast.error(message)
toast.error(message);
}
queryClient.invalidateQueries(['instance', instance_uuid, 'fileList']);
}
queryClient.invalidateQueries([
'instance',
instance_uuid,
'fileList',
]);
},
},
// eslint-disable-next-line @typescript-eslint/no-empty-function
(_) => {}
Expand Down Expand Up @@ -407,11 +463,11 @@ export const useEventStream = () => {
if (!token) return;

const connectWebsocket = () => {
const wsAddress = `${core.protocol === 'https' ? 'wss' : 'ws'}://${core.address}:${
core.port ?? LODESTONE_PORT
}/api/${core.apiVersion}/events/all/stream?filter=${JSON.stringify(
eventQuery
)}`;
const wsAddress = `${core.protocol === 'https' ? 'wss' : 'ws'}://${
core.address
}:${core.port ?? LODESTONE_PORT}/api/${
core.apiVersion
}/events/all/stream?filter=${JSON.stringify(eventQuery)}`;

if (wsRef.current) wsRef.current.close();

Expand Down
197 changes: 111 additions & 86 deletions dashboard/src/pages/macros.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,18 @@ import {
killTask,
} from 'utils/apis';
import { InstanceContext } from 'data/InstanceContext';
import { useContext, useEffect, useState, useMemo } from 'react';
import { useContext, useState, useMemo } from 'react';
import { MacroEntry } from 'bindings/MacroEntry';
import clsx from 'clsx';
import { useQueryClient } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { toast } from 'react-toastify';
import { TaskEntry } from '../bindings/TaskEntry';
import { HistoryEntry } from '../bindings/HistoryEntry';

export type MacrosPage = 'All Macros' | 'Running Tasks' | 'History';
const Macros = () => {
useDocumentTitle('Instance Macros - Lodestone');
const { selectedInstance } = useContext(InstanceContext);
const [macros, setMacros] = useState<TableRow[]>([]);
const [tasks, setTasks] = useState<TableRow[]>([]);
const [history, setHistory] = useState<TableRow[]>([]);

const unixToFormattedTime = (unix: string | undefined) => {
if (!unix) return 'N/A';
const date = new Date(parseInt(unix) * 1000);
Expand All @@ -42,66 +40,52 @@ const Macros = () => {

const queryClient = useQueryClient();

const fetchMacros = async (instanceUuid: string) => {
const response: MacroEntry[] = await getMacros(instanceUuid);
setMacros(
response.map(
(macro, i) =>
({
id: i + 1,
name: macro.name,
last_run: unixToFormattedTime(macro.last_run?.toString()),
path: macro.path,
} as TableRow)
)
);
};

const fetchTasks = async (instanceUuid: string) => {
const response = await getTasks(instanceUuid);
setTasks(
response.map(
(task, i) =>
({
id: i + 1,
name: task.name,
creation_time: unixToFormattedTime(task.creation_time.toString()),
pid: task.pid,
} as TableRow)
)
);
};

const fetchHistory = async (instanceUuid: string) => {
const response = await getInstanceHistory(instanceUuid);
setHistory(
response.map(
(entry, i) =>
({
id: i + 1,
name: entry.task.name,
creation_time: unixToFormattedTime(
entry.task.creation_time.toString()
),
finished: unixToFormattedTime(entry.exit_status.time.toString()),
process_id: entry.task.pid,
} as TableRow)
)
);
};

useEffect(() => {
if (!selectedInstance) return;
const macros = useQuery(
['instance', selectedInstance?.uuid, 'macroList'],
() => getMacros(selectedInstance?.uuid as string),
{ enabled: !!selectedInstance, initialData: [], refetchOnMount: 'always' }
).data.map(
hanmindev marked this conversation as resolved.
Show resolved Hide resolved
(macro, i) =>
({
id: i + 1,
name: macro.name,
last_run: unixToFormattedTime(macro.last_run?.toString()),
path: macro.path,
} as TableRow)
);

const fetchAll = async () => {
if (!selectedInstance) return;
fetchMacros(selectedInstance.uuid);
fetchTasks(selectedInstance.uuid);
fetchHistory(selectedInstance.uuid);
};
const tasks = useQuery(
['instance', selectedInstance?.uuid, 'taskList'],
() => getTasks(selectedInstance?.uuid as string),
{ enabled: !!selectedInstance, initialData: [], refetchOnMount: 'always' }
).data.map(
(task, i) =>
hanmindev marked this conversation as resolved.
Show resolved Hide resolved
({
id: i + 1,
name: task.name,
creation_time: unixToFormattedTime(task.creation_time?.toString()),
pid: task.pid,
} as TableRow)
);
hanmindev marked this conversation as resolved.
Show resolved Hide resolved

fetchAll();
}, [selectedInstance]);
const history = useQuery(
['instance', selectedInstance?.uuid, 'historyList'],
() => getInstanceHistory(selectedInstance?.uuid as string),
{
enabled: !!selectedInstance,
initialData: [],
refetchOnMount: 'always',
}
).data.map(
(entry, i) =>
({
id: i + 1,
name: entry.task.name,
creation_time: unixToFormattedTime(entry.task.creation_time.toString()),
finished: unixToFormattedTime(entry.exit_status.time.toString()),
process_id: entry.task.pid,
} as TableRow)
hanmindev marked this conversation as resolved.
Show resolved Hide resolved
);

const [selectedPage, setSelectedPage] = useState<MacrosPage>('All Macros');

Expand Down Expand Up @@ -136,18 +120,25 @@ const Macros = () => {
row.name as string,
[]
);
const newMacros = macros.map((macro) => {
if (macro.name !== row.name) {
return macro;

queryClient.setQueryData(
['instance', selectedInstance?.uuid, 'macroList'],
(oldData: MacroEntry[] | undefined) => {
hanmindev marked this conversation as resolved.
Show resolved Hide resolved
if (oldData === undefined) {
return undefined;
}
return oldData.map((macro) => {
if (macro.name !== row.name) {
return macro;
}
const newMacro = { ...macro };
newMacro.last_run = BigInt(
Math.floor(Date.now() / 1000).toString()
);
return newMacro;
});
}
const newMacro = { ...macro };
newMacro.last_run = unixToFormattedTime(
Math.floor(Date.now() / 1000).toString()
);
return newMacro;
});
setMacros(newMacros);
fetchTasks(selectedInstance.uuid);
);
},
},
],
Expand Down Expand Up @@ -188,17 +179,51 @@ const Macros = () => {
selectedInstance.uuid,
row.pid as string
);
setTasks(tasks.filter((task) => task.id !== row.id)); //rather than refetching, we just update the display
const newHistory = {
id: row.id,
name: row.name,
creation_time: row.creation_time,
finished: unixToFormattedTime(
Math.floor(Date.now() / 1000).toString()
), //unix time in seconds
process_id: row.pid,
};
setHistory([newHistory, ...history]);

let oldTask: TaskEntry | undefined;

queryClient.setQueryData(
['instance', selectedInstance?.uuid, 'taskList'],
(
oldData: TaskEntry[] | undefined
): TaskEntry[] | undefined => {
hanmindev marked this conversation as resolved.
Show resolved Hide resolved
if (oldData === undefined) {
return undefined;
}
return oldData.filter((task) => {
const shouldKeep = task.pid !== row.pid;
if (!shouldKeep) {
oldTask = task;
}

return shouldKeep;
});
}
);

queryClient.setQueryData(
['instance', selectedInstance?.uuid, 'historyList'],
(
oldData: HistoryEntry[] | undefined
hanmindev marked this conversation as resolved.
Show resolved Hide resolved
): HistoryEntry[] | undefined => {
if (oldTask === undefined) {
return oldData;
}
const newHistory: HistoryEntry = {
task: oldTask,
exit_status: {
type: 'Killed',
time: BigInt(Math.floor(Date.now() / 1000).toString()),
},
};

if (oldData === undefined) {
return [newHistory];
}

return [newHistory, ...oldData];
}
);
},
},
],
Expand Down