Skip to content

Commit 0c196e9

Browse files
authored
Feat/nuke it (#33)
* feat: teardown-button * fix: translations * fix: better teardown icon
1 parent b002579 commit 0c196e9

File tree

13 files changed

+724
-7
lines changed

13 files changed

+724
-7
lines changed

src/api/ateliereLive/ingest.ts

+23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
ResourcesCompactIngestResponse,
33
ResourcesIngestResponse,
4+
ResourcesSourceResponse,
45
ResourcesThumbnailResponse
56
} from '../../../types/ateliere-live';
67
import { LIVE_BASE_API_PATH } from '../../constants';
@@ -182,3 +183,25 @@ export async function createSrtSource(
182183
const errorText = await response.text();
183184
throw new Error(errorText);
184185
}
186+
187+
export async function getIngestSources(
188+
ingestUuid: string
189+
): Promise<ResourcesSourceResponse[]> {
190+
const response = await fetch(
191+
new URL(
192+
LIVE_BASE_API_PATH + `/ingests/${ingestUuid}/sources?expand=true`,
193+
process.env.LIVE_URL
194+
),
195+
{
196+
method: 'GET',
197+
headers: {
198+
authorization: getAuthorizationHeader()
199+
}
200+
}
201+
);
202+
if (response.ok) {
203+
return response.json();
204+
}
205+
const errorText = await response.text();
206+
throw new Error(errorText);
207+
}

src/api/manager/teardown.ts

