This is an automated email from the ASF dual-hosted git repository. madhan pushed a commit to branch ranger-2.6 in repository https://gitbox.apache.org/repos/asf/ranger.git
commit d58577f2126c3eff934bde5f2ee9839a0c3020f2 Author: Guru Thejus Arveti <[email protected]> AuthorDate: Wed Sep 18 14:50:59 2024 +0530 RANGER-4936 : Feature for download and upload of individual policies Signed-off-by: Mugdha Varadkar <[email protected]> (cherry picked from commit 5f26ed47af039f7bf8fb40746d18c73a523554e4) --- .../src/views/PolicyListing/PolicyListing.jsx | 205 +++++++++++++++++++-- 1 file changed, 193 insertions(+), 12 deletions(-) diff --git a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyListing.jsx b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyListing.jsx index 3984a9a15..b52b6b12b 100644 --- a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyListing.jsx +++ b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/PolicyListing.jsx @@ -18,6 +18,7 @@ */ import React, { useState, useCallback, useRef, useEffect } from "react"; +import { omit, has } from "lodash"; import { Link, useParams, @@ -112,6 +113,70 @@ function PolicyListing(props) { let navigate = useNavigate(); let { serviceId, policyType } = useParams(); + const [showModal, setShowModal] = useState(false); + const [selectedFile, setSelectedFile] = useState(null); + const [statusMessage, setStatusMessage] = useState(""); + const [loading, setLoading] = useState(false); + + const handleFileSelection = (event) => { + const file = event.target.files[0]; + setSelectedFile(file); // Store selected file + setStatusMessage(""); + }; + + const importNewPolicy = () => { + setShowModal(true); // Show the modal when button is clicked + }; + + const handleCloseModal = () => { + setSelectedFile(null); // Reset the selected file + setStatusMessage(""); + setShowModal(false); + }; + + const handleUpload = () => { + if (!selectedFile) { + setStatusMessage("No file selected !"); + return; + } + + const reader = new FileReader(); + reader.onload = async (e) => { + try { + const json = JSON.parse(e.target.result); + + if (has("service")) { + json["service"] = serviceData.name; + } + + // Make API call with the processed JSON + setLoading(true); // Show loading spinner + await fetchApi({ + url: "plugins/policies", + method: "POST", + headers: { + "Content-Type": "application/json" // Set Content-Type header + }, + data: JSON.stringify(json) // Serialize the JSON + }); + + setLoading(false); // Hide loading spinner + setStatusMessage(""); + setShowModal(false); + setUpdateTable(moment.now()); + toast.success("Successfully imported policy json file !"); + } catch (error) { + setLoading(false); + setStatusMessage( + `Error parsing or processing JSON file: ${error.message}` + ); + console.log(error); + } + }; + + reader.readAsText(selectedFile); + }; + useEffect(() => { let searchFilterParam = {}; let searchParam = {}; @@ -154,6 +219,7 @@ function PolicyListing(props) { setPageLoader(false); localStorage.setItem("newDataAdded", state && state.showLastPage); }, [searchParams]); + useEffect(() => { if (localStorage.getItem("newDataAdded") == "true") { scrollToNewData(policyListingData); @@ -171,6 +237,65 @@ function PolicyListing(props) { return sortArr.map(({ desc }) => (desc ? "desc" : "asc")).join(","); }; + const downloadPolicy = async (id) => { + try { + const response = await fetchApi({ + url: `plugins/policies/${id}` + }); + + if (response.status !== 200) { + toast.error("Error downloading the policy !"); + return; + } + + let data = response.data || null; + + data = JSON.parse(JSON.stringify(data)); + + const fieldsToRemove = [ + "createdBy", + "createTime", + "guid", + "id", + "resourceSignature", + "updatedBy", + "updateTime", + "version" + ]; + + data = omit(data, fieldsToRemove); + + // Create a blob with the JSON data + const blob = new Blob([JSON.stringify(data, null, 2)], { + type: "application/json" + }); + const url = URL.createObjectURL(blob); + + // Create a link element and set its href to the blob URL + const a = document.createElement("a"); + a.href = url; + a.download = + data["serviceType"] + + "_" + + data["service"] + + "_" + + data["name"] + + "-" + + "policy_" + + id + + ".json" || "policy-data.json"; // Set the default filename for the download + document.body.appendChild(a); + a.click(); + + // Clean up + document.body.removeChild(a); + URL.revokeObjectURL(url); + } catch (error) { + toast.error("Error downloading the policy !"); + console.error("Error fetching data: ", error); + } + }; + const fetchPolicyInfo = useCallback( async ({ pageSize, pageIndex, sortBy, gotoPage }) => { setLoader(true); @@ -559,6 +684,18 @@ function PolicyListing(props) { </Button> {(isSystemAdmin() || isKeyAdmin() || isUser()) && ( <> + <Button + className="me-2" + variant="outline-dark" + size="sm" + title="Download" + onClick={() => downloadPolicy(original.id)} + data-name="downloadPolicy" + data-id={original.id} + data-cy={original.id} + > + <i className="fa-fw fa fa-download fa-fw fa fa-large"></i> + </Button> <Link className="btn btn-outline-dark btn-sm me-2" title="Edit" @@ -584,7 +721,8 @@ function PolicyListing(props) { </div> ); }, - disableSortBy: true + disableSortBy: true, + minWidth: 190 } ], [] @@ -752,7 +890,7 @@ function PolicyListing(props) { <BlockUi isUiBlock={blockUI} /> <div className="policy-listing"> <Row className="mb-3"> - <Col sm={10}> + <Col sm={9}> <div className="filter-icon-wrap"> <StructuredFilter key="policy-listing-search-filter" @@ -774,18 +912,31 @@ function PolicyListing(props) { /> </div> </Col> - <Col sm={2}> + <Col sm={3}> <div className="float-end mb-1"> {(isSystemAdmin() || isKeyAdmin() || isUser()) && ( - <Button - variant="primary" - size="sm" - onClick={addPolicy} - data-js="addNewPolicy" - data-cy="addNewPolicy" - > - Add New Policy - </Button> + <div> + <Button + variant="primary" + size="sm" + className="ms-1" + onClick={addPolicy} + data-js="addNewPolicy" + data-cy="addNewPolicy" + > + Add New Policy + </Button> + <Button + variant="primary" + size="sm" + className="ms-1" + onClick={importNewPolicy} + data-js="importNewPolicy" + data-cy="importNewPolicy" + > + Import New Policy + </Button> + </div> )} </div> </Col> @@ -805,6 +956,36 @@ function PolicyListing(props) { /> </div> + {/* Modal for file upload */} + {showModal && ( + <Modal show={showModal} onHide={handleCloseModal}> + <Modal.Header closeButton> + <Modal.Title>Upload JSON Policy</Modal.Title> + </Modal.Header> + <Modal.Body> + <input + type="file" + accept="application/json" + onChange={handleFileSelection} + /> + {loading && <p>Uploading...</p>} + {!loading && statusMessage && <p>{statusMessage}</p>} + </Modal.Body> + <Modal.Footer> + <Button + size="sm" + variant="secondary" + onClick={handleCloseModal} + > + Close + </Button> + <Button size="sm" variant="primary" onClick={handleUpload}> + Upload + </Button> + </Modal.Footer> + </Modal> + )} + <Modal show={deletePolicyModal.showPopup} onHide={toggleClose}> <Modal.Header closeButton> <span className="text-word-break">
