Initial Commit

This commit is contained in:
2025-09-07 01:46:37 -04:00
commit 66986cca51
272 changed files with 15331 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import React, {useState, useEffect, ChangeEvent, useCallback} from 'react';
import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons';
import {documentationLinks} from 'constants/links';
import {NewTabLink} from 'components/new-tab-link';
import Input from 'components/forms/Input';
import {CopyIconButton} from 'components/buttons/copy-icon-button';
import {DialogForm, Error} from 'components/modal/modal-form-response/style';
const DeleteChannelForm = ({setIsValid, alias}: {setIsValid: (isValid: boolean) => void; alias: string}): React.JSX.Element => {
const [error, setError] = useState<string | null>(null);
const [enteredAlias, setEnteredAlias] = useState('');
const validate = useCallback((): boolean => {
if (!enteredAlias) {
setError('Please enter a channel alias');
setIsValid(false);
return false;
}
if (enteredAlias !== alias) {
setError('Entered alias does not match the channels alias');
setIsValid(false);
return false;
}
setError(null);
setIsValid(true);
return true;
}, [enteredAlias, alias, setIsValid]);
useEffect(() => {
if (enteredAlias.length) {
validate();
}
}, [enteredAlias, validate]);
const handleEnteredAlias = (event: ChangeEvent<HTMLInputElement>): void => {
if (error) {
setError(null);
}
setEnteredAlias(event.target.value);
};
return (
<DialogForm>
<h3 className="testId-deleteChannelForm">
Delete Channel <NewTabLink link={documentationLinks.deleteChannel} icon={faQuestionCircle} iconColor="black" />
</h3>
<p>To delete channel, please enter the channel alias:</p>
<strong>
<CopyIconButton text={alias} quoted />
</strong>
<Input name="alias" error={!!error} onChange={handleEnteredAlias} value={enteredAlias} />
{error && <Error className="error-text testId-displayMessage">{error}</Error>}
</DialogForm>
);
};
export default DeleteChannelForm;

View File

@@ -0,0 +1,111 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import React, {useState} from 'react';
import {useDispatch} from 'react-redux';
import LoggerFactory from 'services/logger/LoggerFactory';
import {deleteChannel} from 'services/Channel.service';
import {listChannels} from 'store/action/channels';
import {transformToPortalError} from 'utility/error-handler';
import {deleteChannelErrorMessages} from 'constants/error-messages';
import {MultiStepModal} from 'components/modal/multi-step-modal';
import DeleteChannelForm from './delete-channel-form';
import {FormResponse} from 'components/modal/modal-form-response';
interface IDeleteChannelModal {
isOpen: boolean;
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
channelId: string;
alias: string;
redirect?: () => void;
}
export const DeleteChannelModal = ({isOpen, setIsOpen, channelId, alias, redirect}: IDeleteChannelModal): React.JSX.Element => {
const logger = LoggerFactory.getLogger('components/channel-icon-menu/delete-channel/DeleteChannelModal');
const dispatch = useDispatch();
const [isFormValid, setIsFormValid] = useState(false);
const [isFetching, setIsFetching] = useState(false);
const [deleteChannelResponse, setDeleteChannelResponse] = useState<{error: string | boolean | Error; status: string | number; data?: any} | null>(null);
const handleSubmit = async () => {
if (!isFormValid) {
return;
}
try {
setIsFetching(true);
logger.info('Deleting a channel [%s][%s]', alias, channelId);
const response = await deleteChannel({channelId, alias});
logger.info('Channel [%s][%s] was successfully deleted', alias, channelId);
setDeleteChannelResponse({status: 'ok', error: false, data: response});
setIsFetching(false);
} catch (e) {
const {status, message, requestPayload, statusCode} = transformToPortalError(e);
setIsFetching(false);
const errorMessage = (deleteChannelErrorMessages as Record<string, string>)[status] || message || deleteChannelErrorMessages['default'];
setDeleteChannelResponse({
status: statusCode || 'error',
error: errorMessage
});
logger.error(`${errorMessage} [%s]`, status, requestPayload);
}
};
const handleCloseModal = (): void => {
setIsOpen(false);
};
const onDeleteSuccess = async (): Promise<void> => {
setIsOpen(false);
logger.info('Updating the list of channels after the [%s][%s] channel was deleted', alias, channelId);
await dispatch(listChannels() as any);
logger.info('The list of channels was updated successfully');
if (redirect) {
redirect();
}
};
return (
<MultiStepModal
isOpen={isOpen}
closeModal={deleteChannelResponse?.status === 'ok' ? onDeleteSuccess : handleCloseModal}
steps={[
{
title: 'Delete Channel',
component: <DeleteChannelForm alias={alias} setIsValid={setIsFormValid} />,
saveButton: {
text: 'Delete Channel',
className: 'testId-deleteChannel',
disabled: !isFormValid || isFetching,
onClick: handleSubmit
}
},
{
title: 'Delete Channel',
component: <FormResponse response={deleteChannelResponse || {error: '', status: '', data: null}} />,
cancelDisabled: true,
saveButton: {
className: 'testId-doneButton',
onClick: onDeleteSuccess
}
}
]}
/>
);
};
export default DeleteChannelModal;

