diff --git a/apps/client/src/features/app-settings/AppSettings.tsx b/apps/client/src/features/app-settings/AppSettings.tsx
index 28c0098f13..beefc05f7a 100644
--- a/apps/client/src/features/app-settings/AppSettings.tsx
+++ b/apps/client/src/features/app-settings/AppSettings.tsx
@@ -3,6 +3,7 @@ import { ErrorBoundary } from '@sentry/react';
import { useKeyDown } from '../../common/hooks/useKeyDown';
import AboutPanel from './panel/about-panel/AboutPanel';
+import AutomationsPanel from './panel/automations-panel/AutomationsPanel';
import ClientControlPanel from './panel/client-control-panel/ClientControlPanel';
import FeatureSettingsPanel from './panel/feature-settings-panel/FeatureSettingsPanel';
import GeneralPanel from './panel/general-panel/GeneralPanel';
@@ -31,6 +32,7 @@ export default function AppSettings() {
{panel === 'feature_settings' && }
{panel === 'sources' && }
{panel === 'integrations' && }
+ {panel === 'automations' && }
{panel === 'client_control' && }
{panel === 'about' && }
{panel === 'network' && }
diff --git a/apps/client/src/features/app-settings/panel-utils/PanelUtils.tsx b/apps/client/src/features/app-settings/panel-utils/PanelUtils.tsx
index 4020d73d61..2f3cdab0ee 100644
--- a/apps/client/src/features/app-settings/panel-utils/PanelUtils.tsx
+++ b/apps/client/src/features/app-settings/panel-utils/PanelUtils.tsx
@@ -52,8 +52,9 @@ export function Table({ className, children }: { className?: string; children: R
);
}
-export function ListGroup({ children }: { children: ReactNode }) {
- return
;
+export function ListGroup({ className, children }: { className?: string; children: ReactNode }) {
+ const classes = cx([style.listGroup, className]);
+ return ;
}
export function ListItem({ children }: { children: ReactNode }) {
diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.module.scss b/apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.module.scss
new file mode 100644
index 0000000000..a6b401da3c
--- /dev/null
+++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.module.scss
@@ -0,0 +1,12 @@
+.card {
+ position: relative;
+}
+
+.append {
+ position: absolute;
+ top: -1rem;
+ background-color: $gray-1300;
+ left: 2rem;
+ padding: 0.25rem 1rem;
+ border-radius: 2px;
+}
diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.tsx b/apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.tsx
new file mode 100644
index 0000000000..4121decf3d
--- /dev/null
+++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationCard.tsx
@@ -0,0 +1,18 @@
+import { PropsWithChildren, ReactNode } from 'react';
+
+import * as Panel from '../../panel-utils/PanelUtils';
+
+import style from './AutomationCard.module.scss';
+
+interface AutomationCardProps {
+ append: ReactNode;
+}
+export default function AutomationCard(props: PropsWithChildren) {
+ const { append, children } = props;
+ return (
+
+
{children}
+
{append}
+
+ );
+}
diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.module.scss b/apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.module.scss
new file mode 100644
index 0000000000..bfe2e48dd1
--- /dev/null
+++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.module.scss
@@ -0,0 +1,17 @@
+.cardCollapsed {
+ background-color: $gray-1200;
+ display: flex;
+ justify-content: space-between;
+}
+
+.cardExpanded {
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+}
+
+.threeCols {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1rem;
+}
diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.tsx b/apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.tsx
new file mode 100644
index 0000000000..9956591d6f
--- /dev/null
+++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationItem.tsx
@@ -0,0 +1,72 @@
+import { PropsWithChildren, useState } from 'react';
+import { Button, Input, Select } from '@chakra-ui/react';
+
+import * as Panel from '../../panel-utils/PanelUtils';
+
+import AutomationCard from './AutomationCard';
+
+import style from './AutomationCard.module.scss';
+
+interface AutomationCardProps {
+ title: string;
+ trigger: string;
+}
+
+export default function AutomationItem(props: AutomationCardProps) {
+ const { title, trigger } = props;
+ const [expanded, setExpanded] = useState(false);
+
+ if (expanded) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
Automation title
+
{title}
+
+
+
+
+ );
+}
+
+function AutomationCardExpanded(props: PropsWithChildren) {
+ const { title, trigger, children } = props;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+ );
+}
diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationManagement.module.scss b/apps/client/src/features/app-settings/panel/automations-panel/AutomationManagement.module.scss
new file mode 100644
index 0000000000..32368827af
--- /dev/null
+++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationManagement.module.scss
@@ -0,0 +1,5 @@
+.list {
+ display: flex;
+ flex-direction: column;
+ gap: 3rem;
+}
diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationManagement.tsx b/apps/client/src/features/app-settings/panel/automations-panel/AutomationManagement.tsx
new file mode 100644
index 0000000000..dba0bd38e2
--- /dev/null
+++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationManagement.tsx
@@ -0,0 +1,69 @@
+import { Button } from '@chakra-ui/react';
+import { TimerLifeCycle } from 'ontime-types';
+
+import * as Panel from '../../panel-utils/PanelUtils';
+
+import AutomationItem from './AutomationItem';
+
+import style from "./AutomationCard.module.scss";
+
+const data = [
+ {
+ id: '1',
+ title: 'Automation 1',
+ trigger: TimerLifeCycle.onClock,
+ filterRule: 'all',
+ filter: [],
+ output: [],
+ },
+ {
+ id: '2',
+ title: 'Automation 1',
+ trigger: TimerLifeCycle.onClock,
+ filterRule: 'all',
+ filter: [],
+ output: [],
+ },
+ {
+ id: '3',
+ title: 'Automation 1',
+ trigger: TimerLifeCycle.onClock,
+ filterRule: 'all',
+ filter: [],
+ output: [],
+ },
+];
+
+export default function AutomationManagement() {
+ return (
+
+
+ Manage automations
+
+
+
+
+
+
+
+
+ undefined}
+ onKeyDown={() => console.log('prevent escapee')}
+ >
+
+
+ {data.map((automation) => {
+ return ;
+ })}
+
+
+
+ );
+}
diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationSettings.tsx b/apps/client/src/features/app-settings/panel/automations-panel/AutomationSettings.tsx
new file mode 100644
index 0000000000..e4aace642b
--- /dev/null
+++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationSettings.tsx
@@ -0,0 +1,114 @@
+import { Controller, useForm } from 'react-hook-form';
+import { Button, Input, Switch } from '@chakra-ui/react';
+
+import { isOnlyNumbers } from '../../../../common/utils/regex';
+import * as Panel from '../../panel-utils/PanelUtils';
+
+interface AutomationSettingsOptions {
+ enabledIn: boolean;
+ portIn: number;
+}
+
+const automationSettingsPlaceholder = {
+ enabledIn: false,
+ portIn: 8888,
+};
+
+const style = {
+ flex: 'flex',
+};
+export default function AutomationSettings() {
+ const {
+ control,
+ handleSubmit,
+ reset,
+ register,
+ setError,
+ formState: { errors, isSubmitting, isDirty, isValid },
+ } = useForm({
+ mode: 'onChange',
+ defaultValues: automationSettingsPlaceholder,
+ values: automationSettingsPlaceholder,
+ resetOptions: {
+ keepDirtyValues: true,
+ },
+ });
+
+ return (
+
+
+ Automation settings
+
+
+
+
+
+
+
+
+ undefined}
+ onKeyDown={() => console.log('prevent escapee')}
+ >
+
+ OSC Input
+ {errors?.root && {errors.root.message}}
+
+
+
+ (
+
+ )}
+ />
+
+
+
+
+
+
+ Automation
+ {errors?.root && {errors.root.message}}
+
+
+
+ (
+
+ )}
+ />
+
+
+
+
+ );
+}
diff --git a/apps/client/src/features/app-settings/panel/automations-panel/AutomationsPanel.tsx b/apps/client/src/features/app-settings/panel/automations-panel/AutomationsPanel.tsx
new file mode 100644
index 0000000000..f038628fb8
--- /dev/null
+++ b/apps/client/src/features/app-settings/panel/automations-panel/AutomationsPanel.tsx
@@ -0,0 +1,42 @@
+import { Alert, AlertDescription, AlertIcon } from '@chakra-ui/react';
+
+import ExternalLink from '../../../../common/components/external-link/ExternalLink';
+import useScrollIntoView from '../../../../common/hooks/useScrollIntoView';
+import type { PanelBaseProps } from '../../panel-list/PanelList';
+import * as Panel from '../../panel-utils/PanelUtils';
+
+import AutomationManagement from './AutomationManagement';
+import AutomationSettings from './AutomationSettings';
+
+const integrationDocsUrl = 'https://docs.getontime.no/api/integrations/';
+
+export default function IntegrationsPanel({ location }: PanelBaseProps) {
+ const oscRef = useScrollIntoView('osc', location);
+ const httpRef = useScrollIntoView('http', location);
+
+ return (
+ <>
+ Automation
+
+
+
+
+ Integrations allow Ontime to receive commands or send its data to other systems in your workflow.
+
+ Currently supported protocols are OSC (Open Sound Control), HTTP and Websockets.
+ WebSockets are used for Ontime and cannot be configured independently.
+ See the docs
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/apps/client/src/features/app-settings/panel/automations-panel/automationUtils.ts b/apps/client/src/features/app-settings/panel/automations-panel/automationUtils.ts
new file mode 100644
index 0000000000..1f0456cfa8
--- /dev/null
+++ b/apps/client/src/features/app-settings/panel/automations-panel/automationUtils.ts
@@ -0,0 +1,19 @@
+import { TimerLifeCycle } from 'ontime-types';
+
+type CycleLabel = {
+ id: number;
+ label: string;
+ value: keyof typeof TimerLifeCycle;
+};
+
+export const cycles: CycleLabel[] = [
+ { id: 1, label: 'On Load', value: 'onLoad' },
+ { id: 2, label: 'On Start', value: 'onStart' },
+ { id: 3, label: 'On Pause', value: 'onPause' },
+ { id: 4, label: 'On Stop', value: 'onStop' },
+ { id: 5, label: 'Every second', value: 'onClock' },
+ { id: 5, label: 'On Timer Update', value: 'onUpdate' },
+ { id: 6, label: 'On Finish', value: 'onFinish' },
+ { id: 7, label: 'On Warning', value: 'onWarning' },
+ { id: 8, label: 'On Danger', value: 'onDanger' },
+];
diff --git a/apps/client/src/features/app-settings/useAppSettingsMenu.tsx b/apps/client/src/features/app-settings/useAppSettingsMenu.tsx
index 7f87b4d2db..a277ace683 100644
--- a/apps/client/src/features/app-settings/useAppSettingsMenu.tsx
+++ b/apps/client/src/features/app-settings/useAppSettingsMenu.tsx
@@ -54,6 +54,14 @@ const staticOptions = [
{ id: 'integrations__http', label: 'HTTP settings' },
],
},
+ {
+ id: 'automations',
+ label: 'Automations',
+ secondary: [
+ { id: 'automations__settings', label: 'Automation settings' },
+ { id: 'automations__manage', label: 'Manage automations' },
+ ],
+ },
{
id: 'network',
label: 'Network',