diff --git a/src/api/ateliereLive/pipelines/multiviews/multiviews.ts b/src/api/ateliereLive/pipelines/multiviews/multiviews.ts index ac76f6c..3f1b31d 100644 --- a/src/api/ateliereLive/pipelines/multiviews/multiviews.ts +++ b/src/api/ateliereLive/pipelines/multiviews/multiviews.ts @@ -142,8 +142,14 @@ export async function createMultiviewForPipeline( ); if (response.ok) { + Log().info( + `Created multiview for pipeline '${pipelineUUID}' from preset` + ); return await response.json(); } + Log().info( + `ERROR: Could not create multiview for pipeline '${pipelineUUID}' from preset` + ); throw await response.text(); } ); @@ -173,8 +179,14 @@ export async function deleteMultiviewFromPipeline( ); if (response.ok) { + Log().info( + `Deleted multiview ${multiviewId} for pipeline '${pipelineUUID}' from preset` + ); return; } + Log().info( + `ERROR: Could not delete multiview ${multiviewId} for pipeline '${pipelineUUID}' from preset` + ); throw await response.text(); } @@ -216,7 +228,13 @@ export async function updateMultiviewForPipeline( } ); if (response.ok) { + Log().info( + `Updated multiview ${multiviewId} for pipeline '${pipelineUUID}' from preset` + ); return await response.json(); } + Log().info( + `ERROR: Could not update multiview ${multiviewId} for pipeline '${pipelineUUID}' from preset` + ); throw await response.text(); } diff --git a/src/api/manager/multiview-presets.ts b/src/api/manager/multiview-presets.ts index 5ecf9b0..ef8fb51 100644 --- a/src/api/manager/multiview-presets.ts +++ b/src/api/manager/multiview-presets.ts @@ -18,13 +18,3 @@ export async function getMultiviewPreset( .collection('multiview-presets') .findOne({ _id: new ObjectId(id) })) as WithId; } - -// TODO Add this when possibility to update and add mv-presets are added -// export async function putMultiviewPreset( -// newMultiviewPreset: MultiviewPreset -// ): Promise { -// const db = await getDatabase(); -// await db -// .collection('multiview-presets') -// .insertOne({ ...newMultiviewPreset, _id: new ObjectId() }); -// } diff --git a/src/api/manager/multiviews.ts b/src/api/manager/multiviews.ts index cf84509..7d51cfe 100644 --- a/src/api/manager/multiviews.ts +++ b/src/api/manager/multiviews.ts @@ -1,23 +1,23 @@ import { ObjectId, WithId } from 'mongodb'; -import { MultiviewPreset } from '../../interfaces/preset'; +import { TMultiviewLayout } from '../../interfaces/preset'; import { getDatabase } from '../mongoClient/dbClient'; -export async function getMultiviewLayouts(): Promise { +export async function getMultiviewLayouts(): Promise { const db = await getDatabase(); - return await db.collection('multiviews').find({}).toArray(); + return await db.collection('multiviews').find({}).toArray(); } export async function getMultiviewLayout( id: string -): Promise> { +): Promise> { const db = await getDatabase(); return (await db - .collection('multiviews') - .findOne({ _id: new ObjectId(id) })) as WithId; + .collection('multiviews') + .findOne({ _id: new ObjectId(id) })) as WithId; } export async function putMultiviewLayout( - newMultiviewLayout: MultiviewPreset + newMultiviewLayout: TMultiviewLayout ): Promise { const db = await getDatabase(); const collection = db.collection('multiviews'); diff --git a/src/api/manager/productions.ts b/src/api/manager/productions.ts index d0b884f..3459255 100644 --- a/src/api/manager/productions.ts +++ b/src/api/manager/productions.ts @@ -17,6 +17,7 @@ export async function getProduction(id: string): Promise { .collection('productions') .findOne({ _id: new ObjectId(id) })) as ProductionWithId; } + export async function setProductionsIsActiveFalse(): Promise< UpdateResult > { @@ -25,6 +26,7 @@ export async function setProductionsIsActiveFalse(): Promise< .collection('productions') .updateMany({}, { $set: { isActive: false } }); } + export async function putProduction( id: string, production: Production diff --git a/src/api/manager/workflow.ts b/src/api/manager/workflow.ts index afcfa72..d0946ae 100644 --- a/src/api/manager/workflow.ts +++ b/src/api/manager/workflow.ts @@ -16,7 +16,9 @@ import { } from '../ateliereLive/pipelines/pipelines'; import { createMultiviewForPipeline, - deleteAllMultiviewsFromPipeline + deleteAllMultiviewsFromPipeline, + deleteMultiviewFromPipeline, + updateMultiviewForPipeline } from '../ateliereLive/pipelines/multiviews/multiviews'; import { getSourceIdFromSourceName, @@ -52,6 +54,7 @@ import { getDatabase } from '../mongoClient/dbClient'; import { updatedMonitoringForProduction } from './job/syncMonitoring'; import { createControlPanelWebSocket } from '../ateliereLive/websocket'; import { ObjectId } from 'mongodb'; +import { MultiviewSettings } from '../../interfaces/multiview'; const isUsed = (pipeline: ResourcesPipelineResponse) => { const hasStreams = pipeline.streams.length > 0; @@ -810,7 +813,7 @@ export async function startProduction( { step: 'pipeline_outputs', success: false }, { step: 'multiviews', success: false } ], - error: 'Unknown error occured' + error: 'Could not start multiviews' }; } return { @@ -993,3 +996,265 @@ export async function startProduction( }; } } + +export async function postMultiviewersOnRunningProduction( + production: Production, + additions: MultiviewSettings[] +) { + try { + const multiview = production.production_settings.pipelines[0].multiviews; + if (!multiview) { + Log().error( + `No multiview settings specified for production: ${production.name}` + ); + throw `No multiview settings specified for production: ${production.name}`; + } + + const productionSettings = { + ...production.production_settings, + pipelines: production.production_settings.pipelines.map((pipeline) => { + return { + ...pipeline, + multiviews: additions + }; + }) + }; + + const runtimeMultiviews = await createMultiviewForPipeline( + productionSettings, + production.sources + ).catch(async (error) => { + Log().error( + `Failed to create multiview for pipeline '${productionSettings.pipelines[0].pipeline_name}/${productionSettings.pipelines[0].pipeline_id}'`, + error + ); + throw `Failed to create multiview for pipeline '${productionSettings.pipelines[0].pipeline_name}/${productionSettings.pipelines[0].pipeline_id}': ${error}`; + }); + + const multiviewsWithUpdatedId: MultiviewSettings[] = [ + ...multiview.slice(0, multiview.length - runtimeMultiviews.length), + ...runtimeMultiviews.map((runtimeMultiview, index) => { + return { + ...multiview[multiview.length - runtimeMultiviews.length + index], + multiview_id: runtimeMultiview.id + }; + }) + ]; + + await putProduction(production._id.toString(), { + ...production, + production_settings: { + ...production.production_settings, + pipelines: production.production_settings.pipelines.map((pipeline) => { + return { + ...pipeline, + multiviews: multiviewsWithUpdatedId + }; + }) + } + }).catch(async (error) => { + Log().error( + `Failed to save multiviews for pipeline '${productionSettings.pipelines[0].pipeline_name}/${productionSettings.pipelines[0].pipeline_id}' to database`, + error + ); + throw error; + }); + + return { + ok: true, + value: { + success: true, + steps: [ + { + step: 'create_multiview', + success: true + } + ] + } + }; + } catch (e) { + Log().error('Could not create multiviews'); + Log().error(e); + if (typeof e !== 'string') { + return { + ok: false, + value: { + success: true, + steps: [ + { + step: 'create_multiview', + success: false + } + ] + }, + error: 'Could not create multiviews' + }; + } + return { + ok: false, + value: { + success: true, + steps: [ + { + step: 'create_multiview', + success: false, + message: e + } + ] + }, + error: e + }; + } +} + +export async function putMultiviewersOnRunningProduction( + production: Production, + updates: MultiviewSettings[] +) { + try { + updates.map(async (multiview) => { + const views = multiview.layout.views; + + if ( + multiview.multiview_id && + production.production_settings.pipelines[0].pipeline_id + ) { + await updateMultiviewForPipeline( + production.production_settings.pipelines[0].pipeline_id, + multiview.multiview_id, + views + ); + } + }); + + return { + ok: true, + value: { + success: true, + steps: [ + { + step: 'update_multiview', + success: true + } + ] + } + }; + } catch (e) { + Log().error('Could not update multiviews'); + Log().error(e); + if (typeof e !== 'string') { + return { + ok: false, + value: { + success: true, + steps: [ + { + step: 'update_multiview', + success: false + } + ] + }, + error: 'Could not update multiviews' + }; + } + return { + ok: false, + value: { + success: true, + steps: [ + { + step: 'update_multiview', + success: false, + message: e + } + ] + }, + error: e + }; + } +} + +export async function deleteMultiviewersOnRunningProduction( + production: Production, + removals: MultiviewSettings[] +) { + try { + const pipeline = production.production_settings.pipelines.find((p) => + p.multiviews ? p.multiviews?.length > 0 : undefined + ); + const multiviewIndexArray = pipeline?.multiviews + ? pipeline.multiviews.map((p) => p.for_pipeline_idx) + : undefined; + + const multiviewIndex = multiviewIndexArray?.find((p) => p !== undefined); + + const pipelineUUID = + multiviewIndex !== undefined + ? production.production_settings.pipelines[multiviewIndex].pipeline_id + : undefined; + + if (!pipelineUUID) return; + + await Promise.allSettled( + removals.map((multiview) => { + if (multiview.multiview_id) { + deleteMultiviewFromPipeline( + pipelineUUID, + multiview.multiview_id + ).catch((error) => { + Log().error( + `Failed to remove multiview '${multiview.multiview_id}' from pipeline '${pipelineUUID}'`, + error + ); + throw `Failed to remove multiview '${multiview.multiview_id}' from pipeline '${pipelineUUID}': ${error}`; + }); + } + }) + ); + + return { + ok: true, + value: { + success: true, + steps: [ + { + step: 'delete_multiview', + success: true + } + ] + } + }; + } catch (e) { + Log().error('Could not delete multiviews'); + Log().error(e); + if (typeof e !== 'string') { + return { + ok: false, + value: { + success: true, + steps: [ + { + step: 'delete_multiview', + success: false + } + ] + }, + error: 'Could not delete multiviews' + }; + } + return { + ok: false, + value: { + success: true, + steps: [ + { + step: 'delete_multiview', + success: false, + message: e + } + ] + }, + error: e + }; + } +} diff --git a/src/app/api/manager/multiview-presets/route.ts b/src/app/api/manager/multiview-presets/route.ts index c15d7b3..f6d4d5d 100644 --- a/src/app/api/manager/multiview-presets/route.ts +++ b/src/app/api/manager/multiview-presets/route.ts @@ -1,13 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { isAuthenticated } from '../../../../api/manager/auth'; -import { - getMultiviewPresets - // TODO Add this when possibility to update and add mv-presets are added - // putMultiviewPreset -} from '../../../../api/manager/multiview-presets'; -// TODO Add this when possibility to update and add mv-presets are added -// import { Log } from '../../../../api/logger'; -// import { MultiviewPreset } from '../../../../interfaces/preset'; +import { getMultiviewPresets } from '../../../../api/manager/multiview-presets'; export async function GET(request: NextRequest): Promise { if (!(await isAuthenticated())) { @@ -24,25 +17,3 @@ export async function GET(request: NextRequest): Promise { }); } } - -// TODO Add this when possibility to update and add mv-presets are added -// export async function PUT(request: NextRequest): Promise { -// if (!(await isAuthenticated())) { -// return new NextResponse(`Not Authorized!`, { -// status: 403 -// }); -// } - -// try { -// const body = (await request.json()) as MultiviewPreset; -// const newMultiviewPreset = await putMultiviewPreset(body); -// return await new NextResponse(JSON.stringify(newMultiviewPreset), { -// status: 200 -// }); -// } catch (error) { -// Log().warn('Could not update preset', error); -// return new NextResponse(`Error searching DB! Error: ${error}`, { -// status: 500 -// }); -// } -// } diff --git a/src/app/api/manager/multiviewersOnRunningProduction/[id]/route.ts b/src/app/api/manager/multiviewersOnRunningProduction/[id]/route.ts new file mode 100644 index 0000000..3869fab --- /dev/null +++ b/src/app/api/manager/multiviewersOnRunningProduction/[id]/route.ts @@ -0,0 +1,51 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { Log } from '../../../../../api/logger'; +import { isAuthenticated } from '../../../../../api/manager/auth'; +import { + deleteMultiviewersOnRunningProduction, + putMultiviewersOnRunningProduction +} from '../../../../../api/manager/workflow'; + +export async function PUT(request: NextRequest): Promise { + if (!(await isAuthenticated())) { + return new NextResponse(`Not Authorized!`, { + status: 403 + }); + } + + const { production, updates } = await request.json(); + return putMultiviewersOnRunningProduction(production, updates) + .then((result) => { + return new NextResponse(JSON.stringify(result)); + }) + .catch((error) => { + Log().error(error); + const errorResponse = { + ok: false, + error: 'Could not update multiviewers' + }; + return new NextResponse(JSON.stringify(errorResponse), { status: 500 }); + }); +} + +export async function DELETE(request: NextRequest): Promise { + if (!(await isAuthenticated())) { + return new NextResponse(`Not Authorized!`, { + status: 403 + }); + } + + const { production, removals } = await request.json(); + return deleteMultiviewersOnRunningProduction(production, removals) + .then((result) => { + return new NextResponse(JSON.stringify(result)); + }) + .catch((error) => { + Log().error(error); + const errorResponse = { + ok: false, + error: 'Could not remove multiviewers' + }; + return new NextResponse(JSON.stringify(errorResponse), { status: 500 }); + }); +} diff --git a/src/app/api/manager/multiviewersOnRunningProduction/route.ts b/src/app/api/manager/multiviewersOnRunningProduction/route.ts new file mode 100644 index 0000000..cbc0496 --- /dev/null +++ b/src/app/api/manager/multiviewersOnRunningProduction/route.ts @@ -0,0 +1,26 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { Log } from '../../../../api/logger'; +import { isAuthenticated } from '../../../../api/manager/auth'; +import { postMultiviewersOnRunningProduction } from '../../../../api/manager/workflow'; + +export async function POST(request: NextRequest): Promise { + if (!(await isAuthenticated())) { + return new NextResponse(`Not Authorized!`, { + status: 403 + }); + } + + const { production, additions } = await request.json(); + return postMultiviewersOnRunningProduction(production, additions) + .then((result) => { + return new NextResponse(JSON.stringify(result)); + }) + .catch((error) => { + Log().error(error); + const errorResponse = { + ok: false, + error: 'Could not add multiviewers' + }; + return new NextResponse(JSON.stringify(errorResponse), { status: 500 }); + }); +} diff --git a/src/app/api/manager/multiviews/route.tsx b/src/app/api/manager/multiviews/route.tsx index de39777..1552d56 100644 --- a/src/app/api/manager/multiviews/route.tsx +++ b/src/app/api/manager/multiviews/route.tsx @@ -33,7 +33,7 @@ export async function PUT(request: NextRequest): Promise { try { const body = (await request.json()) as MultiviewPreset; const newMultiviewLayout = await putMultiviewLayout(body); - return await new NextResponse(JSON.stringify(newMultiviewLayout), { + return new NextResponse(JSON.stringify(newMultiviewLayout), { status: 200 }); } catch (error) { diff --git a/src/app/production/[id]/page.tsx b/src/app/production/[id]/page.tsx index b2ded7e..0b6f092 100644 --- a/src/app/production/[id]/page.tsx +++ b/src/app/production/[id]/page.tsx @@ -57,6 +57,12 @@ import { useUpdateStream } from '../../../hooks/streams'; import { usePutProductionPipelineSourceAlignmentAndLatency } from '../../../hooks/productions'; import { useIngestSourceId } from '../../../hooks/ingests'; import cloneDeep from 'lodash.clonedeep'; +import { + useAddMultiviewersOnRunningProduction, + useRemoveMultiviewersOnRunningProduction, + useUpdateMultiviewersOnRunningProduction +} from '../../../hooks/workflow'; +import { MultiviewSettings } from '../../../interfaces/multiview'; export default function ProductionConfiguration({ params }: PageProps) { const t = useTranslate(); @@ -94,6 +100,12 @@ export default function ProductionConfiguration({ params }: PageProps) { const [updateMultiviewViews] = useMultiviews(); const [updateSourceInputSlotOnMultiviewLayouts] = useUpdateSourceInputSlotOnMultiviewLayouts(); + const [addMultiviewersOnRunningProduction] = + useAddMultiviewersOnRunningProduction(); + const [updateMultiviewersOnRunningProduction] = + useUpdateMultiviewersOnRunningProduction(); + const [removeMultiviewersOnRunningProduction] = + useRemoveMultiviewersOnRunningProduction(); //FROM LIVE API const [pipelines, loadingPipelines, , refreshPipelines] = usePipelines(); @@ -153,7 +165,7 @@ export default function ProductionConfiguration({ params }: PageProps) { }; useEffect(() => { - if (updateMuliviewLayouts && productionSetup && !productionSetup.isActive) { + if (updateMuliviewLayouts && productionSetup) { updateSourceInputSlotOnMultiviewLayouts(productionSetup).then( (updatedSetup) => { if (!updatedSetup) return; @@ -265,7 +277,12 @@ export default function ProductionConfiguration({ params }: PageProps) { const updatePreset = (preset: Preset) => { if (!productionSetup?._id) return; - putProduction(productionSetup?._id.toString(), { + + const presetMultiviews = preset.pipelines[0].multiviews; + const productionMultiviews = + productionSetup.production_settings.pipelines[0].multiviews; + + const updatedPreset = { ...productionSetup, production_settings: { ...preset, @@ -283,7 +300,56 @@ export default function ProductionConfiguration({ params }: PageProps) { }; }) } - }).then(() => { + }; + + if (productionSetup.isActive && presetMultiviews && productionMultiviews) { + const productionMultiviewsMap = new Map( + productionMultiviews.map((item) => [item.multiview_id, item]) + ); + const presetMultiviewsMap = new Map( + presetMultiviews.map((item) => [item.multiview_id, item]) + ); + + const additions: MultiviewSettings[] = []; + const updates: MultiviewSettings[] = []; + + presetMultiviews.forEach((newItem) => { + const oldItem = productionMultiviewsMap.get(newItem.multiview_id); + + if (!oldItem) { + additions.push(newItem); + } else if (JSON.stringify(oldItem) !== JSON.stringify(newItem)) { + updates.push(newItem); + } + }); + + const removals = productionMultiviews.filter( + (oldItem) => !presetMultiviewsMap.has(oldItem.multiview_id) + ); + + if (additions.length > 0) { + addMultiviewersOnRunningProduction( + (productionSetup?._id.toString(), updatedPreset), + additions + ); + } + + if (updates.length > 0) { + updateMultiviewersOnRunningProduction( + (productionSetup?._id.toString(), updatedPreset), + updates + ); + } + + if (removals.length > 0) { + removeMultiviewersOnRunningProduction( + (productionSetup?._id.toString(), updatedPreset), + removals + ); + } + } + + putProduction(productionSetup?._id.toString(), updatedPreset).then(() => { refreshProduction(); }); }; @@ -735,7 +801,7 @@ export default function ProductionConfiguration({ params }: PageProps) { <> { @@ -778,7 +844,7 @@ export default function ProductionConfiguration({ params }: PageProps) { updatePreset={updatePreset} /> diff --git a/src/components/modal/Modal.tsx b/src/components/modal/Modal.tsx index 007b9df..a06d2a5 100644 --- a/src/components/modal/Modal.tsx +++ b/src/components/modal/Modal.tsx @@ -6,7 +6,7 @@ import { Modal as BaseModal } from '@mui/base'; type BaseModalProps = { open: boolean; forwardRef?: LegacyRef | null; - outsideClick: () => void; + outsideClick?: () => void; className?: string; }; @@ -16,16 +16,21 @@ export function Modal({ open, children, outsideClick, className }: ModalProps) { const element = useRef(null); useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (element.current && !element.current.contains(event.target as Node)) { - outsideClick(); - } - }; + if (outsideClick) { + const handleClickOutside = (event: MouseEvent) => { + if ( + element.current && + !element.current.contains(event.target as Node) + ) { + outsideClick(); + } + }; - document.addEventListener('click', handleClickOutside, true); - return () => { - document.removeEventListener('click', handleClickOutside, true); - }; + document.addEventListener('click', handleClickOutside, true); + return () => { + document.removeEventListener('click', handleClickOutside, true); + }; + } }, [outsideClick]); return ( diff --git a/src/components/modal/UpdateMultiviewersModal.tsx b/src/components/modal/UpdateMultiviewersModal.tsx new file mode 100644 index 0000000..526b12b --- /dev/null +++ b/src/components/modal/UpdateMultiviewersModal.tsx @@ -0,0 +1,37 @@ +import { useTranslate } from '../../i18n/useTranslate'; +import { Button } from '../button/Button'; +import { Modal } from './Modal'; + +type UpdateMultiviewersModalProps = { + open: boolean; + onAbort: () => void; + onConfirm: () => void; +}; + +export function UpdateMultiviewersModal({ + open, + onAbort, + onConfirm +}: UpdateMultiviewersModalProps) { + const t = useTranslate(); + return ( + +
+