View File

@@ -0,0 +1,4 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
export {default} from './delete-channel-modal';

View File

@@ -0,0 +1,117 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import React, {useState, useEffect, useCallback} from 'react';
import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons';
import {CapabilitiesType} from 'constants/capabilities';
import {documentationLinks} from 'constants/links';
import {Label} from 'components/forms/label';
import Checkbox from 'components/forms/Checkbox';
import {NewTabLink} from 'components/new-tab-link';
import {Dropdown} from 'components/drop-down';
import {InputWithTags} from 'components/ui/input-with-tags';
import {DialogForm, Error, Options} from 'components/modal/modal-form-response/style';
import {Capabilities} from 'components/create-token-components';
import Theme from 'theme';
const forkChannelOption = ['force', 'additive'];
const ForkChannelForm = ({
setFormData,
setIsValid,
channelList,
alias
}: {
setFormData: (data: any) => void;
setIsValid: (isValid: boolean) => void;
channelList: any[];
alias: string;
}): React.JSX.Element => {
const [error, setError] = useState<string | null>(null);
const [tags, setTags] = useState<string[]>([]);
const [forkOptions, setForkOptions] = useState<string[]>([]);
const [destinationChannelId, setDestinationChannelId] = useState('');
const [selectedCapabilities, setSelectedCapabilities] = useState<any[]>([]);
const [force, additive] = forkChannelOption;
const destinationChannelsList = channelList.filter(channel => channel.alias !== alias);
const validate = useCallback((): boolean => {
if (!destinationChannelId) {
setIsValid(false);
return false;
}
setError(null);
setIsValid(true);
setFormData({
streamCapabilities: selectedCapabilities.map(({value}) => value),
streamTags: tags,
options: forkOptions,
destinationChannelId
});
return true;
}, [destinationChannelId, selectedCapabilities, tags, forkOptions, setIsValid, setFormData]);
useEffect(() => {
validate();
}, [validate]);
const handleForkOptions = (value: string) => {
if (forkOptions.indexOf(value) > -1) {
const currentForkOptions = forkOptions.filter(item => item !== value);
setForkOptions(currentForkOptions);
} else {
setForkOptions([...forkOptions, value]);
}
};
const handleSetDestinationChannelId = (channelAlias: string): void => {
const channels = channelList.filter(channel => channel.alias === channelAlias);
if (channels[0]) {
const {channelId} = channels[0];
setDestinationChannelId(channelId);
setError(null);
}
};
return (
<DialogForm>
<h3 className="testId-forkChannelForm">
Fork Channel <NewTabLink link={documentationLinks.forkChannel} icon={faQuestionCircle} iconColor="black" />
</h3>
<p>
Fork <strong>"{alias}"</strong> to destination channel:
</p>
<Dropdown
name={'alias'}
label="Destination Channel Alias"
items={destinationChannelsList}
itemKey={'alias'}
onSelect={handleSetDestinationChannelId}
className="testId-destinationChannelAlias"
/>
{error && <Error>Please select a Channel from the list</Error>}
<Capabilities
label="Capabilities:"
labelColor={Theme.colors.gray900}
iconColor={Theme.colors.gray900}
capabilitiesSetTitle={CapabilitiesType.Forking}
selectedItems={selectedCapabilities}
setSelectedItems={setSelectedCapabilities}
/>
<Label text="Options" />
<Options>
<Checkbox value={force} id={force} onChange={() => handleForkOptions(force)} checked={forkOptions.indexOf(force) > -1} label={force} />
<Checkbox value={additive} id={additive} onChange={() => handleForkOptions(additive)} checked={forkOptions.indexOf(additive) > -1} label={additive} />
</Options>
<Label htmlFor="input-tag" text="Stream Tags" />
<InputWithTags id="input-tag" onTagListChange={setTags} defaultValue={tags} />
</DialogForm>
);
};
export default ForkChannelForm;

