From 7e5a8e9737ba15c4d12922d10360c9d467144599 Mon Sep 17 00:00:00 2001 From: Johan Marcusson Date: Thu, 4 Jul 2024 16:46:42 +0200 Subject: [PATCH 1/3] add modal that shows running config and generated config for a device --- public/components/DeviceList/DeviceList.js | 40 +++ .../components/DeviceList/ShowConfigModal.js | 235 ++++++++++++++++++ public/styles/main.css | 15 ++ 3 files changed, 290 insertions(+) create mode 100644 public/components/DeviceList/ShowConfigModal.js diff --git a/public/components/DeviceList/DeviceList.js b/public/components/DeviceList/DeviceList.js index ede5633..783ac5b 100644 --- a/public/components/DeviceList/DeviceList.js +++ b/public/components/DeviceList/DeviceList.js @@ -20,6 +20,7 @@ import { deleteData } from "../../utils/sendData"; import DeviceInfoBlock from "./DeviceInfoBlock"; import AddMgmtDomainModal from "./AddMgmtDomainModal"; import UpdateMgmtDomainModal from "./UpdateMgmtDomainModal"; +import ShowConfigModal from "./ShowConfigModal"; const io = require("socket.io-client"); @@ -58,6 +59,9 @@ class DeviceList extends React.Component { mgmtUpdateModalInput: {}, mgmtAddModalInput: {}, mgmtDomainsData: [], + showConfigModalOpen: false, + showConfigModalHostname: null, + showConfigModalState: null, }; discovered_device_ids = new Set(); @@ -840,6 +844,22 @@ class DeviceList extends React.Component { }); } + showConfigModalOpen(hostname, state) { + this.setState({ + showConfigModalOpen: true, + showConfigModalHostname: hostname, + showConfigModalState: state, + }); + } + + showConfigModalClose() { + this.setState({ + showConfigModalOpen: false, + showConfigModalHostname: null, + showConfigModalState: null, + }); + } + changeStateAction(device_id, state) { console.log(`Change state for device_id: ${device_id}`); const credentials = localStorage.getItem("token"); @@ -985,6 +1005,13 @@ class DeviceList extends React.Component { text="Make unmanaged" onClick={() => this.changeStateAction(device.id, "UNMANAGED")} />, + + this.showConfigModalOpen(device.hostname, device.state) + } + />, this.changeStateAction(device.id, "MANAGED")} />, + + this.showConfigModalOpen(device.hostname, device.state) + } + />, this.handleDeleteMgmtDomain(v)} onUpdate={(v) => this.handleUpdateMgmtDomains(v)} /> + this.showConfigModalClose()} + />
}); + getData( + `${process.env.API_URL}/api/v1.0/device/${hostname}/running_config`, + credentials, + ) + .then((resp) => { + setRunningConfig(resp.data); + }) + .catch((error) => { + // loading status + setErrors([error]); + setRunningConfig({ config: error.message }); + }); + } + + function getGeneratedConfig() { + setGeneratedConfig({ + generated_config: , + }); + const credentials = localStorage.getItem("token"); + getData( + `${process.env.API_URL}/api/v1.0/device/${hostname}/generate_config`, + credentials, + ) + .then((resp) => { + setGeneratedConfig(resp.data.config); + }) + .catch((error) => { + // loading status + setErrors([error]); + setRunningConfig({ generated_config: error.message }); + }); + } + + useEffect(() => { + if (hostname) { + if (state === "MANAGED") { + getRunningConfig(); + } + getGeneratedConfig(); + } else { + clearForm(); + } + }, [hostname, state]); + + return ( + + Show config for {hostname} + + + + + +

Device Running Config

+
+ +

NMS Generated Config

+
+
+ + + + + navigator.clipboard.writeText(runningConfig.config) + } + icon="copy" + size="small" + /> + } + position="bottom right" + /> + getRunningConfig()} + icon="refresh" + size="small" + /> + } + position="bottom right" + /> + + + {typeof runningConfig.config === "object" ? ( + runningConfig.config + ) : ( +
{runningConfig.config}
+ )} +
+
+ + + + navigator.clipboard.writeText( + generatedConfig.generated_config, + ) + } + icon="copy" + size="small" + /> + } + position="bottom center" + /> + getGeneratedConfig()} + icon="refresh" + size="small" + /> + } + position="bottom center" + /> + + navigator.clipboard.writeText( + JSON.stringify( + generatedConfig.available_variables, + null, + 2, + ), + ) + } + icon="code" + size="small" + /> + } + position="bottom center" + /> + + + + {typeof generatedConfig.generated_config === "object" ? ( +
{generatedConfig.generated_config}
+ ) : ( +
+                      {generatedConfig.generated_config}
+                    
+ )} +
+
+
+ + +
    + {errors.map((err, index) => ( + // eslint-disable-next-line react/no-array-index-key +
  • {err.message}
  • + ))} +
