(etke.cc-exclusive) Server Actions page (#457)

* WIP: add scheduler commands

* refactor scheduler commands page

* WIP on CRUD for ScheduledCommands

* more refactoring, finish CRUD On scheduled pages

* Add info text about Scheduler service

* Finish recurring commands add/edit

* add more texts

* fix server status behavior on not-loaded-yet state; adjust texts; lint fixes

* add link to the help pages in the commands palette

* Move Commands Panel to ServerSchedulesPage

* Rename Server Schedules to Server Actions

* more texts, a bit changed visual of the actions page, lint fix

* add docs

* fix tests

* Add UTC label to scheduled command create/edit
This commit is contained in:
Borislav Pantaleev
2025-04-11 12:41:47 +03:00
committed by GitHub
parent e2d3c0792b
commit 0832c43d76
28 changed files with 1235 additions and 62 deletions

View File

@@ -23,7 +23,7 @@ const CACHED_MANY_REF: Record<string, any> = {};
const jsonClient = async (url: string, options: Options = {}) => {
const token = localStorage.getItem("access_token");
console.log("httpClient " + url);
options.credentials = GetConfig().corsCredentials;
options.credentials = GetConfig().corsCredentials as RequestCredentials;
if (token !== null) {
options.user = {
authenticated: true,
@@ -322,6 +322,22 @@ export interface ServerCommand {
export type ServerCommandsResponse = Record<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>;
@@ -337,6 +353,14 @@ 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[]>;
createScheduledCommand: (etkeAdminUrl: string, command: Partial<ScheduledCommand>) => Promise<ScheduledCommand>;
updateScheduledCommand: (etkeAdminUrl: string, command: ScheduledCommand) => Promise<ScheduledCommand>;
deleteScheduledCommand: (etkeAdminUrl: string, id: string) => Promise<{ success: boolean }>;
createRecurringCommand: (etkeAdminUrl: string, command: Partial<RecurringCommand>) => Promise<RecurringCommand>;
updateRecurringCommand: (etkeAdminUrl: string, command: RecurringCommand) => Promise<RecurringCommand>;
deleteRecurringCommand: (etkeAdminUrl: string, id: string) => Promise<{ success: boolean }>;
}
const resourceMap = {
@@ -1188,6 +1212,212 @@ const baseDataProvider: SynapseDataProvider = {
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 [];
},
createScheduledCommand: async (scheduledCommandsUrl: string, command: Partial<ScheduledCommand>) => {
try {
const response = await fetch(`${scheduledCommandsUrl}/schedules`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("access_token")}`,
},
body: JSON.stringify(command),
});
if (!response.ok) {
console.error(`Error creating scheduled command: ${response.status} ${response.statusText}`);
throw new Error("Failed to create scheduled command");
}
if (response.status === 204) {
return command as ScheduledCommand;
}
const json = await response.json();
return json as ScheduledCommand;
} catch (error) {
console.error("Error creating scheduled command", error);
throw error;
}
},
updateScheduledCommand: async (scheduledCommandsUrl: string, command: ScheduledCommand) => {
try {
// Use the base endpoint without ID and use PUT for upsert
const response = await fetch(`${scheduledCommandsUrl}/schedules`, {
method: "PUT", // Using PUT on the base endpoint
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("access_token")}`,
},
body: JSON.stringify(command),
});
if (!response.ok) {
const jsonErr = JSON.parse(await response.text());
console.error(`Error updating scheduled command: ${response.status} ${response.statusText}`);
throw new Error(jsonErr.error);
}
// According to docs, successful response is 204 No Content
if (response.status === 204) {
// Return the command object we sent since the server doesn't return data
return command;
}
// If server does return data (though docs suggest it returns 204)
const json = await response.json();
console.log("JSON", json);
return json as ScheduledCommand;
} catch (error) {
console.error("Error updating scheduled command", error);
throw error;
}
},
deleteScheduledCommand: async (scheduledCommandsUrl: string, id: string) => {
try {
const response = await fetch(`${scheduledCommandsUrl}/schedules/${id}`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${localStorage.getItem("access_token")}`,
},
});
if (!response.ok) {
console.error(`Error deleting scheduled command: ${response.status} ${response.statusText}`);
return { success: false };
}
return { success: true };
} catch (error) {
console.error("Error deleting scheduled command", error);
return { success: false };
}
},
createRecurringCommand: async (recurringCommandsUrl: string, command: Partial<RecurringCommand>) => {
try {
const response = await fetch(`${recurringCommandsUrl}/recurrings`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("access_token")}`,
},
body: JSON.stringify(command),
});
if (!response.ok) {
console.error(`Error creating recurring command: ${response.status} ${response.statusText}`);
throw new Error("Failed to create recurring command");
}
if (response.status === 204) {
// Return the command object we sent since the server doesn't return data
return command as RecurringCommand;
}
const json = await response.json();
return json as RecurringCommand;
} catch (error) {
console.error("Error creating recurring command", error);
throw error;
}
},
updateRecurringCommand: async (recurringCommandsUrl: string, command: RecurringCommand) => {
try {
const response = await fetch(`${recurringCommandsUrl}/recurrings`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${localStorage.getItem("access_token")}`,
},
body: JSON.stringify(command),
});
if (!response.ok) {
console.error(`Error updating recurring command: ${response.status} ${response.statusText}`);
throw new Error("Failed to update recurring command");
}
if (response.status === 204) {
// Return the command object we sent since the server doesn't return data
return command as RecurringCommand;
}
const json = await response.json();
return json as RecurringCommand;
} catch (error) {
console.error("Error updating recurring command", error);
throw error;
}
},
deleteRecurringCommand: async (recurringCommandsUrl: string, id: string) => {
try {
const response = await fetch(`${recurringCommandsUrl}/recurrings/${id}`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${localStorage.getItem("access_token")}`,
},
});
if (!response.ok) {
console.error(`Error deleting recurring command: ${response.status} ${response.statusText}`);
return { success: false };
}
return { success: true };
} catch (error) {
console.error("Error deleting recurring command", error);
return { success: false };
}
},
};
const dataProvider = withLifecycleCallbacks(baseDataProvider, [