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
Prev Previous commit
Next Next commit
WIP on CRUD for ScheduledCommands
  • Loading branch information
beastafk committed Apr 2, 2025
commit 1c10c3d8bcd6b87eccbb9e40a8f16364bc10f85b
7 changes: 6 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { merge } from "lodash";
import polyglotI18nProvider from "ra-i18n-polyglot";

import { Admin, CustomRoutes, Resource, resolveBrowserLocale } from "react-admin";
import { Admin, CustomRoutes, EditGuesser, Resource, resolveBrowserLocale } from "react-admin";

import { Route } from "react-router-dom";

Expand All @@ -27,6 +27,8 @@ 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/scheduler/components/SchedulerCommandsPage";
import ScheduledCommandEdit from "./components/etke.cc/scheduler/components/ScheduledCommandEdit";
import RecurringCommandEdit from "./components/etke.cc/scheduler/components/RecurringCommandEdit";
// TODO: Can we use lazy loading together with browser locale?
const messages = {
de: germanMessages,
Expand Down Expand Up @@ -67,6 +69,9 @@ export const App = () => (
<Route path="/import_users" element={<UserImport />} />
<Route path="/server_status" element={<ServerStatusPage />} />
<Route path="/scheduler_commands" element={<SchedulerCommandsPage />} />
<Route path="/scheduled_commands/:id" element={<ScheduledCommandEdit />} />
<Route path="/scheduled_commands/create" element={<ScheduledCommandEdit />} />
<Route path="/recurring_command/:id" element={<RecurringCommandEdit />} />
<Route path="/server_notifications" element={<ServerNotificationsPage />} />
</CustomRoutes>
<Resource {...users} />
Expand Down
113 changes: 113 additions & 0 deletions src/components/etke.cc/scheduler/components/RecurringCommandEdit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { useState, useEffect } from "react";
import {
useParams,
useNavigate,
} from "react-router-dom";
import {
SimpleForm,
TextInput,
DateTimeInput,
SaveButton,
useNotify,
useDataProvider,
Loading,
Button,
} from "react-admin";
import {
Card,
CardContent,
CardHeader,
Box,
Typography,
} from "@mui/material";
import { useAppContext } from "../../../../Context";
import { useRecurringCommands } from "../hooks/useRecurringCommands";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";

const RecurringCommandEdit = () => {
const { id } = useParams();
const navigate = useNavigate();
const notify = useNotify();
const dataProvider = useDataProvider();
const { etkeccAdmin } = useAppContext();
const [command, setCommand] = useState({
command: "",
args: "",
time: "00:00",
scheduled_at: new Date().toISOString(),
});
const [loading, setLoading] = useState(id !== "create");
const { data: recurringCommands, isLoading: isLoadingList } = useRecurringCommands();
const isCreating = id === "create";
const pageTitle = isCreating ? "Create Recurring Command" : "Edit Recurring Command";

useEffect(() => {
if (!isCreating && recurringCommands) {
const commandToEdit = recurringCommands.find(cmd => cmd.id === id);
if (commandToEdit) {
setCommand(commandToEdit);
}
setLoading(false);
}
}, [id, recurringCommands, isCreating]);

const handleSubmit = async (data) => {
try {
let result;

if (isCreating) {
result = await dataProvider.createRecurringCommand(etkeccAdmin, data);
notify("Recurring command created successfully", { type: "success" });
} else {
result = await dataProvider.updateRecurringCommand(etkeccAdmin, {
...data,
id: id,
});
notify("Recurring command updated successfully", { type: "success" });
}

navigate("/scheduler");
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
notify(`Error: ${errorMessage}`, { type: "error" });
}
};

if (loading || isLoadingList) {
return <Loading />;
}

return (
<Box sx={{ mt: 2 }}>
<Button
label="Back"
onClick={() => navigate("/scheduler")}
startIcon={<ArrowBackIcon />}
sx={{ mb: 2 }}
/>

<Card>
<CardHeader title={pageTitle} />
<CardContent>
<SimpleForm
onSubmit={handleSubmit}
record={command}
warnWhenUnsavedChanges
>
<Box display="flex" flexDirection="column" gap={2}>
<TextInput source="command" label="Command" fullWidth required />
<TextInput source="args" label="Arguments" fullWidth multiline />
<TextInput source="time" label="Time (HH:MM in UTC)" fullWidth required />
<DateTimeInput source="scheduled_at" label="Initial execution date and time" fullWidth required />
<Box mt={2}>
<SaveButton label={isCreating ? "Create" : "Update"} />
</Box>
</Box>
</SimpleForm>
</CardContent>
</Card>
</Box>
);
};

export default RecurringCommandEdit;
Original file line number Diff line number Diff line change
@@ -1,36 +1,76 @@
import { Loading } from "react-admin";

import { Loading, Button, useNotify, useRefresh, useCreatePath } from "react-admin";
import { DateField } from "react-admin";

import { Datagrid } from "react-admin";

import { Paper } from "@mui/material";
import { ListContextProvider, TextField } from "react-admin";

import { Box, Paper } from "@mui/material";
import { ListContextProvider, TextField, DeleteButton, TopToolbar } from "react-admin";
import { ResourceContextProvider, useList } from "react-admin";
import { DATE_FORMAT } from "../../../../utils/date";
import { useRecurringCommands } from "../hooks/useRecurringCommands";
import { useAppContext } from "../../../../Context";
import { useDataProvider } from "react-admin";
import AddIcon from "@mui/icons-material/Add";
import { Link } from "react-router-dom";

const ListActions = () => {
const createPath = useCreatePath();

return (
<TopToolbar>
<Button
label="Create"
component={Link}
to={createPath({
type: "create",
resource: "recurring-command",
})}
startIcon={<AddIcon />}
/>
</TopToolbar>
);
};

const RecurringCommandsList = () => {
const { data, isLoading, error } = useRecurringCommands();
const { etkeccAdmin } = useAppContext();
const dataProvider = useDataProvider();
const notify = useNotify();
const refresh = useRefresh();

const listContext = useList({
resource: "scheduled_commands",
resource: "recurring_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">
const handleDelete = async (id) => {
try {
await dataProvider.deleteRecurringCommand(etkeccAdmin, id);
notify("Recurring command deleted successfully", { type: "success" });
refresh();
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
notify(`Error: ${errorMessage}`, { type: "error" });
}
};

return (<ResourceContextProvider value="recurring_commands">
<ListContextProvider value={listContext}>
<ListActions />
<Paper>
<Datagrid bulkActionButtons={false} rowClick="edit">
<Datagrid bulkActionButtons={false} rowClick="show">
<TextField source="command" />
<TextField source="id" />
<TextField source="args" />
<TextField source="time" label="Time (UTC)" />
<DateField options={DATE_FORMAT} showTime source="scheduled_at" label="Scheduled at (local time)" />
<DeleteButton resource="recurring_commands" onClick={(e) => {
e.stopPropagation();
handleDelete(e.currentTarget.dataset.id);
}} />
</Datagrid>
</Paper>
</ListContextProvider>
Expand Down
Loading