netbox-atlassian 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netbox_atlassian/__init__.py +73 -0
- netbox_atlassian/atlassian_client.py +346 -0
- netbox_atlassian/forms.py +101 -0
- netbox_atlassian/navigation.py +22 -0
- netbox_atlassian/templates/netbox_atlassian/device_tab.html +178 -0
- netbox_atlassian/templates/netbox_atlassian/settings.html +274 -0
- netbox_atlassian/templates/netbox_atlassian/vm_tab.html +178 -0
- netbox_atlassian/urls.py +17 -0
- netbox_atlassian/views.py +306 -0
- netbox_atlassian-0.1.0.dist-info/METADATA +186 -0
- netbox_atlassian-0.1.0.dist-info/RECORD +13 -0
- netbox_atlassian-0.1.0.dist-info/WHEEL +5 -0
- netbox_atlassian-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
{% extends 'dcim/device/base.html' %}
|
|
2
|
+
{% load helpers %}
|
|
3
|
+
|
|
4
|
+
{% block content %}
|
|
5
|
+
<div class="row">
|
|
6
|
+
<div class="col-12">
|
|
7
|
+
<!-- Search Terms Info -->
|
|
8
|
+
<div class="alert alert-info mb-3">
|
|
9
|
+
<i class="mdi mdi-magnify"></i>
|
|
10
|
+
<strong>Searching for:</strong>
|
|
11
|
+
{% for term in search_terms %}
|
|
12
|
+
<span class="badge bg-secondary text-white ms-1">{{ term }}</span>{% if not forloop.last %}<span class="text-muted ms-1">OR</span>{% endif %}
|
|
13
|
+
{% empty %}
|
|
14
|
+
<span class="text-muted">No search terms available</span>
|
|
15
|
+
{% endfor %}
|
|
16
|
+
<br>
|
|
17
|
+
<small class="text-muted">
|
|
18
|
+
Fields: {% for field in enabled_fields %}{{ field.name }}{% if not forloop.last %}, {% endif %}{% endfor %}
|
|
19
|
+
</small>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="row">
|
|
23
|
+
<!-- Jira Issues Column -->
|
|
24
|
+
<div class="col-md-6 mb-4">
|
|
25
|
+
<div class="card h-100">
|
|
26
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
27
|
+
<h5 class="card-title mb-0">
|
|
28
|
+
<i class="mdi mdi-jira text-primary"></i> Jira Issues
|
|
29
|
+
{% if jira_results.total > 0 %}
|
|
30
|
+
<span class="badge bg-primary text-white">{{ jira_results.total }} issue{{ jira_results.total|pluralize }}</span>
|
|
31
|
+
{% endif %}
|
|
32
|
+
</h5>
|
|
33
|
+
{% if jira_url %}
|
|
34
|
+
<a href="{{ jira_url }}" target="_blank" class="btn btn-sm btn-outline-primary">
|
|
35
|
+
<i class="mdi mdi-open-in-new"></i> Open Jira
|
|
36
|
+
</a>
|
|
37
|
+
{% endif %}
|
|
38
|
+
</div>
|
|
39
|
+
<div class="card-body">
|
|
40
|
+
{% if not jira_configured %}
|
|
41
|
+
<div class="alert alert-warning mb-0">
|
|
42
|
+
<i class="mdi mdi-alert"></i> Jira not configured
|
|
43
|
+
</div>
|
|
44
|
+
{% elif jira_results.error %}
|
|
45
|
+
<div class="alert alert-danger mb-0">
|
|
46
|
+
<i class="mdi mdi-alert-circle"></i> {{ jira_results.error }}
|
|
47
|
+
</div>
|
|
48
|
+
{% elif jira_results.issues %}
|
|
49
|
+
<div class="table-responsive">
|
|
50
|
+
<table class="table table-sm table-hover mb-0">
|
|
51
|
+
<thead>
|
|
52
|
+
<tr>
|
|
53
|
+
<th>Key</th>
|
|
54
|
+
<th>Summary</th>
|
|
55
|
+
<th>Status</th>
|
|
56
|
+
<th>Type</th>
|
|
57
|
+
</tr>
|
|
58
|
+
</thead>
|
|
59
|
+
<tbody>
|
|
60
|
+
{% for issue in jira_results.issues %}
|
|
61
|
+
<tr>
|
|
62
|
+
<td>
|
|
63
|
+
<a href="{{ issue.url }}" target="_blank" class="fw-bold">
|
|
64
|
+
{{ issue.key }}
|
|
65
|
+
</a>
|
|
66
|
+
</td>
|
|
67
|
+
<td>
|
|
68
|
+
<span title="{{ issue.summary }}">
|
|
69
|
+
{{ issue.summary|truncatechars:50 }}
|
|
70
|
+
</span>
|
|
71
|
+
</td>
|
|
72
|
+
<td>
|
|
73
|
+
{% if issue.status_category == 'done' %}
|
|
74
|
+
<span class="badge bg-success">{{ issue.status }}</span>
|
|
75
|
+
{% elif issue.status_category == 'indeterminate' %}
|
|
76
|
+
<span class="badge bg-primary">{{ issue.status }}</span>
|
|
77
|
+
{% else %}
|
|
78
|
+
<span class="badge bg-secondary">{{ issue.status }}</span>
|
|
79
|
+
{% endif %}
|
|
80
|
+
</td>
|
|
81
|
+
<td>
|
|
82
|
+
{% if issue.type_icon %}
|
|
83
|
+
<img src="{{ issue.type_icon }}" alt="{{ issue.type }}" width="16" height="16" class="me-1">
|
|
84
|
+
{% endif %}
|
|
85
|
+
{{ issue.type }}
|
|
86
|
+
</td>
|
|
87
|
+
</tr>
|
|
88
|
+
{% endfor %}
|
|
89
|
+
</tbody>
|
|
90
|
+
</table>
|
|
91
|
+
</div>
|
|
92
|
+
{% if jira_results.total > jira_results.issues|length %}
|
|
93
|
+
<div class="text-muted small mt-2">
|
|
94
|
+
Showing {{ jira_results.issues|length }} of {{ jira_results.total }} results
|
|
95
|
+
</div>
|
|
96
|
+
{% endif %}
|
|
97
|
+
{% else %}
|
|
98
|
+
<div class="text-muted text-center py-4">
|
|
99
|
+
<i class="mdi mdi-information-outline fs-1"></i>
|
|
100
|
+
<p class="mb-0">No Jira issues found</p>
|
|
101
|
+
</div>
|
|
102
|
+
{% endif %}
|
|
103
|
+
|
|
104
|
+
{% if jira_results.cached %}
|
|
105
|
+
<div class="text-muted small mt-2">
|
|
106
|
+
<i class="mdi mdi-cached"></i> Results from cache
|
|
107
|
+
</div>
|
|
108
|
+
{% endif %}
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<!-- Confluence Pages Column -->
|
|
114
|
+
<div class="col-md-6 mb-4">
|
|
115
|
+
<div class="card h-100">
|
|
116
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
117
|
+
<h5 class="card-title mb-0">
|
|
118
|
+
<i class="mdi mdi-file-document text-info"></i> Confluence Pages
|
|
119
|
+
{% if confluence_results.total > 0 %}
|
|
120
|
+
<span class="badge bg-info text-white">{{ confluence_results.total }} page{{ confluence_results.total|pluralize }}</span>
|
|
121
|
+
{% endif %}
|
|
122
|
+
</h5>
|
|
123
|
+
{% if confluence_url %}
|
|
124
|
+
<a href="{{ confluence_url }}" target="_blank" class="btn btn-sm btn-outline-info">
|
|
125
|
+
<i class="mdi mdi-open-in-new"></i> Open Confluence
|
|
126
|
+
</a>
|
|
127
|
+
{% endif %}
|
|
128
|
+
</div>
|
|
129
|
+
<div class="card-body">
|
|
130
|
+
{% if not confluence_configured %}
|
|
131
|
+
<div class="alert alert-warning mb-0">
|
|
132
|
+
<i class="mdi mdi-alert"></i> Confluence not configured
|
|
133
|
+
</div>
|
|
134
|
+
{% elif confluence_results.error %}
|
|
135
|
+
<div class="alert alert-danger mb-0">
|
|
136
|
+
<i class="mdi mdi-alert-circle"></i> {{ confluence_results.error }}
|
|
137
|
+
</div>
|
|
138
|
+
{% elif confluence_results.pages %}
|
|
139
|
+
<div class="list-group list-group-flush">
|
|
140
|
+
{% for page in confluence_results.pages %}
|
|
141
|
+
<a href="{{ page.url }}" target="_blank" class="list-group-item list-group-item-action">
|
|
142
|
+
<div class="d-flex w-100 justify-content-between">
|
|
143
|
+
<h6 class="mb-1">{{ page.title }}</h6>
|
|
144
|
+
<small class="text-muted">{{ page.space_key }}</small>
|
|
145
|
+
</div>
|
|
146
|
+
{% if page.breadcrumb %}
|
|
147
|
+
<small class="text-muted">{{ page.breadcrumb }}</small>
|
|
148
|
+
{% endif %}
|
|
149
|
+
<small class="text-muted d-block">
|
|
150
|
+
Last modified by {{ page.last_modified_by }}
|
|
151
|
+
</small>
|
|
152
|
+
</a>
|
|
153
|
+
{% endfor %}
|
|
154
|
+
</div>
|
|
155
|
+
{% if confluence_results.total > confluence_results.pages|length %}
|
|
156
|
+
<div class="text-muted small mt-2">
|
|
157
|
+
Showing {{ confluence_results.pages|length }} of {{ confluence_results.total }} results
|
|
158
|
+
</div>
|
|
159
|
+
{% endif %}
|
|
160
|
+
{% else %}
|
|
161
|
+
<div class="text-muted text-center py-4">
|
|
162
|
+
<i class="mdi mdi-information-outline fs-1"></i>
|
|
163
|
+
<p class="mb-0">No Confluence pages found</p>
|
|
164
|
+
</div>
|
|
165
|
+
{% endif %}
|
|
166
|
+
|
|
167
|
+
{% if confluence_results.cached %}
|
|
168
|
+
<div class="text-muted small mt-2">
|
|
169
|
+
<i class="mdi mdi-cached"></i> Results from cache
|
|
170
|
+
</div>
|
|
171
|
+
{% endif %}
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
{% endblock %}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
{% extends 'generic/object.html' %}
|
|
2
|
+
{% load form_helpers %}
|
|
3
|
+
|
|
4
|
+
{% block title %}Atlassian Plugin Settings{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block content %}
|
|
7
|
+
<div class="row">
|
|
8
|
+
<div class="col-12">
|
|
9
|
+
<div class="card">
|
|
10
|
+
<div class="card-header">
|
|
11
|
+
<h5 class="card-title mb-0">
|
|
12
|
+
<i class="mdi mdi-cog"></i> Atlassian Plugin Configuration
|
|
13
|
+
</h5>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="card-body">
|
|
16
|
+
<div class="alert alert-info">
|
|
17
|
+
<i class="mdi mdi-information"></i>
|
|
18
|
+
Settings are read-only here. To configure the plugin, edit your NetBox
|
|
19
|
+
<code>configuration.py</code> file.
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="row">
|
|
23
|
+
<!-- Jira Settings -->
|
|
24
|
+
<div class="col-md-6">
|
|
25
|
+
<h5><i class="mdi mdi-jira text-primary"></i> Jira Configuration</h5>
|
|
26
|
+
<table class="table table-sm">
|
|
27
|
+
<tr>
|
|
28
|
+
<th>URL:</th>
|
|
29
|
+
<td>
|
|
30
|
+
{% if config.jira_url %}
|
|
31
|
+
<a href="{{ config.jira_url }}" target="_blank">{{ config.jira_url }}</a>
|
|
32
|
+
{% else %}
|
|
33
|
+
<span class="text-muted">Not configured</span>
|
|
34
|
+
{% endif %}
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
<tr>
|
|
38
|
+
<th>Username:</th>
|
|
39
|
+
<td>{{ config.jira_username|default:"Not configured" }}</td>
|
|
40
|
+
</tr>
|
|
41
|
+
<tr>
|
|
42
|
+
<th>Password:</th>
|
|
43
|
+
<td>{% if config.jira_password %}********{% else %}<span class="text-muted">Not configured</span>{% endif %}</td>
|
|
44
|
+
</tr>
|
|
45
|
+
<tr>
|
|
46
|
+
<th>Verify SSL:</th>
|
|
47
|
+
<td>{{ config.jira_verify_ssl|yesno:"Yes,No" }}</td>
|
|
48
|
+
</tr>
|
|
49
|
+
<tr>
|
|
50
|
+
<th>Max Results:</th>
|
|
51
|
+
<td>{{ config.jira_max_results|default:10 }}</td>
|
|
52
|
+
</tr>
|
|
53
|
+
<tr>
|
|
54
|
+
<th>Projects Filter:</th>
|
|
55
|
+
<td>{{ config.jira_projects|join:", "|default:"All projects" }}</td>
|
|
56
|
+
</tr>
|
|
57
|
+
</table>
|
|
58
|
+
|
|
59
|
+
<button type="button" class="btn btn-sm btn-primary" id="test-jira-btn">
|
|
60
|
+
<i class="mdi mdi-connection"></i> Test Jira Connection
|
|
61
|
+
</button>
|
|
62
|
+
<span id="jira-result" class="ms-2"></span>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<!-- Confluence Settings -->
|
|
66
|
+
<div class="col-md-6">
|
|
67
|
+
<h5><i class="mdi mdi-file-document text-info"></i> Confluence Configuration</h5>
|
|
68
|
+
<table class="table table-sm">
|
|
69
|
+
<tr>
|
|
70
|
+
<th>URL:</th>
|
|
71
|
+
<td>
|
|
72
|
+
{% if config.confluence_url %}
|
|
73
|
+
<a href="{{ config.confluence_url }}" target="_blank">{{ config.confluence_url }}</a>
|
|
74
|
+
{% else %}
|
|
75
|
+
<span class="text-muted">Not configured</span>
|
|
76
|
+
{% endif %}
|
|
77
|
+
</td>
|
|
78
|
+
</tr>
|
|
79
|
+
<tr>
|
|
80
|
+
<th>Username:</th>
|
|
81
|
+
<td>{{ config.confluence_username|default:"Not configured" }}</td>
|
|
82
|
+
</tr>
|
|
83
|
+
<tr>
|
|
84
|
+
<th>Password:</th>
|
|
85
|
+
<td>{% if config.confluence_password %}********{% else %}<span class="text-muted">Not configured</span>{% endif %}</td>
|
|
86
|
+
</tr>
|
|
87
|
+
<tr>
|
|
88
|
+
<th>Verify SSL:</th>
|
|
89
|
+
<td>{{ config.confluence_verify_ssl|yesno:"Yes,No" }}</td>
|
|
90
|
+
</tr>
|
|
91
|
+
<tr>
|
|
92
|
+
<th>Max Results:</th>
|
|
93
|
+
<td>{{ config.confluence_max_results|default:10 }}</td>
|
|
94
|
+
</tr>
|
|
95
|
+
<tr>
|
|
96
|
+
<th>Spaces Filter:</th>
|
|
97
|
+
<td>{{ config.confluence_spaces|join:", "|default:"All spaces" }}</td>
|
|
98
|
+
</tr>
|
|
99
|
+
</table>
|
|
100
|
+
|
|
101
|
+
<button type="button" class="btn btn-sm btn-info" id="test-confluence-btn">
|
|
102
|
+
<i class="mdi mdi-connection"></i> Test Confluence Connection
|
|
103
|
+
</button>
|
|
104
|
+
<span id="confluence-result" class="ms-2"></span>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<hr>
|
|
109
|
+
|
|
110
|
+
<!-- Search Configuration -->
|
|
111
|
+
<h5><i class="mdi mdi-magnify"></i> Search Configuration</h5>
|
|
112
|
+
<table class="table table-sm">
|
|
113
|
+
<thead>
|
|
114
|
+
<tr>
|
|
115
|
+
<th>Field Name</th>
|
|
116
|
+
<th>Device Attribute</th>
|
|
117
|
+
<th>Enabled</th>
|
|
118
|
+
</tr>
|
|
119
|
+
</thead>
|
|
120
|
+
<tbody>
|
|
121
|
+
{% for field in config.search_fields %}
|
|
122
|
+
<tr>
|
|
123
|
+
<td>{{ field.name }}</td>
|
|
124
|
+
<td><code>{{ field.attribute }}</code></td>
|
|
125
|
+
<td>
|
|
126
|
+
{% if field.enabled %}
|
|
127
|
+
<span class="badge bg-success">Yes</span>
|
|
128
|
+
{% else %}
|
|
129
|
+
<span class="badge bg-secondary">No</span>
|
|
130
|
+
{% endif %}
|
|
131
|
+
</td>
|
|
132
|
+
</tr>
|
|
133
|
+
{% empty %}
|
|
134
|
+
<tr>
|
|
135
|
+
<td colspan="3" class="text-muted">No search fields configured</td>
|
|
136
|
+
</tr>
|
|
137
|
+
{% endfor %}
|
|
138
|
+
</tbody>
|
|
139
|
+
</table>
|
|
140
|
+
|
|
141
|
+
<!-- General Settings -->
|
|
142
|
+
<h5><i class="mdi mdi-tune"></i> General Settings</h5>
|
|
143
|
+
<table class="table table-sm">
|
|
144
|
+
<tr>
|
|
145
|
+
<th>Timeout:</th>
|
|
146
|
+
<td>{{ config.timeout|default:30 }} seconds</td>
|
|
147
|
+
</tr>
|
|
148
|
+
<tr>
|
|
149
|
+
<th>Cache Timeout:</th>
|
|
150
|
+
<td>{{ config.cache_timeout|default:300 }} seconds</td>
|
|
151
|
+
</tr>
|
|
152
|
+
<tr>
|
|
153
|
+
<th>Device Type Filter:</th>
|
|
154
|
+
<td>{{ config.device_types|join:", "|default:"All device types" }}</td>
|
|
155
|
+
</tr>
|
|
156
|
+
</table>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<!-- Configuration Example -->
|
|
161
|
+
<div class="card mt-3">
|
|
162
|
+
<div class="card-header">
|
|
163
|
+
<h5 class="card-title mb-0">
|
|
164
|
+
<i class="mdi mdi-code-tags"></i> Configuration Example
|
|
165
|
+
</h5>
|
|
166
|
+
</div>
|
|
167
|
+
<div class="card-body">
|
|
168
|
+
<p>Add to your <code>configuration.py</code>:</p>
|
|
169
|
+
<pre><code>PLUGINS_CONFIG = {
|
|
170
|
+
"netbox_atlassian": {
|
|
171
|
+
# Jira settings (on-prem)
|
|
172
|
+
"jira_url": "https://jira.example.com",
|
|
173
|
+
"jira_username": "api_user",
|
|
174
|
+
"jira_password": "api_token_or_password",
|
|
175
|
+
"jira_verify_ssl": True,
|
|
176
|
+
"jira_projects": [], # Empty = all projects, or ["PROJ1", "PROJ2"]
|
|
177
|
+
|
|
178
|
+
# Confluence settings (on-prem)
|
|
179
|
+
"confluence_url": "https://confluence.example.com",
|
|
180
|
+
"confluence_username": "api_user",
|
|
181
|
+
"confluence_password": "api_token_or_password",
|
|
182
|
+
"confluence_verify_ssl": True,
|
|
183
|
+
"confluence_spaces": [], # Empty = all spaces, or ["SPACE1", "SPACE2"]
|
|
184
|
+
|
|
185
|
+
# Search configuration - searches use OR logic
|
|
186
|
+
"search_fields": [
|
|
187
|
+
{"name": "Hostname", "attribute": "name", "enabled": True},
|
|
188
|
+
{"name": "Serial", "attribute": "serial", "enabled": True},
|
|
189
|
+
{"name": "Asset Tag", "attribute": "asset_tag", "enabled": False},
|
|
190
|
+
{"name": "Role", "attribute": "role.name", "enabled": False},
|
|
191
|
+
{"name": "Primary IP", "attribute": "primary_ip4.address", "enabled": False},
|
|
192
|
+
],
|
|
193
|
+
|
|
194
|
+
# Results limits
|
|
195
|
+
"jira_max_results": 10,
|
|
196
|
+
"confluence_max_results": 10,
|
|
197
|
+
|
|
198
|
+
# General
|
|
199
|
+
"timeout": 30,
|
|
200
|
+
"cache_timeout": 300,
|
|
201
|
+
"device_types": [], # Empty = all devices, or ["cisco", "juniper"]
|
|
202
|
+
}
|
|
203
|
+
}</code></pre>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<script>
|
|
210
|
+
document.getElementById('test-jira-btn').addEventListener('click', function() {
|
|
211
|
+
var btn = this;
|
|
212
|
+
var resultSpan = document.getElementById('jira-result');
|
|
213
|
+
|
|
214
|
+
btn.disabled = true;
|
|
215
|
+
btn.innerHTML = '<i class="mdi mdi-loading mdi-spin"></i> Testing...';
|
|
216
|
+
resultSpan.innerHTML = '';
|
|
217
|
+
|
|
218
|
+
fetch('{% url "plugins:netbox_atlassian:test_jira" %}', {
|
|
219
|
+
method: 'POST',
|
|
220
|
+
headers: {
|
|
221
|
+
'X-CSRFToken': '{{ csrf_token }}',
|
|
222
|
+
'Content-Type': 'application/json'
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
.then(response => response.json())
|
|
226
|
+
.then(data => {
|
|
227
|
+
if (data.success) {
|
|
228
|
+
resultSpan.innerHTML = '<span class="text-success"><i class="mdi mdi-check-circle"></i> ' + data.message + '</span>';
|
|
229
|
+
} else {
|
|
230
|
+
resultSpan.innerHTML = '<span class="text-danger"><i class="mdi mdi-alert-circle"></i> ' + data.error + '</span>';
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
.catch(error => {
|
|
234
|
+
resultSpan.innerHTML = '<span class="text-danger"><i class="mdi mdi-alert-circle"></i> Request failed</span>';
|
|
235
|
+
})
|
|
236
|
+
.finally(() => {
|
|
237
|
+
btn.disabled = false;
|
|
238
|
+
btn.innerHTML = '<i class="mdi mdi-connection"></i> Test Jira Connection';
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
document.getElementById('test-confluence-btn').addEventListener('click', function() {
|
|
243
|
+
var btn = this;
|
|
244
|
+
var resultSpan = document.getElementById('confluence-result');
|
|
245
|
+
|
|
246
|
+
btn.disabled = true;
|
|
247
|
+
btn.innerHTML = '<i class="mdi mdi-loading mdi-spin"></i> Testing...';
|
|
248
|
+
resultSpan.innerHTML = '';
|
|
249
|
+
|
|
250
|
+
fetch('{% url "plugins:netbox_atlassian:test_confluence" %}', {
|
|
251
|
+
method: 'POST',
|
|
252
|
+
headers: {
|
|
253
|
+
'X-CSRFToken': '{{ csrf_token }}',
|
|
254
|
+
'Content-Type': 'application/json'
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
.then(response => response.json())
|
|
258
|
+
.then(data => {
|
|
259
|
+
if (data.success) {
|
|
260
|
+
resultSpan.innerHTML = '<span class="text-success"><i class="mdi mdi-check-circle"></i> ' + data.message + '</span>';
|
|
261
|
+
} else {
|
|
262
|
+
resultSpan.innerHTML = '<span class="text-danger"><i class="mdi mdi-alert-circle"></i> ' + data.error + '</span>';
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
.catch(error => {
|
|
266
|
+
resultSpan.innerHTML = '<span class="text-danger"><i class="mdi mdi-alert-circle"></i> Request failed</span>';
|
|
267
|
+
})
|
|
268
|
+
.finally(() => {
|
|
269
|
+
btn.disabled = false;
|
|
270
|
+
btn.innerHTML = '<i class="mdi mdi-connection"></i> Test Confluence Connection';
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
</script>
|
|
274
|
+
{% endblock %}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
{% extends 'virtualization/virtualmachine/base.html' %}
|
|
2
|
+
{% load helpers %}
|
|
3
|
+
|
|
4
|
+
{% block content %}
|
|
5
|
+
<div class="row">
|
|
6
|
+
<div class="col-12">
|
|
7
|
+
<!-- Search Terms Info -->
|
|
8
|
+
<div class="alert alert-info mb-3">
|
|
9
|
+
<i class="mdi mdi-magnify"></i>
|
|
10
|
+
<strong>Searching for:</strong>
|
|
11
|
+
{% for term in search_terms %}
|
|
12
|
+
<span class="badge bg-secondary text-white ms-1">{{ term }}</span>{% if not forloop.last %}<span class="text-muted ms-1">OR</span>{% endif %}
|
|
13
|
+
{% empty %}
|
|
14
|
+
<span class="text-muted">No search terms available</span>
|
|
15
|
+
{% endfor %}
|
|
16
|
+
<br>
|
|
17
|
+
<small class="text-muted">
|
|
18
|
+
Fields: {% for field in enabled_fields %}{{ field.name }}{% if not forloop.last %}, {% endif %}{% endfor %}
|
|
19
|
+
</small>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="row">
|
|
23
|
+
<!-- Jira Issues Column -->
|
|
24
|
+
<div class="col-md-6 mb-4">
|
|
25
|
+
<div class="card h-100">
|
|
26
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
27
|
+
<h5 class="card-title mb-0">
|
|
28
|
+
<i class="mdi mdi-jira text-primary"></i> Jira Issues
|
|
29
|
+
{% if jira_results.total > 0 %}
|
|
30
|
+
<span class="badge bg-primary text-white">{{ jira_results.total }} issue{{ jira_results.total|pluralize }}</span>
|
|
31
|
+
{% endif %}
|
|
32
|
+
</h5>
|
|
33
|
+
{% if jira_url %}
|
|
34
|
+
<a href="{{ jira_url }}" target="_blank" class="btn btn-sm btn-outline-primary">
|
|
35
|
+
<i class="mdi mdi-open-in-new"></i> Open Jira
|
|
36
|
+
</a>
|
|
37
|
+
{% endif %}
|
|
38
|
+
</div>
|
|
39
|
+
<div class="card-body">
|
|
40
|
+
{% if not jira_configured %}
|
|
41
|
+
<div class="alert alert-warning mb-0">
|
|
42
|
+
<i class="mdi mdi-alert"></i> Jira not configured
|
|
43
|
+
</div>
|
|
44
|
+
{% elif jira_results.error %}
|
|
45
|
+
<div class="alert alert-danger mb-0">
|
|
46
|
+
<i class="mdi mdi-alert-circle"></i> {{ jira_results.error }}
|
|
47
|
+
</div>
|
|
48
|
+
{% elif jira_results.issues %}
|
|
49
|
+
<div class="table-responsive">
|
|
50
|
+
<table class="table table-sm table-hover mb-0">
|
|
51
|
+
<thead>
|
|
52
|
+
<tr>
|
|
53
|
+
<th>Key</th>
|
|
54
|
+
<th>Summary</th>
|
|
55
|
+
<th>Status</th>
|
|
56
|
+
<th>Type</th>
|
|
57
|
+
</tr>
|
|
58
|
+
</thead>
|
|
59
|
+
<tbody>
|
|
60
|
+
{% for issue in jira_results.issues %}
|
|
61
|
+
<tr>
|
|
62
|
+
<td>
|
|
63
|
+
<a href="{{ issue.url }}" target="_blank" class="fw-bold">
|
|
64
|
+
{{ issue.key }}
|
|
65
|
+
</a>
|
|
66
|
+
</td>
|
|
67
|
+
<td>
|
|
68
|
+
<span title="{{ issue.summary }}">
|
|
69
|
+
{{ issue.summary|truncatechars:50 }}
|
|
70
|
+
</span>
|
|
71
|
+
</td>
|
|
72
|
+
<td>
|
|
73
|
+
{% if issue.status_category == 'done' %}
|
|
74
|
+
<span class="badge bg-success">{{ issue.status }}</span>
|
|
75
|
+
{% elif issue.status_category == 'indeterminate' %}
|
|
76
|
+
<span class="badge bg-primary">{{ issue.status }}</span>
|
|
77
|
+
{% else %}
|
|
78
|
+
<span class="badge bg-secondary">{{ issue.status }}</span>
|
|
79
|
+
{% endif %}
|
|
80
|
+
</td>
|
|
81
|
+
<td>
|
|
82
|
+
{% if issue.type_icon %}
|
|
83
|
+
<img src="{{ issue.type_icon }}" alt="{{ issue.type }}" width="16" height="16" class="me-1">
|
|
84
|
+
{% endif %}
|
|
85
|
+
{{ issue.type }}
|
|
86
|
+
</td>
|
|
87
|
+
</tr>
|
|
88
|
+
{% endfor %}
|
|
89
|
+
</tbody>
|
|
90
|
+
</table>
|
|
91
|
+
</div>
|
|
92
|
+
{% if jira_results.total > jira_results.issues|length %}
|
|
93
|
+
<div class="text-muted small mt-2">
|
|
94
|
+
Showing {{ jira_results.issues|length }} of {{ jira_results.total }} results
|
|
95
|
+
</div>
|
|
96
|
+
{% endif %}
|
|
97
|
+
{% else %}
|
|
98
|
+
<div class="text-muted text-center py-4">
|
|
99
|
+
<i class="mdi mdi-information-outline fs-1"></i>
|
|
100
|
+
<p class="mb-0">No Jira issues found</p>
|
|
101
|
+
</div>
|
|
102
|
+
{% endif %}
|
|
103
|
+
|
|
104
|
+
{% if jira_results.cached %}
|
|
105
|
+
<div class="text-muted small mt-2">
|
|
106
|
+
<i class="mdi mdi-cached"></i> Results from cache
|
|
107
|
+
</div>
|
|
108
|
+
{% endif %}
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<!-- Confluence Pages Column -->
|
|
114
|
+
<div class="col-md-6 mb-4">
|
|
115
|
+
<div class="card h-100">
|
|
116
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
117
|
+
<h5 class="card-title mb-0">
|
|
118
|
+
<i class="mdi mdi-file-document text-info"></i> Confluence Pages
|
|
119
|
+
{% if confluence_results.total > 0 %}
|
|
120
|
+
<span class="badge bg-info text-white">{{ confluence_results.total }} page{{ confluence_results.total|pluralize }}</span>
|
|
121
|
+
{% endif %}
|
|
122
|
+
</h5>
|
|
123
|
+
{% if confluence_url %}
|
|
124
|
+
<a href="{{ confluence_url }}" target="_blank" class="btn btn-sm btn-outline-info">
|
|
125
|
+
<i class="mdi mdi-open-in-new"></i> Open Confluence
|
|
126
|
+
</a>
|
|
127
|
+
{% endif %}
|
|
128
|
+
</div>
|
|
129
|
+
<div class="card-body">
|
|
130
|
+
{% if not confluence_configured %}
|
|
131
|
+
<div class="alert alert-warning mb-0">
|
|
132
|
+
<i class="mdi mdi-alert"></i> Confluence not configured
|
|
133
|
+
</div>
|
|
134
|
+
{% elif confluence_results.error %}
|
|
135
|
+
<div class="alert alert-danger mb-0">
|
|
136
|
+
<i class="mdi mdi-alert-circle"></i> {{ confluence_results.error }}
|
|
137
|
+
</div>
|
|
138
|
+
{% elif confluence_results.pages %}
|
|
139
|
+
<div class="list-group list-group-flush">
|
|
140
|
+
{% for page in confluence_results.pages %}
|
|
141
|
+
<a href="{{ page.url }}" target="_blank" class="list-group-item list-group-item-action">
|
|
142
|
+
<div class="d-flex w-100 justify-content-between">
|
|
143
|
+
<h6 class="mb-1">{{ page.title }}</h6>
|
|
144
|
+
<small class="text-muted">{{ page.space_key }}</small>
|
|
145
|
+
</div>
|
|
146
|
+
{% if page.breadcrumb %}
|
|
147
|
+
<small class="text-muted">{{ page.breadcrumb }}</small>
|
|
148
|
+
{% endif %}
|
|
149
|
+
<small class="text-muted d-block">
|
|
150
|
+
Last modified by {{ page.last_modified_by }}
|
|
151
|
+
</small>
|
|
152
|
+
</a>
|
|
153
|
+
{% endfor %}
|
|
154
|
+
</div>
|
|
155
|
+
{% if confluence_results.total > confluence_results.pages|length %}
|
|
156
|
+
<div class="text-muted small mt-2">
|
|
157
|
+
Showing {{ confluence_results.pages|length }} of {{ confluence_results.total }} results
|
|
158
|
+
</div>
|
|
159
|
+
{% endif %}
|
|
160
|
+
{% else %}
|
|
161
|
+
<div class="text-muted text-center py-4">
|
|
162
|
+
<i class="mdi mdi-information-outline fs-1"></i>
|
|
163
|
+
<p class="mb-0">No Confluence pages found</p>
|
|
164
|
+
</div>
|
|
165
|
+
{% endif %}
|
|
166
|
+
|
|
167
|
+
{% if confluence_results.cached %}
|
|
168
|
+
<div class="text-muted small mt-2">
|
|
169
|
+
<i class="mdi mdi-cached"></i> Results from cache
|
|
170
|
+
</div>
|
|
171
|
+
{% endif %}
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
{% endblock %}
|
netbox_atlassian/urls.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
URL patterns for NetBox Atlassian Plugin
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from django.urls import path
|
|
6
|
+
|
|
7
|
+
from .views import (
|
|
8
|
+
AtlassianSettingsView,
|
|
9
|
+
TestConfluenceConnectionView,
|
|
10
|
+
TestJiraConnectionView,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
urlpatterns = [
|
|
14
|
+
path("settings/", AtlassianSettingsView.as_view(), name="settings"),
|
|
15
|
+
path("test-jira/", TestJiraConnectionView.as_view(), name="test_jira"),
|
|
16
|
+
path("test-confluence/", TestConfluenceConnectionView.as_view(), name="test_confluence"),
|
|
17
|
+
]
|