View File

@@ -0,0 +1,113 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import React, {useState} from 'react';
import {useSelector} from 'react-redux';
import LoggerFactory from 'services/logger/LoggerFactory';
import {forkChannel} from 'services/Channel.service';
import {AppStore} from 'store';
import {channelsSelector} from 'store/action/channels';
import {transformToPortalError} from 'utility/error-handler';
import {forkChannelErrorMessages} from 'constants/error-messages';
import {MultiStepModal} from 'components/modal/multi-step-modal';
import ForkChannelForm from './fork-channel-form';
import {FormResponse} from 'components/modal/modal-form-response';
interface IForkChannelModal {
isOpen: boolean;
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
channelId: string;
alias: string;
}
export const ForkChannelModal = ({isOpen, setIsOpen, channelId, alias}: IForkChannelModal): React.JSX.Element => {
const logger = LoggerFactory.getLogger('components/channel-icon-menu/fork-channel/ForkChannelModal');
const channelsState = useSelector((state: AppStore) => channelsSelector(state));
const channelList = channelsState.channels || [];
const [formData, setFormData] = useState({
streamCapabilities: [] as string[],
streamTags: [] as string[],
options: [] as string[],
destinationChannelId: ''
});
const [isFormValid, setIsFormValid] = useState(false);
const [isFetching, setIsFetching] = useState(false);
const [forkResponse, setForkResponse] = useState<{error: string | boolean | Error; status: string | number; data?: any} | null>(null);
const handleSubmit = async () => {
if (!isFormValid) {
return;
}
try {
setIsFetching(true);
const {streamCapabilities, streamTags, options, destinationChannelId} = formData;
logger.info('Forking a channel [%s] to [%s]', channelId, destinationChannelId);
const response = await forkChannel({
sourceChannelId: channelId,
destinationChannelId,
streamCapabilities,
streamTags,
options
});
logger.info('The channel [%s] was successfully forked to [%s]', channelId, destinationChannelId);
setForkResponse({
status: 'ok',
error: false,
data: response
});
setIsFetching(false);
} catch (e) {
const {status, message, requestPayload, statusCode} = transformToPortalError(e);
setIsFetching(false);
const errorMessage = (forkChannelErrorMessages as Record<string, string>)[status] || message || forkChannelErrorMessages['default'];
setForkResponse({
status: statusCode || 'error',
error: errorMessage
});
logger.error(`${errorMessage} [%s]`, status, requestPayload);
}
};
const handleCloseModal = (): void => {
setIsOpen(false);
};
return (
<MultiStepModal
isOpen={isOpen}
closeModal={handleCloseModal}
steps={[
{
title: 'Fork Channel',
component: <ForkChannelForm alias={alias} channelList={channelList} setIsValid={setIsFormValid} setFormData={setFormData} />,
saveButton: {
text: 'Fork Channel',
className: 'testId-forkChannel',
disabled: !isFormValid || isFetching,
onClick: handleSubmit
}
},
{
title: 'Fork Channel',
component: <FormResponse response={forkResponse || {error: '', status: '', data: null}} />,
cancelDisabled: true,
saveButton: {className: 'testId-doneButton'}
}
]}
/>
);
};
export default ForkChannelModal;

View File

@@ -0,0 +1,4 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
export {default} from './fork-channel-modal';

View File

