Skip to content
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

Add GraphQL output to the "Generate Intended Config" view #840

Merged
merged 2 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/840.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added GraphQL output to the "Generate Intended Config" view.
Binary file modified docs/images/generate-intended-config-ui-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/images/generate-intended-config-ui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/user/app_feature_intended.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ curl -s -X GET \
http://nautobot/api/plugins/golden-config/generate-intended-config/?device_id=231b8765-054d-4abe-bdbf-cd60e049cd8d
```

The returned response will contain the rendered configuration for the specified device. The web UI provides a simple form to input the device and displays the rendered configuration when submitted.
The returned response will contain the rendered configuration for the specified device and the GraphQL data that was used. The web UI provides a simple form to input the device and displays the rendered configuration when submitted.

![Intended Configuration Web UI](../images/generate-intended-config-ui.png#only-light)
![Intended Configuration Web UI](../images/generate-intended-config-ui-dark.png#only-dark)
Expand Down
1 change: 1 addition & 0 deletions nautobot_golden_config/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,4 @@ class GenerateIntendedConfigSerializer(serializers.Serializer): # pylint: disab

intended_config = serializers.CharField(read_only=True)
intended_config_lines = serializers.ListField(read_only=True, child=serializers.CharField())
graphql_data = serializers.JSONField(read_only=True)
5 changes: 3 additions & 2 deletions nautobot_golden_config/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,21 +253,22 @@ def get(self, request, *args, **kwargs):

filesystem_path = self._get_jinja_template_path(settings, device, git_repository)

status_code, context = graph_ql_query(request, device, settings.sot_agg_query.query)
status_code, graphql_data = graph_ql_query(request, device, settings.sot_agg_query.query)
if status_code == status.HTTP_200_OK:
try:
intended_config = self._render_config_nornir_serial(
device=device,
jinja_template=filesystem_path.name,
jinja_root_path=filesystem_path.parent,
graphql_data=context,
graphql_data=graphql_data,
)
except Exception as exc:
raise GenerateIntendedConfigException(f"Error rendering Jinja template: {exc}") from exc
return Response(
data={
"intended_config": intended_config,
"intended_config_lines": intended_config.split("\n"),
"graphql_data": graphql_data,
},
status=status.HTTP_200_OK,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@
.button-container {
margin-bottom: 24px;
}
pre:has(code) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bootstrap has a default of 11.5px here so the code block inside of the pre block doesn't fill the box completely.

default

image

with this fix

image

padding: 0;
}
pre code {
display: block;
height: 50vh;
overflow: auto;
resize: vertical;
word-wrap: normal;
overflow-wrap: normal;
word-break: normal;
white-space: pre;
padding: 11.5px;
}
.nav-tabs li.disabled a {
pointer-events: none;
}
</style>
{% endblock extra_styles %}

Expand All @@ -23,7 +40,7 @@
<a href="{% url 'plugins:nautobot_golden_config:goldenconfig_list' %}">Config Overview</a> page.
</p>
<p>
This will render the configuration for the selected device using Jinja templates from the golden config <code>jinja_repository</code>
This will render the configuration for the selected device using Jinja templates from the Golden Config <code>jinja_repository</code>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This will render the configuration for the selected device using Jinja templates from the Golden Config <code>jinja_repository</code>
This will render the configuration for the selected device using Jinja templates from the Golden Config Setting <code>jinja_repository</code>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though, the setting is just called Golden Config, so I could go either way on this one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cmsirbu is going to try to get some input on the wording here

Git repository for that device.
This feature allows developers to test their configuration templates without running a full "intended configuration" job. See the
<a href="{% static 'nautobot_golden_config/docs/user/app_feature_intended.html' %}#developing-intended-configuration-templates">
Expand All @@ -32,27 +49,37 @@
</p>
<p>
<strong>Note:</strong>
This will perform a <code>git pull</code> on the golden config Jinja template repository to ensure the latest templates are used.
This will fetch the latest templates from the Golden Config Jinja template repository.
</p>
{% render_field form.device %}
{% render_field form.git_repository %}
</div>
</div>
<div class="button-container text-right">
<button type="submit" class="btn btn-primary">Render</button>
<a href="{{ return_url }}" class="btn btn-default">Cancel</a>
</div>
</div>
<div class="col-lg-6 col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Intended Configuration</strong>
<button type="button" class="btn btn-inline btn-default copy-rendered-config" data-clipboard-target="#rendered_config">
<button type="button" class="btn btn-inline btn-default copy-rendered-config" data-clipboard-target="#id_rendered_config_tabs .active pre code">
<span class="mdi mdi-content-copy"></span>
</button>
</div>
<div class="panel-body">
<textarea readonly="readonly" cols="40" rows="10" class="form-control" placeholder="Rendered Config" id="rendered_config"></textarea>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active" id="id_intended_config_tab"><a href="#id_intended_config_tab_content" aria-controls="id_rendered_config_tabs" role="tab" data-toggle="tab">Configuration</a></li>
<li role="presentation" class="disabled" id="id_graphql_data_tab"><a href="#id_graphql_data_tab_content" aria-controls="id_graphql_data_tab_content" role="tab" data-toggle="tab">GraphQL Data</a></li>
</ul>
<div class="tab-content" id="id_rendered_config_tabs">
<div class="tab-pane active" id="id_intended_config_tab_content">
<pre><code id="id_rendered_config_code_block"></code></pre>
</div>
<div class="tab-pane" id="id_graphql_data_tab_content">
<pre><code class="language-json" id="id_graphql_data_code_block"></code></pre>
</div>
</div>
</div>
</div>
</div>
Expand All @@ -71,10 +98,20 @@
event.preventDefault(); // Prevent default form submission

try {
const rendered_config = document.getElementById("rendered_config");
rendered_config.innerHTML = "Loading...";
const rendered_config_code_block = document.getElementById("id_rendered_config_code_block");
const device = document.getElementById("id_device").value;
const url = "{% url 'plugins-api:nautobot_golden_config-api:generate_intended_config' %}";
const graphql_data_code_block = document.getElementById("id_graphql_data_code_block");
const graphql_data_tab = document.getElementById("id_graphql_data_tab");

rendered_config_code_block.innerHTML = "Loading...";

// switch to the intended config tab and disable the graphql data tab
$("#id_intended_config_tab a").tab("show");
graphql_data_tab.classList.add("disabled");
graphql_data_tab.classList.remove("active");

// fetch the intended config
const data = {device_id: device};
const query_params = new URLSearchParams(data).toString();
const response = await fetch(url + "?" + query_params, {
Expand All @@ -84,12 +121,21 @@
const responseData = await response.json();
if (!response.ok) {
const msg = responseData.detail ? responseData.detail : response.statusText;
rendered_config.innerHTML = sanitize(`An error occurred:\n\n${msg}`);
rendered_config_code_block.innerHTML = sanitize(`An error occurred:\n\n${msg}`);
} else {
rendered_config.innerHTML = sanitize(responseData.intended_config);
// populate the rendered config
rendered_config_code_block.innerHTML = sanitize(responseData.intended_config);

// populate and syntax highlight the graphql data
graphql_data_code_block.innerHTML = JSON.stringify(responseData.graphql_data, null, 4);
delete graphql_data_code_block.dataset.highlighted;
hljs.highlightElement(graphql_data_code_block);

// enable the graphql data tab
id_graphql_data_tab.classList.remove("disabled");
}
} catch (error) {
rendered_config.innerHTML = sanitize(`An error occurred:\n\n${error.message}`);
rendered_config_code_block.innerHTML = sanitize(`An error occurred:\n\n${error.message}`);
}
}
</script>
Expand Down