diff --git a/.flake8 b/.flake8
index aaa63b60..e3ba27d5 100644
--- a/.flake8
+++ b/.flake8
@@ -1,4 +1,4 @@
[flake8]
# E501: Line length is enforced by Black, so flake8 doesn't need to check it
# W503: Black disagrees with this rule, as does PEP 8; Black wins
-ignore = E501, W503, F811, F401, F405, E203
+ignore = E501, W503
diff --git a/development/nautobot_config.py b/development/nautobot_config.py
index c858f2d1..e30defc8 100644
--- a/development/nautobot_config.py
+++ b/development/nautobot_config.py
@@ -4,11 +4,9 @@
import sys
from django.utils.module_loading import import_string
-
from nautobot.core.settings import * # noqa: F403
from nautobot.core.settings_funcs import is_truthy, parse_redis_connection
-
#
# Misc. settings
#
@@ -200,7 +198,7 @@
# Modify django_jinja Environment for test cases
django_jinja_config = None
-for template in TEMPLATES:
+for template in TEMPLATES: # noqa: F405
if template["BACKEND"].startswith("django_jinja"):
django_jinja_config = template
@@ -213,4 +211,4 @@
jinja_options["undefined"] = "jinja2.StrictUndefined"
# Import filter function to have it register filter with django_jinja
-from nautobot_golden_config.tests import jinja_filters # noqa: E402
+from nautobot_golden_config.tests import jinja_filters # noqa: E402, F401
diff --git a/docs/dev/dev_adr.md b/docs/dev/dev_adr.md
index 88f6f593..a8591a0b 100644
--- a/docs/dev/dev_adr.md
+++ b/docs/dev/dev_adr.md
@@ -143,3 +143,17 @@ This function performs an additional permission validation, to check if the requ
Over time device(s) platform may change; whether this is a device refresh or full replacement. A Django `post_save` signal is used on the `ConfigCompliance` model and provides a reliable and efficient way to manage configuration compliance objects. This signal deletes any `ConfigCompliance` objects that don't match the current platform. This decision was made to avoid compliance reporting inconsistencies that can arise when outdated or irrelevant objects remain in the database which were generated with the previous platform.
This has a computational impact when updating a Device object's platform. This is similar to the computational impact of an SQL `cascade` option on a delete. This is largely unavoidable and should be limited in impact, such that it will only be the removal of the number of `ConfigCompliance` objects, which is no bigger than the number of `Config Features`, which is generally intended to be a small amount.
+
+### Configuration Deployment and Remediation
+
+Configuration remediation and deployments of any of the attributes based on the configuration compliance object are calculated based on the last run of the `ConfigCompliance` job. After a configuration deployment to fix any of these attributes (remediation, intended, missing) a new `ConfigCompliance` job must be run before all the compliance results will be updated.
+
+
+### Manual ConfigPlans
+
+When generating a manual `ConfigPlan` the Jinja2 template render has access to Django ORM methods like `.all()`, this also means that methods like `.delete()` can be called, the `render_template` functionality used by Golden Config inherits a Jinja2 Sandbox exception that will block unsafe calls. Golden Config will simply re-raise the exception `jinja2.exceptions.SecurityError: > is not safely callable`.
+
+
+### Hidden Jobs and JobButtons
+
+The configuration deployment and plans features of Golden Config come packaged with Jobs and JobButtons to execute the functionality. In order to to provide a repeatable and consistent behavior these Jobs and JobButtons are designed to only be executed via specialized views. They're not intended to be executed manually from the Jobs/JobButtons menus.
diff --git a/docs/user/app_getting_started.md b/docs/user/app_getting_started.md
index cdbb71e5..b64154af 100644
--- a/docs/user/app_getting_started.md
+++ b/docs/user/app_getting_started.md
@@ -126,11 +126,50 @@ Compliance requires Backups and Intended Configurations in order to be executed.
# Config Plans
-Coming soon
+Follow the steps below to get up and running for the configuration plans element of the plugin.
+
+1. Enable the feature in the `PLUGIN_SETTINGS`. The configuration should have `"enable_plan": True` set in the `PLUGINS_CONFIG` dictionary for `nautobot_golden_config`.
+2. Follow the steps in [Compliance](#compliance).
+ - Compliance is necessary if ConfigPlans will be generated utilizing any of the attributes provided by a Compliance object.
+ - This step may be skipped if only `manual` ConfigPlans are going to be generated.
+3. Create a ConfigPlan
+
+ 1. Navigate to `Golden Config -> Config Plans`
+ 2. Click on `ADD` button.
+ 3. Fill out the plan details and plan filters.
+ - The options dynamically change in the form based on the `plan type` selected.
+ - If the `plan type` is Intended, Remediation, Missing.
+ - Select the Compliance Features to use to generate the plan. If none are selected all features will be in scope.
+ - If the `plan type` is Manual.
+ - Create a manual plan to accomplish the goal. Note: Access to `obj` is available to dynamically populate fields via Jinja2 syntax.
+ 4. Click `Generate`
+
+> For in-depth details see [Navigating Config Plans](./app_feature_config_plans.md)
# Config Deploy
-Coming soon
+Follow the steps below to get up and running for the configuration deployment element of the plugin.
+
+1. Enable the feature in the `PLUGIN_SETTINGS`. The configuration should have `"enable_deploy": True` set in the `PLUGINS_CONFIG` dictionary for `nautobot_golden_config`.
+2. Follow the steps in [Config Plans](#config-plans).
+3. Navigate to the specific ConfigPlan to deploy, or multi-select them from the ConfigPlan list view.
+ - If deploying from a specific ConfigPlan object. Click `Deploy` button and accept the warnings.
+ - If deploying from the ConfigPlan list view. Click `Deploy Selected` button and accept the warnings.
+4. Interpret the results from the popup modal and navigate to the job result as needed for more details.
+
+> Config Deployments utilize the dispatchers from nornir-nautobot just like the other functionality of Golden Config. See [Troubleshooting Dispatchers](./troubleshooting/troubleshoot_dispatchers.md) for more details.
+
+# Config Remediation
+
+Follow the steps below to get up and running for the configuration remediation element of the plugin.
+
+1. Navigate to `Golden Config -> Compliance Rules`.
+2. Select the rules in which you'd like to enable remediation on.
+3. Edit the `Compliance Rule` and turn on the `Remediation` toggle button.
+4. Run the `Compliance` job again which will generate the initial remediation plan for the feature.
+5. Navigate to `Golden Config -> Config Compliance`, select the device and notice a remediation section is not present for the compliance details for the feature.
+
+> For in-depth details see [Navigating Config Plans](./app_feature_remediation.md)
# Load Properties from Git
diff --git a/docs/user/troubleshooting/troubleshoot_general.md b/docs/user/troubleshooting/troubleshoot_general.md
index 934921d5..1aefdd46 100755
--- a/docs/user/troubleshooting/troubleshoot_general.md
+++ b/docs/user/troubleshooting/troubleshoot_general.md
@@ -64,3 +64,7 @@ If none of these troubleshooting steps helped identify the problem please visit
- [Troubleshoot Credentials](./troubleshoot_credentials.md)
- [Troubleshoot Dispatchers](./troubleshoot_dispatchers.md)
+
+### Detailed Diagrams
+
+Golden config flow diagrams are available on the repositories [wiki](https://github.com/nautobot/nautobot-plugin-golden-config/wiki#diagrams).
diff --git a/nautobot_golden_config/jobs.py b/nautobot_golden_config/jobs.py
index 84a9def1..9c2c9707 100644
--- a/nautobot_golden_config/jobs.py
+++ b/nautobot_golden_config/jobs.py
@@ -25,12 +25,12 @@
from nautobot_golden_config.nornir_plays.config_compliance import config_compliance
from nautobot_golden_config.nornir_plays.config_deployment import config_deployment
from nautobot_golden_config.nornir_plays.config_intended import config_intended
+from nautobot_golden_config.utilities import constant
from nautobot_golden_config.utilities.config_plan import (
config_plan_default_status,
generate_config_set_from_compliance_feature,
generate_config_set_from_manual,
)
-from nautobot_golden_config.utilities import constant
from nautobot_golden_config.utilities.git import GitRepo
from nautobot_golden_config.utilities.helper import get_job_filter
diff --git a/nautobot_golden_config/migrations/0002_custom_data.py b/nautobot_golden_config/migrations/0002_custom_data.py
index 1435fbd8..d96c71f0 100644
--- a/nautobot_golden_config/migrations/0002_custom_data.py
+++ b/nautobot_golden_config/migrations/0002_custom_data.py
@@ -1,6 +1,6 @@
# Generated by Django 3.1.3 on 2021-02-22 01:27
-from django.db import migrations, models
+from django.db import migrations
class Migration(migrations.Migration):
diff --git a/nautobot_golden_config/migrations/0015_convert_sotagg_queries_part2.py b/nautobot_golden_config/migrations/0015_convert_sotagg_queries_part2.py
index c0b4d6c1..a1244046 100644
--- a/nautobot_golden_config/migrations/0015_convert_sotagg_queries_part2.py
+++ b/nautobot_golden_config/migrations/0015_convert_sotagg_queries_part2.py
@@ -1,4 +1,4 @@
-from django.db import migrations, models
+from django.db import migrations
def save_existing_sotagg_queries(apps, schema_editor):
diff --git a/nautobot_golden_config/migrations/0016_convert_sotagg_queries_part3.py b/nautobot_golden_config/migrations/0016_convert_sotagg_queries_part3.py
index 0fc55074..93c96f4f 100644
--- a/nautobot_golden_config/migrations/0016_convert_sotagg_queries_part3.py
+++ b/nautobot_golden_config/migrations/0016_convert_sotagg_queries_part3.py
@@ -1,7 +1,5 @@
-from datetime import date
-
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
diff --git a/nautobot_golden_config/migrations/0017_convert_sotagg_queries_part4.py b/nautobot_golden_config/migrations/0017_convert_sotagg_queries_part4.py
index c90d5b9c..1ba69ceb 100644
--- a/nautobot_golden_config/migrations/0017_convert_sotagg_queries_part4.py
+++ b/nautobot_golden_config/migrations/0017_convert_sotagg_queries_part4.py
@@ -1,7 +1,6 @@
-from datetime import date
import logging
+from datetime import date
-from django.core.validators import ValidationError
from django.db import migrations
logger = logging.getLogger("nautobot")
diff --git a/nautobot_golden_config/migrations/0020_convert_dynamicgroup_part_2.py b/nautobot_golden_config/migrations/0020_convert_dynamicgroup_part_2.py
index 639d82ca..03125222 100644
--- a/nautobot_golden_config/migrations/0020_convert_dynamicgroup_part_2.py
+++ b/nautobot_golden_config/migrations/0020_convert_dynamicgroup_part_2.py
@@ -1,8 +1,7 @@
# Generated by Django 3.2.14 on 2022-07-11 14:18
-from django.db import migrations, models
+from django.db import migrations
from django.utils.text import slugify
-import django.db.models.deletion
def create_dynamic_groups(apps, schedma_editor):
diff --git a/nautobot_golden_config/nornir_plays/config_compliance.py b/nautobot_golden_config/nornir_plays/config_compliance.py
index 5ba24372..bb33f5b5 100644
--- a/nautobot_golden_config/nornir_plays/config_compliance.py
+++ b/nautobot_golden_config/nornir_plays/config_compliance.py
@@ -3,35 +3,31 @@
import difflib
import logging
import os
-
from collections import defaultdict
from datetime import datetime
-from netutils.config.compliance import parser_map, section_config, _open_file_config
+from nautobot_plugin_nornir.constants import NORNIR_SETTINGS
+from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory
+from netutils.config.compliance import _open_file_config, parser_map, section_config
from nornir import InitNornir
from nornir.core.plugins.inventory import InventoryPluginRegister
from nornir.core.task import Result, Task
-
from nornir_nautobot.exceptions import NornirNautobotException
from nornir_nautobot.utils.logger import NornirLogger
-from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory
-from nautobot_plugin_nornir.constants import NORNIR_SETTINGS
-
from nautobot_golden_config.choices import ComplianceRuleConfigTypeChoice
+from nautobot_golden_config.models import ComplianceRule, ConfigCompliance, GoldenConfig
+from nautobot_golden_config.nornir_plays.processor import ProcessGoldenConfig
from nautobot_golden_config.utilities.db_management import close_threaded_db_connections
-from nautobot_golden_config.models import ComplianceRule, ConfigCompliance, GoldenConfigSetting, GoldenConfig
from nautobot_golden_config.utilities.helper import (
get_device_to_settings_map,
get_job_filter,
- verify_settings,
- render_jinja_template,
get_json_config,
+ render_jinja_template,
+ verify_settings,
)
-from nautobot_golden_config.nornir_plays.processor import ProcessGoldenConfig
from nautobot_golden_config.utilities.utils import get_platform
-
InventoryPluginRegister.register("nautobot-inventory", NautobotORMInventory)
LOGGER = logging.getLogger(__name__)
diff --git a/nautobot_golden_config/nornir_plays/config_intended.py b/nautobot_golden_config/nornir_plays/config_intended.py
index be5f18e9..e00518e4 100644
--- a/nautobot_golden_config/nornir_plays/config_intended.py
+++ b/nautobot_golden_config/nornir_plays/config_intended.py
@@ -1,35 +1,32 @@
"""Nornir job for generating the intended config."""
# pylint: disable=relative-beyond-top-level
-import os
import logging
-
+import os
from datetime import datetime
-from nornir import InitNornir
-from nornir.core.plugins.inventory import InventoryPluginRegister
-from nornir.core.task import Result, Task
from django.template import engines
from jinja2.sandbox import SandboxedEnvironment
-
+from nautobot_plugin_nornir.constants import NORNIR_SETTINGS
+from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory
+from nautobot_plugin_nornir.utils import get_dispatcher
+from nornir import InitNornir
+from nornir.core.plugins.inventory import InventoryPluginRegister
+from nornir.core.task import Result, Task
from nornir_nautobot.exceptions import NornirNautobotException
from nornir_nautobot.plugins.tasks.dispatcher import dispatcher
from nornir_nautobot.utils.logger import NornirLogger
-from nautobot_plugin_nornir.plugins.inventory.nautobot_orm import NautobotORMInventory
-from nautobot_plugin_nornir.constants import NORNIR_SETTINGS
-from nautobot_plugin_nornir.utils import get_dispatcher
-
+from nautobot_golden_config.models import GoldenConfig
+from nautobot_golden_config.nornir_plays.processor import ProcessGoldenConfig
from nautobot_golden_config.utilities.constant import PLUGIN_CFG
from nautobot_golden_config.utilities.db_management import close_threaded_db_connections
-from nautobot_golden_config.models import GoldenConfigSetting, GoldenConfig
+from nautobot_golden_config.utilities.graphql import graph_ql_query
from nautobot_golden_config.utilities.helper import (
get_device_to_settings_map,
get_job_filter,
- verify_settings,
render_jinja_template,
+ verify_settings,
)
-from nautobot_golden_config.utilities.graphql import graph_ql_query
-from nautobot_golden_config.nornir_plays.processor import ProcessGoldenConfig
InventoryPluginRegister.register("nautobot-inventory", NautobotORMInventory)
LOGGER = logging.getLogger(__name__)
diff --git a/nautobot_golden_config/static/run_job.js b/nautobot_golden_config/static/run_job.js
index 3229e30e..81e286d1 100644
--- a/nautobot_golden_config/static/run_job.js
+++ b/nautobot_golden_config/static/run_job.js
@@ -7,207 +7,189 @@
* @param {string} jobClass - The jobs `class_path` as defined on the job detail page.
* @param {Object} data - The object containing payload data to send to the job.
* @param {string} redirectUrlTemplate - The redirect url to provide, you have access to jobData with the syntax like `{jobData.someKey}`, leave `undefined` if none is required.
+ * @param {string} callBack - The promise function to return the success or failure message, leave `undefined` no callback is required.
*/
-function startJob(jobClass, data, redirectUrlTemplate) {
- var jobApi = `/api/extras/jobs/${jobClass}/run/`;
+function startJob(jobClass, data, redirectUrlTemplate, callBack) {
+ var jobApi = `/api/extras/jobs/${jobClass}/run/`;
- $.ajax({
- type: 'POST',
- url: jobApi,
- contentType: "application/json",
- data: JSON.stringify(data),
- dataType: 'json',
- headers: {
- 'X-CSRFToken': nautobot_csrf_token
- },
- beforeSend: function() {
- // Normalize to base as much as you can.
- $('#jobStatus').html("Pending").show();
- $('#loaderImg').show();
- $('#jobResults').hide();
- $('#redirectLink').hide();
- $('#detailMessages').hide();
- },
- success: function(jobData) {
- $('#jobStatus').html("Started").show();
- var jobResultUrl = "/extras/job-results/" + jobData.result.id + "/";
- $('#jobResults').html(iconLink(jobResultUrl, "mdi-open-in-new", "Job Details")).show();
- pollJobStatus(jobData.result.url);
- if (typeof redirectUrlTemplate !== "undefined") {
- var redirectUrl = _renderTemplate(redirectUrlTemplate, jobData);
- $('#redirectLink').html(iconLink(redirectUrl, "mdi-open-in-new", "Info"));
- }
+ if (typeof callBack === "undefined") {
+ var callBack = getMessage;
+ }
+
+ $.ajax({
+ type: 'POST',
+ url: jobApi,
+ contentType: "application/json",
+ data: JSON.stringify(data),
+ dataType: 'json',
+ headers: {
+ 'X-CSRFToken': nautobot_csrf_token
+ },
+ beforeSend: function() {
+ // Normalize to base as much as you can.
+ $('#jobStatus').html("Pending").show();
+ $('#loaderImg').show();
+ $('#jobResults').hide();
+ $('#redirectLink').hide();
+ $('#detailMessages').hide();
},
- error: function(e) {
- $("#loaderImg").hide();
- console.log("There was an error with your request...");
- console.log("error: " + JSON.stringify(e));
- $('#jobStatus').html("Failed").show();
- $('#detailMessages').show();
- $('#detailMessages').attr('class', 'alert alert-danger text-center');
- $('#detailMessages').html("Error: " + e.responseText);
- }
- });
+ success: function(jobData) {
+ $('#jobStatus').html("Started").show();
+ var jobResultUrl = "/extras/job-results/" + jobData.result.id + "/";
+ $('#jobResults').html(iconLink(jobResultUrl, "mdi-open-in-new", "Job Details")).show();
+ pollJobStatus(jobData.result.url, callBack);
+ if (typeof redirectUrlTemplate !== "undefined") {
+ var redirectUrl = _renderTemplate(redirectUrlTemplate, jobData);
+ $('#redirectLink').html(iconLink(redirectUrl, "mdi-open-in-new", "Info"));
+ }
+ },
+ error: function(e) {
+ $("#loaderImg").hide();
+ console.log("There was an error with your request...");
+ console.log("error: " + JSON.stringify(e));
+ $('#jobStatus').html("Failed").show();
+ $('#detailMessages').show();
+ $('#detailMessages').attr('class', 'alert alert-danger text-center');
+ $('#detailMessages').html("Error: " + e.responseText);
+ }
+ });
}
/**
- * Polls the status of a job with the given job ID.
- *
- * This function makes an AJAX request to the server,
- * to get the current status of the job with the specified job ID.
- * It continues to poll the status until the job completes or fails.
- * The job status is updated in the HTML element with ID 'jobStatus'.
- * If the job encounters an error, additional error details are shown.
- * The call is not made async, so that the parent call will wait until
- * this is completed.
- *
- * @requires nautobot_csrf_token - The CSRF token obtained from Nautobot.
- * @param {string} jobId - The ID of the job to poll.
- * @returns {void}
- */
-function pollJobStatus(jobId) {
- $.ajax({
- url: jobId,
- type: "GET",
- async: false,
- dataType: "json",
- headers: {
- 'X-CSRFToken': nautobot_csrf_token
- },
- success: function(data) {
- $('#jobStatus').html(data.status.value.charAt(0).toUpperCase() + data.status.value.slice(1)).show();
- if (["errored", "failed"].includes(data.status.value)) {
- $("#loaderImg").hide();
- $('#detailMessages').show();
- $('#detailMessages').attr('class', 'alert alert-warning text-center');
- $('#detailMessages').html("Job started but failed during the Job run. This job may have partially completed. See Job Results for more details on the errors.");
- } else if (["running", "pending"].includes(data.status.value)) {
- // Job is still processing, continue polling
- setTimeout(function() {
- pollJobStatus(jobId);
- }, 1000); // Poll every 1 seconds
- } else if (data.status.value == "completed") {
+* Polls the status of a job with the given job ID.
+*
+* This function makes an AJAX request to the server,
+* to get the current status of the job with the specified job ID.
+* It continues to poll the status until the job completes or fails.
+* The job status is updated in the HTML element with ID 'jobStatus'.
+* If the job encounters an error, additional error details are shown.
+* The call is not made async, so that the parent call will wait until
+* this is completed.
+*
+* @requires nautobot_csrf_token - The CSRF token obtained from Nautobot.
+* @param {string} jobId - The ID of the job to poll.
+* @returns {void}
+*/
+function pollJobStatus(jobId, callBack) {
+$.ajax({
+ url: jobId,
+ type: "GET",
+ async: false,
+ dataType: "json",
+ headers: {
+ 'X-CSRFToken': nautobot_csrf_token
+ },
+ success: function (data) {
+ $('#jobStatus').html(data.status.value.charAt(0).toUpperCase() + data.status.value.slice(1)).show();
+ if (["errored", "failed"].includes(data.status.value)) {
+ $("#loaderImg").hide();
+ $('#detailMessages').show();
+ $('#detailMessages').attr('class', 'alert alert-warning text-center');
+ $('#detailMessages').html("Job started but failed during the Job run. This job may have partially completed. See Job Results for more details on the errors.");
+ } else if (["running", "pending"].includes(data.status.value)) {
+ // Job is still processing, continue polling
+ setTimeout(function () {
+ pollJobStatus(jobId, callBack);
+ }, 1000); // Poll every 1 seconds
+ } else if (data.status.value == "completed") {
+ $("#loaderImg").hide();
+ $('#detailMessages').show();
+ callBack(data.id)
+ .then((message) => {
+ $('#redirectLink').show();
+ $('#detailMessages').attr('class', 'alert alert-success text-center');
+ $('#detailMessages').html(message)
+ })
+ .catch((message) => {
+ $('#detailMessages').attr('class', 'alert alert-warning text-center');
+ $('#detailMessages').html(message)
+ })
+ }
+ },
+ error: function(e) {
$("#loaderImg").hide();
+ console.log("There was an error with your request...");
+ console.log("error: " + JSON.stringify(e));
$('#detailMessages').show();
- configPlanCount(data.id)
- .then(function(planCount) {
- $('#redirectLink').show();
- $('#detailMessages').attr('class', 'alert alert-success text-center');
- $('#detailMessages').html(
- "Job Completed Successfully."+
- "
Number of Config Plans generated: " + planCount
- )
- })
- .catch(function(error) {
- $('#detailMessages').attr('class', 'alert alert-warning text-center');
- $('#detailMessages').html(
- "Job completed successfully, but no Config Plans were generated."+
- "
If this is unexpected, please validate your input parameters."
- )
- });
+ $('#detailMessages').attr('class', 'alert alert-danger text-center');
+ $('#detailMessages').html("Error: " + e.responseText);
}
- },
- error: function(e) {
- $("#loaderImg").hide();
- console.log("There was an error with your request...");
- console.log("error: " + JSON.stringify(e));
- $('#detailMessages').show();
- $('#detailMessages').attr('class', 'alert alert-danger text-center');
- $('#detailMessages').html("Error: " + e.responseText);
- }
- });
-}
-
+ })
+};
/**
- * Converts a list of form data objects to a dictionary.
- *
- * @param {FormData} formData - The form data object to be converted.
- * @param {string[]} listKeys - The list of keys for which values should be collected as lists.
- * @returns {Object} - The dictionary representation of the form data.
- */
+* Converts a list of form data objects to a dictionary.
+*
+* @param {FormData} formData - The form data object to be converted.
+* @param {string[]} listKeys - The list of keys for which values should be collected as lists.
+* @returns {Object} - The dictionary representation of the form data.
+*/
function formDataToDictionary(formData, listKeys) {
- const dict = {};
+const dict = {};
- formData.forEach(item => {
- const { name, value } = item;
- if (listKeys.includes(name)) {
- if (!dict[name]) {
- dict[name] = [value];
- } else {
- dict[name].push(value);
- }
+formData.forEach(item => {
+ const { name, value } = item;
+ if (listKeys.includes(name)) {
+ if (!dict[name]) {
+ dict[name] = [value];
} else {
- dict[name] = value;
+ dict[name].push(value);
}
- });
+ } else {
+ dict[name] = value;
+ }
+});
- return dict;
+return dict;
}
/**
- * Generates an HTML anchor link with an icon.
- *
- * @param {string} url - The URL to link to.
- * @param {string} icon - The name of the Material Design Icon to use.
- * @param {string} title - The title to display when hovering over the icon.
- * @returns {string} - The HTML anchor link with the specified icon.
- */
+* Generates an HTML anchor link with an icon.
+*
+* @param {string} url - The URL to link to.
+* @param {string} icon - The name of the Material Design Icon to use.
+* @param {string} title - The title to display when hovering over the icon.
+* @returns {string} - The HTML anchor link with the specified icon.
+*/
function iconLink(url, icon, title) {
- const linkUrl = `` +
- ` ` +
- ` ` +
- ` ` +
- ``
- return linkUrl
+const linkUrl = `` +
+` ` +
+` ` +
+` ` +
+``
+return linkUrl
}
/**
- * Renders a template string with placeholders replaced by corresponding values from jobData.
- *
- * @param {string} templateString - The template string with placeholders in the form of `{jobData.someKey}`.
- * @param {Object} jobData - The object containing data to replace placeholders in the template.
- * @returns {string} - The rendered string with placeholders replaced by actual values from jobData.
- */
+* Renders a template string with placeholders replaced by corresponding values from jobData.
+*
+* @param {string} templateString - The template string with placeholders in the form of `{jobData.someKey}`.
+* @param {Object} jobData - The object containing data to replace placeholders in the template.
+* @returns {string} - The rendered string with placeholders replaced by actual values from jobData.
+*/
function _renderTemplate(templateString, data) {
- // Create a regular expression to match placeholders in the template
- const placeholderRegex = /\{jobData\.([^\}]+)\}/g;
+// Create a regular expression to match placeholders in the template
+const placeholderRegex = /\{jobData\.([^\}]+)\}/g;
- // Replace placeholders with corresponding values from jobData
- const renderedString = templateString.replace(placeholderRegex, (match, key) => {
- const keys = key.split(".");
- let value = data;
- for (const k of keys) {
- if (value.hasOwnProperty(k)) {
- value = value[k];
- } else {
- return match; // If the key is not found, keep the original placeholder
- }
+// Replace placeholders with corresponding values from jobData
+const renderedString = templateString.replace(placeholderRegex, (match, key) => {
+ const keys = key.split(".");
+ let value = data;
+ for (const k of keys) {
+ if (value.hasOwnProperty(k)) {
+ value = value[k];
+ } else {
+ return match; // If the key is not found, keep the original placeholder
}
- return value;
- });
+ }
+ return value;
+});
- return renderedString;
+return renderedString;
}
-
-function configPlanCount(jobResultId) {
- return new Promise(function(resolve, reject) {
- var configPlanApi = `/api/plugins/golden-config/config-plan/?job_result_id=${jobResultId}`;
- $.ajax({
- url: configPlanApi,
- type: "GET",
- dataType: "json",
- headers: {
- 'X-CSRFToken': nautobot_csrf_token
- },
- success: function(data) {
- var count = data.count;
- resolve(count);
- },
- error: function(e) {
- resolve(e);
- }
- });
- });
-}
\ No newline at end of file
+function getMessage(jobResultId) {
+return new Promise((resolve) => {
+ resolve("Job Completed Successfully.");
+});
+}
diff --git a/nautobot_golden_config/views.py b/nautobot_golden_config/views.py
index 6f6cc4c4..3d52bf2b 100644
--- a/nautobot_golden_config/views.py
+++ b/nautobot_golden_config/views.py
@@ -28,13 +28,14 @@
from nautobot.utilities.forms import ConfirmationForm
from nautobot.utilities.utils import copy_safe_request, csv_format
from nautobot.utilities.views import ContentTypePermissionRequiredMixin, ObjectPermissionRequiredMixin
+
from nautobot_golden_config import filters, forms, models, tables
from nautobot_golden_config.api import serializers
from nautobot_golden_config.jobs import DeployConfigPlans
-from nautobot_golden_config.utilities.config_postprocessing import get_config_postprocessing
from nautobot_golden_config.utilities import constant
+from nautobot_golden_config.utilities.config_postprocessing import get_config_postprocessing
from nautobot_golden_config.utilities.graphql import graph_ql_query
-from nautobot_golden_config.utilities.helper import get_device_to_settings_map, add_message
+from nautobot_golden_config.utilities.helper import add_message, get_device_to_settings_map
LOGGER = logging.getLogger(__name__)
@@ -497,7 +498,7 @@ def diff_structured_data(backup_data, intended_data):
+ "\n"
+ output[first_occurence:second_occurence]
+ "@@"
- + output[second_occurence + 2 :]
+ + output[second_occurence + 2 :] # noqa: E203
)
template_name = "nautobot_golden_config/configcompliance_details.html"
diff --git a/pyproject.toml b/pyproject.toml
index e85aede0..bcad57af 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -95,7 +95,8 @@ exclude = '''
[tool.pylint.master]
# Include the pylint_django plugin to avoid spurious warnings about Django patterns
load-plugins="pylint_django"
-ignore=["jinja_filters.py", ".venv"]
+ignore-patterns=["jinja_filters.py", ".venv"]
+ignore-paths = '^.*/migrations/.*$'
[tool.pylint.basic]
# No docstrings required for private methods (Pylint default), or for test_ functions, or for inner Meta classes.