@@ -0,0 +1,65 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import React, {useState} from 'react';
import {IconDefinition} from '@fortawesome/free-solid-svg-icons';
import {IconMenu} from 'components/icon-menu';
import {IconMenuPosition} from 'components/icon-menu/types';
import ForkChannelModal from './fork-channel';
import DeleteChannelModal from './delete-channel';
import KillChannelModal from './kill-channel';
interface IChannelIconMenu {
data: {
name: string;
channelId: string;
applicationId: string;
alias: string;
};
redirect?: () => void;
showTail?: boolean;
position?: IconMenuPosition;
icon?: IconDefinition;
margin?: number;
}
export const ChannelIconMenu = (props: IChannelIconMenu): React.JSX.Element => {
const {data, icon, redirect, showTail, position, margin} = props;
const {alias, channelId} = data;
const [isForkModalOpen, setIsForkModalOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [isKillModalOpen, setIsKillModalOpen] = useState(false);
const items = [
{
value: 'delete',
title: 'Delete',
className: 'testId-deleteMenuItem',
onClick: () => setIsDeleteModalOpen(true)
},
{
value: 'kill',
title: 'Kill',
className: 'testId-killMenuItem',
onClick: () => setIsKillModalOpen(true)
},
{
value: 'fork',
title: 'Fork',
className: 'testId-forkMenuItem',
onClick: () => setIsForkModalOpen(true)
}
];
return (
<>
<IconMenu icon={icon} items={items} showTail={showTail} position={position} margin={margin} />
{isForkModalOpen && <ForkChannelModal isOpen={isForkModalOpen} setIsOpen={setIsForkModalOpen} channelId={channelId} alias={alias} />}
{isDeleteModalOpen && (
<DeleteChannelModal isOpen={isDeleteModalOpen} setIsOpen={setIsDeleteModalOpen} channelId={channelId} alias={alias} redirect={redirect} />
)}
{isKillModalOpen && <KillChannelModal isOpen={isKillModalOpen} setIsOpen={setIsKillModalOpen} channelId={channelId} alias={alias} />}
</>
);
};

View File

@@ -0,0 +1,4 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
export {default} from './kill-channel-modal';

View File

@@ -0,0 +1,137 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import React, {useState, useEffect, ChangeEvent, useCallback} from 'react';
import {faQuestionCircle} from '@fortawesome/free-solid-svg-icons';
import {documentationLinks} from 'constants/links';
import Checkbox from 'components/forms/Checkbox';
import {Label} from 'components/label';
import {NewTabLink} from 'components/new-tab-link';
import {DialogForm, Error, Options} from 'components/modal/modal-form-response/style';
import {CopyIconButton} from 'components/buttons/copy-icon-button';
import {Tooltip, Position} from 'components/tooltip';
import {Input} from 'components/ui';
const keepStreams = 'keep-streams';
const keepStreamsTooltipMessage = 'Keeps the removed streams alive';
const destroyRequired = 'destroy-required';
const destroyRequiredTooltipMessage = 'Returns an error if destroying of a stream fails';
const defaultReason = 'portal:killed';
const KillChannelForm = ({
setFormData,
setIsValid,
alias
}: {
setFormData: (data: any) => void;
setIsValid: (isValid: boolean) => void;
alias: string;
}): React.JSX.Element => {
const [error, setError] = useState<string | null>(null);
const [enteredAlias, setEnteredAlias] = useState('');
const [reason, setReason] = useState(defaultReason);
const [options, setOptions] = useState<string[]>([]);
const validate = useCallback((): boolean => {
if (!enteredAlias) {
setError('Please enter a channel alias');
setIsValid(false);
return false;
}
if (enteredAlias !== alias) {
setError('Entered alias does not match the channels alias');
setIsValid(false);
return false;
}
setError(null);
setIsValid(true);
setFormData({
reason,
options,
enteredAlias
});
return true;
}, [enteredAlias, alias, reason, options, setIsValid, setFormData, setError]);
useEffect(() => {
if (enteredAlias.length) {
validate();
}
}, [enteredAlias, validate]);
const handleAlias = (event: ChangeEvent<HTMLInputElement>): void => {
if (error) {
setError(null);
}
setEnteredAlias(event.target.value);
};
const handleKillOptions = (value: string) => {
if (options.indexOf(value) > -1) {
const currentOptions = options.filter(item => item !== value);
setOptions(currentOptions);
} else {
setOptions([...options, value]);
}
};
const handleReason = (event: ChangeEvent<HTMLInputElement>): void => {
if (error) {
setError(null);
}
setReason(event.target.value);
};
return (
<DialogForm>
<h3 className="testId-killChannelForm">
Kill Channel <NewTabLink link={documentationLinks.killChannel} icon={faQuestionCircle} iconColor="black" />
</h3>
<p>Terminates all streams and removes them from the channel.</p>
<p>To kill the channel, please enter the channel alias:</p>
<strong>
<CopyIconButton text={alias} quoted />
</strong>
<Input error={!!error} onChange={handleAlias} value={enteredAlias} name="alias" />
{error && (
<Error className="error-text">
Please enter the channel alias <i>{alias}</i>
</Error>
)}
<h5>Kill options:</h5>
<Options>
<Tooltip position={Position.Bottom} message={keepStreamsTooltipMessage}>
<Checkbox
value={keepStreams}
id={keepStreams}
onChange={() => handleKillOptions(keepStreams)}
checked={options.indexOf(keepStreams) > -1}
label={keepStreams}
/>
</Tooltip>
<Tooltip position={Position.Bottom} message={destroyRequiredTooltipMessage}>
<Checkbox
value={destroyRequired}
id={destroyRequired}
onChange={() => handleKillOptions(destroyRequired)}
checked={options.indexOf(destroyRequired) > -1}
label={destroyRequired}
/>
</Tooltip>
</Options>
<Label htmlFor="reason" text="Reason:" />
<Input id="reason" error={!!error} onChange={handleReason} value={reason} />
</DialogForm>
);
};
export default KillChannelForm;

View File

@@ -0,0 +1,106 @@
/**
* Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
*/
import React, {useState} from 'react';
import LoggerFactory from 'services/logger/LoggerFactory';
import {killChannel} from 'services/Channel.service';
import {transformToPortalError} from 'utility/error-handler';
import {killChannelErrorMessages} from 'constants/error-messages';
import {MultiStepModal} from 'components/modal/multi-step-modal';
import KillChannelForm from './kill-channel-form';
import {FormResponse} from 'components/modal/modal-form-response';
interface IKillChannelModal {
isOpen: boolean;
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
channelId: string;
alias: string;
}
export const KillChannelModal = ({isOpen, setIsOpen, channelId, alias}: IKillChannelModal): React.JSX.Element => {
const logger = LoggerFactory.getLogger('components/channel-icon-menu/kill-channel/KillChannelModal');
const [formData, setFormData] = useState({
reason: '',
options: [] as string[],
enteredAlias: ''
});
const [isFormValid, setIsFormValid] = useState(false);
const [isFetching, setIsFetching] = useState(false);
const [killResponse, setKillResponse] = useState<{error: string | boolean | Error; status: string | number; data?: any} | null>(null);
const handleSubmit = async () => {
if (!isFormValid) {
return;
}
try {
setIsFetching(true);
const {reason, options} = formData;
logger.info('Killing a channel [%s] with reason [%s]', channelId, reason);
const response = await killChannel({
channelId,
reason,
options,
enteredAlias: formData.enteredAlias
});
logger.info('The channel [%s] was successfully killed', channelId);
setKillResponse({
status: 'ok',
error: false,
data: response
});
setIsFetching(false);
} catch (e) {
const {status, message, requestPayload, statusCode} = transformToPortalError(e);
setIsFetching(false);
const errorMessage = (killChannelErrorMessages as Record<string, string>)[status] || message || killChannelErrorMessages['default'];
setKillResponse({
status: statusCode || 'error',
error: errorMessage
});
logger.error(`${errorMessage} [%s]`, status, requestPayload);
}
};
const handleCloseModal = (): void => {
setIsOpen(false);
};
return (
<MultiStepModal
isOpen={isOpen}
closeModal={handleCloseModal}
steps={[
{
title: 'Kill Channel',
component: <KillChannelForm alias={alias} setIsValid={setIsFormValid} setFormData={setFormData} />,
saveButton: {
text: 'Kill Channel',
className: 'testId-killChannel',
disabled: !isFormValid || isFetching,
onClick: handleSubmit
}
},
{
title: 'Kill Channel',
component: <FormResponse response={killResponse || {error: '', status: '', data: null}} />,
cancelDisabled: true,
saveButton: {className: 'testId-doneButton'}
}
]}
/>
);
};
export default KillChannelModal;