diff --git a/src/App.vue b/src/App.vue index 9f4d4e22a..fc19580ca 100644 --- a/src/App.vue +++ b/src/App.vue @@ -343,6 +343,7 @@ import { useAppInterfaceStore } from './stores/appInterface' import { useMainVehicleStore } from './stores/mainVehicle' import { useWidgetManagerStore } from './stores/widgetManager' import { ConfigComponent } from './types/general' +import ConfigurationActionsView from './views/ConfigurationActionsView.vue' import ConfigurationAlertsView from './views/ConfigurationAlertsView.vue' import ConfigurationDevelopmentView from './views/ConfigurationDevelopmentView.vue' import ConfigurationGeneralView from './views/ConfigurationGeneralView.vue' @@ -410,6 +411,11 @@ const configMenu = [ title: 'Mission', component: markRaw(ConfigurationMissionView) as ConfigComponent, }, + { + icon: 'mdi-run-fast', + title: 'Actions', + component: markRaw(ConfigurationActionsView) as ConfigComponent, + }, ] watch( diff --git a/src/libs/actions/http-request.ts b/src/libs/actions/http-request.ts new file mode 100644 index 000000000..a6d02f6e0 --- /dev/null +++ b/src/libs/actions/http-request.ts @@ -0,0 +1,160 @@ +import { + availableCockpitActions, + CockpitAction, + CockpitActionsFunction, + deleteAction, + registerActionCallback, + registerNewAction, +} from '../joystick/protocols/cockpit-actions' +import { getCockpitActionVariableData } from './data-lake' + +const httpRequestActionIdPrefix = 'http-request-action' + +/** + * The types of HTTP methods that can be used. + */ +export enum HttpRequestMethod { + GET = 'GET', + POST = 'POST', + PUT = 'PUT', + DELETE = 'DELETE', + PATCH = 'PATCH', +} +export const availableHttpRequestMethods: HttpRequestMethod[] = Object.values(HttpRequestMethod) + +export type HttpRequestActionConfig = { + /** + * The name of the action. + */ + name: string + /** + * The URL to send the request to. + */ + url: string + /** + * The HTTP method to use. + */ + method: HttpRequestMethod + /** + * The headers to send with the request. + */ + headers: Record + /** + * The URL parameters to send with the request. + */ + urlParams: Record + /** + * The body of the request. + */ + body: string +} + +let registeredHttpRequestActionConfigs: Record = {} + +export const registerHttpRequestActionConfig = (action: HttpRequestActionConfig): void => { + const id = `${httpRequestActionIdPrefix} (${action.name})` + registeredHttpRequestActionConfigs[id] = action + saveHttpRequestActionConfigs() + updateCockpitActions() +} + +export const getHttpRequestActionConfig = (id: string): HttpRequestActionConfig | undefined => { + return registeredHttpRequestActionConfigs[id] +} + +export const getAllHttpRequestActionConfigs = (): Record => { + return registeredHttpRequestActionConfigs +} + +export const deleteHttpRequestActionConfig = (id: string): void => { + delete registeredHttpRequestActionConfigs[id] + saveHttpRequestActionConfigs() + updateCockpitActions() +} + +export const updateHttpRequestActionConfig = (id: string, updatedAction: HttpRequestActionConfig): void => { + registeredHttpRequestActionConfigs[id] = updatedAction + saveHttpRequestActionConfigs() + updateCockpitActions() +} + +export const updateCockpitActions = (): void => { + Object.entries(availableCockpitActions).forEach(([id]) => { + if (id.includes(httpRequestActionIdPrefix)) { + deleteAction(id as CockpitActionsFunction) + } + }) + + const httpResquestActions = getAllHttpRequestActionConfigs() + for (const [id, action] of Object.entries(httpResquestActions)) { + try { + const cockpitAction = new CockpitAction(id as CockpitActionsFunction, action.name) + registerNewAction(cockpitAction) + registerActionCallback(cockpitAction, getHttpRequestActionCallback(id)) + } catch (error) { + console.error(`Error registering action ${id}: ${error}`) + } + } +} + +export const loadHttpRequestActionConfigs = (): void => { + const savedActions = localStorage.getItem('cockpit-http-request-actions') + if (savedActions) { + registeredHttpRequestActionConfigs = JSON.parse(savedActions) + } +} + +export const saveHttpRequestActionConfigs = (): void => { + localStorage.setItem('cockpit-http-request-actions', JSON.stringify(registeredHttpRequestActionConfigs)) +} + +export type HttpRequestActionCallback = () => void + +export const getHttpRequestActionCallback = (id: string): HttpRequestActionCallback => { + const action = getHttpRequestActionConfig(id) + if (!action) { + throw new Error(`Action with id ${id} not found.`) + } + + let parsedBody = action.body + const parsedUrlParams = action.urlParams + + const cockpitInputsInBody = action.body.match(/{{\s*([^{}\s]+)\s*}}/g) + if (cockpitInputsInBody) { + for (const input of cockpitInputsInBody) { + const parsedInput = input.replace('{{', '').replace('}}', '').trim() + const inputData = getCockpitActionVariableData(parsedInput) + if (inputData) { + parsedBody = parsedBody.replace(input, inputData.toString()) + } + } + } + + const cockpitInputsInUrlParams = Object.entries(action.urlParams).filter( + ([, value]) => typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}') + ) + if (cockpitInputsInUrlParams) { + for (const [key, value] of cockpitInputsInUrlParams) { + const parsedInput = value.replace('{{', '').replace('}}', '').trim() + const inputData = getCockpitActionVariableData(parsedInput) + if (inputData) { + parsedUrlParams[key] = inputData.toString() + } + } + } + + const url = new URL(action.url) + + url.search = new URLSearchParams(parsedUrlParams).toString() + + return () => { + fetch(url, { + method: action.method, + headers: action.headers, + body: action.method === HttpRequestMethod.GET ? undefined : parsedBody, + }) + } +} + +loadHttpRequestActionConfigs() +updateCockpitActions() diff --git a/src/views/ConfigurationActionsView.vue b/src/views/ConfigurationActionsView.vue new file mode 100644 index 000000000..171fea4f7 --- /dev/null +++ b/src/views/ConfigurationActionsView.vue @@ -0,0 +1,675 @@ + + + + +