diff options
Diffstat (limited to 'bitbake')
-rw-r--r-- | bitbake/lib/toaster/bldcontrol/localhostbecontroller.py | 13 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/static/js/base.js | 229 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/templates/base.html | 133 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/templates/projecttopbar.html | 18 |
4 files changed, 61 insertions, 332 deletions
diff --git a/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py b/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py index b5cf5591fd..854a6bbfe2 100644 --- a/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py +++ b/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py | |||
@@ -48,7 +48,6 @@ class LocalhostBEController(BuildEnvironmentController): | |||
48 | 48 | ||
49 | def __init__(self, be): | 49 | def __init__(self, be): |
50 | super(LocalhostBEController, self).__init__(be) | 50 | super(LocalhostBEController, self).__init__(be) |
51 | self.dburl = settings.getDATABASE_URL() | ||
52 | self.pokydirname = None | 51 | self.pokydirname = None |
53 | self.islayerset = False | 52 | self.islayerset = False |
54 | 53 | ||
@@ -126,9 +125,17 @@ class LocalhostBEController(BuildEnvironmentController): | |||
126 | port = i.split(" ")[-1] | 125 | port = i.split(" ")[-1] |
127 | logger.debug("localhostbecontroller: Found bitbake server port %s" % port) | 126 | logger.debug("localhostbecontroller: Found bitbake server port %s" % port) |
128 | 127 | ||
129 | cmd = "bash -c \"source %s/oe-init-build-env-memres -1 %s && DATABASE_URL=%s %s --observe-only -u toasterui --remote-server=0.0.0.0:-1 -t xmlrpc\"" % (self.pokydirname, self.be.builddir, self.dburl, own_bitbake) | 128 | cmd = "bash -c \"source %s/oe-init-build-env-memres -1 %s && %s --observe-only -u toasterui --remote-server=0.0.0.0:-1 -t xmlrpc\"" % \ |
129 | (self.pokydirname, self.be.builddir, own_bitbake) | ||
130 | |||
131 | # Use a copy of the current environment and add the DATABASE_URL | ||
132 | # for the bitbake observer process. | ||
133 | env = os.environ.copy() | ||
134 | env['DATABASE_URL'] = settings.getDATABASE_URL() | ||
135 | |||
130 | with open(toaster_ui_log_filepath, "a+") as f: | 136 | with open(toaster_ui_log_filepath, "a+") as f: |
131 | p = subprocess.Popen(cmd, cwd = self.be.builddir, shell=True, stdout=f, stderr=f) | 137 | p = subprocess.Popen(cmd, cwd = self.be.builddir, shell=True, |
138 | stdout=f, stderr=f, env=env) | ||
132 | 139 | ||
133 | def _toaster_ui_started(filepath, filepos = 0): | 140 | def _toaster_ui_started(filepath, filepos = 0): |
134 | if not os.path.exists(filepath): | 141 | if not os.path.exists(filepath): |
diff --git a/bitbake/lib/toaster/toastergui/static/js/base.js b/bitbake/lib/toaster/toastergui/static/js/base.js deleted file mode 100644 index ed22a4ebc1..0000000000 --- a/bitbake/lib/toaster/toastergui/static/js/base.js +++ /dev/null | |||
@@ -1,229 +0,0 @@ | |||
1 | 'use strict'; | ||
2 | |||
3 | function basePageInit(ctx) { | ||
4 | |||
5 | var newBuildButton = $("#new-build-button"); | ||
6 | var newBuildTargetInput; | ||
7 | var newBuildTargetBuildBtn; | ||
8 | var projectNameForm = $("#project-name-change-form"); | ||
9 | var projectNameContainer = $("#project-name-container"); | ||
10 | var projectName = $("#project-name"); | ||
11 | var projectNameFormToggle = $("#project-change-form-toggle"); | ||
12 | var projectNameChangeCancel = $("#project-name-change-cancel"); | ||
13 | |||
14 | /* initially the current project is used unless overridden by the new build | ||
15 | * button in top right nav | ||
16 | */ | ||
17 | var selectedProject = libtoaster.ctx; | ||
18 | |||
19 | var selectedTarget; | ||
20 | |||
21 | var newBuildProjectInput = $("#new-build-button #project-name-input"); | ||
22 | var newBuildProjectSaveBtn = $("#new-build-button #save-project-button"); | ||
23 | |||
24 | /* Project name change functionality */ | ||
25 | projectNameFormToggle.click(function(e){ | ||
26 | e.preventDefault(); | ||
27 | projectNameContainer.hide(); | ||
28 | projectNameForm.fadeIn(); | ||
29 | }); | ||
30 | |||
31 | projectNameChangeCancel.click(function(e){ | ||
32 | e.preventDefault(); | ||
33 | projectNameForm.hide(); | ||
34 | projectNameContainer.fadeIn(); | ||
35 | }); | ||
36 | |||
37 | $("#project-name-change-btn").click(function(e){ | ||
38 | var newProjectName = $("#project-name-change-input").val(); | ||
39 | |||
40 | libtoaster.editCurrentProject({ projectName: newProjectName }, function (){ | ||
41 | projectName.html(newProjectName); | ||
42 | libtoaster.ctx.projectName = newProjectName; | ||
43 | projectNameChangeCancel.click(); | ||
44 | }); | ||
45 | }); | ||
46 | |||
47 | _checkProjectBuildable(); | ||
48 | |||
49 | $("#project-topbar .nav li a").each(function(){ | ||
50 | if (window.location.pathname === $(this).attr('href')) | ||
51 | $(this).parent().addClass('active'); | ||
52 | else | ||
53 | $(this).parent().removeClass('active'); | ||
54 | }); | ||
55 | |||
56 | if ($(".total-builds").length !== 0){ | ||
57 | libtoaster.getProjectInfo(libtoaster.ctx.projectPageUrl, function(prjInfo){ | ||
58 | if (prjInfo.completedbuilds) | ||
59 | $(".total-builds").text(prjInfo.completedbuilds.length); | ||
60 | }); | ||
61 | } | ||
62 | |||
63 | /* Hide the button if we're on the project,newproject or importlyaer page | ||
64 | * or if there are no projects yet defined | ||
65 | * only show if there isn't already a build-target-input already | ||
66 | */ | ||
67 | if (ctx.numProjects > 0 && | ||
68 | ctx.currentUrl.search('newproject') < 0 && | ||
69 | $(".build-target-input").length === 1) { | ||
70 | |||
71 | newBuildTargetInput = $("#new-build-button .build-target-input"); | ||
72 | newBuildTargetBuildBtn = $("#new-build-button").find(".build-button"); | ||
73 | |||
74 | _setupNewBuildButton(); | ||
75 | newBuildButton.show(); | ||
76 | } else if ($(".build-target-input").length > 0) { | ||
77 | newBuildTargetInput = $("#project-topbar .build-target-input"); | ||
78 | newBuildTargetBuildBtn = $("#project-topbar .build-button"); | ||
79 | } else { | ||
80 | return; | ||
81 | } | ||
82 | |||
83 | /* Hide the change project icon when there is only one project */ | ||
84 | if (ctx.numProjects === 1) { | ||
85 | $('#project .icon-pencil').hide(); | ||
86 | } | ||
87 | |||
88 | /* If we have a project setup the typeahead */ | ||
89 | if (selectedProject.recipesTypeAheadUrl){ | ||
90 | libtoaster.makeTypeahead(newBuildTargetInput, selectedProject.recipesTypeAheadUrl, { format: "json" }, function (item) { | ||
91 | selectedTarget = item; | ||
92 | newBuildTargetBuildBtn.removeAttr("disabled"); | ||
93 | }); | ||
94 | } | ||
95 | |||
96 | newBuildTargetInput.on('input', function () { | ||
97 | if ($(this).val().length === 0) { | ||
98 | newBuildTargetBuildBtn.attr("disabled", "disabled"); | ||
99 | } else { | ||
100 | newBuildTargetBuildBtn.removeAttr("disabled"); | ||
101 | } | ||
102 | }); | ||
103 | |||
104 | newBuildTargetBuildBtn.click(function (e) { | ||
105 | e.preventDefault(); | ||
106 | |||
107 | if (!newBuildTargetInput.val()) { | ||
108 | return; | ||
109 | } | ||
110 | |||
111 | /* We use the value of the input field so as to maintain any command also | ||
112 | * added e.g. core-image-minimal:clean | ||
113 | */ | ||
114 | selectedTarget = { name: newBuildTargetInput.val() }; | ||
115 | |||
116 | /* Fire off the build */ | ||
117 | libtoaster.startABuild(selectedProject.projectBuildsUrl, | ||
118 | selectedProject.projectId, selectedTarget.name, function(){ | ||
119 | window.location.replace(selectedProject.projectBuildsUrl); | ||
120 | }, null); | ||
121 | }); | ||
122 | |||
123 | function _checkProjectBuildable() { | ||
124 | if (selectedProject.projectId === undefined || selectedProject.projectIsDefault) { | ||
125 | return; | ||
126 | } | ||
127 | |||
128 | libtoaster.getProjectInfo(selectedProject.projectPageUrl, | ||
129 | function (data) { | ||
130 | if (data.machine === null || data.machine.name === undefined || data.layers.length === 0) { | ||
131 | /* we can't build anything without a machine and some layers */ | ||
132 | $("#new-build-button #targets-form").hide(); | ||
133 | $("#new-build-button .alert").show(); | ||
134 | } else { | ||
135 | $("#new-build-button #targets-form").show(); | ||
136 | $("#new-build-button .alert").hide(); | ||
137 | |||
138 | /* we can build this project; enable input fields */ | ||
139 | newBuildTargetInput.removeAttr("disabled"); | ||
140 | } | ||
141 | }, null); | ||
142 | } | ||
143 | |||
144 | /* Setup New build button in the top nav bar */ | ||
145 | function _setupNewBuildButton() { | ||
146 | |||
147 | /* If we don't have a current project then present the set project | ||
148 | * form. | ||
149 | */ | ||
150 | if (selectedProject.projectId === undefined || selectedProject.projectIsDefault) { | ||
151 | $('#change-project-form').show(); | ||
152 | $('#project .icon-pencil').hide(); | ||
153 | } | ||
154 | |||
155 | libtoaster.makeTypeahead(newBuildProjectInput, selectedProject.projectsTypeAheadUrl, { format : "json" }, function (item) { | ||
156 | /* successfully selected a project */ | ||
157 | newBuildProjectSaveBtn.removeAttr("disabled"); | ||
158 | selectedProject = item; | ||
159 | }); | ||
160 | |||
161 | /* Any typing in the input apart from enter key is going to invalidate | ||
162 | * the value that has been set by selecting a suggestion from the typeahead | ||
163 | */ | ||
164 | newBuildProjectInput.on('input', function (event) { | ||
165 | if (event.keyCode === 13) { | ||
166 | return; | ||
167 | } | ||
168 | newBuildProjectSaveBtn.attr("disabled", "disabled"); | ||
169 | }); | ||
170 | |||
171 | |||
172 | newBuildProjectSaveBtn.click(function () { | ||
173 | selectedProject.projectId = selectedProject.id; | ||
174 | /* Update the typeahead project_id paramater */ | ||
175 | _checkProjectBuildable(); | ||
176 | |||
177 | newBuildTargetInput.removeAttr("disabled"); | ||
178 | |||
179 | /* We've got a new project so now we need to update the | ||
180 | * target urls. We can get this from the new project's info | ||
181 | */ | ||
182 | $.getJSON(selectedProject.projectPageUrl, { format: "json" }, | ||
183 | function(projectInfo){ | ||
184 | /* Update the typeahead to use the new selectedProject */ | ||
185 | selectedProject = projectInfo; | ||
186 | |||
187 | libtoaster.makeTypeahead(newBuildTargetInput, selectedProject.recipesTypeAheadUrl, { format: "json" }, function (item) { | ||
188 | /* successfully selected a target */ | ||
189 | selectedTarget = item; | ||
190 | newBuildTargetBuildBtn.removeAttr("disabled"); | ||
191 | }); | ||
192 | |||
193 | }); | ||
194 | newBuildTargetInput.val(""); | ||
195 | |||
196 | /* set up new form aspect */ | ||
197 | $("#new-build-button #project a").text(selectedProject.name).attr('href', selectedProject.projectPageUrl); | ||
198 | $("#new-build-button .alert a").attr('href', selectedProject.projectPageUrl); | ||
199 | $("#project .icon-pencil").show(); | ||
200 | |||
201 | $("#change-project-form").slideUp({ 'complete' : function () { | ||
202 | $("#new-build-button #project").show(); | ||
203 | }}); | ||
204 | }); | ||
205 | |||
206 | $('#new-build-button #project .icon-pencil').click(function () { | ||
207 | newBuildProjectSaveBtn.attr("disabled", "disabled"); | ||
208 | newBuildProjectInput.val($("#new-build-button #project a").text()); | ||
209 | $("#cancel-change-project").show(); | ||
210 | $(this).parent().hide(); | ||
211 | $("#change-project-form").slideDown(); | ||
212 | }); | ||
213 | |||
214 | $("#new-build-button #cancel-change-project").click(function () { | ||
215 | $("#change-project-form").hide(function () { | ||
216 | $('#new-build-button #project').show(); | ||
217 | }); | ||
218 | |||
219 | newBuildProjectInput.val(""); | ||
220 | newBuildProjectSaveBtn.attr("disabled", "disabled"); | ||
221 | }); | ||
222 | |||
223 | /* Keep the dropdown open even unless we click outside the dropdown area */ | ||
224 | $(".new-build").click (function (event) { | ||
225 | event.stopPropagation(); | ||
226 | }); | ||
227 | }; | ||
228 | |||
229 | } | ||
diff --git a/bitbake/lib/toaster/toastergui/templates/base.html b/bitbake/lib/toaster/toastergui/templates/base.html index 11ac2a0355..e0b15cef12 100644 --- a/bitbake/lib/toaster/toastergui/templates/base.html +++ b/bitbake/lib/toaster/toastergui/templates/base.html | |||
@@ -3,15 +3,15 @@ | |||
3 | {% load projecttags %} | 3 | {% load projecttags %} |
4 | {% load project_url_tag %} | 4 | {% load project_url_tag %} |
5 | <html lang="en"> | 5 | <html lang="en"> |
6 | <head> | 6 | <head> |
7 | <title> | 7 | <title> |
8 | {% block title %} Toaster {% endblock %} | 8 | {% block title %} Toaster {% endblock %} |
9 | </title> | 9 | </title> |
10 | <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}" type="text/css"/> | 10 | <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}" type="text/css"/> |
11 | <link rel="stylesheet" href="{% static 'css/bootstrap-responsive.min.css' %}" type='text/css'/> | 11 | <link rel="stylesheet" href="{% static 'css/bootstrap-responsive.min.css' %}" type='text/css'/> |
12 | <link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}" type='text/css'/> | 12 | <link rel="stylesheet" href="{% static 'css/font-awesome.min.css' %}" type='text/css'/> |
13 | <link rel="stylesheet" href="{% static 'css/prettify.css' %}" type='text/css'/> | 13 | <link rel="stylesheet" href="{% static 'css/prettify.css' %}" type='text/css'/> |
14 | <link rel="stylesheet" href="{% static 'css/default.css' %}" type='text/css'/> | 14 | <link rel="stylesheet" href="{% static 'css/default.css' %}" type='text/css'/> |
15 | 15 | ||
16 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | 16 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
17 | <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> | 17 | <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> |
@@ -40,7 +40,6 @@ | |||
40 | projectId : {{project.id}}, | 40 | projectId : {{project.id}}, |
41 | projectPageUrl : {% url 'project' project.id as purl %}{{purl|json}}, | 41 | projectPageUrl : {% url 'project' project.id as purl %}{{purl|json}}, |
42 | projectName : {{project.name|json}}, | 42 | projectName : {{project.name|json}}, |
43 | projectIsDefault: {% if project.is_default %}true{% else %}false{% endif %}, | ||
44 | recipesTypeAheadUrl: {% url 'xhr_recipestypeahead' project.id as paturl%}{{paturl|json}}, | 43 | recipesTypeAheadUrl: {% url 'xhr_recipestypeahead' project.id as paturl%}{{paturl|json}}, |
45 | layersTypeAheadUrl: {% url 'xhr_layerstypeahead' project.id as paturl%}{{paturl|json}}, | 44 | layersTypeAheadUrl: {% url 'xhr_layerstypeahead' project.id as paturl%}{{paturl|json}}, |
46 | machinesTypeAheadUrl: {% url 'xhr_machinestypeahead' project.id as paturl%}{{paturl|json}}, | 45 | machinesTypeAheadUrl: {% url 'xhr_machinestypeahead' project.id as paturl%}{{paturl|json}}, |
@@ -51,37 +50,24 @@ | |||
51 | projectId : undefined, | 50 | projectId : undefined, |
52 | projectPageUrl : undefined, | 51 | projectPageUrl : undefined, |
53 | projectName : undefined, | 52 | projectName : undefined, |
54 | projectIsDefault: false, | ||
55 | {% endif %} | 53 | {% endif %} |
56 | }; | 54 | }; |
57 | </script> | 55 | </script> |
58 | <script src="{% static 'js/base.js' %}"></script> | 56 | {% block extraheadcontent %} |
59 | <script> | 57 | {% endblock %} |
60 | $(document).ready(function () { | 58 | </head> |
61 | /* Vars needed for base.js */ | ||
62 | var ctx = {}; | ||
63 | ctx.numProjects = {{projects|length}}; | ||
64 | ctx.currentUrl = "{{request.path|escapejs}}"; | ||
65 | |||
66 | basePageInit(ctx); | ||
67 | }); | ||
68 | </script> | ||
69 | 59 | ||
70 | {% block extraheadcontent %} | 60 | <body style="height: 100%"> |
71 | {% endblock %} | ||
72 | </head> | ||
73 | 61 | ||
74 | <body style="height: 100%"> | 62 | {% csrf_token %} |
75 | 63 | <div id="loading-notification" class="alert lead text-center" style="display:none"> | |
76 | {% csrf_token %} | 64 | Loading <i class="fa-pulse icon-spinner"></i> |
77 | <div id="loading-notification" class="alert lead text-center" style="display:none"> | 65 | </div> |
78 | Loading <i class="fa-pulse icon-spinner"></i> | ||
79 | </div> | ||
80 | 66 | ||
81 | <div id="change-notification" class="alert lead alert-info" style="display:none"> | 67 | <div id="change-notification" class="alert lead alert-info" style="display:none"> |
82 | <button type="button" class="close" id="hide-alert">×</button> | 68 | <button type="button" class="close" id="hide-alert">×</button> |
83 | <span id="change-notification-msg"></span> | 69 | <span id="change-notification-msg"></span> |
84 | </div> | 70 | </div> |
85 | 71 | ||
86 | <div class="navbar navbar-fixed-top"> | 72 | <div class="navbar navbar-fixed-top"> |
87 | <div class="navbar-inner"> | 73 | <div class="navbar-inner"> |
@@ -125,72 +111,19 @@ | |||
125 | 111 | ||
126 | <!-- new project button; only show in build mode --> | 112 | <!-- new project button; only show in build mode --> |
127 | {% if BUILD_MODE %} | 113 | {% if BUILD_MODE %} |
128 | <div class="btn-group pull-right"> | 114 | <div class="btn-group pull-right"> |
129 | <a class="btn" id="new-project-button" href="{% url 'newproject' %}">New project</a> | 115 | <a class="btn" id="new-project-button" href="{% url 'newproject' %}">New project</a> |
130 | </div> | 116 | </div> |
131 | {% endif %} | ||
132 | |||
133 | <!-- | ||
134 | New build popover; only shown if there is at least one user-created project | ||
135 | and we're in build mode | ||
136 | --> | ||
137 | {% if BUILD_MODE and non_cli_projects.count > 0 %} | ||
138 | <div class="btn-group pull-right" id="new-build-button" style="display:none"> | ||
139 | <button class="btn dropdown-toggle" data-toggle="dropdown"> | ||
140 | New build | ||
141 | <i class="icon-caret-down"></i> | ||
142 | </button> | ||
143 | <ul class="dropdown-menu new-build multi-select"> | ||
144 | <li> | ||
145 | <h3>New build</h3> | ||
146 | <h6> | ||
147 | Project: | ||
148 | <span id="project"> | ||
149 | {% if project.id and not project.is_default %} | ||
150 | <a class="lead" href="{% project_url project %}">{{project.name}}</a> | ||
151 | {% else %} | ||
152 | <a class="lead" href="#"></a> | ||
153 | {% endif %} | ||
154 | <i class="icon-pencil"></i> | ||
155 | </span> | ||
156 | </h6> | ||
157 | <form id="change-project-form" style="display:none;"> | ||
158 | <div class="input-append"> | ||
159 | <input type="text" class="input-medium" id="project-name-input" placeholder="Type a project name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead"/> | ||
160 | <button id="save-project-button" class="btn" type="button">Save</button> | ||
161 | <a href="#" id="cancel-change-project" class="btn btn-link" style="display: none">Cancel</a> | ||
162 | </div> | ||
163 | <p><a id="view-all-projects" href="{% url 'all-projects' %}">View all projects</a></p> | ||
164 | </form> | ||
165 | </li> | ||
166 | <li> | ||
167 | <div class="alert" style="display:none;"> | ||
168 | <p>This project configuration is incomplete, so you cannot run builds.</p> | ||
169 | <p><a href="{% if project.id %}{% url 'project' project.id %}{% endif %}">View project configuration</a></p> | ||
170 | </div> | ||
171 | </li> | ||
172 | <li id="targets-form"> | ||
173 | <h6>Recipe(s):</h6> | ||
174 | <form> | ||
175 | <input type="text" class="input-xlarge build-target-input" placeholder="Type a recipe name" autocomplete="off" data-minLength="1" data-autocomplete="off" data-provide="typeahead" disabled/> | ||
176 | <div class="row-fluid"> | ||
177 | <button class="btn btn-primary build-button" disabled>Build</button> | ||
178 | </div> | ||
179 | </form> | ||
180 | </li> | ||
181 | </ul> | ||
182 | </div> | ||
183 | {% endif %} | 117 | {% endif %} |
118 | </div> | ||
119 | </div> | ||
184 | </div> | 120 | </div> |
185 | </div> | ||
186 | </div> | ||
187 | 121 | ||
188 | <div class="container-fluid top-padded"> | 122 | <div class="container-fluid top-padded"> |
189 | <div class="row-fluid"> | 123 | <div class="row-fluid"> |
190 | {% block pagecontent %} | 124 | {% block pagecontent %} |
191 | {% endblock %} | 125 | {% endblock %} |
192 | </div> | 126 | </div> |
193 | </div> | 127 | </div> |
194 | </body> | 128 | </body> |
195 | </html> | 129 | </html> |
196 | |||
diff --git a/bitbake/lib/toaster/toastergui/templates/projecttopbar.html b/bitbake/lib/toaster/toastergui/templates/projecttopbar.html index d8f7cbdbe8..8b44acccfd 100644 --- a/bitbake/lib/toaster/toastergui/templates/projecttopbar.html +++ b/bitbake/lib/toaster/toastergui/templates/projecttopbar.html | |||
@@ -1,3 +1,21 @@ | |||
1 | {% load static %} | ||
2 | <script src="{% static 'js/projecttopbar.js' %}"></script> | ||
3 | <script> | ||
4 | $(document).ready(function () { | ||
5 | var ctx = { | ||
6 | numProjectLayers : {{project.get_project_layer_versions.count}}, | ||
7 | machine : "{{project.get_current_machine_name|default_if_none:""}}", | ||
8 | } | ||
9 | |||
10 | try { | ||
11 | projectTopBarInit(ctx); | ||
12 | } catch (e) { | ||
13 | document.write("Sorry, An error has occurred loading this page"); | ||
14 | console.warn(e); | ||
15 | } | ||
16 | }); | ||
17 | </script> | ||
18 | |||
1 | <div class="alert alert-success lead" id="project-created-notification" style="margin-top:15px; display:none"> | 19 | <div class="alert alert-success lead" id="project-created-notification" style="margin-top:15px; display:none"> |
2 | <button type="button" class="close" data-dismiss="alert">×</button> | 20 | <button type="button" class="close" data-dismiss="alert">×</button> |
3 | Your project <strong>{{project.name}}</strong> has been created. You can now <a href="{% url 'projectmachines' project.id %}">select your target machine</a> and <a href="{% url 'projectsoftwarerecipes' project.id %}">choose image recipes</a> to build. | 21 | Your project <strong>{{project.name}}</strong> has been created. You can now <a href="{% url 'projectmachines' project.id %}">select your target machine</a> and <a href="{% url 'projectsoftwarerecipes' project.id %}">choose image recipes</a> to build. |