mirror of
https://github.com/cpvalente/ontime.git
synced 2026-07-26 10:38:55 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1bc6c3e852 | |||
| 257f2259e0 | |||
| 8ae3bb2df0 | |||
| ff1496d29f | |||
| 04455c08ef | |||
| 6c86ece1ff | |||
| 9a2fa527c6 | |||
| 165920b9f2 | |||
| ee3b3c0735 | |||
| ecaf0a209b |
@@ -73,13 +73,29 @@ export async function getAliases(): Promise<Alias[]> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description HTTP request to mutate aliases
|
||||
* @description HTTP request to create an alias
|
||||
* @return {Promise}
|
||||
*/
|
||||
export async function postAliases(data: Alias[]) {
|
||||
export async function postAlias(data: Alias) {
|
||||
return axios.post(`${ontimeURL}/aliases`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description HTTP request to update aliases
|
||||
* @return {Promise}
|
||||
*/
|
||||
export async function updateAliases(data: Partial<Alias>) {
|
||||
return axios.put(`${ontimeURL}/aliases`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description HTTP request to delete alias
|
||||
* @return {Promise}
|
||||
*/
|
||||
export async function deleteAlias(alias: string) {
|
||||
return axios.delete(`${ontimeURL}/aliases/${alias}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description HTTP request to retrieve user fields
|
||||
* @return {Promise}
|
||||
|
||||
@@ -7,6 +7,7 @@ import IntegrationsPanel from './panel/integrations-panel/IntegrationsPanel';
|
||||
import LogPanel from './panel/log-panel/LogPanel';
|
||||
import ProjectPanel from './panel/project-panel/ProjectPanel';
|
||||
import ProjectSettingsPanel from './panel/project-settings-panel/ProjectSettingsPanel';
|
||||
import UrlPresetPanel from './panel/url-preset-panel/UrlPresetPanel';
|
||||
import PanelContent from './panel-content/PanelContent';
|
||||
import PanelList from './panel-list/PanelList';
|
||||
import { useSettingsStore } from './settingsStore';
|
||||
@@ -30,6 +31,7 @@ export default function AppSettings() {
|
||||
{selectedPanel === 'project' && <ProjectPanel />}
|
||||
{selectedPanel === 'integrations' && <IntegrationsPanel />}
|
||||
{selectedPanel === 'project_settings' && <ProjectSettingsPanel />}
|
||||
{selectedPanel === 'url_presets' && <UrlPresetPanel />}
|
||||
{selectedPanel === 'about' && <AboutPanel />}
|
||||
{selectedPanel === 'log' && <LogPanel />}
|
||||
</PanelContent>
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Button, Input } from '@chakra-ui/react';
|
||||
|
||||
import style from './UrlPresetPanel.module.scss';
|
||||
|
||||
export type UrlPresetFormValues = {
|
||||
alias?: string;
|
||||
pathAndParams?: string;
|
||||
enabled?: boolean;
|
||||
};
|
||||
|
||||
interface UrlPresetFormProps {
|
||||
alias?: string;
|
||||
enabled?: boolean;
|
||||
pathAndParams?: string;
|
||||
onCancel: () => void;
|
||||
onSubmit: (values: UrlPresetFormValues) => Promise<void>;
|
||||
submitError: string | null;
|
||||
}
|
||||
|
||||
export default function UrlPresetForm({
|
||||
onSubmit,
|
||||
onCancel,
|
||||
submitError,
|
||||
alias,
|
||||
enabled,
|
||||
pathAndParams,
|
||||
}: UrlPresetFormProps) {
|
||||
const {
|
||||
handleSubmit,
|
||||
register,
|
||||
formState: { isSubmitting, isValid },
|
||||
setFocus,
|
||||
} = useForm<UrlPresetFormValues>({
|
||||
defaultValues: { alias, pathAndParams, enabled },
|
||||
values: { alias, pathAndParams, enabled },
|
||||
resetOptions: {
|
||||
keepDirtyValues: true,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setFocus('alias');
|
||||
}, [setFocus]);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className={style.form}>
|
||||
<div className={style.formInput}>
|
||||
<Input
|
||||
variant='ontime-filled'
|
||||
size='sm'
|
||||
maxLength={50}
|
||||
placeholder='Alias'
|
||||
autoComplete='off'
|
||||
{...register('alias')}
|
||||
/>
|
||||
</div>
|
||||
<div className={style.formInput}>
|
||||
<Input
|
||||
variant='ontime-filled'
|
||||
size='sm'
|
||||
maxLength={100}
|
||||
placeholder='Path and params'
|
||||
autoComplete='off'
|
||||
{...register('pathAndParams')}
|
||||
/>
|
||||
</div>
|
||||
<div className={style.actionButtons}>
|
||||
<Button onClick={onCancel} variant='ontime-ghosted' size='sm'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
isDisabled={!isValid}
|
||||
type='submit'
|
||||
isLoading={isSubmitting}
|
||||
variant='ontime-filled'
|
||||
padding='0 2em'
|
||||
size='sm'
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
{submitError && <span className={style.error}>{submitError}</span>}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { postAlias } from '../../../../common/api/ontimeApi';
|
||||
import useAliases from '../../../../common/hooks-query/useAliases';
|
||||
import ModalLoader from '../../../modals/modal-loader/ModalLoader';
|
||||
import * as Panel from '../PanelUtils';
|
||||
|
||||
import UrlPresetForm, { UrlPresetFormValues } from './UrlPresetForm';
|
||||
import UrlPresetListItem from './UrlPresetListItem';
|
||||
|
||||
interface UrlPresetListProps {
|
||||
isCreatingPresetURL: boolean;
|
||||
onToggleCreate: () => void;
|
||||
}
|
||||
|
||||
export default function UrlPresetList({ isCreatingPresetURL, onToggleCreate }: UrlPresetListProps) {
|
||||
const { data, isFetching, refetch } = useAliases();
|
||||
|
||||
const handleRefetch = async () => {
|
||||
await refetch();
|
||||
};
|
||||
|
||||
const handleSubmitCreate = async (values: UrlPresetFormValues) => {
|
||||
try {
|
||||
// TODO: fix this
|
||||
// @ts-ignore
|
||||
await postAlias({
|
||||
...values,
|
||||
enabled: true,
|
||||
});
|
||||
await refetch();
|
||||
} catch (error) {
|
||||
// some error handling here
|
||||
}
|
||||
};
|
||||
|
||||
if (isFetching) {
|
||||
return <ModalLoader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Panel.Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Alias</th>
|
||||
<th>Path and Params</th>
|
||||
<th>Enabled</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{isCreatingPresetURL ? (
|
||||
<tr>
|
||||
<td colSpan={99}>
|
||||
<UrlPresetForm onCancel={onToggleCreate} onSubmit={handleSubmitCreate} submitError='' />
|
||||
{/* {submitError && <span className={style.createSubmitError}>{submitError}</span>} */}
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
{(data || []).map((alias) => {
|
||||
return (
|
||||
<UrlPresetListItem
|
||||
alias={alias.alias}
|
||||
enabled={alias.enabled}
|
||||
pathAndParams={alias.pathAndParams}
|
||||
onRefetch={handleRefetch}
|
||||
key={alias.alias}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</Panel.Table>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { IconButton, Menu, MenuButton, MenuItem, MenuList, Switch } from '@chakra-ui/react';
|
||||
import { IoEllipsisHorizontal } from '@react-icons/all-files/io5/IoEllipsisHorizontal';
|
||||
|
||||
import { deleteAlias, updateAliases } from '../../../../common/api/ontimeApi';
|
||||
|
||||
import UrlAliasForm, { UrlPresetFormValues } from './UrlPresetForm';
|
||||
|
||||
interface UrlPresetListItemProps {
|
||||
alias: string;
|
||||
enabled: boolean;
|
||||
pathAndParams: string;
|
||||
onRefetch: () => Promise<void>;
|
||||
}
|
||||
|
||||
export default function UrlPresetListItem({ alias, enabled, pathAndParams, onRefetch }: UrlPresetListItemProps) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
const handleToggle = useCallback(async () => {
|
||||
// TODO: Error handling
|
||||
await updateAliases({
|
||||
alias,
|
||||
enabled: !enabled,
|
||||
pathAndParams,
|
||||
});
|
||||
|
||||
await onRefetch();
|
||||
}, [alias, enabled, onRefetch, pathAndParams]);
|
||||
|
||||
const handleToggleEditMode = useCallback(() => {
|
||||
setIsEditing(!isEditing);
|
||||
}, [isEditing]);
|
||||
|
||||
const handleSubmitUpdate = useCallback(
|
||||
async (values: UrlPresetFormValues) => {
|
||||
try {
|
||||
await updateAliases(values);
|
||||
await onRefetch();
|
||||
} catch (error) {
|
||||
// some error handling here
|
||||
}
|
||||
},
|
||||
[onRefetch],
|
||||
);
|
||||
|
||||
const handleDelete = useCallback(async () => {
|
||||
await deleteAlias(alias);
|
||||
await onRefetch();
|
||||
}, [alias, onRefetch]);
|
||||
|
||||
const handleRenderAliases = useMemo(() => {
|
||||
if (!isEditing) {
|
||||
return (
|
||||
<>
|
||||
<td
|
||||
style={{
|
||||
width: '45%',
|
||||
}}
|
||||
>
|
||||
{alias}
|
||||
</td>
|
||||
<td
|
||||
style={{
|
||||
width: '45%',
|
||||
}}
|
||||
>
|
||||
{pathAndParams}
|
||||
</td>
|
||||
<td>
|
||||
<Switch variant='ontime-on-light' isChecked={enabled} onChange={handleToggle} />
|
||||
</td>
|
||||
<td>
|
||||
<ActionMenu onDelete={handleDelete} onChangeEditMode={handleToggleEditMode} />
|
||||
</td>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<td colSpan={99}>
|
||||
<UrlAliasForm
|
||||
alias={alias}
|
||||
enabled={enabled}
|
||||
pathAndParams={pathAndParams}
|
||||
onCancel={handleToggleEditMode}
|
||||
onSubmit={handleSubmitUpdate}
|
||||
submitError=''
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
}, [alias, enabled, handleDelete, handleSubmitUpdate, handleToggle, handleToggleEditMode, isEditing, pathAndParams]);
|
||||
|
||||
return <tr key={alias}>{handleRenderAliases}</tr>;
|
||||
}
|
||||
|
||||
function ActionMenu({ onChangeEditMode, onDelete }: { onChangeEditMode: () => void; onDelete: () => Promise<void> }) {
|
||||
const handleEdit = () => {
|
||||
onChangeEditMode();
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu variant='ontime-on-dark' size='sm'>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label='Options'
|
||||
icon={<IoEllipsisHorizontal />}
|
||||
variant='ontime-ghosted'
|
||||
size='sm'
|
||||
/>
|
||||
<MenuList>
|
||||
<MenuItem onClick={handleEdit}>Edit</MenuItem>
|
||||
<MenuItem onClick={onDelete}>Delete</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
.form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.formInput {
|
||||
flex: 2;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.actionButtons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { useState } from 'react';
|
||||
import { Alert, AlertDescription, AlertIcon, AlertTitle, Button } from '@chakra-ui/react';
|
||||
|
||||
import ModalLink from '../../../modals/ModalLink';
|
||||
import * as Panel from '../PanelUtils';
|
||||
|
||||
import UrlPresetList from './UrlPresetList';
|
||||
|
||||
import style from './UrlPresetPanel.module.scss';
|
||||
|
||||
const aliasesDocsUrl = 'https://ontime.gitbook.io/v2/features/url-aliases';
|
||||
|
||||
export default function UrlPresetPanel() {
|
||||
const [isCreatingPresetURL, setIsCreatingPresetURL] = useState(false);
|
||||
|
||||
const handleToggleCreate = () => {
|
||||
setIsCreatingPresetURL((prev) => !prev);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Panel.Header>URL Presets</Panel.Header>
|
||||
<Panel.Section>
|
||||
<Panel.Card>
|
||||
<main>
|
||||
<Alert status='info' variant='ontime-on-light-info'>
|
||||
<AlertIcon />
|
||||
<div className={style.column}>
|
||||
<AlertTitle>URL Presets</AlertTitle>
|
||||
<AlertDescription>
|
||||
Custom presets allow providing a short name for any ontime URL. <br />
|
||||
It serves two primary purposes: <br />
|
||||
- Providing dynamic URLs for automation or unattended screens <br />- Simplifying complex URLs
|
||||
<ModalLink href={aliasesDocsUrl}>For more information, see the docs</ModalLink>
|
||||
</AlertDescription>
|
||||
</div>
|
||||
</Alert>
|
||||
<div
|
||||
style={{
|
||||
marginTop: '1rem',
|
||||
}}
|
||||
>
|
||||
<Panel.SubHeader>
|
||||
Manage URL presets
|
||||
<Button variant='ontime-filled' onClick={handleToggleCreate}>
|
||||
New
|
||||
</Button>
|
||||
</Panel.SubHeader>
|
||||
</div>
|
||||
<UrlPresetList isCreatingPresetURL={isCreatingPresetURL} onToggleCreate={handleToggleCreate} />
|
||||
</main>
|
||||
</Panel.Card>
|
||||
</Panel.Section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -35,6 +35,7 @@ export const settingPanels: Readonly<SettingsOption[]> = [
|
||||
],
|
||||
},
|
||||
{ id: 'log', label: 'Log', split: true },
|
||||
{ id: 'url_presets', label: 'URL Presets' },
|
||||
{
|
||||
id: 'about',
|
||||
label: 'About',
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { Alert, AlertDescription, AlertIcon, AlertTitle, Button, IconButton, Input, Switch } from '@chakra-ui/react';
|
||||
import { IoOpenOutline } from '@react-icons/all-files/io5/IoOpenOutline';
|
||||
import { IoRemove } from '@react-icons/all-files/io5/IoRemove';
|
||||
import { Alias } from 'ontime-types';
|
||||
|
||||
import { logAxiosError } from '../../../common/api/apiUtils';
|
||||
import { postAliases } from '../../../common/api/ontimeApi';
|
||||
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
||||
import useAliases from '../../../common/hooks-query/useAliases';
|
||||
import { useEmitLog } from '../../../common/stores/logger';
|
||||
import { handleLinks } from '../../../common/utils/linkUtils';
|
||||
import ModalLoader from '../modal-loader/ModalLoader';
|
||||
import { inputProps } from '../modalHelper';
|
||||
import ModalLink from '../ModalLink';
|
||||
import OntimeModalFooter from '../OntimeModalFooter';
|
||||
|
||||
import style from './SettingsModal.module.scss';
|
||||
|
||||
const aliasesDocsUrl = 'https://ontime.gitbook.io/v2/features/url-aliases';
|
||||
|
||||
// we wrap the array in an object to be simplify react-hook-form
|
||||
type Aliases = {
|
||||
aliases: Alias[];
|
||||
};
|
||||
|
||||
export default function AliasesForm() {
|
||||
const { data, status, isFetching, refetch } = useAliases();
|
||||
const { emitError } = useEmitLog();
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
register,
|
||||
reset,
|
||||
formState: { isSubmitting, isDirty, isValid },
|
||||
} = useForm<Aliases>({
|
||||
defaultValues: { aliases: data },
|
||||
values: { aliases: data || [] },
|
||||
resetOptions: {
|
||||
keepDirtyValues: true,
|
||||
},
|
||||
});
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
name: 'aliases',
|
||||
control,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
reset({ aliases: data });
|
||||
}
|
||||
}, [data, reset]);
|
||||
|
||||
const onSubmit = async (formData: Aliases) => {
|
||||
try {
|
||||
await postAliases(formData.aliases);
|
||||
} catch (error) {
|
||||
logAxiosError('Error saving aliases', error);
|
||||
} finally {
|
||||
await refetch();
|
||||
}
|
||||
};
|
||||
|
||||
const onReset = () => {
|
||||
reset({ aliases: data });
|
||||
};
|
||||
|
||||
const addNew = () => {
|
||||
if (fields.length > 20) {
|
||||
emitError('Maximum amount of aliases reached (20)');
|
||||
return;
|
||||
}
|
||||
append({
|
||||
enabled: false,
|
||||
alias: '',
|
||||
pathAndParams: '',
|
||||
});
|
||||
};
|
||||
|
||||
const disableInputs = status === 'pending';
|
||||
const hasTooManyOptions = fields.length >= 20;
|
||||
|
||||
if (isFetching) {
|
||||
return <ModalLoader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} id='aliases' className={style.sectionContainer}>
|
||||
<div style={{ height: '16px' }} />
|
||||
<Alert status='info' variant='ontime-on-light-info'>
|
||||
<AlertIcon />
|
||||
<div className={style.column}>
|
||||
<AlertTitle>URL Aliases</AlertTitle>
|
||||
<AlertDescription>
|
||||
Custom aliases allow providing a short name for any ontime URL. <br />
|
||||
It serves two primary purposes: <br />
|
||||
- Providing dynamic URLs for automation or unattended screens <br />- Simplifying complex URLs
|
||||
<ModalLink href={aliasesDocsUrl}>For more information, see the docs</ModalLink>
|
||||
</AlertDescription>
|
||||
</div>
|
||||
</Alert>
|
||||
<div style={{ height: '16px' }} />
|
||||
<ul className={style.aliases}>
|
||||
{fields.map((alias, index) => {
|
||||
return (
|
||||
<li className={style.aliasRow} key={alias.id}>
|
||||
<IconButton
|
||||
onClick={() => remove(index)}
|
||||
aria-label='delete'
|
||||
size='xs'
|
||||
icon={<IoRemove />}
|
||||
colorScheme='red'
|
||||
isDisabled={disableInputs}
|
||||
data-testid={`field__delete_${index}`}
|
||||
/>
|
||||
<Input
|
||||
{...inputProps}
|
||||
{...register(`aliases.${index}.alias`)}
|
||||
width='12em'
|
||||
size='xs'
|
||||
variant='ontime-filled-on-light'
|
||||
placeholder='URL Alias'
|
||||
isDisabled={disableInputs}
|
||||
data-testid={`field__alias_${index}`}
|
||||
/>
|
||||
<Input
|
||||
{...inputProps}
|
||||
{...register(`aliases.${index}.pathAndParams`)}
|
||||
className={style.grow}
|
||||
size='xs'
|
||||
variant='ontime-filled-on-light'
|
||||
placeholder='URL (portion after ontime Port)'
|
||||
isDisabled={disableInputs}
|
||||
data-testid={`field__url_${index}`}
|
||||
/>
|
||||
<TooltipActionBtn
|
||||
clickHandler={(event) => handleLinks(event, alias.alias)}
|
||||
tooltip='Test alias'
|
||||
aria-label='Test alias'
|
||||
size='xs'
|
||||
variant='ontime-ghost-on-light'
|
||||
icon={<IoOpenOutline />}
|
||||
colorScheme='red'
|
||||
isDisabled={disableInputs}
|
||||
data-testid={`field__test_${index}`}
|
||||
/>
|
||||
<Switch
|
||||
{...register(`aliases.${index}.enabled`)}
|
||||
variant='ontime-on-light'
|
||||
isDisabled={disableInputs}
|
||||
data-testid={`field__enable_${index}`}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<Button
|
||||
onClick={addNew}
|
||||
className={style.shiftRight}
|
||||
isDisabled={hasTooManyOptions}
|
||||
size='xs'
|
||||
colorScheme='blue'
|
||||
variant='outline'
|
||||
padding='0 2em'
|
||||
>
|
||||
Add new
|
||||
</Button>
|
||||
<OntimeModalFooter
|
||||
formId='aliases'
|
||||
handleRevert={onReset}
|
||||
isDirty={isDirty}
|
||||
isValid={isValid}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { ModalBody, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/r
|
||||
|
||||
import ModalWrapper from '../ModalWrapper';
|
||||
|
||||
import AliasesForm from './AliasesForm';
|
||||
import AppSettingsModal from './AppSettings';
|
||||
import CuesheetSettingsForm from './CuesheetSettingsForm';
|
||||
import EditorSettings from './EditorSettings';
|
||||
@@ -26,7 +25,7 @@ export default function SettingsModal(props: ModalManagerProps) {
|
||||
<Tab>Editor</Tab>
|
||||
<Tab>Cuesheet</Tab>
|
||||
<Tab>Views</Tab>
|
||||
<Tab>URL Aliases</Tab>
|
||||
<Tab>URL Presets</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
@@ -44,9 +43,6 @@ export default function SettingsModal(props: ModalManagerProps) {
|
||||
<TabPanel>
|
||||
<ViewSettingsForm />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<AliasesForm />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</ModalBody>
|
||||
|
||||
@@ -71,11 +71,17 @@ export class DataProvider {
|
||||
return data.aliases;
|
||||
}
|
||||
|
||||
// TODO: Remove this when I change the data model
|
||||
static async setAliases(newData: Alias[]) {
|
||||
data.aliases = newData;
|
||||
await this.persist();
|
||||
}
|
||||
|
||||
static async addAlias(newData: Alias) {
|
||||
data.aliases.push(newData);
|
||||
await this.persist();
|
||||
}
|
||||
|
||||
static getUserFields() {
|
||||
return { ...data.userFields };
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { LogOrigin } from 'ontime-types';
|
||||
import type {
|
||||
Alias,
|
||||
DatabaseModel,
|
||||
GetInfo,
|
||||
HttpSettings,
|
||||
@@ -19,7 +18,7 @@ import { copyFile, rename, writeFile } from 'fs/promises';
|
||||
|
||||
import { fileHandler } from '../utils/parser.js';
|
||||
import { DataProvider } from '../classes/data-provider/DataProvider.js';
|
||||
import { failEmptyObjects, failIsNotArray } from '../utils/routerUtils.js';
|
||||
import { failEmptyObjects } from '../utils/routerUtils.js';
|
||||
import { runtimeService } from '../services/runtime-service/RuntimeService.js';
|
||||
import { eventStore } from '../stores/EventStore.js';
|
||||
import {
|
||||
@@ -160,30 +159,77 @@ export const getInfo = async (_req: Request, res: Response<GetInfo>) => {
|
||||
});
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/ontime/aliases'
|
||||
// Create controller for POST request to '/ontime/presets'
|
||||
// Returns -
|
||||
export const getAliases = async (_req: Request, res: Response) => {
|
||||
export const getPresets = async (_req: Request, res: Response) => {
|
||||
const aliases = DataProvider.getAliases();
|
||||
res.status(200).send(aliases);
|
||||
};
|
||||
|
||||
// Create controller for POST request to '/ontime/aliases'
|
||||
// Create controller for POST request to '/ontime/presets'
|
||||
// Returns ACK message
|
||||
export const postAliases = async (req: Request, res: Response) => {
|
||||
if (failIsNotArray(req.body, res)) {
|
||||
return;
|
||||
}
|
||||
export const postPreset = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const newAliases: Alias[] = [];
|
||||
req.body.forEach((a) => {
|
||||
newAliases.push({
|
||||
enabled: a.enabled,
|
||||
alias: a.alias,
|
||||
pathAndParams: a.pathAndParams,
|
||||
});
|
||||
});
|
||||
await DataProvider.setAliases(newAliases);
|
||||
res.status(200).send(newAliases);
|
||||
const newAlias = {
|
||||
enabled: req.body.enabled,
|
||||
alias: req.body.alias,
|
||||
pathAndParams: req.body.pathAndParams,
|
||||
};
|
||||
|
||||
await DataProvider.addAlias(newAlias);
|
||||
res.status(200).send(newAlias);
|
||||
} catch (error) {
|
||||
res.status(400).send({ message: String(error) });
|
||||
}
|
||||
};
|
||||
|
||||
// Update controller for PUT request to '/ontime/presets/:preset'
|
||||
// Returns ACK message
|
||||
export const updatePreset = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { alias, enabled, pathAndParams } = req.body;
|
||||
|
||||
const aliases = DataProvider.getAliases();
|
||||
|
||||
const index = aliases.findIndex((a) => a.alias === alias);
|
||||
|
||||
if (index === -1) {
|
||||
return res.status(404).send({ message: `Alias ${alias} not found` });
|
||||
}
|
||||
|
||||
aliases[index] = {
|
||||
alias,
|
||||
enabled,
|
||||
pathAndParams,
|
||||
};
|
||||
|
||||
await DataProvider.setAliases(aliases);
|
||||
|
||||
res.status(200).send(aliases);
|
||||
} catch (error) {
|
||||
res.status(400).send({ message: String(error) });
|
||||
}
|
||||
};
|
||||
|
||||
// Delete controller for DELETE request to '/ontime/presets/:preset'
|
||||
// Returns ACK message
|
||||
export const deletePreset = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const alias = req.params.alias;
|
||||
|
||||
const aliases = DataProvider.getAliases();
|
||||
|
||||
const index = aliases.findIndex((a) => a.alias === alias);
|
||||
|
||||
if (index === -1) {
|
||||
return res.status(404).send({ message: `Alias ${alias} not found` });
|
||||
}
|
||||
|
||||
aliases.splice(index, 1);
|
||||
|
||||
await DataProvider.setAliases(aliases);
|
||||
|
||||
res.status(200).send(aliases);
|
||||
} catch (error) {
|
||||
res.status(400).send({ message: String(error) });
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ export const viewValidator = [
|
||||
];
|
||||
|
||||
/**
|
||||
* @description Validates object for POST /ontime/aliases
|
||||
* @description Validates object for POST /ontime/presets
|
||||
*/
|
||||
export const validateAliases = [
|
||||
export const validatePresets = [
|
||||
body().isArray(),
|
||||
body('*.enabled').isBoolean(),
|
||||
body('*.alias').isString().trim(),
|
||||
@@ -39,6 +39,36 @@ export const validateAliases = [
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* @description Validates object for POST /ontime/presets
|
||||
*/
|
||||
export const validateCreatePreset = [
|
||||
body('alias').isString().trim(),
|
||||
body('pathAndParams').isString().trim(),
|
||||
body('enabled').isBoolean(),
|
||||
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* @description Validates object for PUT /ontime/presets
|
||||
*/
|
||||
export const validateUpdatePreset = [
|
||||
body('alias').isString().trim(),
|
||||
body('pathAndParams').isString().trim().optional(),
|
||||
body('enabled').isBoolean().optional(),
|
||||
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) return res.status(422).json({ errors: errors.array() });
|
||||
next();
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* @description Validates object for POST /ontime/userfields
|
||||
*/
|
||||
@@ -121,7 +151,7 @@ export const validatePatchProjectFile = [
|
||||
body('project').isObject().optional({ nullable: false }),
|
||||
body('settings').isObject().optional({ nullable: false }),
|
||||
body('viewSettings').isObject().optional({ nullable: false }),
|
||||
body('aliases').isArray().optional({ nullable: false }),
|
||||
body('aliases').isArray().optional({ nullable: false }), // TODO
|
||||
body('userFields').isObject().optional({ nullable: false }),
|
||||
body('osc').isObject().optional({ nullable: false }),
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { uploadFile } from '../utils/upload.js';
|
||||
import {
|
||||
dbDownload,
|
||||
dbUpload,
|
||||
getAliases,
|
||||
getInfo,
|
||||
getOSC,
|
||||
getHTTP,
|
||||
@@ -12,7 +11,6 @@ import {
|
||||
getViewSettings,
|
||||
patchPartialProjectFile,
|
||||
poll,
|
||||
postAliases,
|
||||
postOSC,
|
||||
postSettings,
|
||||
postUserFields,
|
||||
@@ -32,10 +30,13 @@ import {
|
||||
postId,
|
||||
getAuthentication,
|
||||
getClientSecrect as getClientSecret,
|
||||
postPreset,
|
||||
updatePreset,
|
||||
deletePreset,
|
||||
getPresets,
|
||||
} from '../controllers/ontimeController.js';
|
||||
|
||||
import {
|
||||
validateAliases,
|
||||
validateOSC,
|
||||
validatePatchProjectFile,
|
||||
validateSettings,
|
||||
@@ -48,6 +49,8 @@ import {
|
||||
validateSheetid,
|
||||
validateWorksheet,
|
||||
validateSheetOptions,
|
||||
validateCreatePreset,
|
||||
validateUpdatePreset,
|
||||
} from '../controllers/ontimeController.validate.js';
|
||||
import { projectSanitiser } from '../controllers/projectController.validate.js';
|
||||
import { sanitizeProjectFilename } from '../utils/sanitizeProjectFilename.js';
|
||||
@@ -82,10 +85,16 @@ router.get('/views', getViewSettings);
|
||||
router.post('/views', viewValidator, postViewSettings);
|
||||
|
||||
// create route between controller and '/ontime/aliases' endpoint
|
||||
router.get('/aliases', getAliases);
|
||||
router.get('/presets', getPresets);
|
||||
|
||||
// create route between controller and '/ontime/aliases' endpoint
|
||||
router.post('/aliases', validateAliases, postAliases);
|
||||
router.post('/presets', validateCreatePreset, postPreset);
|
||||
|
||||
// create route between controller and /ontime/aliases' endpoint
|
||||
router.put('/presets', validateUpdatePreset, updatePreset);
|
||||
|
||||
// create route between controller and '/ontime/aliases' endpoint
|
||||
router.delete('/presets/:preset', deletePreset);
|
||||
|
||||
// create route between controller and '/ontime/aliases' endpoint
|
||||
router.get('/userfields', getUserFields);
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
parseSettings,
|
||||
parseUserFields,
|
||||
parseViewSettings,
|
||||
parsePresets,
|
||||
} from './parserFunctions.js';
|
||||
import { parseExcelDate } from './time.js';
|
||||
import { configService } from '../services/ConfigService.js';
|
||||
@@ -329,6 +330,7 @@ export const parseJson = async (jsonData: Partial<DatabaseModel>): Promise<Datab
|
||||
settings: parseSettings(jsonData) ?? dbModel.settings,
|
||||
viewSettings: parseViewSettings(jsonData) ?? dbModel.viewSettings,
|
||||
aliases: parseAliases(jsonData),
|
||||
presets: parsePresets(jsonData),
|
||||
userFields: parseUserFields(jsonData),
|
||||
osc: parseOsc(jsonData) ?? dbModel.osc,
|
||||
http: parseHttp(jsonData) ?? dbModel.http,
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
isOntimeBlock,
|
||||
isOntimeCycle,
|
||||
HttpSubscription,
|
||||
Presets,
|
||||
} from 'ontime-types';
|
||||
|
||||
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
|
||||
@@ -250,6 +251,32 @@ export const parseAliases = (data): Alias[] => {
|
||||
return newAliases;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse presets portion of an entry
|
||||
* @param {object} data - data object
|
||||
* @returns {object} - event object data
|
||||
*/
|
||||
export const parsePresets = (data): Presets => {
|
||||
const newPresets: { [key: string]: { alias: string; pathAndParams: string; enabled: boolean } } = {};
|
||||
if ('presets' in data) {
|
||||
console.log('Found Presets definition, importing...');
|
||||
try {
|
||||
for (const preset in data.presets) {
|
||||
const newPreset = {
|
||||
alias: data.presets[preset].alias,
|
||||
pathAndParams: data.presets[preset].pathAndParams,
|
||||
enabled: data.presets[preset].enabled,
|
||||
};
|
||||
newPresets[preset] = newPreset;
|
||||
}
|
||||
console.log(`Uploaded ${Object.keys(newPresets).length} preset(s)`);
|
||||
} catch (error) {
|
||||
console.log(`Error: ${error}`);
|
||||
}
|
||||
}
|
||||
return newPresets;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse userFields entry
|
||||
* @param {object} data - data object
|
||||
|
||||
@@ -436,6 +436,13 @@
|
||||
"pathAndParams": "lower?bg=ff2&text=f00&size=0.6&transition=5"
|
||||
}
|
||||
],
|
||||
"presets": {
|
||||
"test": {
|
||||
"enabled": true,
|
||||
"preset": "test",
|
||||
"pathAndParams": "lower?bg=ff2&text=f00&size=0.6&transition=5"
|
||||
}
|
||||
},
|
||||
"userFields": {
|
||||
"user0": "user0",
|
||||
"user1": "user1",
|
||||
|
||||
@@ -436,6 +436,13 @@
|
||||
"pathAndParams": "lower?bg=ff2&text=f00&size=0.6&transition=5"
|
||||
}
|
||||
],
|
||||
"presets": {
|
||||
"test": {
|
||||
"enabled": true,
|
||||
"preset": "test",
|
||||
"pathAndParams": "lower?bg=ff2&text=f00&size=0.6&transition=5"
|
||||
}
|
||||
},
|
||||
"userFields": {
|
||||
"user0": "user0",
|
||||
"user1": "user1",
|
||||
|
||||
Vendored
+7
@@ -436,6 +436,13 @@
|
||||
"pathAndParams": "lower?bg=ff2&text=f00&size=0.6&transition=5"
|
||||
}
|
||||
],
|
||||
"presets": {
|
||||
"test": {
|
||||
"enabled": true,
|
||||
"preset": "test",
|
||||
"pathAndParams": "lower?bg=ff2&text=f00&size=0.6&transition=5"
|
||||
}
|
||||
},
|
||||
"userFields": {
|
||||
"user0": "user0",
|
||||
"user1": "user1",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Settings } from './core/Settings.type.js';
|
||||
import { UserFields } from './core/UserFields.type.js';
|
||||
import { ViewSettings } from './core/Views.type.js';
|
||||
import { HttpSettings } from '../index.js';
|
||||
import { Presets } from './core/Presets.type.js';
|
||||
|
||||
export type DatabaseModel = {
|
||||
rundown: OntimeRundown;
|
||||
@@ -13,6 +14,7 @@ export type DatabaseModel = {
|
||||
settings: Settings;
|
||||
viewSettings: ViewSettings;
|
||||
aliases: Alias[];
|
||||
presets: Presets;
|
||||
userFields: UserFields;
|
||||
osc: OSCSettings;
|
||||
http: HttpSettings;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export type Presets = {
|
||||
[key: string]: {
|
||||
alias: string;
|
||||
pathAndParams: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
};
|
||||
@@ -27,6 +27,9 @@ export type { TimeFormat } from './definitions/core/TimeFormat.type.js';
|
||||
// ---> Aliases
|
||||
export type { Alias } from './definitions/core/Alias.type.js';
|
||||
|
||||
// ---> Presets
|
||||
export type { Presets } from './definitions/core/Presets.type.js';
|
||||
|
||||
// ---> User Fields
|
||||
export type { UserFields } from './definitions/core/UserFields.type.js';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user