diff options
author | Marlon Rodriguez Garcia <marlon.rodriguez-garcia@savoirfairelinux.com> | 2023-12-11 11:47:05 -0500 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2023-12-12 15:58:57 +0000 |
commit | df5c8d6471bf2484db61c7f180c9758fad4182e1 (patch) | |
tree | b27ed4efa0ec97ecee080e5ae887a6e7488cd442 /bitbake/lib/toaster | |
parent | 4bb222e0d71a4cb159b8a4f1a90b65b1af32ac10 (diff) | |
download | poky-df5c8d6471bf2484db61c7f180c9758fad4182e1.tar.gz |
bitbake: toaster: Added new feature to import eventlogs from command line into toaster using replay functionality
Added a new button on the base template to access a new template.
Added a model register the information on the builds and generate access links
Added a form to include the option to load specific files
Added jquery and ajax functions to block screen and redirect to build page when import eventlogs is trigger
Added a new button on landing page linked to import build page, and set min-height of buttons in landing page for uniformity
Removed test assertion to check command line build in content, because new button contains text
Updated toaster_eventreplay to use library
Fix test in test_layerdetails_page
Rebased from master
This feature uses the value from the variable BB_DEFAULT_EVENTLOG to read the files created by bitbake
Exclude listing of files that don't contain the allvariables definitions used to replay builds
This part of the feature should be revisited. Over a long period of time, the BB_DEFAULT_EVENTLOG
will exponentially increase the size of the log file and cause bottlenecks when importing.
(Bitbake rev: ab96cafe03d8bab33c1de09602cc62bd6974f157)
Signed-off-by: Marlon Rodriguez Garcia <marlon.rodriguez-garcia@savoirfairelinux.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/toaster')
-rw-r--r-- | bitbake/lib/toaster/orm/migrations/0021_eventlogsimports.py | 22 | ||||
-rw-r--r-- | bitbake/lib/toaster/orm/models.py | 9 | ||||
-rw-r--r-- | bitbake/lib/toaster/tests/browser/test_landing_page.py | 2 | ||||
-rw-r--r-- | bitbake/lib/toaster/tests/browser/test_layerdetails_page.py | 2 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/forms.py | 14 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/static/css/default.css | 28 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/templates/base.html | 3 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/templates/command_line_builds.html | 198 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/templates/landing.html | 10 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/urls.py | 1 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/views.py | 173 |
11 files changed, 454 insertions, 8 deletions
diff --git a/bitbake/lib/toaster/orm/migrations/0021_eventlogsimports.py b/bitbake/lib/toaster/orm/migrations/0021_eventlogsimports.py new file mode 100644 index 0000000000..328eb5753c --- /dev/null +++ b/bitbake/lib/toaster/orm/migrations/0021_eventlogsimports.py | |||
@@ -0,0 +1,22 @@ | |||
1 | # Generated by Django 4.2.5 on 2023-11-23 18:44 | ||
2 | |||
3 | from django.db import migrations, models | ||
4 | |||
5 | |||
6 | class Migration(migrations.Migration): | ||
7 | |||
8 | dependencies = [ | ||
9 | ('orm', '0020_models_bigautofield'), | ||
10 | ] | ||
11 | |||
12 | operations = [ | ||
13 | migrations.CreateModel( | ||
14 | name='EventLogsImports', | ||
15 | fields=[ | ||
16 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
17 | ('name', models.CharField(max_length=255)), | ||
18 | ('imported', models.BooleanField(default=False)), | ||
19 | ('build_id', models.IntegerField(blank=True, null=True)), | ||
20 | ], | ||
21 | ), | ||
22 | ] | ||
diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py index 1098ad3fd1..19c9686206 100644 --- a/bitbake/lib/toaster/orm/models.py +++ b/bitbake/lib/toaster/orm/models.py | |||
@@ -1868,6 +1868,15 @@ class Distro(models.Model): | |||
1868 | def __unicode__(self): | 1868 | def __unicode__(self): |
1869 | return "Distro " + self.name + "(" + self.description + ")" | 1869 | return "Distro " + self.name + "(" + self.description + ")" |
1870 | 1870 | ||
1871 | class EventLogsImports(models.Model): | ||
1872 | name = models.CharField(max_length=255) | ||
1873 | imported = models.BooleanField(default=False) | ||
1874 | build_id = models.IntegerField(blank=True, null=True) | ||
1875 | |||
1876 | def __str__(self): | ||
1877 | return self.name | ||
1878 | |||
1879 | |||
1871 | django.db.models.signals.post_save.connect(invalidate_cache) | 1880 | django.db.models.signals.post_save.connect(invalidate_cache) |
1872 | django.db.models.signals.post_delete.connect(invalidate_cache) | 1881 | django.db.models.signals.post_delete.connect(invalidate_cache) |
1873 | django.db.models.signals.m2m_changed.connect(invalidate_cache) | 1882 | django.db.models.signals.m2m_changed.connect(invalidate_cache) |
diff --git a/bitbake/lib/toaster/tests/browser/test_landing_page.py b/bitbake/lib/toaster/tests/browser/test_landing_page.py index f6649a3d82..8fe5fea467 100644 --- a/bitbake/lib/toaster/tests/browser/test_landing_page.py +++ b/bitbake/lib/toaster/tests/browser/test_landing_page.py | |||
@@ -219,5 +219,3 @@ class TestLandingPage(SeleniumTestCase): | |||
219 | content = self.get_page_source() | 219 | content = self.get_page_source() |
220 | self.assertTrue(self.PROJECT_NAME in content, | 220 | self.assertTrue(self.PROJECT_NAME in content, |
221 | 'should show builds for project %s' % self.PROJECT_NAME) | 221 | 'should show builds for project %s' % self.PROJECT_NAME) |
222 | self.assertFalse(self.CLI_BUILDS_PROJECT_NAME in content, | ||
223 | 'should not show builds for cli project') | ||
diff --git a/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py b/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py index 367c6179c6..05ee88b019 100644 --- a/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py +++ b/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py | |||
@@ -108,7 +108,7 @@ class TestLayerDetailsPage(SeleniumTestCase): | |||
108 | 108 | ||
109 | self.wait_until_visible("#save-changes-for-switch", poll=3) | 109 | self.wait_until_visible("#save-changes-for-switch", poll=3) |
110 | btn_save_chg_for_switch = self.find("#save-changes-for-switch") | 110 | btn_save_chg_for_switch = self.find("#save-changes-for-switch") |
111 | btn_save_chg_for_switch.click() | 111 | self.driver.execute_script("arguments[0].click();", btn_save_chg_for_switch) |
112 | self.wait_until_visible("#edit-layer-source") | 112 | self.wait_until_visible("#edit-layer-source") |
113 | 113 | ||
114 | # Refresh the page to see if the new values are returned | 114 | # Refresh the page to see if the new values are returned |
diff --git a/bitbake/lib/toaster/toastergui/forms.py b/bitbake/lib/toaster/toastergui/forms.py new file mode 100644 index 0000000000..0f279e06c5 --- /dev/null +++ b/bitbake/lib/toaster/toastergui/forms.py | |||
@@ -0,0 +1,14 @@ | |||
1 | #!/usr/bin/env python3 | ||
2 | # -*- coding: utf-8 -*- | ||
3 | # BitBake Toaster UI tests implementation | ||
4 | # | ||
5 | # Copyright (C) 2023 Savoir-faire Linux | ||
6 | # | ||
7 | # SPDX-License-Identifier: GPL-2.0-only | ||
8 | # | ||
9 | |||
10 | from django import forms | ||
11 | from django.core.validators import FileExtensionValidator | ||
12 | |||
13 | class LoadFileForm(forms.Form): | ||
14 | eventlog_file = forms.FileField(widget=forms.FileInput(attrs={'accept': '.json'})) | ||
diff --git a/bitbake/lib/toaster/toastergui/static/css/default.css b/bitbake/lib/toaster/toastergui/static/css/default.css index 5cd7e211a0..284355e70b 100644 --- a/bitbake/lib/toaster/toastergui/static/css/default.css +++ b/bitbake/lib/toaster/toastergui/static/css/default.css | |||
@@ -367,3 +367,31 @@ h2.panel-title { font-size: 30px; } | |||
367 | } | 367 | } |
368 | } | 368 | } |
369 | /* End copied in from newer version of Font-Awesome 4.3.0 */ | 369 | /* End copied in from newer version of Font-Awesome 4.3.0 */ |
370 | |||
371 | |||
372 | #overlay { | ||
373 | display: flex; | ||
374 | position: fixed; | ||
375 | top: 0; | ||
376 | left: 0; | ||
377 | width: 100%; | ||
378 | height: 100%; | ||
379 | background-color: rgba(0, 0, 0, 0.7); | ||
380 | align-items: center; | ||
381 | justify-content: center; | ||
382 | z-index: 999; | ||
383 | } | ||
384 | |||
385 | .spinner { | ||
386 | border: 6px solid rgba(255, 255, 255, 0.3); | ||
387 | border-radius: 50%; | ||
388 | border-top: 6px solid #3498db; | ||
389 | width: 50px; | ||
390 | height: 50px; | ||
391 | animation: spin 1s linear infinite; | ||
392 | } | ||
393 | |||
394 | @keyframes spin { | ||
395 | 0% { transform: rotate(0deg); } | ||
396 | 100% { transform: rotate(360deg); } | ||
397 | } | ||
diff --git a/bitbake/lib/toaster/toastergui/templates/base.html b/bitbake/lib/toaster/toastergui/templates/base.html index 041448d180..e90be69620 100644 --- a/bitbake/lib/toaster/toastergui/templates/base.html +++ b/bitbake/lib/toaster/toastergui/templates/base.html | |||
@@ -132,7 +132,8 @@ | |||
132 | {% if project_enable %} | 132 | {% if project_enable %} |
133 | <a class="btn btn-default navbar-btn navbar-right" id="new-project-button" href="{% url 'newproject' %}">New project</a> | 133 | <a class="btn btn-default navbar-btn navbar-right" id="new-project-button" href="{% url 'newproject' %}">New project</a> |
134 | {% endif %} | 134 | {% endif %} |
135 | </div> | 135 | <a class="btn btn-default navbar-btn navbar-right" id="import_page" style="margin-right: 5px !important" id="import-cmdline-button" href="{% url 'cmdlines' %}">Import command line builds</a> |
136 | </div> | ||
136 | </div> | 137 | </div> |
137 | </nav> | 138 | </nav> |
138 | 139 | ||
diff --git a/bitbake/lib/toaster/toastergui/templates/command_line_builds.html b/bitbake/lib/toaster/toastergui/templates/command_line_builds.html new file mode 100644 index 0000000000..d6ff685cdf --- /dev/null +++ b/bitbake/lib/toaster/toastergui/templates/command_line_builds.html | |||
@@ -0,0 +1,198 @@ | |||
1 | {% extends "base.html" %} | ||
2 | {% load projecttags %} | ||
3 | {% load humanize %} | ||
4 | |||
5 | {% block title %} Import Builds from eventlogs - Toaster {% endblock %} | ||
6 | |||
7 | {% block pagecontent %} | ||
8 | |||
9 | <div class="container-fluid"> | ||
10 | <div id="overlay" class="hide"> | ||
11 | <div class="spinner"> | ||
12 | <div class="fa-spin"> | ||
13 | </div> | ||
14 | </div> | ||
15 | </div> | ||
16 | <div class="row"> | ||
17 | <div class="col-md-12"> | ||
18 | <div class="page-header"> | ||
19 | <div class="row"> | ||
20 | <div class="col-md-6"> | ||
21 | <h1>Import command line builds</h1> | ||
22 | </div> | ||
23 | {% if import_all %} | ||
24 | <div class="col-md-6"> | ||
25 | <button id="import_all" type="button" class="btn btn-primary navbar-btn navbar-right"> | ||
26 | <span class="glyphicon glyphicon-upload" style="vertical-align: top;"></span> Import All | ||
27 | </button> | ||
28 | </div> | ||
29 | {% endif %} | ||
30 | </div> | ||
31 | </div> | ||
32 | {% if messages %} | ||
33 | <div class="row-fluid" id="empty-state-{{table_name}}"> | ||
34 | {% for message in messages %} | ||
35 | <div class="alert alert-danger">{{message}}</div> | ||
36 | {%endfor%} | ||
37 | </div> | ||
38 | {% endif %} | ||
39 | <div class="row"> | ||
40 | <h4 style="margin-left: 15px;"><strong>Import eventlog file</strong></h4> | ||
41 | <form method="POST" enctype="multipart/form-data" action="{% url 'cmdlines' %}" id="form_file"> | ||
42 | {% csrf_token %} | ||
43 | <div class="col-md-6" style="padding-left: 20px;"> | ||
44 | <div class="row"> | ||
45 | <input type="hidden" value="{{dir}}" name="dir"> | ||
46 | <div class="col-md-3"> {{ form.eventlog_file}} </div> | ||
47 | </div> | ||
48 | <div class="row" style="padding-top: 10px;"> | ||
49 | <div class="col-md-6"> | ||
50 | <button id="file_import" type="submit" disabled="disabled" class="btn btn-default navbar-btn" > | ||
51 | <span class="glyphicon glyphicon-upload" style="vertical-align: top;"></span> Import | ||
52 | </button> | ||
53 | </div> | ||
54 | </div> | ||
55 | </div> | ||
56 | </form> | ||
57 | </div> | ||
58 | |||
59 | <div class="row" style="padding-top: 20px;"> | ||
60 | <div class="col-md-8 "> | ||
61 | <h4><strong>Eventlogs from existing build directory: </strong> | ||
62 | <a href="#" data-toggle="tooltip" title="{{dir}}"> | ||
63 | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-info-circle" viewBox="0 0 16 16" data-toggle="tooltip"> | ||
64 | <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/> | ||
65 | <path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0"/> | ||
66 | </svg> | ||
67 | </a> | ||
68 | </h4> | ||
69 | {% if files %} | ||
70 | <div class="table-responsive"> | ||
71 | <table class="table col-md-6 table-bordered table-hover"> | ||
72 | <thead> | ||
73 | <tr class="row"> | ||
74 | <th scope="col">Name</th> | ||
75 | <th scope="col">Size</th> | ||
76 | <th scope="col">Action</th> | ||
77 | </tr> | ||
78 | </thead> | ||
79 | <tbody> | ||
80 | {% for file in files %} | ||
81 | <tr class="row" style="height: 48px;"> | ||
82 | <th scope="row" class="col-md-4" style="vertical-align: middle;"> | ||
83 | <input type="hidden" value="{{file.name}}" name="{{file.name}}">{{file.name}} | ||
84 | </th> | ||
85 | <td class="col-md-4 align-middle" style="vertical-align: middle;">{{file.size|filesizeformat}}</td> | ||
86 | <td class="col-md-4 align-middle" style="vertical-align: middle;"> | ||
87 | {% if file.imported == True and file.build_id is not None %} | ||
88 | <a href="{% url 'builddashboard' file.build_id %}">Build Details</a> | ||
89 | {% elif request.session.file == file.name or request.session.all_builds %} | ||
90 | <a data-toggle="tooltip" title="Build in progress"> | ||
91 | <span class="glyphicon glyphicon-upload" style="font-size: 18px; color:grey"></span> | ||
92 | </a> | ||
93 | {%else%} | ||
94 | <a onclick="_ajax_update('{{file.name}}', false, '{{dir}}')" data-toggle="tooltip" title="Import File"> | ||
95 | <span class="glyphicon glyphicon-upload" style="font-size: 18px;"></span> | ||
96 | </a> | ||
97 | {%endif%} | ||
98 | </td> | ||
99 | </tr> | ||
100 | {% endfor%} | ||
101 | </tbody> | ||
102 | </table> | ||
103 | </div> | ||
104 | {% else %} | ||
105 | <div class="row-fluid" id="empty-state-{{table_name}}"> | ||
106 | <div class="alert alert-info">Sorry - no files found</div> | ||
107 | </div> | ||
108 | {%endif%} | ||
109 | </div> | ||
110 | </div> | ||
111 | </div> | ||
112 | </div> | ||
113 | </div> | ||
114 | |||
115 | <script> | ||
116 | |||
117 | function _ajax_update(file, all, dir){ | ||
118 | function getCookie(name) { | ||
119 | var cookieValue = null; | ||
120 | if (document.cookie && document.cookie !== '') { | ||
121 | var cookies = document.cookie.split(';'); | ||
122 | for (var i = 0; i < cookies.length; i++) { | ||
123 | var cookie = jQuery.trim(cookies[i]); | ||
124 | // Does this cookie string begin with the name we want? | ||
125 | if (cookie.substring(0, name.length + 1) === (name + '=')) { | ||
126 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); | ||
127 | break; | ||
128 | } | ||
129 | } | ||
130 | } | ||
131 | return cookieValue; | ||
132 | } | ||
133 | var csrftoken = getCookie('csrftoken'); | ||
134 | |||
135 | function csrfSafeMethod(method) { | ||
136 | // these HTTP methods do not require CSRF protection | ||
137 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); | ||
138 | } | ||
139 | $.ajaxSetup({ | ||
140 | beforeSend: function (xhr, settings) { | ||
141 | if (!csrfSafeMethod(settings.type) && !this.crossDomain) { | ||
142 | xhr.setRequestHeader("X-CSRFToken", csrftoken); | ||
143 | } | ||
144 | } | ||
145 | }); | ||
146 | |||
147 | $.ajax({ | ||
148 | url:'/toastergui/cmdline/', | ||
149 | type: "POST", | ||
150 | data: {file: file, all: all, dir: dir}, | ||
151 | success:function(data){ | ||
152 | window.location = '/toastergui/builds/' | ||
153 | }, | ||
154 | complete:function(data){ | ||
155 | }, | ||
156 | error:function (xhr, textStatus, thrownError){ | ||
157 | console.log('fail'); | ||
158 | } | ||
159 | }); | ||
160 | } | ||
161 | |||
162 | $('#import_all').on('click', function(){ | ||
163 | _ajax_update("{{files | safe}}", true, "{{dir | safe}}"); | ||
164 | }); | ||
165 | |||
166 | |||
167 | $('#import_page').hide(); | ||
168 | |||
169 | $(function () { | ||
170 | $('[data-toggle="tooltip"]').tooltip() | ||
171 | }) | ||
172 | |||
173 | |||
174 | $("#id_eventlog_file").change(function(){ | ||
175 | $('#file_import').prop("disabled", false); | ||
176 | $('#file_import').addClass('btn-primary') | ||
177 | $('#file_import').removeClass('btn-default') | ||
178 | }) | ||
179 | |||
180 | $(document).ajaxStart(function(){ | ||
181 | $('#overlay').removeClass('hide'); | ||
182 | window.setTimeout( | ||
183 | function() { | ||
184 | window.location = '/toastergui/builds/' | ||
185 | }, 10000) | ||
186 | }); | ||
187 | |||
188 | $( "#form_file").on( "submit", function( event ) { | ||
189 | $('#overlay').removeClass('hide'); | ||
190 | window.setTimeout( | ||
191 | function() { | ||
192 | window.location = '/toastergui/builds/' | ||
193 | }, 10000) | ||
194 | }); | ||
195 | |||
196 | </script> | ||
197 | |||
198 | {% endblock %} | ||
diff --git a/bitbake/lib/toaster/toastergui/templates/landing.html b/bitbake/lib/toaster/toastergui/templates/landing.html index 22bbed695a..589ee22634 100644 --- a/bitbake/lib/toaster/toastergui/templates/landing.html +++ b/bitbake/lib/toaster/toastergui/templates/landing.html | |||
@@ -15,7 +15,7 @@ | |||
15 | <p>A web interface to <a href="https://www.openembedded.org">OpenEmbedded</a> and <a href="https://docs.yoctoproject.org/bitbake.html">BitBake</a>, the <a href="https://www.yoctoproject.org">Yocto Project</a> build system.</p> | 15 | <p>A web interface to <a href="https://www.openembedded.org">OpenEmbedded</a> and <a href="https://docs.yoctoproject.org/bitbake.html">BitBake</a>, the <a href="https://www.yoctoproject.org">Yocto Project</a> build system.</p> |
16 | 16 | ||
17 | <p class="top-air"> | 17 | <p class="top-air"> |
18 | <a class="btn btn-info btn-lg" href="http://docs.yoctoproject.org/toaster-manual/setup-and-use.html#setting-up-and-using-toaster"> | 18 | <a class="btn btn-info btn-lg" href="http://docs.yoctoproject.org/toaster-manual/setup-and-use.html#setting-up-and-using-toaster" style="min-width: 460px;"> |
19 | Toaster is ready to capture your command line builds | 19 | Toaster is ready to capture your command line builds |
20 | </a> | 20 | </a> |
21 | </p> | 21 | </p> |
@@ -23,7 +23,7 @@ | |||
23 | {% if lvs_nos %} | 23 | {% if lvs_nos %} |
24 | {% if project_enable %} | 24 | {% if project_enable %} |
25 | <p class="top-air"> | 25 | <p class="top-air"> |
26 | <a class="btn btn-primary btn-lg" href="{% url 'newproject' %}"> | 26 | <a class="btn btn-primary btn-lg" href="{% url 'newproject' %}" style="min-width: 460px;"> |
27 | Create your first Toaster project to run manage builds | 27 | Create your first Toaster project to run manage builds |
28 | </a> | 28 | </a> |
29 | </p> | 29 | </p> |
@@ -42,6 +42,12 @@ | |||
42 | </div> | 42 | </div> |
43 | {% endif %} | 43 | {% endif %} |
44 | 44 | ||
45 | <p class="top-air"> | ||
46 | <a class="btn btn-info btn-lg" href="{% url 'cmdlines' %}" style="min-width: 460px;"> | ||
47 | Import command line event logs from build directory | ||
48 | </a> | ||
49 | </p> | ||
50 | |||
45 | <ul class="list-unstyled lead"> | 51 | <ul class="list-unstyled lead"> |
46 | <li> | 52 | <li> |
47 | <a href="http://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual"> | 53 | <a href="http://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual"> |
diff --git a/bitbake/lib/toaster/toastergui/urls.py b/bitbake/lib/toaster/toastergui/urls.py index 2c138d3d7d..7f8489d3aa 100644 --- a/bitbake/lib/toaster/toastergui/urls.py +++ b/bitbake/lib/toaster/toastergui/urls.py | |||
@@ -95,6 +95,7 @@ urlpatterns = [ | |||
95 | # project URLs | 95 | # project URLs |
96 | url(r'^newproject/$', views.newproject, name='newproject'), | 96 | url(r'^newproject/$', views.newproject, name='newproject'), |
97 | 97 | ||
98 | url(r'^cmdline/$', views.CommandLineBuilds.as_view(), name='cmdlines'), | ||
98 | url(r'^projects/$', | 99 | url(r'^projects/$', |
99 | tables.ProjectsTable.as_view(template_name="projects-toastertable.html"), | 100 | tables.ProjectsTable.as_view(template_name="projects-toastertable.html"), |
100 | name='all-projects'), | 101 | name='all-projects'), |
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index 735d304ad8..3b5b9f5bd9 100644 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py | |||
@@ -6,24 +6,36 @@ | |||
6 | # SPDX-License-Identifier: GPL-2.0-only | 6 | # SPDX-License-Identifier: GPL-2.0-only |
7 | # | 7 | # |
8 | 8 | ||
9 | import ast | ||
9 | import re | 10 | import re |
11 | import subprocess | ||
12 | import sys | ||
13 | |||
14 | import bb.cooker | ||
15 | from bb.ui import toasterui | ||
16 | from bb.ui import eventreplay | ||
10 | 17 | ||
11 | from django.db.models import F, Q, Sum | 18 | from django.db.models import F, Q, Sum |
12 | from django.db import IntegrityError | 19 | from django.db import IntegrityError |
13 | from django.shortcuts import render, redirect, get_object_or_404 | 20 | from django.shortcuts import render, redirect, get_object_or_404, HttpResponseRedirect |
14 | from django.utils.http import urlencode | 21 | from django.utils.http import urlencode |
15 | from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe | 22 | from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe |
16 | from orm.models import LogMessage, Variable, Package_Dependency, Package | 23 | from orm.models import LogMessage, Variable, Package_Dependency, Package |
17 | from orm.models import Task_Dependency, Package_File | 24 | from orm.models import Task_Dependency, Package_File |
18 | from orm.models import Target_Installed_Package, Target_File | 25 | from orm.models import Target_Installed_Package, Target_File |
19 | from orm.models import TargetKernelFile, TargetSDKFile, Target_Image_File | 26 | from orm.models import TargetKernelFile, TargetSDKFile, Target_Image_File |
20 | from orm.models import BitbakeVersion, CustomImageRecipe | 27 | from orm.models import BitbakeVersion, CustomImageRecipe, EventLogsImports |
21 | 28 | ||
22 | from django.urls import reverse, resolve | 29 | from django.urls import reverse, resolve |
30 | from django.contrib import messages | ||
31 | |||
23 | from django.core.exceptions import ObjectDoesNotExist | 32 | from django.core.exceptions import ObjectDoesNotExist |
33 | from django.core.files.storage import FileSystemStorage | ||
34 | from django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile | ||
24 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger | 35 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger |
25 | from django.http import HttpResponseNotFound, JsonResponse | 36 | from django.http import HttpResponseNotFound, JsonResponse |
26 | from django.utils import timezone | 37 | from django.utils import timezone |
38 | from django.views.generic import TemplateView | ||
27 | from datetime import timedelta, datetime | 39 | from datetime import timedelta, datetime |
28 | from toastergui.templatetags.projecttags import json as jsonfilter | 40 | from toastergui.templatetags.projecttags import json as jsonfilter |
29 | from decimal import Decimal | 41 | from decimal import Decimal |
@@ -32,6 +44,10 @@ import os | |||
32 | from os.path import dirname | 44 | from os.path import dirname |
33 | import mimetypes | 45 | import mimetypes |
34 | 46 | ||
47 | from toastergui.forms import LoadFileForm | ||
48 | |||
49 | from collections import namedtuple | ||
50 | |||
35 | import logging | 51 | import logging |
36 | 52 | ||
37 | from toastermain.logs import log_view_mixin | 53 | from toastermain.logs import log_view_mixin |
@@ -41,6 +57,7 @@ logger = logging.getLogger("toaster") | |||
41 | # Project creation and managed build enable | 57 | # Project creation and managed build enable |
42 | project_enable = ('1' == os.environ.get('TOASTER_BUILDSERVER')) | 58 | project_enable = ('1' == os.environ.get('TOASTER_BUILDSERVER')) |
43 | is_project_specific = ('1' == os.environ.get('TOASTER_PROJECTSPECIFIC')) | 59 | is_project_specific = ('1' == os.environ.get('TOASTER_PROJECTSPECIFIC')) |
60 | import_page = False | ||
44 | 61 | ||
45 | class MimeTypeFinder(object): | 62 | class MimeTypeFinder(object): |
46 | # setting this to False enables additional non-standard mimetypes | 63 | # setting this to False enables additional non-standard mimetypes |
@@ -1940,3 +1957,155 @@ if True: | |||
1940 | except (ObjectDoesNotExist, IOError): | 1957 | except (ObjectDoesNotExist, IOError): |
1941 | return toaster_render(request, "unavailable_artifact.html") | 1958 | return toaster_render(request, "unavailable_artifact.html") |
1942 | 1959 | ||
1960 | |||
1961 | class CommandLineBuilds(TemplateView): | ||
1962 | model = EventLogsImports | ||
1963 | template_name = 'command_line_builds.html' | ||
1964 | |||
1965 | def get_context_data(self, **kwargs): | ||
1966 | context = super(CommandLineBuilds, self).get_context_data(**kwargs) | ||
1967 | #get value from BB_DEFAULT_EVENTLOG defined in bitbake.conf | ||
1968 | eventlog = subprocess.check_output(['bitbake-getvar', 'BB_DEFAULT_EVENTLOG', '--value']) | ||
1969 | if eventlog: | ||
1970 | logs_dir = os.path.dirname(eventlog.decode().strip('\n')) | ||
1971 | files = os.listdir(logs_dir) | ||
1972 | imported_files = EventLogsImports.objects.all() | ||
1973 | files_list = [] | ||
1974 | |||
1975 | # Filter files that end with ".json" | ||
1976 | event_files = [] | ||
1977 | for file in files: | ||
1978 | if file.endswith(".json"): | ||
1979 | # because BB_DEFAULT_EVENTLOG is a directory, we need to check if the file is a valid eventlog | ||
1980 | with open("{}/{}".format(logs_dir, file)) as efile: | ||
1981 | content = efile.read() | ||
1982 | if 'allvariables' in content: | ||
1983 | event_files.append(file) | ||
1984 | |||
1985 | #build dict for template using db data | ||
1986 | for event_file in event_files: | ||
1987 | if imported_files.filter(name=event_file): | ||
1988 | files_list.append({ | ||
1989 | 'name': event_file, | ||
1990 | 'imported': True, | ||
1991 | 'build_id': imported_files.filter(name=event_file)[0].build_id, | ||
1992 | 'size': os.path.getsize("{}/{}".format(logs_dir, event_file)) | ||
1993 | }) | ||
1994 | else: | ||
1995 | files_list.append({ | ||
1996 | 'name': event_file, | ||
1997 | 'imported': False, | ||
1998 | 'build_id': None, | ||
1999 | 'size': os.path.getsize("{}/{}".format(logs_dir, event_file)) | ||
2000 | }) | ||
2001 | context['import_all'] = True | ||
2002 | |||
2003 | context['files'] = files_list | ||
2004 | context['dir'] = logs_dir | ||
2005 | else: | ||
2006 | context['files'] = [] | ||
2007 | context['dir'] = '' | ||
2008 | |||
2009 | # enable session variable | ||
2010 | if not self.request.session.get('file'): | ||
2011 | self.request.session['file'] = "" | ||
2012 | |||
2013 | context['form'] = LoadFileForm() | ||
2014 | context['project_enable'] = project_enable | ||
2015 | return context | ||
2016 | |||
2017 | def post(self, request, **kwargs): | ||
2018 | logs_dir = request.POST.get('dir') | ||
2019 | all_files = request.POST.get('all') | ||
2020 | |||
2021 | imported_files = EventLogsImports.objects.all() | ||
2022 | try: | ||
2023 | if all_files == 'true': | ||
2024 | # use of session variable to deactivate icon for builds in progress | ||
2025 | request.session['all_builds'] = True | ||
2026 | request.session.modified = True | ||
2027 | request.session.save() | ||
2028 | |||
2029 | files = ast.literal_eval(request.POST.get('file')) | ||
2030 | for file in files: | ||
2031 | if imported_files.filter(name=file.get('name')).exists(): | ||
2032 | imported_files.filter(name=file.get('name'))[0].imported = True | ||
2033 | else: | ||
2034 | with open("{}/{}".format(logs_dir, file.get('name'))) as eventfile: | ||
2035 | # load variables from the first line | ||
2036 | variables = None | ||
2037 | while line := eventfile.readline().strip(): | ||
2038 | try: | ||
2039 | variables = json.loads(line)['allvariables'] | ||
2040 | break | ||
2041 | except (KeyError, json.JSONDecodeError): | ||
2042 | continue | ||
2043 | if not variables: | ||
2044 | raise Exception("File content missing build variables") | ||
2045 | eventfile.seek(0) | ||
2046 | params = namedtuple('ConfigParams', ['observe_only'])(True) | ||
2047 | player = eventreplay.EventPlayer(eventfile, variables) | ||
2048 | |||
2049 | toasterui.main(player, player, params) | ||
2050 | event_log_import = EventLogsImports.objects.create(name=file.get('name'), imported=True) | ||
2051 | event_log_import.build_id = Build.objects.last().id | ||
2052 | event_log_import.save() | ||
2053 | else: | ||
2054 | if self.request.FILES.get('eventlog_file'): | ||
2055 | file = self.request.FILES['eventlog_file'] | ||
2056 | else: | ||
2057 | file = request.POST.get('file') | ||
2058 | # use of session variable to deactivate icon for build in progress | ||
2059 | request.session['file'] = file | ||
2060 | request.session['all_builds'] = False | ||
2061 | request.session.modified = True | ||
2062 | request.session.save() | ||
2063 | |||
2064 | if imported_files.filter(name=file).exists(): | ||
2065 | imported_files.filter(name=file)[0].imported = True | ||
2066 | else: | ||
2067 | if isinstance(file, InMemoryUploadedFile) or isinstance(file, TemporaryUploadedFile): | ||
2068 | variables = None | ||
2069 | while line := file.readline().strip(): | ||
2070 | try: | ||
2071 | variables = json.loads(line)['allvariables'] | ||
2072 | break | ||
2073 | except (KeyError, json.JSONDecodeError): | ||
2074 | continue | ||
2075 | if not variables: | ||
2076 | raise Exception("File content missing build variables") | ||
2077 | file.seek(0) | ||
2078 | params = namedtuple('ConfigParams', ['observe_only'])(True) | ||
2079 | player = eventreplay.EventPlayer(file, variables) | ||
2080 | if not os.path.exists('{}/{}'.format(logs_dir, file.name)): | ||
2081 | fs = FileSystemStorage(location=logs_dir) | ||
2082 | fs.save(file.name, file) | ||
2083 | toasterui.main(player, player, params) | ||
2084 | else: | ||
2085 | with open("{}/{}".format(logs_dir, file)) as eventfile: | ||
2086 | # load variables from the first line | ||
2087 | variables = None | ||
2088 | while line := eventfile.readline().strip(): | ||
2089 | try: | ||
2090 | variables = json.loads(line)['allvariables'] | ||
2091 | break | ||
2092 | except (KeyError, json.JSONDecodeError): | ||
2093 | continue | ||
2094 | if not variables: | ||
2095 | raise Exception("File content missing build variables") | ||
2096 | eventfile.seek(0) | ||
2097 | params = namedtuple('ConfigParams', ['observe_only'])(True) | ||
2098 | player = eventreplay.EventPlayer(eventfile, variables) | ||
2099 | toasterui.main(player, player, params) | ||
2100 | event_log_import = EventLogsImports.objects.create(name=file, imported=True) | ||
2101 | event_log_import.build_id = Build.objects.last().id | ||
2102 | event_log_import.save() | ||
2103 | request.session['file'] = "" | ||
2104 | except Exception: | ||
2105 | messages.add_message( | ||
2106 | self.request, | ||
2107 | messages.ERROR, | ||
2108 | "The file content is not in the correct format. Update file content or upload a different file." | ||
2109 | ) | ||
2110 | return HttpResponseRedirect("/toastergui/cmdline/") | ||
2111 | return HttpResponseRedirect('/toastergui/builds/') | ||