Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
98f596b
Display LifeCyclePolicies in UI (read-only)
Arnei Oct 10, 2024
5e5a850
Fix typescript complaint
Arnei Oct 14, 2024
c2d1c40
Add Create, Edit, Delete for LifeCycle Policies
Arnei Nov 4, 2024
2dd5a74
Merge branch 'main' into lifecycle-write
Arnei Dec 11, 2024
aae8eda
Merge branch 'main' into lifecycle-write
Arnei Dec 12, 2024
d6ae3d8
Merge branch 'main' into lifecycle-write
Arnei Jan 3, 2025
9fff939
Merge branch 'main' into lifecycle-write
Arnei Jan 15, 2025
f40dc0f
Merge branch 'main' into lifecycle-write
Arnei Jan 17, 2025
9caae0f
Merge branch 'main' into lifecycle-write
Arnei Jan 31, 2025
865e34c
Fix modal usage errors
Arnei Jan 31, 2025
691ffac
Merge branch 'main' into lifecycle-write
Arnei Feb 11, 2025
cd4becf
Merge branch 'main' into lifecycle-write
Arnei Feb 26, 2025
e876307
Merge branch 'main' into lifecycle-write
Arnei Feb 27, 2025
5e15fb4
Merge branch 'main' into lifecycle-write
Arnei Mar 10, 2025
05854ff
Merge branch 'main' into lifecycle-write
Arnei Mar 20, 2025
ef31bea
Merge branch 'main' into lifecycle-write
Arnei Apr 2, 2025
c31bda6
Merge branch 'main' into lifecycle-write
Arnei Apr 2, 2025
c3c8672
Fix various eslint complaints
Arnei Apr 2, 2025
565ded6
Merge branch 'main' into lifecycle-write
Arnei Apr 2, 2025
326bb92
Merge branch 'main' into lifecycle-write
Arnei Apr 2, 2025
8815f37
Fix undefined string
Arnei Apr 9, 2025
659fbc8
Merge branch 'main' into lifecycle-write
Arnei May 15, 2025
d293d54
Merge branch 'main' into lifecycle-write
Arnei May 15, 2025
92c93a7
Merge branch 'main' into lifecycle-write
Arnei May 19, 2025
e225495
Merge branch 'main' into lifecycle-write
Arnei May 26, 2025
e3307b1
Fix various issues from the latest merges
Arnei May 26, 2025
fc186fc
Merge branch 'develop' into lifecycle-write
Arnei Jun 16, 2025
bbc7599
Validate lifecycle filters
Arnei Jun 17, 2025
f723045
Merge branch 'develop' into lifecycle-write
Arnei Jun 18, 2025
592d63e
Merge branch 'develop' into lifecycle-write
Arnei Jun 18, 2025
effe789
Fix eslint const instead of let
Arnei Aug 5, 2025
b82cc67
Merge remote-tracking branch 'upstream/develop' into lifecycle-write
Arnei Aug 5, 2025
3294697
Fix eslint for lifecycle
Arnei Aug 6, 2025
77556c1
Merge remote-tracking branch 'upstream/develop' into lifecycle-write
Arnei Nov 5, 2025
56aac9d
Only show reasonable target types for filters
Arnei Nov 5, 2025
58b0e83
Use proper ui for lifecycle workflows
Arnei Nov 6, 2025
68d52cd
Add lifecycle policy tab to event details
Arnei Nov 10, 2025
6aef652
Fix ui for new targetFilters structure
Arnei Nov 18, 2025
17f43d1
Merge remote-tracking branch 'upstream/develop' into lifecycle-write
Arnei Dec 2, 2025
47c0358
Fix issue with ":" in metadata dropdowns
Arnei Dec 2, 2025
dc3f98d
Fix translation key
Arnei Dec 2, 2025
d645ff6
Fix missing delete button for lifecycle policy filters
Arnei Dec 2, 2025
5baebfa
Fix "isActive" not rendering in lifecycle create summary
Arnei Dec 2, 2025
444537e
Deactive focustrap for modal with react-js-cron
Arnei Dec 3, 2025
c2e6726
Fix lifecycle filter validation
Arnei Dec 3, 2025
e55e70e
Make sure filter values reset when changing lifecycle policy filter type
Arnei Dec 3, 2025
d0e5910
Fix LifeCycleDetails dropdowns not showing value on first render
Arnei Jan 19, 2026
61e9229
Remove superfluos logging statement
Arnei Jan 20, 2026
4662b77
Merge remote-tracking branch 'upstream/develop' into lifecycle-write
Arnei Jan 20, 2026
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
1,088 changes: 1,088 additions & 0 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.1",
"react-i18next": "^16.5.0",
"react-icons": "^5.5.0",
"react-js-cron": "^5.0.1",
"react-redux": "^9.2.0",
"react-router": "^7.9.5",
"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