Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
WIP: add scheduler commands
  • Loading branch information
beastafk committed Mar 31, 2025
commit ea8c1c94450ec4abec5d4fecbd104a4543b7740a
3 changes: 2 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import dataProvider from "./synapse/dataProvider";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import ServerStatusPage from "./components/etke.cc/ServerStatusPage";
import ServerNotificationsPage from "./components/etke.cc/ServerNotificationsPage";

import SchedulerCommandsPage from "./components/etke.cc/SchedulerCommandsPage";
// TODO: Can we use lazy loading together with browser locale?
const messages = {
de: germanMessages,
Expand Down Expand Up @@ -66,6 +66,7 @@ export const App = () => (
<CustomRoutes>
<Route path="/import_users" element={<UserImport />} />
<Route path="/server_status" element={<ServerStatusPage />} />
<Route path="/scheduler_commands" element={<SchedulerCommandsPage />} />
<Route path="/server_notifications" element={<ServerNotificationsPage />} />
</CustomRoutes>
<Resource {...users} />
Expand Down
18 changes: 10 additions & 8 deletions src/components/AdminLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ServerStatusBadge from "./etke.cc/ServerStatusBadge";
import { ServerNotificationsBadge } from "./etke.cc/ServerNotificationsBadge";
import { ServerProcessResponse, ServerStatusResponse } from "../synapse/dataProvider";
import { ServerStatusStyledBadge } from "./etke.cc/ServerStatusBadge";
import ManageHistoryIcon from '@mui/icons-material/ManageHistory';

const AdminUserMenu = () => {
const [open, setOpen] = useState(false);
Expand Down Expand Up @@ -60,27 +61,28 @@ const AdminAppBar = () => {

const AdminMenu = (props) => {
const [menu, setMenu] = useState([] as MenuItem[]);
const [serverStatusEnabled, setServerStatusEnabled] = useState(false);
const [etkeRoutesEnabled, setEtkeRoutesEnabled] = useState(false);
useEffect(() => {
setMenu(GetConfig().menu);
if (GetConfig().etkeccAdmin) {
setServerStatusEnabled(true);
setEtkeRoutesEnabled(true);
}
}, []);
const [serverProcess, setServerProcess] = useStore<ServerProcessResponse>("serverProcess", { command: "", locked_at: "" });
const [serverStatus, setServerStatus] = useStore<ServerStatusResponse>("serverStatus", { success: false, ok: false, host: "", results: [] });

return (
<Menu {...props}>
{serverStatusEnabled && <Menu.Item to="/server_status" leftIcon={
<ServerStatusStyledBadge
inSidebar={true}
command={serverProcess.command}
locked_at={serverProcess.locked_at}
isOkay={serverStatus.ok} />
{etkeRoutesEnabled && <Menu.Item key="server_status" to="/server_status" leftIcon={
<ServerStatusStyledBadge
inSidebar={true}
command={serverProcess.command}
locked_at={serverProcess.locked_at}
isOkay={serverStatus.ok} />
}
primaryText="Server Status" />
}
{etkeRoutesEnabled && <Menu.Item key="scheduler_commands" to="/scheduler_commands" leftIcon={<ManageHistoryIcon />} primaryText="Scheduler Commands" />}
<Menu.ResourceItems />
{menu && menu.map((item, index) => {
const { url, icon, label } = item;
Expand Down
120 changes: 120 additions & 0 deletions src/components/etke.cc/SchedulerCommandsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { Box, Paper, Typography } from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import { Stack } from "@mui/material"
import { BooleanField, Datagrid, DateField, List, ListContextProvider, Loading, ResourceContextProvider, TextField, useDataProvider, useList } from "react-admin";
import { useAppContext } from "../../Context";
import ScheduleIcon from '@mui/icons-material/Schedule';
import RestoreIcon from '@mui/icons-material/Restore';
import { DATE_FORMAT } from "../../utils/date";
const useScheduledCommands = () => {
const { etkeccAdmin } = useAppContext();
const dataProvider = useDataProvider();
const { data, isLoading, error } = useQuery({
queryKey: ["scheduledCommands"],
queryFn: () => dataProvider.getScheduledCommands(etkeccAdmin),
});

return { data, isLoading, error };
};

const ScheduledCommandsList = () => {
const { data, isLoading, error } = useScheduledCommands();
const listContext = useList({
resource: "scheduled_commands",
sort: { field: "scheduled_at", order: "DESC" },
perPage: 10,
data: data || [], // Provide the data from your custom hook
isLoading: isLoading,
});
if (isLoading) return <Loading />;

return (<ResourceContextProvider value="scheduled_commands">
<ListContextProvider value={listContext}>
<Paper>
<Datagrid bulkActionButtons={false} rowClick="edit">
<TextField source="command" />
<TextField source="id" />
<TextField source="args" />
<BooleanField source="is_recurring" />
<DateField options={DATE_FORMAT} showTime source="scheduled_at" />
</Datagrid>
</Paper>
</ListContextProvider>
</ResourceContextProvider>
);
};

const useRecurringCommands = () => {
const { etkeccAdmin } = useAppContext();
const dataProvider = useDataProvider();
const { data, isLoading, error } = useQuery({
queryKey: ["recurringCommands"],
queryFn: () => dataProvider.getRecurringCommands(etkeccAdmin),
});

return { data, isLoading, error };
};

const RecurringCommandsList = () => {
const { data, isLoading, error } = useRecurringCommands();
const listContext = useList({
resource: "scheduled_commands",
sort: { field: "scheduled_at", order: "DESC" },
perPage: 10,
data: data || [], // Provide the data from your custom hook
isLoading: isLoading,
});
if (isLoading) return <Loading />;

return (<ResourceContextProvider value="scheduled_commands">
<ListContextProvider value={listContext}>
<Paper>
<Datagrid bulkActionButtons={false} rowClick="edit">
<TextField source="command" />
<TextField source="id" />
<TextField source="args" />
<TextField source="time" />
<DateField options={DATE_FORMAT} showTime source="scheduled_at" />
</Datagrid>
</Paper>
</ListContextProvider>
</ResourceContextProvider>
);
};

const SchedulerCommandsPage = () => {
const { data, isLoading, error } = useScheduledCommands();
const listContext = useList({
resource: "scheduled_commands",
sort: { field: "scheduled_at", order: "DESC" },
perPage: 10,
data: data || [], // Provide the data from your custom hook
isLoading: isLoading,
});
console.log("D1", data);
console.log("D12", listContext);

return (
<Stack spacing={3} mt={3}>
<Stack spacing={1} direction="row" alignItems="center">
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
<Typography variant="h4">Scheduler Commands</Typography>
</Box>
</Stack>
<Stack spacing={1} direction="column" alignItems="center">
<Box sx={{ width: "100%" }}>
<Typography sx={{ mb: 2 }} variant="h5"><ScheduleIcon sx={{ verticalAlign: "middle" }} /> Scheduled:</Typography>
<ScheduledCommandsList />
</Box>
</Stack>
<Stack spacing={1} direction="column" alignItems="center">
<Box sx={{ width: "100%" }}>
<Typography sx={{ mb: 2 }} variant="h5"><RestoreIcon sx={{ verticalAlign: "middle" }} /> Recurring:</Typography>
<RecurringCommandsList />
</Box>
</Stack>
</Stack>
);
};

export default SchedulerCommandsPage;
68 changes: 68 additions & 0 deletions src/synapse/dataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,22 @@ export interface ServerCommandsResponse {
[command: string]: ServerCommand;
}

export interface ScheduledCommand {
args: string;
command: string;
id: string;
is_recurring: boolean;
scheduled_at: string;
}

export interface RecurringCommand {
args: string;
command: string;
id: string;
scheduled_at: string;
time: string;
}

export interface SynapseDataProvider extends DataProvider {
deleteMedia: (params: DeleteMediaParams) => Promise<DeleteMediaResult>;
purgeRemoteMedia: (params: DeleteMediaParams) => Promise<DeleteMediaResult>;
Expand All @@ -343,6 +359,8 @@ export interface SynapseDataProvider extends DataProvider {
getServerNotifications: (etkeAdminUrl: string) => Promise<ServerNotificationsResponse>;
deleteServerNotifications: (etkeAdminUrl: string) => Promise<{ success: boolean }>;
getServerCommands: (etkeAdminUrl: string) => Promise<ServerCommandsResponse>;
getScheduledCommands: (etkeAdminUrl: string) => Promise<ScheduledCommand[]>;
getRecurringCommands: (etkeAdminUrl: string) => Promise<RecurringCommand[]>;
}

const resourceMap = {
Expand Down Expand Up @@ -1190,6 +1208,56 @@ const baseDataProvider: SynapseDataProvider = {
return {
success: false,
}
},
getScheduledCommands: async (scheduledCommandsUrl: string) => {
try{
const response = await fetch(`${scheduledCommandsUrl}/schedules`, {
headers: {
"Authorization": `Bearer ${localStorage.getItem("access_token")}`
}
});
if (!response.ok) {
console.error(`Error fetching scheduled commands: ${response.status} ${response.statusText}`);
return [];
}

const status = response.status;

if (status === 200) {
const json = await response.json();
return json as ScheduledCommand[];
}

return [];
} catch (error) {
console.error("Error fetching scheduled commands, error");
}
return [];
},
getRecurringCommands: async (recurringCommandsUrl: string) => {
try {
const response = await fetch(`${recurringCommandsUrl}/recurrings`, {
headers: {
"Authorization": `Bearer ${localStorage.getItem("access_token")}`
}
});
if (!response.ok) {
console.error(`Error fetching recurring commands: ${response.status} ${response.statusText}`);
return [];
}

const status = response.status;

if (status === 200) {
const json = await response.json();
return json as RecurringCommand[];
}

return [];
} catch (error) {
console.error("Error fetching recurring commands, error");
}
return [];
}
};

Expand Down