Skip to content

Commit

Permalink
add modal that shows running config and generated config for a device
Browse files Browse the repository at this point in the history
  • Loading branch information
indy-independence committed Jul 4, 2024
1 parent 29975c8 commit 6d5253c
Show file tree
Hide file tree
Showing 3 changed files with 290 additions and 0 deletions.
40 changes: 40 additions & 0 deletions public/components/DeviceList/DeviceList.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -58,6 +59,9 @@ class DeviceList extends React.Component {
mgmtUpdateModalInput: {},
mgmtAddModalInput: {},
mgmtDomainsData: [],
showConfigModalOpen: false,
showConfigModalHostname: null,
showConfigModalState: null,
};

discovered_device_ids = new Set();
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -985,6 +1005,13 @@ class DeviceList extends React.Component {
text="Make unmanaged"
onClick={() => this.changeStateAction(device.id, "UNMANAGED")}
/>,
<Dropdown.Item
key="showconfig"
text="Show configuration"
onClick={() =>
this.showConfigModalOpen(device.hostname, device.state)
}
/>,
<Dropdown.Item
key="delete"
text="Delete device..."
Expand Down Expand Up @@ -1019,6 +1046,13 @@ class DeviceList extends React.Component {
text="Make managed"
onClick={() => this.changeStateAction(device.id, "MANAGED")}
/>,
<Dropdown.Item
key="showconfig"
text="Show configuration"
onClick={() =>
this.showConfigModalOpen(device.hostname, device.state)
}
/>,
<Dropdown.Item
key="delete"
text="Delete device..."
Expand Down Expand Up @@ -1351,6 +1385,12 @@ class DeviceList extends React.Component {
onDelete={(v) => this.handleDeleteMgmtDomain(v)}
onUpdate={(v) => this.handleUpdateMgmtDomains(v)}
/>
<ShowConfigModal
hostname={this.state.showConfigModalHostname}
state={this.state.showConfigModalState}
isOpen={this.state.showConfigModalOpen}
closeAction={() => this.showConfigModalClose()}
/>
<div className="table_options">
<Popup
on="click"
Expand Down
235 changes: 235 additions & 0 deletions public/components/DeviceList/ShowConfigModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import React, { useEffect, useState } from "react";
import {
Button,
ButtonGroup,
Loader,
Popup,
Icon,
Modal,
ModalActions,
ModalContent,
ModalDescription,
ModalHeader,
Grid,
GridRow,
GridColumn,
Segment,
} from "semantic-ui-react";
import PropTypes from "prop-types";
import { getData } from "../../utils/getData";

function ShowConfigModal({ hostname, state, isOpen, closeAction }) {
const [runningConfig, setRunningConfig] = useState({ config: "" });
const [generatedConfig, setGeneratedConfig] = useState({
generated_config: "",
});
const [errors, setErrors] = useState([]);

function clearForm() {
setErrors([]);
setRunningConfig({ config: "" });
setGeneratedConfig({ generated_config: "" });
}

function handleCancel() {
clearForm();
closeAction();
}

function getRunningConfig() {
const credentials = localStorage.getItem("token");
setRunningConfig({ config: <Loader className="modalloader" /> });
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: <Loader className="modalloader" />,
});
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 (
<Modal open={isOpen} onClose={handleCancel} size="fullscreen">
<ModalHeader>Show config for {hostname}</ModalHeader>
<ModalContent>
<ModalDescription>
<Grid columns="equal">
<GridRow>
<GridColumn>
<h1>Device Running Config</h1>
</GridColumn>
<GridColumn>
<h1>NMS Generated Config</h1>
</GridColumn>
</GridRow>
<GridRow>
<GridColumn>
<ButtonGroup>
<Popup
content="Copy running config"
floated="right"
trigger={
<Button
onClick={() =>
navigator.clipboard.writeText(runningConfig.config)
}
icon="copy"
size="small"
/>
}
position="bottom right"
/>
<Popup
content="Refresh running config"
floated="right"
trigger={
<Button
onClick={() => getRunningConfig()}
icon="refresh"
size="small"
/>
}
position="bottom right"
/>
</ButtonGroup>
<Segment>
{typeof runningConfig.config === "object" ? (
runningConfig.config
) : (
<pre className="fullconfig">{runningConfig.config}</pre>
)}
</Segment>
</GridColumn>
<GridColumn>
<ButtonGroup>
<Popup
content="Copy NMS generated config"
floated="right"
trigger={
<Button
onClick={() =>
navigator.clipboard.writeText(
generatedConfig.generated_config,
)
}
icon="copy"
size="small"
/>
}
position="bottom center"
/>
<Popup
content="Refresh NMS generated config"
floated="right"
trigger={
<Button
onClick={() => getGeneratedConfig()}
icon="refresh"
size="small"
/>
}
position="bottom center"
/>
<Popup
content="Copy available variables for templates"
floated="right"
trigger={
<Button
onClick={() =>
navigator.clipboard.writeText(
JSON.stringify(
generatedConfig.available_variables,
null,
2,
),
)
}
icon="code"
size="small"
/>
}
position="bottom center"
/>
</ButtonGroup>
<Segment>
<Loader />
{typeof generatedConfig.generated_config === "object" ? (
<div>{generatedConfig.generated_config}</div>
) : (
<pre className="fullconfig">
{generatedConfig.generated_config}
</pre>
)}
</Segment>
</GridColumn>
</GridRow>
<GridRow>
<GridColumn>
<ul id="error_list" style={{ color: "red" }}>
{errors.map((err, index) => (
// eslint-disable-next-line react/no-array-index-key
<li key={index}>{err.message}</li>
))}
</ul>
</GridColumn>
</GridRow>
</Grid>
</ModalDescription>
</ModalContent>
<ModalActions>
<Button color="black" onClick={handleCancel}>
Close <Icon name="cancel" />
</Button>
</ModalActions>
</Modal>
);
}

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;
15 changes: 15 additions & 0 deletions public/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,10 @@ progress::-moz-progress-bar {
font-size: 80%;
}

.fullconfig {
overflow-x: scroll;
}

/* log output styles */
.logoutput {
overflow-y: scroll;
Expand All @@ -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;
}

0 comments on commit 6d5253c

Please sign in to comment.