|
1 |
| -{% extends 'layouts/layout_old.html' %} |
2 |
| - |
3 |
| -{% block css.custom %} |
4 |
| - <link rel='stylesheet' href='/static/css/covmanager.css'> |
5 |
| -{% endblock css.custom %} |
| 1 | +{% extends 'layouts/layout_base.html' %} |
6 | 2 |
|
7 | 3 | {% block title %}Collections{% endblock title %}
|
8 | 4 |
|
9 | 5 | {% block body_content %}
|
10 |
| -<div id="main" class="panel panel-default"> |
11 |
| - <div class="panel-heading"><i class="bi bi-clipboard-data"></i> Coverage Collections</div> |
12 |
| - <div class="panel-body"> |
13 |
| - <div class="panel panel-default" style="float: left;"> |
14 |
| - <div class="panel-heading"><i class="bi bi-archive-fill"></i> Repository Filters</div> |
15 |
| - <div class="panel-body"> |
16 |
| - <div> |
17 |
| - <label>Repository:</label><input type="text" class="form-control" v-model="search.repository.value" |
18 |
| - @focus="suggestions.repository.enabled = true" @blur="suggestions.repository.enabled = false"/> |
19 |
| - <div v-show="suggestions.repository.enabled"> |
20 |
| - <div v-for="name in suggestions.repository.value" v-on:mousedown="take_suggestion('repository', name)">!{ name }!</div> |
21 |
| - </div> |
22 |
| - </div> |
23 |
| - <div><label>Revision:</label><input type="text" class="form-control" v-model="search.revision.value"/></div> |
24 |
| - <div><label>Branch:</label><input type="text" class="form-control" v-model="search.branch.value"/></div> |
25 |
| - </div> |
26 |
| - </div> |
27 |
| - |
28 |
| - <div class="panel panel-default" style="float: left;"> |
29 |
| - <div class="panel-heading"><i class="bi bi-funnel-fill"></i> Misc Filters</div> |
30 |
| - <div class="panel-body"> |
31 |
| - <div> |
32 |
| - <label>Tool:</label> <input type="text" class="form-control" v-model="search.tools.value" |
33 |
| - @focus="suggestions.tools.enabled = true" @blur="suggestions.tools.enabled = false"/> |
34 |
| - <div v-show="suggestions.tools.enabled"> |
35 |
| - <div v-for="name in suggestions.tools.value" v-on:mousedown="take_suggestion('tools', name)">!{ name }!</div> |
36 |
| - </div> |
37 |
| - </div> |
38 |
| - <div><label>Description:</label> <input type="text" class="form-control" v-model="search.description.value"/></div> |
39 |
| - <div><label>Result Limit:</label> <input type="text" class="form-control" v-model="search.limit.value"/></div> |
40 |
| - </div> |
41 |
| - </div> |
42 |
| - |
43 |
| - <div class="panel panel-default" style="float: left;"> |
44 |
| - <div class="panel-heading"><i class="bi bi-calendar-range"></i> Date Filters</div> |
45 |
| - <div class="panel-body"> |
46 |
| - <!-- |
47 |
| - <label>Newer than:</label><datepicker placeholder="Select Date"></datepicker> |
48 |
| - <label>Older than:</label><datepicker placeholder="Select Date"></datepicker> |
49 |
| - --> |
50 |
| - </div> |
51 |
| - </div> |
52 |
| - |
53 |
| - <div class="panel panel-default" style="float: right;"> |
54 |
| - <div class="panel-heading"><i class="bi bi-lightning-charge-fill"></i> Actions</div> |
55 |
| - <div class="panel-body"> |
56 |
| - <button @click="navigate('diff')" class="btn btn-default">View Differences</button> |
57 |
| - <button @click="aggregate()" class="btn btn-default">Aggregate</button> |
58 |
| - <button @click="navigate('patch')" class="btn btn-default">Patch Analysis</button> |
59 |
| - <button @click="summary()" class="btn btn-default">Report Summary</button> |
60 |
| - </div> |
61 |
| - </div> |
62 |
| - |
63 |
| - </div> |
64 |
| - <table class="table table-condensed table-hover table-bordered table-db"> |
65 |
| - <thead> |
66 |
| - <tr> |
67 |
| - <th @click="sortBy('id')" :class="{ active: sortKey == 'id' }" style="width: 25px;">ID</th> |
68 |
| - <th @click="sortBy('created')" :class="{ active: sortKey == 'created' }" style="width: 80px;">Created</th> |
69 |
| - <th @click="sortBy('repository')" :class="{ active: sortKey == 'repository' }" style="width: 50px;">Repository</th> |
70 |
| - <th @click="sortBy('revision')" :class="{ active: sortKey == 'revision' }" style="width: 100px;">Revision</th> |
71 |
| - <th @click="sortBy('branch')" :class="{ active: sortKey == 'branch' }" style="width: 50px;">Branch</th> |
72 |
| - <th @click="sortBy('tools')" :class="{ active: sortKey == 'tools' }" style="width: 50px;">Tools</th> |
73 |
| - <th @click="sortBy('description')" :class="{ active: sortKey == 'description' }" style="width: 150px;">Description</th> |
74 |
| - </tr> |
75 |
| - </thead> |
76 |
| - <tbody> |
77 |
| - <tr v-for="collection in ordered_collections" :id="collection.id" @click="collection_click_handler(collection.id)"> |
78 |
| - <td><a :href="`${collection.id}/browse`">!{ collection.id }!</a></td> |
79 |
| - <td>!{ formatDate(collection.created) }!</td> |
80 |
| - <td><a :href="`../repositories/`">!{ collection.repository }!</a></td> |
81 |
| - <td>!{ collection.revision }!</td> |
82 |
| - <td>!{ collection.branch }!</td> |
83 |
| - <td>!{ collection.tools }!</td> |
84 |
| - <td>!{ collection.description }!</td> |
85 |
| - </tr> |
86 |
| - </tbody> |
87 |
| - </table> |
88 |
| -</div> |
89 |
| - |
90 |
| -<script> |
91 |
| -let URLS = { |
92 |
| - collections_api: '{% url 'covmanager:collections_api' %}', |
93 |
| - diff: '{% url 'covmanager:collections_diff' %}', |
94 |
| - patch: '{% url 'covmanager:collections_patch' %}', |
95 |
| - aggregate: '{% url 'covmanager:collections_aggregate_api' %}', |
96 |
| - repository_search : '{% url 'covmanager:repositories_search_api' %}', |
97 |
| - tools_search : '{% url 'covmanager:tools_search_api' %}', |
98 |
| -} |
99 |
| - |
100 |
| -let pmanager = new HashParamManager() |
101 |
| - |
102 |
| -const app = Vue.createApp({ |
103 |
| - delimiters: ['!{', '}!'], |
104 |
| - data() { |
105 |
| - return { |
106 |
| - collections: null, |
107 |
| - sortKey: "", |
108 |
| - reverse: false, |
109 |
| - block_fetch: true, |
110 |
| - search_initialized: false, |
111 |
| - |
112 |
| - search: { |
113 |
| - branch: { value : "", contains : true }, |
114 |
| - description: { value : "", contains : true }, |
115 |
| - repository: { value : "", contains: true, postfix: "__name" }, |
116 |
| - revision: { value : "", contains : true }, |
117 |
| - tools: { value : "", contains: true, postfix: "__name" }, |
118 |
| - limit: { value : "10", contains : false }, |
119 |
| - }, |
120 |
| - |
121 |
| - suggestions: { |
122 |
| - repository: { value: [], enabled : false}, |
123 |
| - tools: { value: [], enabled : false}, |
124 |
| - }, |
125 |
| - |
126 |
| - selected_collections: [], |
127 |
| - } |
128 |
| - }, |
129 |
| - created() { |
130 |
| - const self = this |
131 |
| - pmanager.forEach(function(k,v) { |
132 |
| - // TODO: This is a shortcut that saves us iterating through this.search |
133 |
| - // for every key that we are trying to map to a field in our search |
134 |
| - // object, but this won't work once we have more postfixes. |
135 |
| - k = k.replace(/__contains$/, '') |
136 |
| - k = k.replace(/__name$/, '') |
137 |
| - |
138 |
| - if (k != "" && k in self.search) { |
139 |
| - self.search[k].value = v |
140 |
| - } |
141 |
| - }) |
142 |
| - |
143 |
| - self.fetch() |
144 |
| - }, |
145 |
| - watch: { |
146 |
| - // This handles all search updates |
147 |
| - search: { handler: 'fetch', deep: true }, |
148 |
| - |
149 |
| - // Watches for changes to values that need suggestions |
150 |
| - 'search.repository.value': function() { |
151 |
| - this.update_suggestions('repository') |
152 |
| - }, |
153 |
| - 'search.tools.value': function() { |
154 |
| - this.update_suggestions('tools') |
155 |
| - }, |
156 |
| - collections: function() { |
157 |
| - /* Whenever our search results change, deselect everything */ |
158 |
| - this.selected_collections = [] |
159 |
| - for (let i = 0; i < this.collections.length; ++i) { |
160 |
| - let target = $("#" + this.collections[i].id) |
161 |
| - if (target) |
162 |
| - target.toggleClass("collection-selected", false) |
163 |
| - } |
164 |
| - } |
165 |
| - }, |
166 |
| - computed: { |
167 |
| - ordered_collections() { |
168 |
| - return _.orderBy(this.collections, [this.sortKey], [this.reverse ? 'desc' : 'asc']) |
169 |
| - }, |
170 |
| - }, |
171 |
| - methods: { |
172 |
| - apiurl: function() { |
173 |
| - let url = URLS.collections_api |
174 |
| - |
175 |
| - for (k in this.search) { |
176 |
| - let obj = this.search[k] |
177 |
| - let v = obj.value |
178 |
| - |
179 |
| - if ("postfix" in obj) { |
180 |
| - k += obj.postfix |
181 |
| - } |
182 |
| - |
183 |
| - if ("contains" in obj && obj.contains) { |
184 |
| - k += "__contains" |
185 |
| - } |
186 |
| - |
187 |
| - pmanager.update_value(k, v) |
188 |
| - } |
189 |
| - |
190 |
| - let query = pmanager.get_query() |
191 |
| - |
192 |
| - if (query) { |
193 |
| - url += "?" + query |
194 |
| - pmanager.update_hash() |
195 |
| - } |
196 |
| - |
197 |
| - return url; |
198 |
| - }, |
199 |
| - fetch: _.throttle (function () { |
200 |
| - this.loading = true |
201 |
| - fetch(this.apiurl(), { |
202 |
| - method: 'GET', |
203 |
| - credentials: 'same-origin' |
204 |
| - }).then(response => { |
205 |
| - if (response.ok) { |
206 |
| - return response.json() |
207 |
| - } |
208 |
| - swal('Oops', E_SERVER_ERROR, 'error') |
209 |
| - this.loading = false |
210 |
| - }).then(json => { |
211 |
| - this.collections = json["results"] |
212 |
| - this.loading = false |
213 |
| - this.block_fetch = false |
214 |
| - }) |
215 |
| - }, 500), |
216 |
| - navigate: function (dst) { |
217 |
| - let ids = [] |
218 |
| - |
219 |
| - if (this.selected_collections.length > 0) { |
220 |
| - ids = this.selected_collections; |
221 |
| - } else { |
222 |
| - for (let i = 0; i < this.ordered_collections.length; ++i) { |
223 |
| - ids.push(this.ordered_collections[i].id) |
224 |
| - } |
225 |
| - } |
226 |
| - |
227 |
| - if (ids) { |
228 |
| - let url = URLS[dst] + '#ids=' + ids.join(',') |
229 |
| - if (window.location.hash) { |
230 |
| - url += "&" + window.location.hash.substr(1) |
231 |
| - } |
232 |
| - |
233 |
| - var win = window.open(url, '_blank'); |
234 |
| - win.focus(); |
235 |
| - } |
236 |
| - }, |
237 |
| - sortBy: function (sortKey) { |
238 |
| - this.reverse = (this.sortKey === sortKey) ? !this.reverse : false |
239 |
| - this.sortKey = sortKey |
240 |
| - }, |
241 |
| - update_suggestions: _.throttle (function (key) { |
242 |
| - fetch(URLS[key + '_search'] + "?name=" + this.search[key].value, { |
243 |
| - method: 'GET', |
244 |
| - credentials: 'same-origin' |
245 |
| - }).then(response => { |
246 |
| - if (response.ok) { |
247 |
| - return response.json() |
248 |
| - } |
249 |
| - swal('Oops', E_SERVER_ERROR, 'error') |
250 |
| - }).then(json => { |
251 |
| - this.suggestions[key].value = json["results"] |
252 |
| - }) |
253 |
| - }, 500), |
254 |
| - take_suggestion: function(key, val) { |
255 |
| - this.suggestions[key].enabled = false |
256 |
| - this.suggestions[key].value = [] |
257 |
| - this.search[key].value = val |
258 |
| - }, |
259 |
| - aggregate: function() { |
260 |
| - let ids = [] |
261 |
| - |
262 |
| - if (this.selected_collections.length > 0) { |
263 |
| - ids = this.selected_collections; |
264 |
| - } else { |
265 |
| - for (let i = 0; i < this.ordered_collections.length; ++i) { |
266 |
| - ids.push(this.ordered_collections[i].id) |
267 |
| - } |
268 |
| - } |
269 |
| - |
270 |
| - if (ids) { |
271 |
| - swal({ |
272 |
| - title: "Create Collection Aggregation", |
273 |
| - text: 'Enter optional new description for result collection', |
274 |
| - content: 'input', |
275 |
| - buttons: true, |
276 |
| - }).then(description => { |
277 |
| - if (!description) { |
278 |
| - /* User pressed cancel button */ |
279 |
| - return; |
280 |
| - } |
281 |
| - |
282 |
| - // Call API, get new collection id back, then navigate to it |
283 |
| - this.loading = true |
284 |
| - |
285 |
| - let data = { |
286 |
| - 'ids': ids.join(",") |
287 |
| - } |
288 |
| - if (description) { |
289 |
| - data['description'] = description |
290 |
| - } |
291 |
| - |
292 |
| - fetch(URLS.aggregate, { |
293 |
| - method: 'POST', |
294 |
| - credentials: 'same-origin', |
295 |
| - headers: { |
296 |
| - "Content-Type": "application/json; charset=utf-8", |
297 |
| - "X-Requested-With": "XMLHttpRequest" |
298 |
| - }, |
299 |
| - body: JSON.stringify(data), |
300 |
| - }) |
301 |
| - .then(response => { |
302 |
| - this.loading = false |
303 |
| - if (response.ok) { |
304 |
| - return response.json() |
305 |
| - } |
306 |
| - swal('Oops', E_SERVER_ERROR, 'error') |
307 |
| - }) |
308 |
| - .then(json => { |
309 |
| - let newid = json["newid"] |
310 |
| - if (newid) { |
311 |
| - window.open(json["newid"] + "/browse", '_self'); |
312 |
| - } else { |
313 |
| - swal('Oops', E_SERVER_ERROR, 'error') |
314 |
| - } |
315 |
| - }) |
316 |
| - }) |
317 |
| - } |
318 |
| - }, |
319 |
| - summary: function() { |
320 |
| - let target_collection = null |
321 |
| - |
322 |
| - if (this.selected_collections.length == 1) { |
323 |
| - target_collection = this.selected_collections[0] |
324 |
| - } else if (this.collections.length == 1) { |
325 |
| - target_collection = this.collections[0].pk |
326 |
| - } else { |
327 |
| - swal('Error', "Function requires exactly one collection to be selected.", 'error') |
328 |
| - return |
329 |
| - } |
330 |
| - |
331 |
| - window.open(`${target_collection}/summary/`, '_blank').focus() |
332 |
| - }, |
333 |
| - collection_click_handler: function(id) { |
334 |
| - var self = this |
335 |
| - let idx = self.selected_collections.indexOf(id) |
336 |
| - let target = $("#" + id) |
337 | 6 |
|
338 |
| - if (idx < 0) { |
339 |
| - self.selected_collections.push(id) |
340 |
| - if (target) |
341 |
| - target.toggleClass("collection-selected", true) |
342 |
| - } else { |
343 |
| - self.selected_collections.splice(idx, 1) |
344 |
| - if (target) |
345 |
| - target.toggleClass("collection-selected", false) |
346 |
| - } |
347 |
| - }, |
348 |
| - formatDate: function (datetime){ |
349 |
| - return formatClientTimestamp(datetime) |
350 |
| - } |
351 |
| - } |
352 |
| -}) |
| 7 | +<collectionslist /> |
353 | 8 |
|
354 |
| -app.mount('#main') |
355 |
| -</script> |
356 | 9 | {% endblock body_content %}
|
0 commit comments