{t('preset.confirm_update_multiviewers')}

+ +
+ + + +
+
+
+ ); +} diff --git a/src/components/modal/configureMultiviewModal/ConfigureMultiviewButton.tsx b/src/components/modal/configureMultiviewModal/ConfigureMultiviewButton.tsx index e253496..0912290 100644 --- a/src/components/modal/configureMultiviewModal/ConfigureMultiviewButton.tsx +++ b/src/components/modal/configureMultiviewModal/ConfigureMultiviewButton.tsx @@ -38,10 +38,14 @@ export function ConfigureMultiviewButton({ disabled ? 'bg-button-bg/50 pointer-events-none' : 'bg-button-bg' }`} > - Multiviewer - + {production?.isActive + ? t('production.manage_multiviewers') + : 'Multiviewer'} + {!production?.isActive && ( + + )} {preset && ( (null); const addNewLayout = usePutMultiviewLayout(); @@ -60,6 +62,14 @@ export function ConfigureMultiviewModal({ }, [multiviews]); const onSave = () => { + if (production?.isActive && !confirmUpdateModalOpen) { + setConfirmUpdateModalOpen(true); + return; + } + if (production?.isActive && confirmUpdateModalOpen) { + setConfirmUpdateModalOpen(false); + } + const presetToUpdate = deepclone(preset); if (!multiviews) { @@ -149,6 +159,9 @@ export function ConfigureMultiviewModal({ }; const addNewMultiview = (newMultiview: MultiviewSettings) => { + // Remove _id from newMultiview to avoid conflicts with existing multiviews + delete newMultiview._id; + setMultiviews((prevMultiviews) => prevMultiviews ? [...prevMultiviews, newMultiview] : [newMultiview] ); @@ -160,7 +173,7 @@ export function ConfigureMultiviewModal({ }; return ( - clearInputs()}> + {!layoutModalOpen && (
{multiviews && @@ -209,7 +222,12 @@ export function ConfigureMultiviewModal({ @@ -244,6 +262,14 @@ export function ConfigureMultiviewModal({ onSave={() => (layoutModalOpen ? onUpdateLayoutPreset() : onSave())} />
+ + {confirmUpdateModalOpen && ( + setConfirmUpdateModalOpen(false)} + onConfirm={() => onSave()} + /> + )}
); } diff --git a/src/components/modal/configureMultiviewModal/MultiviewSettings.tsx b/src/components/modal/configureMultiviewModal/MultiviewSettings.tsx index 45de445..cdf761a 100644 --- a/src/components/modal/configureMultiviewModal/MultiviewSettings.tsx +++ b/src/components/modal/configureMultiviewModal/MultiviewSettings.tsx @@ -61,6 +61,7 @@ export default function MultiviewSettingsConfig({ } handleUpdateMultiview({ ...multiviewLayouts[0], + _id: multiviewLayouts[0]._id?.toString(), for_pipeline_idx: 0 }); } @@ -80,7 +81,11 @@ export default function MultiviewSettingsConfig({ output: multiview?.output || selected.output }; setSelectedMultiviewLayout(updatedMultiview); - handleUpdateMultiview({ ...updatedMultiview, for_pipeline_idx: 0 }); + handleUpdateMultiview({ + ...updatedMultiview, + _id: updatedMultiview._id?.toString(), + for_pipeline_idx: 0 + }); }; const getNumber = (val: string, prev: number) => { diff --git a/src/components/startProduction/StartProductionButton.tsx b/src/components/startProduction/StartProductionButton.tsx index 61b4384..3f7944c 100644 --- a/src/components/startProduction/StartProductionButton.tsx +++ b/src/components/startProduction/StartProductionButton.tsx @@ -106,7 +106,13 @@ export function StartProductionButton({ ), { ...pipelineToUpdateMultiview, - multiviews: [{ ...multiviewLayouts[0], for_pipeline_idx: 0 }] + multiviews: [ + { + ...multiviewLayouts[0], + for_pipeline_idx: 0, + _id: multiviewLayouts[0]._id?.toString() + } + ] } ] } diff --git a/src/hooks/monitoring.ts b/src/hooks/monitoring.ts index f460fd8..2814a80 100644 --- a/src/hooks/monitoring.ts +++ b/src/hooks/monitoring.ts @@ -30,8 +30,7 @@ export function useMonitoring(id: string): DataHook { const getMonitoring = async (id: string): Promise => { const response = await fetch(`/api/manager/monitoring/${id}`, { method: 'GET', - // TODO: Implement api key - headers: [['x-api-key', `Bearer apisecretkey`]] + headers: [['x-api-key', `Bearer ${API_SECRET_KEY}`]] }); if (response.ok) { return response.json(); diff --git a/src/hooks/multiviewPreset.ts b/src/hooks/multiviewPreset.ts index dfb8260..a31a616 100644 --- a/src/hooks/multiviewPreset.ts +++ b/src/hooks/multiviewPreset.ts @@ -52,18 +52,3 @@ export function useMultiviewPresets(): DataHook { return [multiviewPresets, loading, undefined]; } - -// TODO Add this when possibility to update and add mv-presets are added -// export function usePutMultiviewPreset() { -// return async (newMultiviewPreset: MultiviewPreset): Promise => { -// const response = await fetch('/api/manager/presets', { -// method: 'PUT', -// headers: [['x-api-key', `Bearer ${API_SECRET_KEY}`]], -// body: JSON.stringify(newMultiviewPreset) -// }); -// if (response.ok) { -// return; -// } -// throw await response.text(); -// }; -// } diff --git a/src/hooks/workflow.ts b/src/hooks/workflow.ts index d9ccf98..5c8b64e 100644 --- a/src/hooks/workflow.ts +++ b/src/hooks/workflow.ts @@ -8,6 +8,7 @@ import { import { CallbackHook } from './types'; import { Result } from '../interfaces/result'; import { API_SECRET_KEY } from '../utils/constants'; +import { MultiviewSettings } from '../interfaces/multiview'; import { TeardownOptions } from '../api/manager/teardown'; export function useStopProduction(): CallbackHook< @@ -57,6 +58,86 @@ export function useStartProduction(): CallbackHook< return [startProduction, loading]; } +export function useAddMultiviewersOnRunningProduction(): CallbackHook< + (production: Production, additions: MultiviewSettings[]) => void +> { + const [loading, setLoading] = useState(false); + + const addMultiviewersOnRunningProduction = useCallback( + async (production: Production, additions: MultiviewSettings[]) => { + setLoading(true); + + fetch('/api/manager/multiviewersOnRunningProduction', { + method: 'POST', + headers: [['x-api-key', `Bearer ${API_SECRET_KEY}`]], + body: JSON.stringify({ production, additions }) + }).finally(() => setLoading(false)); + }, + [] + ); + + return [addMultiviewersOnRunningProduction, loading]; +} + +export function useUpdateMultiviewersOnRunningProduction(): CallbackHook< + (production: Production, updates: MultiviewSettings[]) => void +> { + const [loading, setLoading] = useState(false); + + const updateMultiviewersOnRunningProduction = useCallback( + async (production: Production, updates: MultiviewSettings[]) => { + setLoading(true); + + updates.forEach(async (multiview) => { + try { + return await fetch( + `/api/manager/multiviewersOnRunningProduction/${multiview.multiview_id}`, + { + method: 'PUT', + headers: [['x-api-key', `Bearer ${API_SECRET_KEY}`]], + body: JSON.stringify({ production, updates }) + } + ); + } finally { + setLoading(false); + } + }); + }, + [] + ); + + return [updateMultiviewersOnRunningProduction, loading]; +} + +export function useRemoveMultiviewersOnRunningProduction(): CallbackHook< + (production: Production, removals: MultiviewSettings[]) => void +> { + const [loading, setLoading] = useState(false); + + const removeMultiviewersOnRunningProduction = useCallback( + async (production: Production, removals: MultiviewSettings[]) => { + setLoading(true); + removals.forEach(async (multiview) => { + try { + return await fetch( + `/api/manager/multiviewersOnRunningProduction/${multiview.multiview_id}`, + { + method: 'DELETE', + headers: [['x-api-key', `Bearer ${API_SECRET_KEY}`]], + body: JSON.stringify({ production, removals }) + } + ); + } finally { + setLoading(false); + } + }); + }, + [] + ); + + return [removeMultiviewersOnRunningProduction, loading]; +} + export function useTeardown(): CallbackHook< (option: TeardownOptions) => Promise> > { diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 442a861..71c196d 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -78,7 +78,8 @@ export const en = { source: 'Source', add: 'Add', add_other_source_type: 'Add other source type', - configure_outputs: 'Configure Outputs' + configure_outputs: 'Configure Outputs', + manage_multiviewers: 'Manage multiviewers' }, configure_alignment_latency: { configure_alignment_latency: @@ -680,7 +681,10 @@ export const en = { layout_already_exist: 'Layout {{layoutNameAlreadyExist}} will be replaced on save', remove_multiview: 'Remove multiview', - add_another_multiview: 'Add another multiview' + add_another_multiview: 'Add another multiview', + confirm_update_multiviewers: + 'Are you sure you want to update multiviewers for the running production?', + confirm_update: 'Update multiviewers' }, error: { missing_sources_in_db: 'Missing sources, please restart production.', diff --git a/src/i18n/locales/sv.ts b/src/i18n/locales/sv.ts index 5c2a9d2..6f85572 100644 --- a/src/i18n/locales/sv.ts +++ b/src/i18n/locales/sv.ts @@ -80,7 +80,8 @@ export const sv = { source: 'Källa', add: 'Lägg till', add_other_source_type: 'Lägg till annan källtyp', - configure_outputs: 'Konfigurera Outputs' + configure_outputs: 'Konfigurera Outputs', + manage_multiviewers: 'Uppdatera multiviewers' }, configure_alignment_latency: { source_name: 'Källnamn', @@ -684,7 +685,10 @@ export const sv = { layout_already_exist: 'Konfigurationen {{layoutNameAlreadyExist}} skrivs över om du sparar', remove_multiview: 'Ta bort multiview', - add_another_multiview: 'Lägg till ny multiview' + add_another_multiview: 'Lägg till ny multiview', + confirm_update_multiviewers: + 'Är du säker på att du vill uppdatera multiview för pågående produktion?', + confirm_update: 'Uppdatera multiviewers' }, error: { missing_sources_in_db: 'Källor saknas, var god starta om produktionen.', diff --git a/src/interfaces/multiview.ts b/src/interfaces/multiview.ts index a138902..593ccc5 100644 --- a/src/interfaces/multiview.ts +++ b/src/interfaces/multiview.ts @@ -42,4 +42,5 @@ export interface MultiviewSettings { name: string; layout: MultiviewStructureLayout; output: MultiviewOutputSettings; + _id?: string; } diff --git a/src/interfaces/preset.ts b/src/interfaces/preset.ts index 8bf4809..6e791cf 100644 --- a/src/interfaces/preset.ts +++ b/src/interfaces/preset.ts @@ -19,7 +19,7 @@ export interface PresetReference { } export interface MultiviewPreset { - _id?: ObjectId; + _id?: ObjectId | string; name: string; layout: MultiviewStructureLayout; output: MultiviewOutputSettings;