-
Notifications
You must be signed in to change notification settings - Fork 66
Multi-Device Collapsing Config Diff #851
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
rifen
wants to merge
6
commits into
nautobot:develop
Choose a base branch
from
rifen:gc-825-multi-diff
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
9036dd6
Initial commit - post testing
rifen 3ab5965
Remove old TODO
rifen 9ee5405
Used icons and toolips to scale config plan information better
rifen 5dccee3
CSS for icons, change matching to words and lines, added message for …
rifen c881b3a
Add message for manual change and remove counts
rifen 97be0a2
Merge branch 'nautobot:develop' into gc-825-multi-diff
rifen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Added Config Plan Confirmation screen with a Backup -> Intended views |
306 changes: 306 additions & 0 deletions
306
nautobot_golden_config/templates/nautobot_golden_config/configplan_confirmation.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,306 @@ | ||
| {% extends 'generic/object_list.html' %} | ||
| {% load buttons %} | ||
| {% load static %} | ||
| {% load helpers %} | ||
|
|
||
| {% block breadcrumbs %} | ||
| <li><a href="{% url 'plugins:nautobot_golden_config:configplan_list' %}">Config Plans</a></li> | ||
| {% endblock %} | ||
|
|
||
| {% block content %} | ||
| <h1>{% block title %}Config Plan Confirmation{% endblock %}</h1> | ||
|
|
||
| <link rel="stylesheet" type="text/css" href="{% static 'nautobot_golden_config/diff2html-3.4.43/diff2html.min.css' %}"/> | ||
| <style> | ||
| .diff-render { | ||
| pointer-events: none; | ||
| } | ||
| .diff-counts { | ||
| padding: 1px; | ||
| } | ||
| .diff-adds { | ||
| color: green; | ||
| } | ||
| .diff-removes { | ||
| color: red; | ||
| } | ||
| .config-plan-tooltip { | ||
| display: inline-block; | ||
| margin-left: 2px; | ||
| margin-top: 2px; | ||
| } | ||
| .glyphicon-file { | ||
| color:#0938e1; | ||
| font-size: 1.2em; | ||
| } | ||
| .glyphicon-ok { | ||
| color: green; | ||
| } | ||
| .glyphicon-remove { | ||
| color: red; | ||
| } | ||
| .rotated { | ||
| transform: rotate(180deg); | ||
| } | ||
| .icon { | ||
| transition: transform 0.3s ease; | ||
| float: right; | ||
| } | ||
| .collapsable-heading:hover { | ||
| cursor: pointer; | ||
| } | ||
| .d2h-tag { | ||
| display: none; | ||
| } | ||
| .d2h-file-collapse { | ||
| display: none; | ||
| } | ||
| </style> | ||
| <noscript> | ||
| <style> | ||
| .icon { | ||
| display: none; | ||
| } | ||
| </style> | ||
| </noscript> | ||
| <div class="card-deck"> | ||
| {% for device in selected_devices %} | ||
| {% with device_id=device.id %} | ||
| <div class="panel panel-default" style="width:100%;"> | ||
| <div id="toggle-device-{{ device_id }}" class="panel-heading collapsable-heading card-header" type="button" data-toggle="collapse" data-target="#device-{{ device_id }}" aria-expanded="false" aria-controls="device-{{ device_id }}"> | ||
| <h5 class="card-title"> | ||
| {{ device.name }} | ||
| <span id="diff-counts-{{ device_id }}" class="diff-counts"> | ||
| | | ||
| <span id="diff-removes" class="diff-removes"> | ||
| <i class="glyphicon glyphicon-minus"></i> | ||
| <span id="diff-count-removes-{{ device_id }}" class="diff-count-removes"> | ||
| </span> | ||
| </span> | ||
| <span id="diff-adds" class="diff-adds"> | ||
| <i class="glyphicon glyphicon-plus"></i> | ||
| <span id="diff-count-adds-{{ device_id }}" class="diff-count-adds"> | ||
| </span> | ||
| </span> | ||
| </span> | ||
| <span id="config-plan-statuses" class="config-plan-statuses"> | ||
| <span id="config-plan-status" class="config-plan-status"> | ||
| | Config Plans: | ||
| <!-- Display the config plan with the matching device_id's status --> | ||
| {% for config_plan in config_plans %} | ||
| {% if config_plan.device_id == device_id %} | ||
| <div class="config-plan-tooltip" data-toggle="tooltip" data-html="true" data-config-plan-type="{{config_plan.plan_type}}" title=" | ||
| <b>Status:</b> | ||
| {% if config_plan.status|stringformat:'s' == 'Approved' %} | ||
| <i id='status-icon-device-{{ device_id }}' class='glyphicon glyphicon-ok'></i>{{ config_plan.status }} | ||
| {% else %} | ||
| <i id='status-icon-device-{{ device_id }}' class='glyphicon glyphicon-remove red'></i>{{ config_plan.status }} | ||
| {% endif %} | ||
| <br> | ||
| <b>Last Updated:</b> {{ config_plan.last_updated }} | ||
| <br> | ||
| <b>Type:</b> {{ config_plan.plan_type|capfirst }} | ||
| "> | ||
| <i class="glyphicon glyphicon-file"></i> | ||
| </div> | ||
| {% endif %} | ||
| {% endfor %} | ||
| </span> | ||
| </span> | ||
| <span id="icon" class="icon"> | ||
| <i id="collapse-icon-device-{{ device_id }}" class="glyphicon glyphicon-chevron-up" style="cursor"></i> | ||
| </span> | ||
| </span> | ||
| </h5> | ||
| <div class="collapse collapsible-div" id="device-{{ device_id }}"> | ||
| <div class="card-text" id="diff-container-{{ device_id }}"> | ||
| <div id="diffoutput-{{ device_id }}" data-compliance-config="{{ device.goldenconfig.compliance_config|escape }}" last_backup="{{ device.goldenconfig.backup_last_success_date}}" last_intended="{{ device.goldenconfig.intended_last_success_date}}"></div> | ||
| <div id="diffrender-{{ device_id }}" class="diffrender"></div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| {% endwith %} | ||
| {% endfor %} | ||
| </div> | ||
| <!-- Deploy Button --> | ||
| {% if perms.extras.run_job %} | ||
| {% include "nautobot_golden_config/job_result_modal.html" with modal_title="Deploy Config Plans" %} | ||
| <button id="startJob" type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#modalPopup" data-config-plan-ids="{{ config_plan_ids }}"> | ||
| <span class="mdi mdi-upload-multiple" aria-hidden="true"></span> Deploy Plans | ||
| </button> | ||
| {% endif %} | ||
| {% endblock %} | ||
| {% block javascript %} | ||
| {{ block.super }} | ||
| <script src="{% static 'run_job.js' %}"></script> | ||
| <script src="{% static 'nautobot_golden_config/diff2html-3.4.43/diff2html.min.js' %}"></script> | ||
| <script> | ||
| // Logic for device cards and diff display | ||
| document.addEventListener("DOMContentLoaded", function() { | ||
| // Function to count the number of added and removed lines in a diff | ||
| // Currently only counts lines starting with + or - | ||
| function countDiffs(diffString) { | ||
| var diffLines = diffString.split("\n"); | ||
| var addedLines = diffLines.filter(function(line) { | ||
| return line.startsWith("+"); | ||
| }); | ||
| var removedLines = diffLines.filter(function(line) { | ||
| return line.startsWith("-"); | ||
| }); | ||
|
|
||
| return { | ||
| added: addedLines.length, | ||
| removed: removedLines.length | ||
| }; | ||
| } | ||
|
|
||
| // Select all elements with the collapsable-heading class | ||
| var collapsableHeadings = document.querySelectorAll(".collapsable-heading"); | ||
|
|
||
| // Loop through each collapsable heading and add an event listener that rotates the icon | ||
| collapsableHeadings.forEach((heading) => { | ||
| heading.addEventListener("click", function() { | ||
| var device_id = heading.id.replace("toggle-device-", ""); | ||
| var collapseIcon = document.getElementById("collapse-icon-device-" + device_id); | ||
| if (collapseIcon) { | ||
| collapseIcon.classList.toggle("rotated"); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| // Select all elements that start with diffoutput- in the ID | ||
| var diffContainers = document.querySelectorAll("[id^='diffoutput-']"); | ||
|
|
||
| // Loop through each container ultimately rendering each diff | ||
| diffContainers.forEach( | ||
| function(container) { | ||
|
|
||
| // Get the data attributes from the container | ||
| var complianceConfig = container.getAttribute("data-compliance-config"); | ||
| var lastBackup = container.getAttribute("last_backup"); | ||
| var lastIntended = container.getAttribute("last_intended"); | ||
|
|
||
| // If complianceConfig is not null, then render the diff | ||
| if (complianceConfig) { | ||
| // Split the compliance config into lines | ||
| var complianceConfigLines = complianceConfig.split("\n"); | ||
|
|
||
| // Find the start of the compliance config indicated by @@ and join the lines | ||
| var complianceConfigStartIndex = complianceConfigLines.findIndex(function(line) { | ||
| return line.startsWith("@@"); | ||
| }); | ||
| var complianceConfigJoinedLines = complianceConfigLines.slice(complianceConfigStartIndex).join("\n"); | ||
|
|
||
| var diffCounts = countDiffs(complianceConfigJoinedLines); | ||
|
|
||
| // Display the diff count | ||
| var diffCountAdds = document.getElementById("diff-count-adds-" + container.id.replace("diffoutput-", "")); | ||
| var diffCountRemoves = document.getElementById("diff-count-removes-" + container.id.replace("diffoutput-", "")); | ||
| diffCountAdds.innerHTML = diffCounts.added; | ||
| diffCountRemoves.innerHTML = diffCounts.removed; | ||
|
|
||
| // If the sum of added or removed is greater then 1000, then hide the diff and display a message | ||
| // Tested 30k, 20k, 10k, 5k and 3k lines, 3k kept things functional, but 1k was the best for performance | ||
| // The browser will hang if the diff is too large | ||
| if (diffCounts.added > 1000 || diffCounts.removed > 1000) { | ||
| var diffContainer = document.getElementById("diff-container-" + container.id.replace("diffoutput-", "")); | ||
| diffContainer.innerHTML = "<p>Diff too large to display. This feature supports only 1000 changed lines.</p>"; | ||
| return; | ||
| } | ||
| // Create the input string for the diff2html library | ||
| var str_input = `--- Backup ${lastBackup}\n+++ Intended ${lastIntended}\n` + complianceConfigJoinedLines + "\n"; | ||
|
|
||
| // Configuration for the diff2html library | ||
| var configuration = { | ||
| drawFileList: false, | ||
| matching: "words", | ||
| outputFormat: "side-by-side", | ||
| colorScheme: "auto" | ||
| }; | ||
|
|
||
| // Get the target element and render the diff | ||
| var targetId = container.id.replace("diffoutput-", "diffrender-"); | ||
| var targetElement = document.getElementById(targetId); | ||
| var diffContent = Diff2Html.html( | ||
| str_input, | ||
| configuration, | ||
| ); | ||
| targetElement.innerHTML = diffContent; | ||
|
|
||
| // Stop the click event from propagating when clicking on the diffrender element | ||
| var diffrenderElements = document.querySelectorAll('.diffrender'); | ||
| diffrenderElements.forEach(function(element) { | ||
| element.addEventListener('click', function(event) { | ||
| event.stopPropagation(); | ||
| }); | ||
| }); | ||
|
|
||
| } else { | ||
| // Get config_plan type | ||
| var configPlanTooltip = document.querySelector(`#toggle-device-${container.id.replace("diffoutput-", "")} .config-plan-tooltip`); | ||
| var configPlanType = configPlanTooltip ? configPlanTooltip.getAttribute("data-config-plan-type") : null; | ||
|
|
||
| if (configPlanType === "manual") { | ||
| // If config_plan.plan_type is manual, then display a message | ||
| var diffContainer = document.getElementById("diff-container-" + container.id.replace("diffoutput-", "")); | ||
| diffContainer.innerHTML = "<p>No diff for manual changes.</p>"; | ||
| // Remove diff-counts-{{ device_id }} element from the DOM | ||
| var diffCounts = document.getElementById("diff-counts-" + container.id.replace("diffoutput-", "")); | ||
| diffCounts.remove(); | ||
|
|
||
| } else { | ||
| // If complianceConfig is null, then set diff-counts to 0 | ||
| var diffCountAdds = document.getElementById("diff-count-adds-" + container.id.replace("diffoutput-", "")); | ||
| var diffCountRemoves = document.getElementById("diff-count-removes-" + container.id.replace("diffoutput-", "")); | ||
| diffCountAdds.innerHTML = 0; | ||
| diffCountRemoves.innerHTML = 0; | ||
| // If config_plan.plan_type is unknown, then display a default message | ||
| var diffContainer = document.getElementById("diff-container-" + container.id.replace("diffoutput-", "")); | ||
| diffContainer.innerHTML = "<p>No compliance config found for this device. Run Compliance for this device to see a diff.</p>"; | ||
| } | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
|
|
||
| // CSRF token for AJAX requests | ||
| var nautobot_csrf_token = "{{ csrf_token }}"; | ||
|
|
||
| // Add event listener Deploy Selected Plans button | ||
| // This will prompt the user to confirm the deployment | ||
| document.addEventListener("DOMContentLoaded", function() { | ||
| var startJobButton = document.getElementById("startJob"); | ||
| startJobButton.addEventListener("click", function(event) { | ||
| var userConfirmed = confirm("Warning! This will deploy configurations to the devices you have selected, are you sure you want to deploy?"); | ||
| if (!userConfirmed) { | ||
| // If user clicked "Cancel", stop the modal from showing | ||
| event.preventDefault(); | ||
| event.stopPropagation(); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| // Add click event listener to Deploy Plans button to start the job | ||
| // This will start the job to deploy the selected plans | ||
| document.getElementById("startJob").onclick = function() { | ||
| var configPlanIds = document.getElementById("startJob").getAttribute("data-config-plan-ids"); | ||
|
|
||
| // Convert configPlanIds to Data structure needed for startJob function in run_job.js | ||
| var jobData = { | ||
| "commit": true, | ||
| "data": { | ||
| "config_plan": JSON.parse(configPlanIds), | ||
| "debug": false, | ||
| }, | ||
| }; | ||
|
|
||
| // Send the jobs to the startJob function in run_job.js | ||
| startJob("Deploy Config Plans", jobData); | ||
| }; | ||
|
|
||
| $(document).ready(function(){ | ||
| $('[data-toggle="tooltip"]').tooltip(); | ||
| }); | ||
| </script> | ||
| {% endblock %} | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.