+300
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
import {
2+
ResourcesCompactIngestResponse,
3+
ResourcesCompactPipelineResponse,
4+
ResourcesConnectionUUIDResponse
5+
} from '../../../types/ateliere-live';
6+
import { FlowStep, TeardownStepNames } from '../../interfaces/production';
7+
import { Result } from '../../interfaces/result';
8+
import { disconnectReceiver } from '../ateliereLive/controlconnections';
9+
import {
10+
deleteSrtSource,
11+
getIngests,
12+
getIngestSources
13+
} from '../ateliereLive/ingest';
14+
import {
15+
deleteMultiviewFromPipeline,
16+
getMultiviewsForPipeline
17+
} from '../ateliereLive/pipelines/multiviews/multiviews';
18+
import {
19+
getPipelineOutputs,
20+
stopAllOutputStreamsByUuid
21+
} from '../ateliereLive/pipelines/outputs/outputs';
22+
import {
23+
getPipelines,
24+
removePipelineStreams,
25+
resetPipeline
26+
} from '../ateliereLive/pipelines/pipelines';
27+
import { deleteStreamByUuid } from '../ateliereLive/streams';
28+
29+
export interface TeardownOptions {
30+
pipelines?: ResourcesCompactPipelineResponse[];
31+
resetPipelines?: boolean;
32+
deleteIngestSRTSources?: boolean;
33+
}
34+
35+
export async function teardown(
36+
options: TeardownOptions
37+
): Promise<Result<FlowStep[]>> {
38+
const {
39+
pipelines: pipes,
40+
resetPipelines = true,
41+
deleteIngestSRTSources = true
42+
} = options;
43+
44+
const steps: FlowStep[] = [];
45+
46+
const addStep = (name: TeardownStepNames, success: boolean, error?: any) => {
47+
steps.push({
48+
step: name,
49+
success,
50+
message: typeof error === 'string' ? error : ''
51+
});
52+
};
53+
54+
const generateResponse = (ok: boolean): Result<FlowStep[]> => {
55+
if (ok) return { ok, value: steps };
56+
return {
57+
ok,
58+
value: steps,
59+
error: steps[steps.length - 1]?.message || 'Unknown error occured'
60+
};
61+
};
62+
63+
// PIPELINES
64+
// STEP 1
65+
// FETCH PIPELINES
66+
let pipelines: ResourcesCompactPipelineResponse[];
67+
68+
if (pipes) {
69+
pipelines = pipes;
70+
} else {
71+
try {
72+
pipelines = await getPipelines().catch(() => {
73+
throw 'Failed to fetch pipelines';
74+
});
75+
} catch (e) {
76+
addStep('pipeline_output_streams', false, 'Failed to fetch pipelines');
77+
return generateResponse(false);
78+
}
79+
}
80+
81+
// STEP 2
82+
// FETCH PIPELINE OUTPUTS
83+
// DELETE PIPELINE OUTPUT STREAMS
84+
try {
85+
for (const pipeline of pipelines) {
86+
const outputs = await getPipelineOutputs(pipeline.uuid).catch(() => {
87+
throw `Failed to fetch outputs for pipeline ${pipeline.name}`;
88+
});
89+
for (const output of outputs) {
90+
if (output.active_streams?.length) {
91+
await stopAllOutputStreamsByUuid(pipeline.uuid, output.uuid).catch(
92+
(e) => {
93+
console.log(e);
94+
throw `Failed to delete streams for output ${output.name} in pipeline ${pipeline.name}`;
95+
}
96+
);
97+
}
98+
}
99+
}
100+
addStep('pipeline_output_streams', true);
101+
} catch (e) {
102+
addStep('pipeline_output_streams', false, e);
103+
return generateResponse(false);
104+
}
105+
106+
// STEP 3
107+
// FETCH PIPELINE MULTIVIEWERS
108+
// DELETE PIPELINE MULTIVIEWERS
109+
try {
110+
for (const pipeline of pipelines) {
111+
const multiviewers = await getMultiviewsForPipeline(pipeline.uuid).catch(
112+
() => {
113+
throw `Failed to fetch multiviewers for pipeline ${pipeline.name}`;
114+
}
115+
);
116+
for (const multiviewer of multiviewers) {
117+
await deleteMultiviewFromPipeline(pipeline.uuid, multiviewer.id).catch(
118+
() => {
119+
throw `Failed to delete multiviewer ${multiviewer.id} in pipeline ${pipeline.name}`;
120+
}
121+
);
122+
}
123+
}
124+
addStep('pipeline_multiviewers', true);
125+
} catch (e) {
126+
addStep('pipeline_multiviewers', false, e);
127+
return generateResponse(false);
128+
}
129+
130+
// STEP 4
131+
// FETCH PIPELINE STREAMS
132+
// DELETE PIPELINE STREAMS
133+
try {
134+
for (const pipeline of pipelines) {
135+
await removePipelineStreams(pipeline.uuid).catch(() => {
136+
throw `Failed to delete streams for pipeline ${pipeline.name}`;
137+
});
138+
}
139+
addStep('pipeline_streams', true);
140+
} catch (e) {
141+
addStep('pipeline_streams', false, e);
142+
return generateResponse(false);
143+
}
144+
145+
// STEP 5
146+
// DELETE PIPELINE CONNECTIONS
147+
try {
148+
for (const pipeline of pipelines) {
149+
const connections: ResourcesConnectionUUIDResponse[] = [
150+
...(pipeline.control_receiver?.incoming_connections || []),
151+
...(pipeline.control_receiver?.outgoing_connections || [])
152+
];
153+
154+
for (const connection of connections) {
155+
await disconnectReceiver(connection.connection_uuid).catch(() => {
156+
throw `Failed to disconnect connection ${connection.connection_uuid} in pipeline ${pipeline.name}`;
157+
});
158+
}
159+
}
160+
addStep('pipeline_control_connections', true);
161+
} catch (e) {
162+
addStep('pipeline_control_connections', false, e);
163+
return generateResponse(false);
164+
}
165+
166+
// STEP 6
167+
// RESET PIPELINES
168+
// ONLY DO THIS STEP IF ENABLED IN OPTIONS
169+
if (resetPipelines) {
170+
try {
171+
for (const pipeline of pipelines) {
172+
await resetPipeline(pipeline.uuid).catch((e) => {
173+
throw `Failed to reset pipeline ${pipeline.name}`;
174+
});
175+
}
176+
addStep('reset_pipelines', true);
177+
} catch (e) {
178+
addStep('reset_pipelines', false, e);
179+
return generateResponse(false);
180+
}
181+
}
182+
183+
// INGESTS
184+
// DO NOT DO THESE STEPS IF PIPELINES WERE SPECIFIED IN THE OPTIONS
185+
if (!pipes) {
186+
let ingests: ResourcesCompactIngestResponse[];
187+
// STEP 7
188+
// FETCH INGESTS
189+
try {
190+
ingests = await getIngests().catch((e) => {
191+
throw 'Failed to fetch ingests';
192+
});
193+
} catch (e) {
194+
addStep('ingest_streams', false, 'Failed to fetch ingests');
195+
return generateResponse(false);
196+
}
197+
198+
// STEP 8
199+
// DELETE INGEST STREAMS
200+
try {
201+
for (const ingest of ingests) {
202+
for (const stream of ingest.streams) {
203+
await deleteStreamByUuid(stream.uuid).catch((e) => {
204+
throw `Failed to delete stream ${stream.uuid} for ingest ${ingest.name}`;
205+
});
206+
}
207+
}
208+
addStep('ingest_streams', true);
209+
} catch (e) {
210+
addStep('ingest_streams', false, e);
211+
return generateResponse(false);
212+
}
213+
214+
// STEP 9
215+
// DELETE INGEST SRC SOURCES
216+
// ONLY DO THIS STEP IF ENABLED IN OPTIONS
217+
if (deleteIngestSRTSources) {
218+
try {
219+
for (const ingest of ingests) {
220+
const sources = await getIngestSources(ingest.uuid);
221+
for (const source of sources) {
222+
if (source.type.includes('SRT')) {
223+
await deleteSrtSource(ingest.uuid, source.source_id).catch(
224+
(e) => {
225+
throw `Failed to delete SRT source ${source.name} for ingest ${ingest.name}`;
226+
}
227+
);
228+
}
229+
}
230+
}
231+
addStep('ingest_src_sources', true);
232+
} catch (e) {
233+
addStep('ingest_src_sources', false, e);
234+
return generateResponse(false);
235+
}
236+
}
237+
}
238+
239+
// STEP 10
240+
// CHECK THAT EVERYTHING WAS REMOVED/DISCONNECTED/DELETED
241+
try {
242+
const newPipelines = await getPipelines().catch((e) => {
243+
throw 'Failed to fetch pipelines';
244+
});
245+
246+
for (const pipeline of newPipelines) {
247+
// CHECK IF ALL OUTPUT STREAMS HAVE BEEN STOPPED
248+
const outputs = await getPipelineOutputs(pipeline.uuid).catch((e) => {
249+
throw `Failed to fetch outputs for pipeline ${pipeline.name}`;
250+
});
251+
for (const output of outputs) {
252+
if (output.active_streams && output.active_streams.length)
253+
throw `Failed to stop all active streams for output ${output.name} in pipeline ${pipeline.name}`;
254+
}
255+
// CHECK IF ALL MULTIVIEWERS HAVE BEEN DELETED
256+
const multiviewers = await getMultiviewsForPipeline(pipeline.uuid).catch(
257+
() => {
258+
throw `Failed to fetch multiviewers for pipeline ${pipeline.name}`;
259+
}
260+
);
261+
if (multiviewers?.length)
262+
throw `Failed to delete all multiviewers for pipeline ${pipeline.name}`;
263+
// CHECK IF ALL PIPELINE STREAMS HAVE BEEN STOPPED
264+
if (pipeline.streams?.length)
265+
throw `Failed to stop all streams for pipeline ${pipeline.name}`;
266+
// CHECK IF ALL PIPELINE CONNECTIONS HAVE BEEN DISCONNECTED
267+
if (pipeline.control_receiver?.incoming_connections?.length)
268+
throw `Failed to disconnect all incoming connections to the control receiver of pipeline ${pipeline.name}`;
269+
if (pipeline.control_receiver?.outgoing_connections?.length)
270+
throw `Failed to disconnect all outgoing connections from the control receiver of pipeline ${pipeline.name}`;
271+
}
272+
273+
if (!pipes) {
274+
const ingests = await getIngests().catch((e) => {
275+
throw 'Failed to fetch ingests';
276+
});
277+
278+
for (const ingest of ingests) {
279+
// CHECK IF ALL INGEST STREAMS HAVE BEEN STOPPED
280+
if (ingest.streams?.length)
281+
throw `Failed to stop ingest streams for ingest ${ingest.name}`;
282+
// CHECK IF ALL SRT SOURCES HAVE BEEN DELETED
283+
// ONLY IF DELETE INGEST SRT SOURCES IS SET TO TRUE IN OPTIONS (DEFAULT)
284+
if (deleteIngestSRTSources && ingest.sources?.length) {
285+
const sources = await getIngestSources(ingest.uuid);
286+
for (const source of sources) {
287+
if (source.type.includes('SRT')) {
288+
throw `Failed to delete SRT source ${source.name} for ingest ${ingest.name}`;
289+
}
290+
}
291+
}
292+
}
293+
}
294+
addStep('teardown_check', true);
295+
return generateResponse(true);
296+
} catch (e) {
297+
addStep('teardown_check', false, e);
298+
return generateResponse(false);
299+
}
300+
}

src/app/api/manager/teardown/route.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { Log } from '../../../../api/logger';
3+
import { isAuthenticated } from '../../../../api/manager/auth';
4+
import { teardown } from '../../../../api/manager/teardown';
5+
6+
export async function POST(request: NextRequest): Promise<NextResponse> {
7+
if (!(await isAuthenticated())) {
8+
return new NextResponse(`Not Authorized!`, {
9+
status: 403
10+
});
11+
}
12+
13+
const options = await request.json();
14+
15+
return teardown(options)
16+
.then((result) => {
17+
return new NextResponse(JSON.stringify(result));
18+
})
19+
.catch((error) => {
20+
Log().error(error);
21+
const errorResponse = {
22+
ok: false,
23+
error: 'unexpected'
24+
};
25+
return new NextResponse(JSON.stringify(errorResponse), { status: 500 });
26+
});
27+
}

src/app/production/[id]/page.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -838,7 +838,6 @@ export default function ProductionConfiguration({ params }: PageProps) {
838838
setSelectedControlPanel={setSelectedControlPanel}
839839
/>
840840
)}
841-
842841
<div className="w-full flex justify-end">
843842
<Pipelines production={productionSetup} />
844843
</div>

0 commit comments

Comments
 (0)