diff --git a/.env.sample b/.env.sample index 8f85e45f..8c3990fc 100644 --- a/.env.sample +++ b/.env.sample @@ -1,10 +1,10 @@ # MongoDB -MONGODB_URI=${MONGODB_URI:-mongodb://api:password@localhost:27017/agile-live-gui} +MONGODB_URI=${MONGODB_URI:-mongodb://api:password@localhost:27017/live-gui} -# AgileLive System Controlleer -AGILE_URL=${AGILE_URL:-https://localhost:8080} -AGILE_CREDENTIALS=${AGILE_CREDENTIALS:-admin:admin} -# This ENV variable disables SSL Verification, the above AGILE_URL doesn't have a proper certificate +# Ateliere Live System Controlleer +LIVE_URL=${LIVE_URL:-https://localhost:8080} +LIVE_CREDENTIALS=${LIVE_CREDENTIALS:-admin:admin} +# This ENV variable disables SSL Verification, use if the above LIVE_URL doesn't have a proper certificate NODE_TLS_REJECT_UNAUTHORIZED=${NODE_TLS_REJECT_UNAUTHORIZED:-1} # NextAuth diff --git a/README.md b/README.md index 335c3e3a..ee23668d 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,12 @@ Start mongodb docker container using `docker-compose up`. It will initialize the If you want to run the GUI and mongodb docker containers add this to the `docker-compose.yml` file: ``` - agileui: + liveui: build: . environment: - MONGODB_URI: mongodb://api:<API_PASSWORD>@host.docker.internal:27017/agile-live-gui - AGILE_URL: https://<SYSTEM_CONTROLLER_IP>:8080 - AGILE_CREDENTIALS: <USERNAME>:<PASSWORD> + MONGODB_URI: mongodb://api:<API_PASSWORD>@host.docker.internal:27017/live-gui + LIVE_URL: https://<SYSTEM_CONTROLLER_IP>:8080 + LIVE_CREDENTIALS: <USERNAME>:<PASSWORD> NODE_TLS_REJECT_UNAUTHORIZED: 0 NEXTAUTH_SECRET: <NEXT_AUTH_SECRET> NEXTAUTH_URL: http://localhost:3000 @@ -34,10 +34,10 @@ If you want to run the GUI and mongodb docker containers add this to the `docker Then copy the `.env.sample` file and name it `.env`, it will contain env variables: -- `MONGODB_URI` - The mongodb connection string including credentials eg. `mongodb://user123:pass123@127.0.0.1:27017/agile-live-gui` +- `MONGODB_URI` - The mongodb connection string including credentials eg. `mongodb://user123:pass123@127.0.0.1:27017/live-gui` -- `AGILE_URL` - The URL to the Agile-live system controller REST API -- `AGILE_CREDENTIALS` - Credentials for the Agile-live system controller REST API +- `LIVE_URL` - The URL to the Ateliere Live system controller REST API +- `LIVE_CREDENTIALS` - Credentials for the Ateliere Live system controller REST API - `NEXTAUTH_SECRET` - The secret used to encrypt the JWT Token - `NEXTAUTH_URL` - The base url for the service, eg. `http://localhost:3000`, used internally by NextAuth. @@ -64,7 +64,7 @@ Run following to run application in development environment: ### External Documentation -https://docs.agilecontent.com/docs/acl/reference/3-0-0/rest_api/ +https://help.ateliere.com/live/docs/reference/7-0-0/rest_api/ ### Contributing diff --git a/docs/production_workflow.md b/docs/production_workflow.md index 4bddda45..f69793ea 100644 --- a/docs/production_workflow.md +++ b/docs/production_workflow.md @@ -2,7 +2,7 @@ ## Terminology -- System referes to the agile live backend +- System refers to the ateliere live backend - Manager refers to this application. ## Assumptions diff --git a/package-lock.json b/package-lock.json index 7078d75e..73c7afff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "agile-live-gui", + "name": "ateliere-live-gui", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "agile-live-gui", + "name": "ateliere-live-gui", "version": "1.0.0", "license": "UNLICENSED", "dependencies": { @@ -851,13 +851,13 @@ } }, "node_modules/@commitlint/is-ignored": { - "version": "17.6.3", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-17.6.3.tgz", - "integrity": "sha512-LQbNdnPbxrpbcrVKR5yf51SvquqktpyZJwqXx3lUMF6+nT9PHB8xn3wLy8pi2EQv5Zwba484JnUwDE1ygVYNQA==", + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-17.8.1.tgz", + "integrity": "sha512-UshMi4Ltb4ZlNn4F7WtSEugFDZmctzFpmbqvpyxD3la510J+PLcnyhf9chs7EryaRFJMdAKwsEKfNK0jL/QM4g==", "dev": true, "dependencies": { - "@commitlint/types": "^17.4.4", - "semver": "7.5.0" + "@commitlint/types": "^17.8.1", + "semver": "7.5.4" }, "engines": { "node": ">=v14" @@ -997,9 +997,9 @@ } }, "node_modules/@commitlint/types": { - "version": "17.4.4", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-17.4.4.tgz", - "integrity": "sha512-amRN8tRLYOsxRr6mTnGGGvB5EmW/4DDjLMgiwK3CCVEmN6Sr/6xePGEpWaspKkckILuUORCwe6VfDBw6uj4axQ==", + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-17.8.1.tgz", + "integrity": "sha512-PXDQXkAmiMEG162Bqdh9ChML/GJZo6vU+7F03ALKDK8zYc6SuAr47LjG7hGYRqUOz+WK0dU7bQ0xzuqFMdxzeQ==", "dev": true, "dependencies": { "chalk": "^4.1.0" @@ -6043,10 +6043,22 @@ "node": ">= 0.4" } }, - "node_modules/ip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", - "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" }, "node_modules/ipaddr.js": { "version": "1.9.1", @@ -7210,6 +7222,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -7625,12 +7642,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -9645,9 +9662,9 @@ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" }, "node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -9827,15 +9844,15 @@ } }, "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, diff --git a/package.json b/package.json index f3a8e128..3d341165 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "name": "agile-live-gui", + "name": "ateliere-live-gui", "version": "1.0.0", "description": "", "scripts": { @@ -15,7 +15,7 @@ "start": "next start", "version:rc": "npm version prerelease --preid=rc", "postversion": "git push && git push --tags", - "generate-types:agile-live": "bash -c 'source .env && curl -O -u $AGILE_CREDENTIALS -k \"$AGILE_URL/static/swagger_docs/swagger.json\"' && swagger-typescript-api -p swagger.json -o ./types -n agile-live.d.ts --no-client" + "generate-types:ateliere-live": "bash -c 'source .env && curl -O -u $LIVE_CREDENTIALS -k \"$LIVE_URL/static/swagger_docs/swagger.json\"' && swagger-typescript-api -p swagger.json -o ./types -n ateliere-live.d.ts --no-client" }, "license": "UNLICENSED", "engines": { diff --git a/scripts/mongo-init.js b/scripts/mongo-init.js index d3f64b1c..7e38304b 100644 --- a/scripts/mongo-init.js +++ b/scripts/mongo-init.js @@ -1,8 +1,8 @@ /* eslint-disable no-undef */ -productionsDb = db.getSiblingDB('agile-live-gui'); +productionsDb = db.getSiblingDB('live-gui'); productionsDb.createUser({ user: 'api', pwd: 'password', - roles: [{ role: 'readWrite', db: 'agile-live-gui' }] + roles: [{ role: 'readWrite', db: 'live-gui' }] }); diff --git a/scripts/mongo-mock.js b/scripts/mongo-mock.js index c34611d7..8ae37996 100644 --- a/scripts/mongo-mock.js +++ b/scripts/mongo-mock.js @@ -1,5 +1,5 @@ /* eslint-disable no-undef */ -const agileDb = db.getSiblingDB('agile-live-gui'); +const liveDb = db.getSiblingDB('live-gui'); function createSource( name, @@ -116,31 +116,31 @@ const sources = [ ) ]; -agileDb.inventory.insertMany(sources); +liveDb.inventory.insertMany(sources); -agileDb.users.insertOne({ +liveDb.users.insertOne({ username: 'admin' }); -agileDb.productions.insertOne({ +liveDb.productions.insertOne({ name: 'stockholm_podcast', sources, selectedPresetRef: undefined }); -agileDb.productions.insertOne({ +liveDb.productions.insertOne({ name: 'umeƄ_podcast', sources: [sources[6], sources[7]], selectedPresetRef: undefined }); -agileDb.productions.insertOne({ +liveDb.productions.insertOne({ name: 'Svenska Nyheter', sources: [sources[0], sources[1], sources[2], sources[3]], selectedPresetRef: undefined }); -agileDb.productions.insertOne({ +liveDb.productions.insertOne({ name: 'Morgon Studion', sources: [sources[0], sources[3]], selectedPresetRef: undefined diff --git a/src/api/agileLive/utils/authheader.ts b/src/api/agileLive/utils/authheader.ts deleted file mode 100644 index 51607d58..00000000 --- a/src/api/agileLive/utils/authheader.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function getAuthorizationHeader() { - if (process.env.AGILE_CREDENTIALS) { - return `Basic ${Buffer.from(process.env.AGILE_CREDENTIALS).toString( - 'base64' - )}`; - } - return ''; -} diff --git a/src/api/agileLive/controlconnections.ts b/src/api/ateliereLive/controlconnections.ts similarity index 82% rename from src/api/agileLive/controlconnections.ts rename to src/api/ateliereLive/controlconnections.ts index cf217f6a..34e6d2b6 100644 --- a/src/api/agileLive/controlconnections.ts +++ b/src/api/ateliereLive/controlconnections.ts @@ -1,5 +1,5 @@ -import { ResourcesCreateControlConnectionResponse } from '../../../types/agile-live'; -import { AGILE_BASE_API_PATH } from '../../constants'; +import { ResourcesCreateControlConnectionResponse } from '../../../types/ateliere-live'; +import { LIVE_BASE_API_PATH } from '../../constants'; import { getAuthorizationHeader } from './utils/authheader'; export async function connectSenderAndReceiver( @@ -9,7 +9,7 @@ export async function connectSenderAndReceiver( port: number ): Promise<ResourcesCreateControlConnectionResponse> { const response = await fetch( - new URL(AGILE_BASE_API_PATH + `/controlconnections`, process.env.AGILE_URL), + new URL(LIVE_BASE_API_PATH + `/controlconnections`, process.env.LIVE_URL), { method: 'POST', headers: { @@ -35,8 +35,8 @@ export async function connectSenderAndReceiver( export async function disconnectReceiver(receiverId: string): Promise<void> { const response = await fetch( new URL( - AGILE_BASE_API_PATH + `/controlconnections/${receiverId}`, - process.env.AGILE_URL + LIVE_BASE_API_PATH + `/controlconnections/${receiverId}`, + process.env.LIVE_URL ), { method: 'DELETE', diff --git a/src/api/agileLive/controlpanels.ts b/src/api/ateliereLive/controlpanels.ts similarity index 71% rename from src/api/agileLive/controlpanels.ts rename to src/api/ateliereLive/controlpanels.ts index 50dfbfdc..4a0b6832 100644 --- a/src/api/agileLive/controlpanels.ts +++ b/src/api/ateliereLive/controlpanels.ts @@ -1,5 +1,5 @@ -import { ResourcesControlPanelResponse } from '../../../types/agile-live'; -import { AGILE_BASE_API_PATH } from '../../constants'; +import { ResourcesControlPanelResponse } from '../../../types/ateliere-live'; +import { LIVE_BASE_API_PATH } from '../../constants'; import { getAuthorizationHeader } from './utils/authheader'; export async function getControlPanels(): Promise< @@ -7,8 +7,8 @@ export async function getControlPanels(): Promise< > { const response = await fetch( new URL( - AGILE_BASE_API_PATH + `/controlpanels?expand=true`, - process.env.AGILE_URL + LIVE_BASE_API_PATH + `/controlpanels?expand=true`, + process.env.LIVE_URL ), { method: 'GET', diff --git a/src/api/agileLive/ingest.ts b/src/api/ateliereLive/ingest.ts similarity index 79% rename from src/api/agileLive/ingest.ts rename to src/api/ateliereLive/ingest.ts index 52d88188..396ef5a1 100644 --- a/src/api/agileLive/ingest.ts +++ b/src/api/ateliereLive/ingest.ts @@ -2,8 +2,8 @@ import { ResourcesCompactIngestResponse, ResourcesIngestResponse, ResourcesThumbnailResponse -} from '../../../types/agile-live'; -import { AGILE_BASE_API_PATH } from '../../constants'; +} from '../../../types/ateliere-live'; +import { LIVE_BASE_API_PATH } from '../../constants'; import { getAuthorizationHeader } from './utils/authheader'; // TODO: create proper cache... @@ -58,10 +58,7 @@ export async function getSourceIdFromSourceName( export async function getIngests(): Promise<ResourcesCompactIngestResponse[]> { const response = await fetch( - new URL( - AGILE_BASE_API_PATH + `/ingests?expand=true`, - process.env.AGILE_URL - ), + new URL(LIVE_BASE_API_PATH + `/ingests?expand=true`, process.env.LIVE_URL), { headers: { authorization: getAuthorizationHeader() @@ -82,8 +79,8 @@ export async function getIngest( ): Promise<ResourcesIngestResponse> { const response = await fetch( new URL( - AGILE_BASE_API_PATH + `/ingests/${uuid}?expand=true`, - process.env.AGILE_URL + LIVE_BASE_API_PATH + `/ingests/${uuid}?expand=true`, + process.env.LIVE_URL ), { headers: { @@ -104,9 +101,9 @@ export async function getSourceThumbnail( ) { const response = await fetch( new URL( - AGILE_BASE_API_PATH + + LIVE_BASE_API_PATH + `/ingests/${ingestUuid}/sources/${sourceId}/thumbnail`, - process.env.AGILE_URL + process.env.LIVE_URL ), { next: { tags: ['image'] }, @@ -129,3 +126,25 @@ export async function getSourceThumbnail( } throw await response.json(); } + +export async function deleteSrtSource(ingestUuid: string, sourceId: number) { + const response = await fetch( + new URL( + LIVE_BASE_API_PATH + `/ingests/${ingestUuid}/sources/${sourceId}`, + process.env.LIVE_URL + ), + { + method: 'DELETE', + headers: { + authorization: getAuthorizationHeader() + }, + next: { + revalidate: 0 + } + } + ); + if (response.ok) { + return response.status; + } + throw await response.text(); +} diff --git a/src/api/agileLive/pipelines/multiviews/multiviews.ts b/src/api/ateliereLive/pipelines/multiviews/multiviews.ts similarity index 91% rename from src/api/agileLive/pipelines/multiviews/multiviews.ts rename to src/api/ateliereLive/pipelines/multiviews/multiviews.ts index 8f8d1ecb..5f24a869 100644 --- a/src/api/agileLive/pipelines/multiviews/multiviews.ts +++ b/src/api/ateliereLive/pipelines/multiviews/multiviews.ts @@ -1,23 +1,23 @@ import { ResourcesPipelineMultiviewResponse, ResourcesView -} from '../../../../../types/agile-live'; +} from '../../../../../types/ateliere-live'; import { SourceReference } from '../../../../interfaces/Source'; import { getAuthorizationHeader } from '../../utils/authheader'; import { createMultiview } from '../../utils/multiview'; import { getSourcesByIds } from '../../../manager/sources'; import { Log } from '../../../logger'; import { ProductionSettings } from '../../../../interfaces/production'; -import { AGILE_BASE_API_PATH } from '../../../../constants'; import { MultiviewSettings } from '../../../../interfaces/multiview'; +import { LIVE_BASE_API_PATH } from '../../../../constants'; export async function getMultiviewsForPipeline( pipelineUUID: string ): Promise<ResourcesPipelineMultiviewResponse[]> { const response = await fetch( new URL( - AGILE_BASE_API_PATH + `/pipelines/${pipelineUUID}/multiviews?expand=true`, - process.env.AGILE_URL + LIVE_BASE_API_PATH + `/pipelines/${pipelineUUID}/multiviews?expand=true`, + process.env.LIVE_URL ), { headers: { @@ -38,9 +38,6 @@ export async function createMultiviewForPipeline( productionSettings: ProductionSettings, sourceRefs: SourceReference[] ): Promise<ResourcesPipelineMultiviewResponse[]> { - // TODO Check if this can be cleaned out. This is an old code and dont know the purpose of it, therefor I dont want to remove it yet. - // const multiviewPresets = await getMultiviewPresets(); - const pipeline = productionSettings.pipelines.find((p) => p.multiviews ? p.multiviews?.length > 0 : undefined ); @@ -130,8 +127,8 @@ export async function createMultiviewForPipeline( } const response = await fetch( new URL( - AGILE_BASE_API_PATH + `/pipelines/${pipelineUUID}/multiviews`, - process.env.AGILE_URL + LIVE_BASE_API_PATH + `/pipelines/${pipelineUUID}/multiviews`, + process.env.LIVE_URL ), { method: 'POST', @@ -161,9 +158,9 @@ export async function deleteMultiviewFromPipeline( ): Promise<void> { const response = await fetch( new URL( - AGILE_BASE_API_PATH + + LIVE_BASE_API_PATH + `/pipelines/${pipelineUUID}/multiviews/${multiviewId}`, - process.env.AGILE_URL + process.env.LIVE_URL ), { method: 'DELETE', @@ -204,9 +201,9 @@ export async function updateMultiviewForPipeline( ); const response = await fetch( new URL( - AGILE_BASE_API_PATH + + LIVE_BASE_API_PATH + `/pipelines/${pipelineUUID}/multiviews/${multiviewId}`, - process.env.AGILE_URL + process.env.LIVE_URL ), { method: 'PUT', diff --git a/src/api/agileLive/pipelines/outputs/outputs.ts b/src/api/ateliereLive/pipelines/outputs/outputs.ts similarity index 84% rename from src/api/agileLive/pipelines/outputs/outputs.ts rename to src/api/ateliereLive/pipelines/outputs/outputs.ts index 3387bcc7..2a2fca75 100644 --- a/src/api/agileLive/pipelines/outputs/outputs.ts +++ b/src/api/ateliereLive/pipelines/outputs/outputs.ts @@ -1,5 +1,5 @@ -import { ResourcesOutputStatusResponse } from '../../../../../types/agile-live'; -import { AGILE_BASE_API_PATH } from '../../../../constants'; +import { ResourcesOutputStatusResponse } from '../../../../../types/ateliere-live'; +import { LIVE_BASE_API_PATH } from '../../../../constants'; import { PipelineOutputSettings } from '../../../../interfaces/pipeline'; import { getAuthorizationHeader } from '../../utils/authheader'; @@ -8,8 +8,8 @@ export async function getPipelineOutputs( ): Promise<ResourcesOutputStatusResponse[]> { const response = await fetch( new URL( - AGILE_BASE_API_PATH + `/pipelines/${pipelineId}/outputs?expand=true`, - process.env.AGILE_URL + LIVE_BASE_API_PATH + `/pipelines/${pipelineId}/outputs?expand=true`, + process.env.LIVE_URL ), { method: 'GET', @@ -33,8 +33,8 @@ export async function stopAllOutputStreamsByUuid( ) { const response = await fetch( new URL( - AGILE_BASE_API_PATH + `/pipelines/${pipeId}/outputs/${outputId}/streams`, - process.env.AGILE_URL + LIVE_BASE_API_PATH + `/pipelines/${pipeId}/outputs/${outputId}/streams`, + process.env.LIVE_URL ), { method: 'DELETE', @@ -59,9 +59,9 @@ export async function stopSingleOutputStream( ) { const response = await fetch( new URL( - AGILE_BASE_API_PATH + + LIVE_BASE_API_PATH + `/pipelines/${pipeId}/outputs/${outputId}/streams/${outputStreamId}`, - process.env.AGILE_URL + process.env.LIVE_URL ), { method: 'DELETE', @@ -87,9 +87,9 @@ export async function startPipelineStream( const requests = streamSettings.map((streamSetting) => { return fetch( new URL( - AGILE_BASE_API_PATH + + LIVE_BASE_API_PATH + `/pipelines/${pipelineId}/outputs/${outputId}/streams`, - process.env.AGILE_URL + process.env.LIVE_URL ), { method: 'POST', diff --git a/src/api/agileLive/pipelines/pipelines.ts b/src/api/ateliereLive/pipelines/pipelines.ts similarity index 95% rename from src/api/agileLive/pipelines/pipelines.ts rename to src/api/ateliereLive/pipelines/pipelines.ts index a2c43c55..32fc3090 100644 --- a/src/api/agileLive/pipelines/pipelines.ts +++ b/src/api/ateliereLive/pipelines/pipelines.ts @@ -1,7 +1,7 @@ import { ResourcesCompactPipelineResponse, ResourcesPipelineResponse -} from '../../../../types/agile-live'; +} from '../../../../types/ateliere-live'; import { PipelineOutputSettings, PipelineSettings @@ -18,15 +18,15 @@ import { import { getPipelineStreams } from './streams/streams'; import { ControlConnection } from '../../../interfaces/controlConnections'; import { Log } from '../../logger'; -import { AGILE_BASE_API_PATH } from '../../../constants'; +import { LIVE_BASE_API_PATH } from '../../../constants'; export async function getPipeline( uuid: string ): Promise<ResourcesPipelineResponse> { const response = await fetch( new URL( - AGILE_BASE_API_PATH + `/pipelines/${uuid}?expand=true`, - process.env.AGILE_URL + LIVE_BASE_API_PATH + `/pipelines/${uuid}?expand=true`, + process.env.LIVE_URL ), { method: 'GET', @@ -49,8 +49,8 @@ export async function getPipelineCompact( ): Promise<ResourcesCompactPipelineResponse> { const response = await fetch( new URL( - AGILE_BASE_API_PATH + `/pipelines/${uuid}?expand=false`, - process.env.AGILE_URL + LIVE_BASE_API_PATH + `/pipelines/${uuid}?expand=false`, + process.env.LIVE_URL ), { method: 'GET', @@ -73,8 +73,8 @@ export async function getPipelines(): Promise< > { const response = await fetch( new URL( - AGILE_BASE_API_PATH + `/pipelines?expand=true`, - process.env.AGILE_URL + LIVE_BASE_API_PATH + `/pipelines?expand=true`, + process.env.LIVE_URL ), { method: 'GET', @@ -102,8 +102,8 @@ export async function getPipelines(): Promise< export async function resetPipeline(pipelineUuid: string): Promise<void> { const response = await fetch( new URL( - AGILE_BASE_API_PATH + `/pipelines/${pipelineUuid}/reset`, - process.env.AGILE_URL + LIVE_BASE_API_PATH + `/pipelines/${pipelineUuid}/reset`, + process.env.LIVE_URL ), { method: 'PUT', diff --git a/src/api/agileLive/pipelines/streams/streams.ts b/src/api/ateliereLive/pipelines/streams/streams.ts similarity index 95% rename from src/api/agileLive/pipelines/streams/streams.ts rename to src/api/ateliereLive/pipelines/streams/streams.ts index 22602f4d..fc5e108f 100644 --- a/src/api/agileLive/pipelines/streams/streams.ts +++ b/src/api/ateliereLive/pipelines/streams/streams.ts @@ -1,5 +1,5 @@ -import { ResourcesUUIDResponse } from '../../../../../types/agile-live'; -import { AGILE_BASE_API_PATH } from '../../../../constants'; +import { ResourcesUUIDResponse } from '../../../../../types/ateliere-live'; +import { LIVE_BASE_API_PATH } from '../../../../constants'; import { AddSourceResult, SourceToPipelineStream, @@ -29,8 +29,8 @@ export async function getPipelineStreams( ): Promise<ResourcesUUIDResponse[]> { const response = await fetch( new URL( - AGILE_BASE_API_PATH + `/pipelines/${pipelineUuid}/streams`, - process.env.AGILE_URL + LIVE_BASE_API_PATH + `/pipelines/${pipelineUuid}/streams`, + process.env.LIVE_URL ), { method: 'GET', @@ -252,8 +252,6 @@ export async function createStream( streams: sourceToPipelineStreams } }; - // TODO Check if this can be cleaned out. This is an old code and dont know the purpose of it, therefor I dont want to remove it yet. - // return sourceToPipelineStreams; } const updatedViewsForSource = viewsForSource.map((v) => { return { ...v, label: source.name }; @@ -333,8 +331,8 @@ export async function createStream( export async function deleteStream(streamUuid: string) { const response = await fetch( new URL( - AGILE_BASE_API_PATH + `/streams/${streamUuid}`, - process.env.AGILE_URL + LIVE_BASE_API_PATH + `/streams/${streamUuid}`, + process.env.LIVE_URL ), { method: 'DELETE', diff --git a/src/api/agileLive/streams.ts b/src/api/ateliereLive/streams.ts similarity index 82% rename from src/api/agileLive/streams.ts rename to src/api/ateliereLive/streams.ts index 1a5ec5f4..ef97cdee 100644 --- a/src/api/agileLive/streams.ts +++ b/src/api/ateliereLive/streams.ts @@ -1,4 +1,4 @@ -import { AGILE_BASE_API_PATH } from '../../constants'; +import { LIVE_BASE_API_PATH } from '../../constants'; import { PipelineStreamSettings } from '../../interfaces/pipeline'; import { getAuthorizationHeader } from './utils/authheader'; @@ -9,7 +9,7 @@ export async function connectIngestToPipeline( throw new Error('Error: No pipeline_id!'); } const response = await fetch( - new URL(AGILE_BASE_API_PATH + `/streams`, process.env.AGILE_URL), + new URL(LIVE_BASE_API_PATH + `/streams`, process.env.LIVE_URL), { method: 'POST', headers: { @@ -30,8 +30,8 @@ export async function connectIngestToPipeline( export async function deleteStreamByUuid(streamUuId: string) { const response = await fetch( new URL( - AGILE_BASE_API_PATH + `/streams/${streamUuId}`, - process.env.AGILE_URL + LIVE_BASE_API_PATH + `/streams/${streamUuId}`, + process.env.LIVE_URL ), { method: 'DELETE', diff --git a/src/api/ateliereLive/utils/authheader.ts b/src/api/ateliereLive/utils/authheader.ts new file mode 100644 index 00000000..0f4dc4ea --- /dev/null +++ b/src/api/ateliereLive/utils/authheader.ts @@ -0,0 +1,8 @@ +export function getAuthorizationHeader() { + if (process.env.LIVE_CREDENTIALS) { + return `Basic ${Buffer.from(process.env.LIVE_CREDENTIALS).toString( + 'base64' + )}`; + } + return ''; +} diff --git a/src/api/agileLive/utils/fwConfigPorts.test.data.ts b/src/api/ateliereLive/utils/fwConfigPorts.test.data.ts similarity index 100% rename from src/api/agileLive/utils/fwConfigPorts.test.data.ts rename to src/api/ateliereLive/utils/fwConfigPorts.test.data.ts diff --git a/src/api/agileLive/utils/fwConfigPorts.test.ts b/src/api/ateliereLive/utils/fwConfigPorts.test.ts similarity index 100% rename from src/api/agileLive/utils/fwConfigPorts.test.ts rename to src/api/ateliereLive/utils/fwConfigPorts.test.ts diff --git a/src/api/agileLive/utils/fwConfigPorts.ts b/src/api/ateliereLive/utils/fwConfigPorts.ts similarity index 100% rename from src/api/agileLive/utils/fwConfigPorts.ts rename to src/api/ateliereLive/utils/fwConfigPorts.ts diff --git a/src/api/agileLive/utils/multiview.ts b/src/api/ateliereLive/utils/multiview.ts similarity index 100% rename from src/api/agileLive/utils/multiview.ts rename to src/api/ateliereLive/utils/multiview.ts diff --git a/src/api/agileLive/utils/pipeline.ts b/src/api/ateliereLive/utils/pipeline.ts similarity index 57% rename from src/api/agileLive/utils/pipeline.ts rename to src/api/ateliereLive/utils/pipeline.ts index 3d141029..5083ebf8 100644 --- a/src/api/agileLive/utils/pipeline.ts +++ b/src/api/ateliereLive/utils/pipeline.ts @@ -1,4 +1,4 @@ -import { ResourcesPipelineResponse } from '../../../../types/agile-live'; +import { ResourcesPipelineResponse } from '../../../../types/ateliere-live'; export function isActive(pipeline: ResourcesPipelineResponse) { return !!pipeline.streams?.length; diff --git a/src/api/manager/inventory.ts b/src/api/manager/inventory.ts index e5796caa..7d47e1ea 100644 --- a/src/api/manager/inventory.ts +++ b/src/api/manager/inventory.ts @@ -1,6 +1,7 @@ -import { ObjectId } from 'mongodb'; +import { ObjectId, UpdateResult } from 'mongodb'; import { getDatabase } from '../mongoClient/dbClient'; import { Numbers } from '../../interfaces/Source'; +import { Log } from '../logger'; interface IResponse { audio_stream?: { @@ -20,3 +21,21 @@ export async function getAudioMapping(id: ObjectId): Promise<IResponse> { throw `Could not find audio mapping for source: ${id.toString()}`; })) as IResponse; } + +export async function purgeInventorySourceItem( + id: string +): Promise<UpdateResult<Document>> { + const db = await getDatabase(); + const objectId = new ObjectId(id); + + // Not possible to delete from API so this adds a purge-flag + // to the source + const result = await db + .collection('inventory') + .updateOne({ _id: objectId, status: 'gone' }, { $set: { status: 'purge' } }) + .catch((error) => { + throw `Was not able to set source-id for ${id} to purge: ${error}`; + }); + + return result as UpdateResult<Document>; +} diff --git a/src/api/manager/job/syncInventory.ts b/src/api/manager/job/syncInventory.ts index 8f63785b..2e813d0a 100644 --- a/src/api/manager/job/syncInventory.ts +++ b/src/api/manager/job/syncInventory.ts @@ -1,14 +1,13 @@ -import { ResourcesIngestResponse } from '../../../../types/agile-live'; +import { ResourcesIngestResponse } from '../../../../types/ateliere-live'; import { Source } from '../../../interfaces/Source'; -import { getIngests, getIngest } from '../../agileLive/ingest'; +import { getIngests, getIngest } from '../../ateliereLive/ingest'; import { upsertSource } from '../sources'; import { getDatabase } from '../../mongoClient/dbClient'; import { WithId } from 'mongodb'; type SourceWithoutLastConnected = Omit<Source, 'lastConnected'>; -// TODO: getSourcesFromAPI should return ResourcesSourceResponse and changed to our model later -async function getSourcesFromAPI() { +async function getSourcesFromAPI(): Promise<SourceWithoutLastConnected[]> { const ingests = await getIngests(); const resolvedIngests = ( await Promise.allSettled(ingests.map((ingest) => getIngest(ingest.uuid))) @@ -51,14 +50,35 @@ async function getSourcesFromAPI() { } /** - * Syncs the inventory with the ingests in Agile Live. + * Syncs the inventory with the ingests in Ateliere Live. * - Adds new sources to the inventory with the status 'new' * - Updates the status of sources depending on wheter or not they are still present in the ingests */ export async function runSyncInventory() { const db = await getDatabase(); - const dbInventory = await db.collection<Source>('inventory').find().toArray(); const apiSources = await getSourcesFromAPI(); + const dbInventory = await db.collection<Source>('inventory').find().toArray(); + + const statusUpdateCheck = ( + inventorySource: WithId<Source>, + apiSource: SourceWithoutLastConnected, + lastConnected: Date + ) => { + const databaseStatus = inventorySource.status; + const apiStatus = apiSource.status; + const currentTime = new Date().getTime(); + const lastConnectedTime = new Date(lastConnected).getTime(); + const monthInMilliseconds = 30 * 24 * 60 * 60 * 1000; + const expiryTime = lastConnectedTime + monthInMilliseconds; + + if (databaseStatus === 'purge' && apiStatus === 'gone') { + return databaseStatus; + } else if (apiStatus === 'gone' && currentTime > expiryTime) { + return 'purge'; + } else { + return apiStatus; + } + }; // Update status of all sources in the inventory to the status found in API. // If a source is not found in the API, it is marked as gone. @@ -73,12 +93,14 @@ export async function runSyncInventory() { // If source was not found in response from API, always mark it as gone return { ...inventorySource, status: 'gone' } satisfies WithId<Source>; } - // Keep all old fields from the inventory source (name, tags, id, audio_stream etc), but update the status and set the lastConnected to the current date + const lastConnected = + apiSource.status !== 'gone' ? new Date() : inventorySource.lastConnected; + + // Keep all old fields from the inventory source (name, tags, id, audio_stream etc), but update the status return { ...inventorySource, - status: apiSource.status, - lastConnected: - apiSource.status !== 'gone' ? new Date() : inventorySource.lastConnected + status: statusUpdateCheck(inventorySource, apiSource, lastConnected), + lastConnected: lastConnected } satisfies WithId<Source>; }); diff --git a/src/api/manager/job/syncMonitoring.ts b/src/api/manager/job/syncMonitoring.ts index e2064596..7619bfdb 100644 --- a/src/api/manager/job/syncMonitoring.ts +++ b/src/api/manager/job/syncMonitoring.ts @@ -1,6 +1,9 @@ import { Production } from '../../../interfaces/production'; import { getDatabase } from '../../mongoClient/dbClient'; -import { getPipeline, getPipelines } from '../../agileLive/pipelines/pipelines'; +import { + getPipeline, + getPipelines +} from '../../ateliereLive/pipelines/pipelines'; import { ResourcesControlPanelResponse, ResourcesControlRequestCounter, @@ -10,9 +13,9 @@ import { ResourcesPipelineResponse, ResourcesReceiverNetworkEndpoint, ResourcesSenderNetworkEndpoint -} from '../../../../types/agile-live'; -import { getControlPanels } from '../../agileLive/controlpanels'; -import { getIngest, getIngests } from '../../agileLive/ingest'; +} from '../../../../types/ateliere-live'; +import { getControlPanels } from '../../ateliereLive/controlpanels'; +import { getIngest, getIngests } from '../../ateliereLive/ingest'; import { MonitoringControlPanelStatusResponse, MonitoringOutputStatusResponse, @@ -350,7 +353,7 @@ export async function updatedMonitoringForProduction( } /** - * Syncs the Monitoring DB with all the active productions and compares with data in Agile Live. + * Syncs the Monitoring DB with all the active productions and compares with data in Ateliere Live. */ export async function runSyncMonitoring() { const db = await getDatabase(); diff --git a/src/api/manager/workflow.ts b/src/api/manager/workflow.ts index e311c710..7538be78 100644 --- a/src/api/manager/workflow.ts +++ b/src/api/manager/workflow.ts @@ -12,39 +12,39 @@ import { getPipelines, removePipelineStreams, resetPipeline -} from '../agileLive/pipelines/pipelines'; +} from '../ateliereLive/pipelines/pipelines'; import { createMultiviewForPipeline, deleteAllMultiviewsFromPipeline -} from '../agileLive/pipelines/multiviews/multiviews'; +} from '../ateliereLive/pipelines/multiviews/multiviews'; import { getSourceIdFromSourceName, getUuidFromIngestName -} from '../agileLive/ingest'; +} from '../ateliereLive/ingest'; import { PipelineStreamSettings } from '../../interfaces/pipeline'; import { connectIngestToPipeline, deleteStreamByUuid -} from '../agileLive/streams'; -import { disconnectReceiver } from '../agileLive/controlconnections'; +} from '../ateliereLive/streams'; +import { disconnectReceiver } from '../ateliereLive/controlconnections'; import { ResourcesCompactPipelineResponse, ResourcesConnectionUUIDResponse, ResourcesPipelineResponse, ResourcesReceiverNetworkEndpoint, ResourcesSenderNetworkEndpoint -} from '../../../types/agile-live'; +} from '../../../types/ateliere-live'; import { getSourcesByIds } from './sources'; import { SourceWithId, SourceToPipelineStream } from '../../interfaces/Source'; import { getAvailablePortsForIngest, getCurrentlyUsedPorts, initDedicatedPorts -} from '../agileLive/utils/fwConfigPorts'; +} from '../ateliereLive/utils/fwConfigPorts'; import { getAudioMapping } from './inventory'; import { Log } from '../logger'; import { putProduction } from './productions'; -import { getControlPanels } from '../agileLive/controlpanels'; +import { getControlPanels } from '../ateliereLive/controlpanels'; import { Result } from '../../interfaces/result'; import { Monitoring } from '../../interfaces/monitoring'; import { getDatabase } from '../mongoClient/dbClient'; @@ -466,7 +466,7 @@ export async function startProduction( throw error; }); - // Fetch expanded pipeline objects from Agile Live + // Fetch expanded pipeline objects from Ateliere Live const pipelinesToUsePromises = production_settings.pipelines.map( (pipeline) => { return getPipeline(pipeline.pipeline_id!); @@ -499,7 +499,7 @@ export async function startProduction( throw `Failed to reset pipelines: ${error}`; }); - // Fetch all control panels from Agile Live + // Fetch all control panels from Ateliere Live const allControlPanels = await getControlPanels(); // Check which control panels that should be used by this production const controlPanelsToUse = allControlPanels.filter((controlPanel) => @@ -531,7 +531,7 @@ export async function startProduction( throw `Failed to stop pipelines during startup: ${error}`; }); - // TODO: This will fetch the pipelines once again from Agile Live, but we already have them in pipelinesToUse + // TODO: This will fetch the pipelines once again from Ateliere Live, but we already have them in pipelinesToUse const usedPorts = await getCurrentlyUsedPorts( pipelinesToUse.map((pipeline) => { return pipeline.uuid; @@ -573,7 +573,7 @@ export async function startProduction( // Try to connect control panels and pipeline-to-pipeline connections start try { - // TODO: This will re-fetch pipelines from the Agile Live API, but we fetched them above into pipelinesToUse + // TODO: This will re-fetch pipelines from the Ateliere Live API, but we fetched them above into pipelinesToUse await connectControlPanelToPipeline( production_settings.control_connection, production_settings.pipelines diff --git a/src/app/api/manager/controlpanels/route.ts b/src/app/api/manager/controlpanels/route.ts index c91e6073..360779fc 100644 --- a/src/app/api/manager/controlpanels/route.ts +++ b/src/app/api/manager/controlpanels/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { isAuthenticated } from '../../../../api/manager/auth'; -import { getControlPanels } from '../../../../api/agileLive/controlpanels'; +import { getControlPanels } from '../../../../api/ateliereLive/controlpanels'; export async function GET(request: NextRequest): Promise<NextResponse> { if (!(await isAuthenticated())) { diff --git a/src/app/api/manager/inventory/[_id]/route.ts b/src/app/api/manager/inventory/[_id]/route.ts new file mode 100644 index 00000000..86eb3821 --- /dev/null +++ b/src/app/api/manager/inventory/[_id]/route.ts @@ -0,0 +1,39 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { isAuthenticated } from '../../../../../api/manager/auth'; +import { Params } from 'next/dist/shared/lib/router/utils/route-matcher'; +import { purgeInventorySourceItem } from '../../../../../api/manager/inventory'; + +export async function PUT( + request: NextRequest, + { params }: { params: Params } +): Promise<NextResponse> { + if (!(await isAuthenticated())) { + return new NextResponse(`Not Authorized!`, { + status: 403 + }); + } + + try { + const response = await purgeInventorySourceItem(params._id); + if (response.acknowledged && response.modifiedCount === 0) { + return new NextResponse(`Did not match requirements`, { + status: 204 + }); + } else if (response.acknowledged) { + return new NextResponse(null, { + status: 200 + }); + } else { + return new NextResponse(`Could not update database-status`, { + status: 500 + }); + } + } catch (error) { + return new NextResponse( + `Error occurred while posting to DB! Error: ${error}`, + { + status: 500 + } + ); + } +} diff --git a/src/app/api/manager/multiviews/[id]/route.ts b/src/app/api/manager/multiviews/[id]/route.ts index ff1193d7..4390f374 100644 --- a/src/app/api/manager/multiviews/[id]/route.ts +++ b/src/app/api/manager/multiviews/[id]/route.ts @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { isAuthenticated } from '../../../../../api/manager/auth'; import { getMultiviewPreset } from '../../../../../api/manager/presets'; import { Params } from 'next/dist/shared/lib/router/utils/route-matcher'; -import { updateMultiviewForPipeline } from '../../../../../api/agileLive/pipelines/multiviews/multiviews'; +import { updateMultiviewForPipeline } from '../../../../../api/ateliereLive/pipelines/multiviews/multiviews'; import { MultiviewViews } from '../../../../../interfaces/multiview'; export async function GET( diff --git a/src/app/api/manager/pipelines/[id]/route.ts b/src/app/api/manager/pipelines/[id]/route.ts index 54055a6e..9f8a6a48 100644 --- a/src/app/api/manager/pipelines/[id]/route.ts +++ b/src/app/api/manager/pipelines/[id]/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -import { getPipeline } from '../../../../../api/agileLive/pipelines/pipelines'; -import { isActive } from '../../../../../api/agileLive/utils/pipeline'; +import { getPipeline } from '../../../../../api/ateliereLive/pipelines/pipelines'; +import { isActive } from '../../../../../api/ateliereLive/utils/pipeline'; import { getSRTMultiviews, getSRTOutputs, diff --git a/src/app/api/manager/pipelines/route.ts b/src/app/api/manager/pipelines/route.ts index 498faabc..1b73e084 100644 --- a/src/app/api/manager/pipelines/route.ts +++ b/src/app/api/manager/pipelines/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from 'next/server'; -import { getPipelines } from '../../../../api/agileLive/pipelines/pipelines'; +import { getPipelines } from '../../../../api/ateliereLive/pipelines/pipelines'; import { isAuthenticated } from '../../../../api/manager/auth'; export async function GET(request: NextRequest): Promise<NextResponse> { diff --git a/src/app/api/manager/sources/[ingest_name]/[source_name]/thumbnail/route.ts b/src/app/api/manager/sources/[ingest_name]/[source_name]/thumbnail/route.ts index 35361687..8d69748f 100644 --- a/src/app/api/manager/sources/[ingest_name]/[source_name]/thumbnail/route.ts +++ b/src/app/api/manager/sources/[ingest_name]/[source_name]/thumbnail/route.ts @@ -3,7 +3,7 @@ import { getSourceIdFromSourceName, getSourceThumbnail, getUuidFromIngestName -} from '../../../../../../../api/agileLive/ingest'; +} from '../../../../../../../api/ateliereLive/ingest'; import { isAuthenticated } from '../../../../../../../api/manager/auth'; type Params = { diff --git a/src/app/api/manager/streams/[id]/route.ts b/src/app/api/manager/streams/[id]/route.ts index 3865e444..00d44df3 100644 --- a/src/app/api/manager/streams/[id]/route.ts +++ b/src/app/api/manager/streams/[id]/route.ts @@ -1,9 +1,9 @@ import { Params } from 'next/dist/shared/lib/router/utils/route-matcher'; import { NextRequest, NextResponse } from 'next/server'; import { isAuthenticated } from '../../../../../api/manager/auth'; -import { deleteStream } from '../../../../../api/agileLive/pipelines/streams/streams'; +import { deleteStream } from '../../../../../api/ateliereLive/pipelines/streams/streams'; import { MultiviewSettings } from '../../../../../interfaces/multiview'; -import { updateMultiviewForPipeline } from '../../../../../api/agileLive/pipelines/multiviews/multiviews'; +import { updateMultiviewForPipeline } from '../../../../../api/ateliereLive/pipelines/multiviews/multiviews'; import { DeleteSourceStep } from '../../../../../interfaces/Source'; import { Result } from '../../../../../interfaces/result'; import { Log } from '../../../../../api/logger'; diff --git a/src/app/api/manager/streams/route.ts b/src/app/api/manager/streams/route.ts index 160c7363..44635884 100644 --- a/src/app/api/manager/streams/route.ts +++ b/src/app/api/manager/streams/route.ts @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { isAuthenticated } from '../../../../api/manager/auth'; import { SourceWithId } from '../../../../interfaces/Source'; import { Production } from '../../../../interfaces/production'; -import { createStream } from '../../../../api/agileLive/pipelines/streams/streams'; +import { createStream } from '../../../../api/ateliereLive/pipelines/streams/streams'; import { Log } from '../../../../api/logger'; export type CreateStreamRequestBody = { source: SourceWithId; diff --git a/src/app/api/syshealth/route.ts b/src/app/api/syshealth/route.ts index 0f58ed99..868a2f10 100644 --- a/src/app/api/syshealth/route.ts +++ b/src/app/api/syshealth/route.ts @@ -1,8 +1,8 @@ import { NextResponse } from 'next/server'; -import { getIngests } from '../../../api/agileLive/ingest'; +import { getIngests } from '../../../api/ateliereLive/ingest'; import { connected } from '../../../api/mongoClient/dbClient'; import { isAuthenticated } from '../../../api/manager/auth'; -import { AGILE_BASE_API_PATH } from '../../../constants'; +import { LIVE_BASE_API_PATH } from '../../../constants'; export async function GET(): Promise<NextResponse> { if (!(await isAuthenticated())) { @@ -10,20 +10,20 @@ export async function GET(): Promise<NextResponse> { status: 403 }); } - const isConnectedToAgile = await getIngests() + const isConnectedToLive = await getIngests() .then(() => true) .catch(() => false); const isConnectedToDatabase = await connected().catch(() => false); - if (isConnectedToAgile && isConnectedToDatabase) { + if (isConnectedToLive && isConnectedToDatabase) { return new NextResponse( JSON.stringify({ message: 'Connected!', database: { connected: isConnectedToDatabase }, - agileApi: { connected: isConnectedToAgile } + liveApi: { connected: isConnectedToLive } }), { status: 200 @@ -36,9 +36,9 @@ export async function GET(): Promise<NextResponse> { return new NextResponse( JSON.stringify({ message: 'Something went wrong with the connection!', - agileApi: { - connected: isConnectedToAgile, - url: new URL(AGILE_BASE_API_PATH, process.env.AGILE_URL) + liveApi: { + connected: isConnectedToLive, + url: new URL(LIVE_BASE_API_PATH, process.env.LIVE_URL) }, database: { connected: isConnectedToDatabase, diff --git a/src/app/production/[id]/page.tsx b/src/app/production/[id]/page.tsx index 2047f186..530c2ec6 100644 --- a/src/app/production/[id]/page.tsx +++ b/src/app/production/[id]/page.tsx @@ -78,7 +78,7 @@ export default function ProductionConfiguration({ params }: PageProps) { const getMultiviewPreset = useGetMultiviewPreset(); const [updateMultiviewViews] = useMultiviews(); - //FROM AGILE API + //FROM LIVE API const [pipelines, loadingPipelines, , refreshPipelines] = usePipelines(); const [controlPanels, loadingControlPanels, , refreshControlPanels] = useControlPanels(); @@ -640,7 +640,7 @@ export default function ProductionConfiguration({ params }: PageProps) { /> </div> </HeaderNavigation> - <div className="flex max-h-full min-h-[100%] flex-row"> + <div className="flex h-[95%] flex-row"> <div className={`overflow-hidden transition-[min-width] w-0 min-w-0 ${ inventoryVisible ? 'min-w-[35%] ml-2 mt-2 max-h-[89vh]' : '' diff --git a/src/components/button/Button.tsx b/src/components/button/Button.tsx index 77f557b2..673a0ca1 100644 --- a/src/components/button/Button.tsx +++ b/src/components/button/Button.tsx @@ -1,7 +1,7 @@ import { PropsWithChildren } from 'react'; import { twMerge } from 'tailwind-merge'; type ButtonProps = { - type?: 'submit'; + type?: 'submit' | 'button'; className?: string; onClick?: () => void; disabled?: boolean; diff --git a/src/components/filter/FilterDropdown.tsx b/src/components/filter/FilterDropdown.tsx index 85c9eb27..5e765835 100644 --- a/src/components/filter/FilterDropdown.tsx +++ b/src/components/filter/FilterDropdown.tsx @@ -15,6 +15,7 @@ function FilterDropdown({ showNdiType, showBmdType, showSrtType, + showMediaSourceGeneratorType, setIsTypeHidden, setIsLocationHidden, setSelectedTags, @@ -22,6 +23,7 @@ function FilterDropdown({ setOnlyShowNdiSources: setOnlyShowNdiSources, setOnlyShowBmdSources: setOnlyShowBmdSources, setOnlyShowSrtSources: setOnlyShowSrtSources, + setOnlyShowMediaSourceGeneratorSources, handleSorting }: { close: () => void; @@ -35,6 +37,7 @@ function FilterDropdown({ showNdiType: boolean; showSrtType: boolean; showBmdType: boolean; + showMediaSourceGeneratorType: boolean; setIsTypeHidden: React.Dispatch<React.SetStateAction<boolean>>; setIsLocationHidden: React.Dispatch<React.SetStateAction<boolean>>; setOnlyShowActiveSources: React.Dispatch<React.SetStateAction<boolean>>; @@ -42,6 +45,9 @@ function FilterDropdown({ setOnlyShowNdiSources: React.Dispatch<React.SetStateAction<boolean>>; setOnlyShowBmdSources: React.Dispatch<React.SetStateAction<boolean>>; setOnlyShowSrtSources: React.Dispatch<React.SetStateAction<boolean>>; + setOnlyShowMediaSourceGeneratorSources: React.Dispatch< + React.SetStateAction<boolean> + >; handleSorting: (reversedOrder: boolean) => void; }) { const t = useTranslate(); @@ -75,22 +81,26 @@ function FilterDropdown({ setIsTypeHidden(true); }; - const showSelectedConfigSources = () => { + const toggleConfigSources = () => { setOnlyShowConfigSources(!showConfigSources); }; - const showSelectedNdiType = () => { + const toggleNdiType = () => { setOnlyShowNdiSources(!showNdiType); }; - const showSelectedSrtType = () => { + const toggleSrtType = () => { setOnlyShowSrtSources(!showSrtType); }; - const showSelectedBmdType = () => { + const toggleBmdType = () => { setOnlyShowBmdSources(!showBmdType); }; + const toggleMediaSourceGeneratorType = () => { + setOnlyShowMediaSourceGeneratorSources(!showMediaSourceGeneratorType); + }; + const deleteTag = (value: string) => { const temp = selectedTags; temp.delete(value); @@ -148,10 +158,11 @@ function FilterDropdown({ const handleClear = () => { setSelectedTags(new Set<string>()); - setOnlyShowConfigSources(false); - setOnlyShowBmdSources(false); - setOnlyShowNdiSources(false); - setOnlyShowSrtSources(false); + setOnlyShowConfigSources(true); + setOnlyShowBmdSources(true); + setOnlyShowNdiSources(true); + setOnlyShowSrtSources(true); + setOnlyShowMediaSourceGeneratorSources(false); }; const typesSearch = (event: ChangeEvent<HTMLInputElement>) => { @@ -259,7 +270,7 @@ function FilterDropdown({ })} </div> <div - className="absolute border w-full z-20 rounded-lg shadow divide-y bg-zinc-700 divide-gray-600" + className="absolute border w-full z-50 rounded-lg shadow divide-y bg-zinc-700 divide-gray-600" hidden={isLocationHidden} > <ul className="px-3 pb-3 text-sm text-p"> @@ -305,7 +316,7 @@ function FilterDropdown({ type="checkbox" className="flex ml-2 w-4 justify-center rounded-lg text-zinc-300" checked={showConfigSources} - onChange={showSelectedConfigSources} + onChange={toggleConfigSources} /> <label className="ml-2 mt-1 text-left text-zinc-300" @@ -320,7 +331,7 @@ function FilterDropdown({ type="checkbox" className="flex ml-2 w-4 justify-center rounded-lg text-zinc-300" checked={showNdiType} - onChange={showSelectedNdiType} + onChange={toggleNdiType} /> <label className="ml-2 mt-1 text-left text-zinc-300" @@ -335,7 +346,7 @@ function FilterDropdown({ type="checkbox" className="flex ml-2 w-4 justify-center rounded-lg text-zinc-300" checked={showSrtType} - onChange={showSelectedSrtType} + onChange={toggleSrtType} /> <label className="ml-2 mt-1 text-left text-zinc-300" @@ -350,7 +361,7 @@ function FilterDropdown({ type="checkbox" className="flex ml-2 w-4 justify-center rounded-lg text-zinc-300" checked={showBmdType} - onChange={showSelectedBmdType} + onChange={toggleBmdType} /> <label className="ml-2 mt-1 text-left text-zinc-300" @@ -359,19 +370,34 @@ function FilterDropdown({ SDI/HDMI </label> </div> + <div className="flex flex-row"> + <input + id="showMediaSourceCheckbox" + type="checkbox" + className="flex ml-2 w-4 justify-center rounded-lg text-zinc-300" + checked={showMediaSourceGeneratorType} + onChange={toggleMediaSourceGeneratorType} + /> + <label + className="ml-2 mt-1 text-left text-zinc-300" + htmlFor="showMediaSourceCheckbox" + > + Test pattern + </label> + </div> </div> <div className="flex self-end justify-end mt-4"> <button onClick={handleClear} id="dropdownCheckboxButton" - className="flex ml-2 mb-2 min-w-[30%] justify-center font-medium rounded-lg py-2.5 text-zinc-300 bg-zinc-800 hover:ring-2 focus:outline-none bg-zink-800 hover:bg-zinc-700 opacity-70" + className="flex ml-2 mb-2 min-w-[30%] justify-center font-medium rounded-lg py-2.5 text-zinc-300 hover:ring-2 focus:outline-none bg-zinc-800 hover:bg-zinc-700 opacity-70" type="button" > {t('clear')} </button> <button onClick={() => close()} - className="flex ml-2 mb-2 min-w-[30%] justify-center font-medium rounded-lg py-2.5 text-zinc-300 bg-zinc-800 hover:ring-2 focus:outline-none bg-zink-800 hover:bg-zinc-700" + className="flex ml-2 mb-2 min-w-[30%] justify-center font-medium rounded-lg py-2.5 text-zinc-300 hover:ring-2 focus:outline-none bg-zinc-800 hover:bg-zinc-700" type="button" > {t('apply')} diff --git a/src/components/filter/FilterOptions.tsx b/src/components/filter/FilterOptions.tsx index 5f6f6a15..132303bd 100644 --- a/src/components/filter/FilterOptions.tsx +++ b/src/components/filter/FilterOptions.tsx @@ -14,9 +14,11 @@ function FilterOptions({ onFilteredSources }: FilterOptionsProps) { const { locations, types, sources } = useContext(FilterContext); const [onlyShowActiveSources, setOnlyShowActiveSources] = useState(false); - const [onlyShowNdiSources, setOnlyShowNdiSources] = useState(false); - const [onlyShowBmdSources, setOnlyShowBmdSources] = useState(false); - const [onlyShowSrtSources, setOnlyShowSrtSources] = useState(false); + const [showNdiType, setShowNdiType] = useState(true); + const [showBmdType, setShowBmdType] = useState(true); + const [showSrtType, setShowSrtType] = useState(true); + const [showMediaSourceGeneratorType, setShowMediaGeneratorSourceType] = + useState(false); const [isFilterHidden, setIsFilterHidden] = useState(true); const [isTypeHidden, setIsTypeHidden] = useState(true); const [isLocationHidden, setIsLocationHidden] = useState(true); @@ -24,46 +26,35 @@ function FilterOptions({ onFilteredSources }: FilterOptionsProps) { const [selectedTags, setSelectedTags] = useState<Set<string>>( new Set<string>() ); - let tempSet = new Map<string, SourceWithId>(sources); + const [filteredSources, setFilteredSources] = useState< + Map<string, SourceWithId> + >(new Map<string, SourceWithId>()); useEffect(() => { - if ( - selectedTags.size === 0 && - searchString.length === 0 && - !onlyShowActiveSources && - !onlyShowNdiSources && - !onlyShowBmdSources && - !onlyShowSrtSources - ) { - resetFilter(); - return; + const tempSet = new Map<string, SourceWithId>(sources); + + if (searchString.length > 0 || selectedTags.size > 0) { + handleSearch(tempSet); + handleTags(tempSet); } - handleSearch(); - handleTags(); - filterSources(); + filterSources(tempSet); + setFilteredSources(tempSet); onFilteredSources(tempSet); - tempSet.clear(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [ searchString, selectedTags, onlyShowActiveSources, - onlyShowNdiSources, - onlyShowBmdSources, - onlyShowSrtSources + showNdiType, + showBmdType, + showSrtType, + showMediaSourceGeneratorType, + sources ]); - const resetFilter = () => { - tempSet = new Map<string, SourceWithId>(sources); - onFilteredSources(sources); - }; - - const handleSearch = () => { - if (searchString.length === 0) { - tempSet = new Map<string, SourceWithId>(sources); - } else { + const handleSearch = (tempSet: Map<string, SourceWithId>) => { + if (searchString.length > 0) { for (const source of tempSet.values()) { const searchValues = [ source.name, @@ -82,8 +73,8 @@ function FilterOptions({ onFilteredSources }: FilterOptionsProps) { } }; - const handleTags = () => { - if (selectedTags.size !== 0) { + const handleTags = (tempSet: Map<string, SourceWithId>) => { + if (selectedTags.size > 0) { const typeTags = new Set<string>(); const locationTags = new Set<string>(); selectedTags.forEach((tag) => { @@ -102,31 +93,34 @@ function FilterOptions({ onFilteredSources }: FilterOptionsProps) { } } }; + const filterSources = (tempSet: Map<string, SourceWithId>) => { + const isFilteringByType = + showNdiType || showBmdType || showSrtType || showMediaSourceGeneratorType; - const filterSources = async () => { - for (const source of tempSet.values()) { - let shouldDelete = false; + if (!isFilteringByType) { + tempSet.clear(); + return; + } - const isFilteringByType = - onlyShowNdiSources || onlyShowBmdSources || onlyShowSrtSources; + for (const source of tempSet.values()) { + const ingestType = source.ingest_type?.toUpperCase() || ''; + let shouldDelete = true; + + if ( + (showNdiType && ingestType === 'NDI') || + (showBmdType && ingestType === 'BMD') || + (showSrtType && ingestType === 'SRT') || + (showMediaSourceGeneratorType && ingestType === 'MEDIASOURCEGENERATOR') + ) { + shouldDelete = false; + } - if (isFilteringByType && !source.ingest_type) { + if (onlyShowActiveSources && source.status === 'gone') { shouldDelete = true; - } else if (source.ingest_type) { - const ingestType = source.ingest_type.toUpperCase(); - - const isNdiSelected = onlyShowNdiSources && ingestType === 'NDI'; - const isBmdSelected = onlyShowBmdSources && ingestType === 'BMD'; - const isSrtSelected = onlyShowSrtSources && ingestType === 'SRT'; + } - if ( - isFilteringByType && - !isNdiSelected && - !isBmdSelected && - !isSrtSelected - ) { - shouldDelete = true; - } + if (shouldDelete) { + tempSet.delete(source._id.toString()); } if (onlyShowActiveSources && source.status === 'gone') { @@ -140,15 +134,17 @@ function FilterOptions({ onFilteredSources }: FilterOptionsProps) { }; const handleSorting = (reversedOrder: boolean) => { - const sortedSourcesArray = Array.from(tempSet.values()).sort((a, b) => { - const dateA = new Date(a.lastConnected).getTime(); - const dateB = new Date(b.lastConnected).getTime(); - return reversedOrder ? dateA - dateB : dateB - dateA; - }); - tempSet = new Map( + const sortedSourcesArray = Array.from(filteredSources.values()).sort( + (a, b) => { + const dateA = new Date(a.lastConnected).getTime(); + const dateB = new Date(b.lastConnected).getTime(); + return reversedOrder ? dateA - dateB : dateB - dateA; + } + ); + const sortedMap = new Map( sortedSourcesArray.map((source) => [source._id.toString(), source]) ); - onFilteredSources(tempSet); + onFilteredSources(sortedMap); }; return ( @@ -178,12 +174,16 @@ function FilterOptions({ onFilteredSources }: FilterOptionsProps) { setIsLocationHidden={setIsLocationHidden} setSelectedTags={setSelectedTags} setOnlyShowActiveSources={setOnlyShowActiveSources} - setOnlyShowNdiSources={setOnlyShowNdiSources} - setOnlyShowBmdSources={setOnlyShowBmdSources} - setOnlyShowSrtSources={setOnlyShowSrtSources} - showBmdType={onlyShowBmdSources} - showNdiType={onlyShowNdiSources} - showSrtType={onlyShowSrtSources} + setOnlyShowNdiSources={setShowNdiType} + setOnlyShowBmdSources={setShowBmdType} + setOnlyShowSrtSources={setShowSrtType} + setOnlyShowMediaSourceGeneratorSources={ + setShowMediaGeneratorSourceType + } + showBmdType={showBmdType} + showNdiType={showNdiType} + showSrtType={showSrtType} + showMediaSourceGeneratorType={showMediaSourceGeneratorType} handleSorting={handleSorting} /> </div> diff --git a/src/components/footer/Footer.tsx b/src/components/footer/Footer.tsx index 1b060011..60b628e0 100644 --- a/src/components/footer/Footer.tsx +++ b/src/components/footer/Footer.tsx @@ -13,7 +13,7 @@ interface IObject { interface IConnection { database?: IObject; - agileApi?: IObject; + liveApi?: IObject; message?: string; } @@ -63,7 +63,7 @@ export default function Footer() { }, []); const settingsDb = connection?.database; - const settingsAgile = connection?.agileApi; + const settingsLive = connection?.liveApi; return ( <div className="flex text-p ml-10"> @@ -77,8 +77,8 @@ export default function Footer() { /> <Status name="system_controller" - connected={settingsAgile?.connected} - adress={settingsAgile?.url} + connected={settingsLive?.connected} + adress={settingsLive?.url} /> <Status name="database" diff --git a/src/components/inventory/Inventory.tsx b/src/components/inventory/Inventory.tsx index d0e212d3..b8e50c66 100644 --- a/src/components/inventory/Inventory.tsx +++ b/src/components/inventory/Inventory.tsx @@ -2,16 +2,18 @@ import { useEffect, useState } from 'react'; import { useSources } from '../../hooks/sources/useSources'; +import { useSetSourceToPurge } from '../../hooks/sources/useSetSourceToPurge'; import { SourceWithId } from '../../interfaces/Source'; import EditView from './editView/EditView'; import SourceList from '../sourceList/SourceList'; import { useTranslate } from '../../i18n/useTranslate'; export default function Inventory() { + const [removeInventorySource, reloadList] = useSetSourceToPurge(); const [updatedSource, setUpdatedSource] = useState< SourceWithId | undefined >(); - const [sources] = useSources(updatedSource); + const [sources] = useSources(reloadList, updatedSource); const [currentSource, setCurrentSource] = useState<SourceWithId | null>(); const t = useTranslate(); @@ -21,10 +23,15 @@ export default function Inventory() { } }, [updatedSource]); + useEffect(() => { + if (reloadList) { + setCurrentSource(null); + } + }, [reloadList]); + const editSource = (source: SourceWithId) => { setCurrentSource(source); }; - return ( <div className="flex "> <SourceList diff --git a/src/components/inventory/editView/EditView.tsx b/src/components/inventory/editView/EditView.tsx index e1d1e156..e8e8ae72 100644 --- a/src/components/inventory/editView/EditView.tsx +++ b/src/components/inventory/editView/EditView.tsx @@ -9,11 +9,13 @@ import ImageComponent from '../../image/ImageComponent'; export default function EditView({ source, updateSource, - close + close, + removeInventorySource }: { source: SourceWithId; updateSource: (source: SourceWithId) => void; close: () => void; + removeInventorySource: (source: SourceWithId) => void; }) { return ( <EditViewContext source={source} updateSource={updateSource}> @@ -27,8 +29,11 @@ export default function EditView({ <div className="flex-auto"> <AudioChannels source={source} /> </div> - - <UpdateButtons close={close} /> + <UpdateButtons + close={close} + removeInventorySource={removeInventorySource} + source={source} + /> </EditViewContext> ); } diff --git a/src/components/inventory/editView/UpdateButtons.tsx b/src/components/inventory/editView/UpdateButtons.tsx index 88c5f03d..1f3309da 100644 --- a/src/components/inventory/editView/UpdateButtons.tsx +++ b/src/components/inventory/editView/UpdateButtons.tsx @@ -4,8 +4,18 @@ import { EditViewContext } from '../EditViewContext'; import { useTranslate } from '../../../i18n/useTranslate'; import styles from './animation.module.scss'; import { Loader } from '../../loader/Loader'; +import { SourceWithId } from '../../../interfaces/Source'; +import { IconTrash } from '@tabler/icons-react'; -export default function UpdateButtons({ close }: { close: () => void }) { +export default function UpdateButtons({ + close, + removeInventorySource, + source +}: { + close: () => void; + removeInventorySource: (source: SourceWithId) => void; + source: SourceWithId; +}) { const t = useTranslate(); const { saved: [saved], @@ -22,6 +32,15 @@ export default function UpdateButtons({ close }: { close: () => void }) { </div> <div className="flex"> + <Button + type="button" + state="warning" + disabled={source.status !== 'gone'} + className="mr-5 relative flex" + onClick={() => removeInventorySource(source)} + > + <IconTrash className="text-p" /> + </Button> <Button state="warning" onClick={close}> {t('close')} </Button> diff --git a/src/components/layout/DefaultLayout.tsx b/src/components/layout/DefaultLayout.tsx index 9723e84c..8ddef3b7 100644 --- a/src/components/layout/DefaultLayout.tsx +++ b/src/components/layout/DefaultLayout.tsx @@ -7,7 +7,7 @@ export default function DefaultLayout({ }) { return ( <div className="flex h-screen justify-between w-full flex-col"> - <div className="flex flex-row rounded p-2 w-full mb-8"> + <div className="flex flex-row rounded p-2 w-full mb-8 h-full"> <div className="p-3 m-2 grow rounded">{children}</div> </div> diff --git a/src/components/multiview/Multiview.tsx b/src/components/multiview/Multiview.tsx index bef1e3c9..a3fe0a21 100644 --- a/src/components/multiview/Multiview.tsx +++ b/src/components/multiview/Multiview.tsx @@ -1,7 +1,7 @@ 'use client'; import { useEffect, useRef, useState } from 'react'; -import { ResourcesCompactPipelineResponse } from '../../../types/agile-live'; +import { ResourcesCompactPipelineResponse } from '../../../types/ateliere-live'; import { usePipeline } from '../../hooks/pipelines'; import { WebRTCPlayer } from '@eyevinn/webrtc-player'; import { Loader } from '../loader/Loader'; diff --git a/src/constants.ts b/src/constants.ts index 36275246..b44e32d7 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,11 +1,11 @@ /** - * The API version of the Agile Live API that is currently supported. + * The API version of the Ateliere Live API that is currently supported. * @constant */ -export const AGILE_API_VERSION = 'v3'; +export const LIVE_API_VERSION = 'v3'; /** - * The base path for the Agile Live API, where all other endpoints can be appended. + * The base path for the Ateliere Live API, where all other endpoints can be appended. * @constant */ -export const AGILE_BASE_API_PATH = '/api/' + AGILE_API_VERSION; +export const LIVE_BASE_API_PATH = '/api/' + LIVE_API_VERSION; diff --git a/src/hooks/controlPanels.ts b/src/hooks/controlPanels.ts index 5e4ebd79..21f8100d 100644 --- a/src/hooks/controlPanels.ts +++ b/src/hooks/controlPanels.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useState } from 'react'; import { DataHook } from './types'; -import { ResourcesControlPanelResponse } from '../../types/agile-live'; +import { ResourcesControlPanelResponse } from '../../types/ateliere-live'; const ONE_MINUTE = 1000 * 60; export function useControlPanels(): [ ...DataHook<ResourcesControlPanelResponse[]>, diff --git a/src/hooks/pipelines.ts b/src/hooks/pipelines.ts index 34084d23..f9ef473a 100644 --- a/src/hooks/pipelines.ts +++ b/src/hooks/pipelines.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useState } from 'react'; import { DataHook } from './types'; -import { ResourcesCompactPipelineResponse } from '../../types/agile-live'; +import { ResourcesCompactPipelineResponse } from '../../types/ateliere-live'; import { ManagerPipelineResponse } from '../interfaces/pipeline'; const ONE_MINUTE = 1000 * 60; diff --git a/src/hooks/sources/useSetSourceToPurge.tsx b/src/hooks/sources/useSetSourceToPurge.tsx new file mode 100644 index 00000000..75818cf5 --- /dev/null +++ b/src/hooks/sources/useSetSourceToPurge.tsx @@ -0,0 +1,44 @@ +import { useState } from 'react'; +import { SourceWithId } from '../../interfaces/Source'; +import { CallbackHook } from '../types'; +import { Log } from '../../api/logger'; + +export function useSetSourceToPurge(): CallbackHook< + (source: SourceWithId) => void +> { + const [reloadList, setReloadList] = useState(false); + + const removeInventorySource = (source: SourceWithId) => { + if (source && source.status === 'gone') { + setReloadList(false); + + fetch(`/api/manager/inventory/${source._id}`, { + method: 'PUT', + // TODO: Implement api key + headers: [['x-api-key', `Bearer apisecretkey`]] + }) + .then((response) => { + if (!response.ok) { + setReloadList(true); + Log().error( + `Failed to set ${source.name} with id: ${source._id} to purge` + ); + } else { + console.log( + `${source.name} with id: ${source._id} is set to purge` + ); + } + setReloadList(true); + }) + .catch((e) => { + Log().error( + `Failed to set ${source.name} with id: ${source._id} to purge: ${e}` + ); + throw `Failed to set ${source.name} with id: ${source._id} to purge: ${e}`; + }); + } else { + setReloadList(false); + } + }; + return [removeInventorySource, reloadList]; +} diff --git a/src/hooks/sources/useSources.tsx b/src/hooks/sources/useSources.tsx index fb7ed1db..1b58418a 100644 --- a/src/hooks/sources/useSources.tsx +++ b/src/hooks/sources/useSources.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { SourceWithId } from '../../interfaces/Source'; export function useSources( + deleteComplete?: boolean, updatedSource?: SourceWithId ): [Map<string, SourceWithId>, boolean] { const [sources, setSources] = useState<Map<string, SourceWithId>>( @@ -10,7 +11,7 @@ export function useSources( const [loading, setLoading] = useState(true); useEffect(() => { - if (!updatedSource) { + if (!updatedSource || deleteComplete) { fetch('/api/manager/sources?mocked=false', { method: 'GET', // TODO: Implement api key @@ -33,6 +34,6 @@ export function useSources( } sources.set(updatedSource._id.toString(), updatedSource); setSources(new Map<string, SourceWithId>(sources)); - }, [updatedSource]); + }, [updatedSource, deleteComplete]); return [sources, loading]; } diff --git a/src/interfaces/Source.ts b/src/interfaces/Source.ts index db4838fc..e59afa4a 100644 --- a/src/interfaces/Source.ts +++ b/src/interfaces/Source.ts @@ -1,6 +1,6 @@ import { ObjectId, WithId } from 'mongodb'; export type SourceType = 'camera' | 'graphics' | 'microphone'; -export type SourceStatus = 'ready' | 'new' | 'gone'; +export type SourceStatus = 'ready' | 'new' | 'gone' | 'purge'; export type VideoStream = { height?: number; width?: number; diff --git a/src/interfaces/pipeline.ts b/src/interfaces/pipeline.ts index 5f1d6cc5..b97f48f5 100644 --- a/src/interfaces/pipeline.ts +++ b/src/interfaces/pipeline.ts @@ -1,4 +1,4 @@ -import { ResourcesPipelineResponse } from '../../types/agile-live'; +import { ResourcesPipelineResponse } from '../../types/ateliere-live'; import { MultiviewSettings } from './multiview'; import { WhepMultiview } from './whep'; diff --git a/src/utils/checkApiConnections.ts b/src/utils/checkApiConnections.ts index d22076c4..e62af199 100644 --- a/src/utils/checkApiConnections.ts +++ b/src/utils/checkApiConnections.ts @@ -5,7 +5,7 @@ interface IObject { interface Response { database?: IObject; - agileApi?: IObject; + liveApi?: IObject; message?: string; } diff --git a/src/utils/pipeline.ts b/src/utils/pipeline.ts index acbbef7d..49ad88c5 100644 --- a/src/utils/pipeline.ts +++ b/src/utils/pipeline.ts @@ -3,7 +3,7 @@ import { ResourcesPipelineResponse, ResourcesOutputActiveStreamMpegTsSrt -} from '../../types/agile-live'; +} from '../../types/ateliere-live'; import { SrtOutput } from '../interfaces/pipeline'; import { WhepMultiview } from '../interfaces/whep'; import { getWhepUrlForSRT } from './whep'; diff --git a/types/agile-live.d.ts b/types/ateliere-live.d.ts similarity index 100% rename from types/agile-live.d.ts rename to types/ateliere-live.d.ts diff --git a/update_gui_version.sh b/update_gui_version.sh index c4e49c63..9c8a9176 100755 --- a/update_gui_version.sh +++ b/update_gui_version.sh @@ -1,2 +1,2 @@ #!/bin/bash -git describe --tags --dirty --match v* > gui-version.txt +git describe --always --tags --dirty --match v* > gui-version.txt