+
+
+
+
+
+ + + +
+ ); +} + +ShowConfigModal.propTypes = { + hostname: PropTypes.string, + state: PropTypes.string, + isOpen: PropTypes.bool.isRequired, + closeAction: PropTypes.func.isRequired, +}; + +ShowConfigModal.defaultProps = { + hostname: null, + state: "MANAGED", +}; + +export default ShowConfigModal; diff --git a/public/styles/main.css b/public/styles/main.css index ad9a637..e3cfcd6 100644 --- a/public/styles/main.css +++ b/public/styles/main.css @@ -364,6 +364,10 @@ progress::-moz-progress-bar { font-size: 80%; } +.fullconfig { + overflow-x: scroll; +} + /* log output styles */ .logoutput { overflow-y: scroll; @@ -378,3 +382,14 @@ progress::-moz-progress-bar { .job_ticket_ref { width: 15em; } + +/* Workaround for an unfixed bug with Loaders not showing up properly in Modals + * https://github.com/Semantic-Org/Semantic-UI-React/issues/3133 + */ +.ui.dimmer .ui.modalloader.loader:before { + border-color: rgba(0, 0, 0, 0.1); +} + +.ui.dimmer .ui.modalloader.loader:after { + border-color: #767676 transparent transparent; +} From 05990c3202f2f2be754da087d622a3ff1bde62d7 Mon Sep 17 00:00:00 2001 From: Johan Marcusson Date: Fri, 5 Jul 2024 11:10:30 +0200 Subject: [PATCH 2/3] have two dropdowns with options for what config to show on left and right column --- .../components/DeviceList/ShowConfigModal.js | 250 ++++++++++-------- 1 file changed, 142 insertions(+), 108 deletions(-) diff --git a/public/components/DeviceList/ShowConfigModal.js b/public/components/DeviceList/ShowConfigModal.js index a10b5d4..d96c516 100644 --- a/public/components/DeviceList/ShowConfigModal.js +++ b/public/components/DeviceList/ShowConfigModal.js @@ -14,6 +14,10 @@ import { GridRow, GridColumn, Segment, + Dropdown, + DropdownDivider, + DropdownHeader, + DropdownItem, } from "semantic-ui-react"; import PropTypes from "prop-types"; import { getData } from "../../utils/getData"; @@ -24,6 +28,10 @@ function ShowConfigModal({ hostname, state, isOpen, closeAction }) { generated_config: "", }); const [errors, setErrors] = useState([]); + const [columnValues, setColumnValues] = useState({ + left: "running_config", + right: "generate_config", + }); function clearForm() { setErrors([]); @@ -81,122 +89,148 @@ function ShowConfigModal({ hostname, state, isOpen, closeAction }) { } else { clearForm(); } - }, [hostname, state]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hostname]); + + const leftColumnOptions = [ + , + , + , + , + , + , + ]; + + const rightColumnOptions = [ + ...leftColumnOptions, + , + , + ]; + + function updateColumn(e, data) { + console.log(data); + const colName = data.name; + const val = data.value; + const column = {}; + if (colName === "left") { + column.left = val; + } else { + column.right = val; + } + if (val !== columnValues[colName]) { + setColumnValues((currentValues) => ({ + ...currentValues, + [colName]: val, + })); + } + } + + const columnHeaders = { + running_config: "Device running config", + generate_config: "NMS generated config", + available_variables: "Template variables", + }; + + const columnRefreshFunctions = { + running_config: getRunningConfig, + generate_config: getGeneratedConfig, + available_variables: getGeneratedConfig, + }; + + const columnContents = Object.entries(columnValues).map( + ([colName, colValue]) => { + const headerText = columnHeaders[colValue] || colValue; + let config = ""; + if (colValue === "running_config") { + config = runningConfig.config; + } else if (colValue === "generate_config") { + config = generatedConfig.generated_config; + } else if (colValue === "available_variables") { + config = JSON.stringify(generatedConfig.available_variables, null, 2); + } else { + return null; + } + return ( + +

{headerText}

+ + navigator.clipboard.writeText(config)} + icon="copy" + size="small" + /> + } + position="bottom right" + /> + columnRefreshFunctions[colValue]()} + icon="refresh" + size="small" + /> + } + position="bottom right" + /> + + + {typeof config === "object" ? ( + config + ) : ( +
{config}
+ )} +
+
+ ); + }, + ); return ( Show config for {hostname} + + + + - -

Device Running Config

-
- -

NMS Generated Config

-
-
- - - - - navigator.clipboard.writeText(runningConfig.config) - } - icon="copy" - size="small" - /> - } - position="bottom right" - /> - getRunningConfig()} - icon="refresh" - size="small" - /> - } - position="bottom right" - /> - - - {typeof runningConfig.config === "object" ? ( - runningConfig.config - ) : ( -
{runningConfig.config}
- )} -
-
- - - - navigator.clipboard.writeText( - generatedConfig.generated_config, - ) - } - icon="copy" - size="small" - /> - } - position="bottom center" - /> - getGeneratedConfig()} - icon="refresh" - size="small" - /> - } - position="bottom center" - /> - - navigator.clipboard.writeText( - JSON.stringify( - generatedConfig.available_variables, - null, - 2, - ), - ) - } - icon="code" - size="small" - /> - } - position="bottom center" - /> - - - - {typeof generatedConfig.generated_config === "object" ? ( -
{generatedConfig.generated_config}
- ) : ( -
-                      {generatedConfig.generated_config}
-                    
- )} -
-
+ {columnContents.filter((contents) => contents !== null)}
From 261208d7b90d7872753ffb16702816f4dc8aa404 Mon Sep 17 00:00:00 2001 From: Johan Marcusson Date: Fri, 5 Jul 2024 14:28:50 +0200 Subject: [PATCH 3/3] option to get last syncto job generated configs --- .../components/DeviceList/ShowConfigModal.js | 114 +++++++++++++++--- 1 file changed, 96 insertions(+), 18 deletions(-) diff --git a/public/components/DeviceList/ShowConfigModal.js b/public/components/DeviceList/ShowConfigModal.js index d96c516..1ba9aeb 100644 --- a/public/components/DeviceList/ShowConfigModal.js +++ b/public/components/DeviceList/ShowConfigModal.js @@ -27,6 +27,12 @@ function ShowConfigModal({ hostname, state, isOpen, closeAction }) { const [generatedConfig, setGeneratedConfig] = useState({ generated_config: "", }); + const [previousConfig, setPreviousConfig] = useState({ + 0: { config: "" }, + 1: { config: "" }, + 2: { config: "" }, + 3: { config: "" }, + }); const [errors, setErrors] = useState([]); const [columnValues, setColumnValues] = useState({ left: "running_config", @@ -76,7 +82,33 @@ function ShowConfigModal({ hostname, state, isOpen, closeAction }) { .catch((error) => { // loading status setErrors([error]); - setRunningConfig({ generated_config: error.message }); + setGeneratedConfig({ generated_config: error.message }); + }); + } + + function getPreviousConfig(number) { + setPreviousConfig((currentValues) => ({ + ...currentValues, + [number]: { config: }, + })); + const credentials = localStorage.getItem("token"); + getData( + `${process.env.API_URL}/api/v1.0/device/${hostname}/previous_config?previous=${number}`, + credentials, + ) + .then((resp) => { + setPreviousConfig((currentValues) => ({ + ...currentValues, + [number]: resp.data, + })); + }) + .catch((error) => { + // loading status + setErrors([error]); + setPreviousConfig((currentValues) => ({ + ...currentValues, + [number]: { config: error.message }, + })); }); } @@ -104,7 +136,27 @@ function ShowConfigModal({ hostname, state, isOpen, closeAction }) { , + , + , + , + , , - , + , ]; function updateColumn(e, data) { - console.log(data); const colName = data.name; const val = data.value; const column = {}; @@ -129,6 +180,11 @@ function ShowConfigModal({ hostname, state, isOpen, closeAction }) { } else { column.right = val; } + // if val starts with previous_, then we need to get the previous value + if (val.startsWith("previous_")) { + const number = parseInt(val.replace("previous_", ""), 10); + getPreviousConfig(number); + } if (val !== columnValues[colName]) { setColumnValues((currentValues) => ({ ...currentValues, @@ -151,19 +207,25 @@ function ShowConfigModal({ hostname, state, isOpen, closeAction }) { const columnContents = Object.entries(columnValues).map( ([colName, colValue]) => { - const headerText = columnHeaders[colValue] || colValue; + let headerText = columnHeaders[colValue] || colValue; let config = ""; + let jobId = 0; if (colValue === "running_config") { config = runningConfig.config; } else if (colValue === "generate_config") { config = generatedConfig.generated_config; + } else if (colValue.startsWith("previous_")) { + const number = parseInt(colValue.replace("previous_", ""), 10); + config = previousConfig[number].config; + jobId = previousConfig[number].job_id; + headerText = `Previous ${number} job config`; } else if (colValue === "available_variables") { config = JSON.stringify(generatedConfig.available_variables, null, 2); } else { return null; } return ( - +

{headerText}

- columnRefreshFunctions[colValue]()} - icon="refresh" - size="small" - /> - } - position="bottom right" - /> + {colValue.startsWith("previous_") && ( + navigator.clipboard.writeText(jobId)} + icon="numbered list" + size="small" + /> + } + position="bottom right" + /> + )} + {colValue in columnRefreshFunctions && ( + columnRefreshFunctions[colValue]()} + icon="refresh" + size="small" + /> + } + position="bottom right" + /> + )} {typeof config === "object" ? (