Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,086 changes: 1,648 additions & 438 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"react-hotkeys-hook": "^5.2.4",
"react-i18next": "^16.5.4",
"react-icons": "^5.5.0",
"react-js-cron": "^5.0.1",
"react-redux": "^9.2.0",
"react-router": "^7.13.0",
"react-select": "^5.10.2",
Expand Down
3 changes: 3 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Acls from "./components/users/Acls";
import About from "./components/About";
import { useAppDispatch } from "./store";
import { fetchOcVersion, fetchUserInfo } from "./slices/userInfoSlice";
import LifeCyclePolicies from "./components/events/LifeCyclePolicies";
import { subscribeToAuthEvents } from "./utils/broadcastSync";
import { useTableFilterStateValidation } from "./hooks/useTableFilterStateValidation";

Expand Down Expand Up @@ -47,6 +48,8 @@ function App() {

<Route path={"/events/series"} element={<Series />} />

<Route path={"/events/lifeCyclePolicies"} element={<LifeCyclePolicies />} />

<Route path={"/recordings/recordings"} element={<Recordings />} />

<Route path={"/systems/jobs"} element={<Jobs />} />
Expand Down
121 changes: 121 additions & 0 deletions src/components/events/LifeCyclePolicies.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import TableFilters from "../shared/TableFilters";
import Table from "../shared/Table";
import Notifications from "../shared/Notifications";
import { loadLifeCyclePoliciesIntoTable } from "../../thunks/tableThunks";
import { fetchFilters } from "../../slices/tableFilterSlice";
import Header from "../Header";
import NavBar from "../NavBar";
import MainView from "../MainView";
import Footer from "../Footer";
import { useAppDispatch, useAppSelector } from "../../store";
import { AsyncThunk } from "@reduxjs/toolkit";
import { getTotalLifeCyclePolicies } from "../../selectors/lifeCycleSelectors";
import { fetchLifeCyclePolicies } from "../../slices/lifeCycleSlice";
import { lifeCyclePoliciesTemplateMap } from "../../configs/tableConfigs/lifeCyclePoliciesTableMap";
import { fetchLifeCyclePolicyActions, fetchLifeCyclePolicyTargetTypes, fetchLifeCyclePolicyTimings } from "../../slices/lifeCycleDetailsSlice";
import { ModalHandle } from "../shared/modals/Modal";
import { eventsLinks } from "./partials/EventsNavigation";
import { resetTableProperties } from "../../slices/tableSlice";
import LifeCyclePolicyDetailsModal from "./partials/modals/LifeCyclePolicyDetailsModal";

/**
* This component renders the table view of policies
*/
const LifeCyclePolicies = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const [displayNavigation, setNavigation] = useState(false);
const newPolicyModalRef = useRef<ModalHandle>(null);

const policiesTotal = useAppSelector(state => getTotalLifeCyclePolicies(state));

useEffect(() => {
// State variable for interrupting the load function
let allowLoadIntoTable = true;

// Clear table of previous data
dispatch(resetTableProperties());

dispatch(fetchFilters("lifeCyclePolicies"));

// Load policies on mount
const loadLifeCyclePolicies = async () => {
// Fetching policies from server
await dispatch(fetchLifeCyclePolicies());

// Load policies into table
if (allowLoadIntoTable) {
dispatch(loadLifeCyclePoliciesIntoTable());
}
};
loadLifeCyclePolicies();

// Fetch policies repeatedly
const fetchInterval = setInterval(loadLifeCyclePolicies, 5000);

return () => {
allowLoadIntoTable = false;
clearInterval(fetchInterval);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const showNewPolicyModal = async () => {
await dispatch(fetchLifeCyclePolicyActions());
await dispatch(fetchLifeCyclePolicyTargetTypes());
await dispatch(fetchLifeCyclePolicyTimings());

newPolicyModalRef.current?.open();
};

return (
<>
<Header />
<NavBar
displayNavigation={displayNavigation}
setNavigation={setNavigation}
navAriaLabel={"EVENTS.EVENTS.NAVIGATION.LABEL"}
links={
eventsLinks
}
create={{
accessRole: "ROLE_UI_EVENTS_CREATE",
onShowModal: showNewPolicyModal,
text: "LIFECYCLE.POLICIES.TABLE.ADD_POLICY",
resource: "lifecyclepolicy",
}}
>
</NavBar>

<MainView open={displayNavigation}>
{/* Include notifications component */}
<Notifications context={"other"}/>

<div className="controls-container">
{/* Include filters component */}
{/* LifeCycle policies are not indexed, can't search or filter them */}
{/* But if we don't include this component, the policies won't load on page load, because the first
fetch request we send to the backend contains invalid params >.> */}
<TableFilters
loadResource={fetchLifeCyclePolicies as AsyncThunk<any, void, any>}
loadResourceIntoTable={loadLifeCyclePoliciesIntoTable}
resource={"lifeCyclePolicies"}
/>

<h1>{t("LIFECYCLE.POLICIES.TABLE.CAPTION")}</h1>
<h4>{t("TABLE_SUMMARY", { numberOfRows: policiesTotal })}</h4>
</div>
{/* Include table component */}
<Table templateMap={lifeCyclePoliciesTemplateMap} />
</MainView>
<Footer />

{/* Include table modal */}
<LifeCyclePolicyDetailsModal />
</>
);
};

export default LifeCyclePolicies;
5 changes: 5 additions & 0 deletions src/components/events/partials/EventsNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ export const eventsLinks: {
accessRole: "ROLE_UI_SERIES_VIEW",
text: "EVENTS.EVENTS.NAVIGATION.SERIES",
},
{
path: "/events/lifeCyclePolicies",
accessRole: "ROLE_UI_LIFECYCLEPOLICIES_VIEW",
text: "LIFECYCLE.NAVIGATION.POLICIES",
},
];
54 changes: 54 additions & 0 deletions src/components/events/partials/LifeCyclePolicyActionCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useAppDispatch } from "../../../store";
import { deleteLifeCyclePolicy, LifeCyclePolicy } from "../../../slices/lifeCycleSlice";
import { fetchLifeCyclePolicyDetails, openModal } from "../../../slices/lifeCycleDetailsSlice";
import ButtonLikeAnchor from "../../shared/ButtonLikeAnchor";
import { LuFileText } from "react-icons/lu";
import { ActionCellDelete } from "../../shared/ActionCellDelete";

/**
* This component renders the title cells of series in the table view
*/
const LifeCyclePolicyActionCell = ({
row,
}: {
row: LifeCyclePolicy
}) => {
const dispatch = useAppDispatch();

const showLifeCyclePolicyDetails = async () => {
await dispatch(fetchLifeCyclePolicyDetails(row.id));

dispatch(openModal(row));
};

const deletingPolicy = (id: string) => {
dispatch(deleteLifeCyclePolicy(id));
};

return (
<>
{/* view details location/recording */}
<ButtonLikeAnchor
onClick={() => showLifeCyclePolicyDetails()}
className={"action-cell-button"}
editAccessRole={"ROLE_UI_LIFECYCLEPOLICY_DETAILS_VIEW"}
// tooltipText={"LIFECYCLE.POLICIES.TABLE.TOOLTIP.DETAILS"} // Disabled due to performance concerns
>
<LuFileText />
</ButtonLikeAnchor>


{/* delete policy */}
<ActionCellDelete
editAccessRole={"ROLE_UI_LIFECYCLEPOLICY_DELETE"}
// tooltipText={"LIFECYCLE.POLICIES.TABLE.TOOLTIP.DELETE"} // Disabled due to performance concerns
resourceId={row.id}
resourceName={row.title}
resourceType={"LIFECYCLE_POLICY"}
deleteMethod={deletingPolicy}
/>
</>
);
};

export default LifeCyclePolicyActionCell;
21 changes: 21 additions & 0 deletions src/components/events/partials/LifeCyclePolicyIsActiveCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { LifeCyclePolicy } from "../../../slices/lifeCycleSlice";

/**
* This component renders the maintenance cells of servers in the table view
*/
const LifeCyclePolicyIsActiveCell = ({
row,
}: {
row: LifeCyclePolicy
}) => {

return (
<input
type="checkbox"
checked={row.isActive}
disabled={true}
/>
);
};

export default LifeCyclePolicyIsActiveCell;
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "../../../../store";
import { getLifeCyclePoliciesForEvent } from "../../../../selectors/eventDetailsSelectors";
import { fetchEventLifeCyclePolicies } from "../../../../slices/eventDetailsSlice";
import ModalContentTable from "../../../shared/modals/ModalContentTable";
import Notifications from "../../../shared/Notifications";
import ButtonLikeAnchor from "../../../shared/ButtonLikeAnchor";
import { LuChevronRight } from "react-icons/lu";
import { useNavigate } from "react-router";
import { fetchLifeCyclePolicyDetails, openModal } from "../../../../slices/lifeCycleDetailsSlice";
import { LifeCyclePolicy } from "../../../../slices/lifeCycleSlice";


/**
* This component shows lifecycle policies that would affect the event
*/
const EventDetailsLifeCyclePolicy = ({
eventId,
}: {
eventId: string,
}) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const navigate = useNavigate();

const policies = useAppSelector(state => getLifeCyclePoliciesForEvent(state));

useEffect(() => {
dispatch(fetchEventLifeCyclePolicies(eventId));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const openPolicyDetails = async (policy: LifeCyclePolicy) => {
await dispatch(fetchLifeCyclePolicyDetails(policy.id));
dispatch(openModal(policy));
navigate("/events/lifeCyclePolicies");
};

return (
<ModalContentTable
modalBodyChildren={<Notifications context="not_corner" />}
>
{/* Disclaimer */}
<div className="obj list-obj">
<header className="no-expand">
{t("EVENTS.EVENTS.DETAILS.LIFECYCLEPOLICIES.DISCLAIMER.TITLE")}
</header>
<div className="obj-container">
<span>{t("EVENTS.EVENTS.DETAILS.LIFECYCLEPOLICIES.DISCLAIMER.MESSAGE")}</span>
</div>
</div>

<div className="obj tbl-container">
{
/* No policies message */
policies.length === 0 && (
<table className="main-tbl">
<tr>
<td colSpan={4}>
{t("EVENTS.EVENTS.DETAILS.LIFECYCLEPOLICIES.EMPTY")}
</td>
</tr>
</table>
)
}

{ policies.length !== 0 && (
<div className="obj-container">
<table className="main-tbl">
<>
<thead>
<tr>
<th>
{t("EVENTS.EVENTS.DETAILS.LIFECYCLEPOLICIES.TABLE_TITLE")}
</th>
<th className="medium" />
</tr>
</thead>
<tbody>
{
policies.map((policy, key) => (
<tr key={key}>
<td>
{policy.title}
</td>

{/* link to 'Details' sub-Tab */}
<td>
<ButtonLikeAnchor
className="details-link"
onClick={() => openPolicyDetails(policy)}
>
{t("EVENTS.EVENTS.DETAILS.MEDIA.DETAILS")}
<LuChevronRight className="details-link-icon"/>
</ButtonLikeAnchor>
</td>
</tr>
))
}
</tbody>
</>
</table>
</div>
)}
</div>
</ModalContentTable>
);
};

export default EventDetailsLifeCyclePolicy;
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ const EventDetailsWorkflowSchedulingTab = ({
<div className="obj-container padded">
{hasCurrentAgentAccess() &&
isRoleWorkflowEdit &&
formik.values.configuration &&
!!workflowConfiguration &&
!!workflowConfiguration.workflowId && (
<div
Expand All @@ -197,7 +198,8 @@ const EventDetailsWorkflowSchedulingTab = ({
workflowId={
workflowConfiguration.workflowId
}
formik={formik}
configuration={formik.values.configuration}
configurationName={"configuration"}
/>
</div>
)}
Expand Down
Loading
Loading