Revision aaccdf92
src/sunstone/models/OpenNebulaJSON/HostJSON.rb | ||
---|---|---|
26 | 26 |
return host_hash |
27 | 27 |
end |
28 | 28 |
|
29 |
self.allocate(host_hash['name'], |
|
29 |
id = self.allocate(host_hash['name'],
|
|
30 | 30 |
host_hash['im_mad'], |
31 | 31 |
host_hash['vm_mad'], |
32 | 32 |
host_hash['cluster_id'].to_i) |
33 |
delete_values = ['name', 'im_mad', 'vm_mad', 'cluster_id'] |
|
34 |
|
|
35 |
template_str = hash_to_str(host_hash, delete_values) |
|
36 |
if !template_str.nil? |
|
37 |
params=Hash.new |
|
38 |
params['template_raw'] = template_str |
|
39 |
params['append'] = true |
|
40 |
self.update(params) |
|
41 |
end |
|
33 | 42 |
end |
34 | 43 |
|
35 | 44 |
def delete |
src/sunstone/models/OpenNebulaJSON/JSONUtils.rb | ||
---|---|---|
113 | 113 |
|
114 | 114 |
str |
115 | 115 |
end |
116 |
|
|
117 |
def hash_to_str(template_hash, delete_values) |
|
118 |
for del in delete_values |
|
119 |
template_hash.delete(del) |
|
120 |
end |
|
121 |
|
|
122 |
if !template_hash.empty? |
|
123 |
template_str = "" |
|
124 |
template_hash.collect do |key,value| |
|
125 |
if value.kind_of?(Array) |
|
126 |
template_str << key.to_s.upcase << " = \[" |
|
127 |
for obj in value |
|
128 |
if obj.kind_of?(Hash) |
|
129 |
obj.collect do |key,value| |
|
130 |
template_str << key.to_s.upcase << " = \""<< value.to_s << "\"\n" |
|
131 |
end |
|
132 |
end |
|
133 |
end |
|
134 |
template_str << "\]\n" |
|
135 |
else |
|
136 |
template_str << key.to_s.upcase << " = \""<< value.to_s << "\"\n" |
|
137 |
end |
|
138 |
end |
|
139 |
end |
|
140 |
return template_str |
|
141 |
end |
|
116 | 142 |
end |
117 | 143 |
end |
src/sunstone/public/app/tabs/hosts-tab/form-panels/create.js | ||
---|---|---|
119 | 119 |
$("#host_type_mad", context).on("change", function() { |
120 | 120 |
$("#vmm_mad", context).val(this.value).change(); |
121 | 121 |
$("#im_mad", context).val(this.value).change(); |
122 |
$(".vcenter_credentials", context).hide(); |
|
123 |
$(".ec2_extra", context).hide(); |
|
124 |
$(".drivers", context).hide(); |
|
125 |
$("#name_container", context).show(); |
|
122 | 126 |
|
123 | 127 |
if (this.value == "custom") { |
124 |
$(".vcenter_credentials", context).hide(); |
|
125 |
$("#name_container", context).show(); |
|
126 | 128 |
Sunstone.showFormPanelSubmit(TAB_ID); |
127 | 129 |
$(".drivers", context).show(); |
128 | 130 |
} else if (this.value == "vcenter") { |
129 | 131 |
$("#name_container", context).hide(); |
130 | 132 |
$(".vcenter_credentials", context).show(); |
131 | 133 |
Sunstone.hideFormPanelSubmit(TAB_ID); |
132 |
$(".drivers", context).hide(); |
|
134 |
} else if (this.value == "ec2") { |
|
135 |
$(".ec2_extra", context).show(); |
|
136 |
Sunstone.showFormPanelSubmit(TAB_ID); |
|
133 | 137 |
} else { |
134 |
$(".vcenter_credentials", context).hide(); |
|
135 |
$("#name_container", context).show(); |
|
136 | 138 |
Sunstone.showFormPanelSubmit(TAB_ID); |
137 |
$(".drivers", context).hide(); |
|
138 | 139 |
} |
139 | 140 |
}); |
140 | 141 |
|
142 |
context.off("click", ".add_custom_tag"); |
|
143 |
context.on("click", ".add_custom_tag", function(){ |
|
144 |
$("tbody.capacity_ec2", context).append( |
|
145 |
"<tr class='row_capacity'>\ |
|
146 |
<td style='display: flex; justify-content: flex-start'>\ |
|
147 |
<input class='capacity_key' type='text' name='key'>\ |
|
148 |
</td>\ |
|
149 |
<td>\ |
|
150 |
<input class='capacity_value' type='number' min='0' name='value'>\ |
|
151 |
</td>\ |
|
152 |
<td style='width: 150%; display: flex; justify-content: flex-end'>\ |
|
153 |
<a href='#''><i class='fa fa-times-circle remove-capacity'></i></a>\ |
|
154 |
</td>\ |
|
155 |
</tr>"); |
|
156 |
}); |
|
157 |
|
|
158 |
context.on("click", "tbody.capacity_ec2 i.remove-capacity", function(){ |
|
159 |
var tr = $(this).closest('tr'); |
|
160 |
tr.remove(); |
|
161 |
}); |
|
162 |
|
|
141 | 163 |
$("#host_type_mad", context).change(); |
142 | 164 |
|
143 | 165 |
$("form.vcenter_credentials", context) |
... | ... | |
222 | 244 |
} |
223 | 245 |
}; |
224 | 246 |
|
247 |
if(vmm_mad == "ec2"){ |
|
248 |
var capacity = []; |
|
249 |
var key = ""; |
|
250 |
var value = ""; |
|
251 |
var obj = {}; |
|
252 |
var region_name = $('input[name="REGION_NAME"]').val(); |
|
253 |
var ec2_access = $('input[name="EC2_ACCESS"]').val(); |
|
254 |
var ec2_secret = $('input[name="EC2_SECRET"]').val(); |
|
255 |
$('tr.row_capacity',context).each(function() { |
|
256 |
key = $("input[name='key']", this).val(); |
|
257 |
value = $("input[name='value']", this).val(); |
|
258 |
obj[key] = value; |
|
259 |
capacity.push(obj); |
|
260 |
}); |
|
261 |
|
|
262 |
host_json["host"]["region_name"] = region_name; |
|
263 |
host_json["host"]["ec2_secret"] = ec2_secret; |
|
264 |
host_json["host"]["ec2_access"] = ec2_access; |
|
265 |
host_json["host"]["capacity"] = capacity; |
|
266 |
} |
|
225 | 267 |
//Create the OpenNebula.Host. |
226 | 268 |
//If it is successfull we refresh the list. |
227 | 269 |
Sunstone.runAction("Host.create", host_json); |
src/sunstone/public/app/tabs/hosts-tab/form-panels/create/wizard.hbs | ||
---|---|---|
80 | 80 |
</div> |
81 | 81 |
</fieldset> |
82 | 82 |
</div> |
83 |
|
|
84 |
<div class="ec2_extra"> |
|
85 |
<fieldset> |
|
86 |
<legend>{{tr "EC2"}}</legend> |
|
87 |
<div class="row"> |
|
88 |
<div class="large-12 columns"> |
|
89 |
<label for="REGION_NAME">{{tr "Region name"}}</label> |
|
90 |
<input type="text" name="REGION_NAME" /> |
|
91 |
</div> |
|
92 |
</div> |
|
93 |
<div class="row"> |
|
94 |
<div class="large-6 columns"> |
|
95 |
<label for="EC2_ACCESS">{{tr "Access key ID"}}</label> |
|
96 |
<input type="text" name="EC2_ACCESS" /> |
|
97 |
</div> |
|
98 |
<div class="large-6 columns"> |
|
99 |
<label for="EC2_SECRET">{{tr "Secret access key"}}</label> |
|
100 |
<input type="text" name="EC2_SECRET" /> |
|
101 |
</div> |
|
102 |
</div> |
|
103 |
<div class="row"> |
|
104 |
<div class="medium-12 columns"> |
|
105 |
<label>{{tr "Capacity"}}</label> |
|
106 |
<div class="row"> |
|
107 |
<div class="large-12 columns"> |
|
108 |
<table class="dataTable capacity_table"> |
|
109 |
<thead> |
|
110 |
<tr> |
|
111 |
<th>{{tr "Key"}}</th> |
|
112 |
<th>{{tr "Value"}}</th> |
|
113 |
<th></th> |
|
114 |
</tr> |
|
115 |
</thead> |
|
116 |
<tbody class="capacity_ec2"> |
|
117 |
</tbody> |
|
118 |
<tfoot> |
|
119 |
<tr> |
|
120 |
<td colspan="3"> |
|
121 |
<a type="button" class="add_custom_tag button small small-12 secondary radius"> |
|
122 |
<i class="fa fa-lg fa-plus-circle"></i> |
|
123 |
</a> |
|
124 |
</td> |
|
125 |
</tr> |
|
126 |
</tfoot> |
|
127 |
</table> |
|
128 |
</div> |
|
129 |
</div> |
|
130 |
</div> |
|
131 |
</div> |
|
132 |
</fieldset> |
|
133 |
</div> |
|
83 | 134 |
</form> |
84 | 135 |
<form data-abide novalidate class="vcenter_credentials" action=""> |
85 | 136 |
<fieldset> |
src/sunstone/public/app/tabs/hosts-tab/panels/info.js | ||
---|---|---|
32 | 32 |
var CanImportWilds = require('../utils/can-import-wilds'); |
33 | 33 |
var Sunstone = require('sunstone'); |
34 | 34 |
var TemplateUtils = require('utils/template-utils'); |
35 |
var CapacityTable = require('utils/custom-tags-table'); |
|
36 |
var EC2Tr = require('utils/panel/ec2-tr'); |
|
35 | 37 |
|
36 | 38 |
/* |
37 | 39 |
TEMPLATES |
... | ... | |
68 | 70 |
that.unshownTemplate = {}; |
69 | 71 |
that.strippedTemplateVcenter = {}; |
70 | 72 |
that.strippedTemplate = {}; |
71 |
var unshownKeys = ['HOST', 'VM', 'WILDS', 'ZOMBIES', 'RESERVED_CPU', 'RESERVED_MEM']; |
|
73 |
var unshownKeys = ['HOST', 'VM', 'WILDS', 'ZOMBIES', 'RESERVED_CPU', 'RESERVED_MEM', "EC2_ACCESS", "EC2_SECRET", "CAPACITY", "REGION_NAME"];
|
|
72 | 74 |
$.each(that.element.TEMPLATE, function(key, value) { |
73 | 75 |
if ($.inArray(key, unshownKeys) > -1) { |
74 | 76 |
that.unshownTemplate[key] = value; |
... | ... | |
126 | 128 |
'maxReservedCPU': realCPU * 2, |
127 | 129 |
'realCPU': realCPU, |
128 | 130 |
'realMEM': Humanize.size(realMEM), |
129 |
'virtualMEMInput': Humanize.size(this.element.HOST_SHARE.MAX_MEM) |
|
131 |
'virtualMEMInput': Humanize.size(this.element.HOST_SHARE.MAX_MEM), |
|
132 |
'ec2_tr': EC2Tr.html(RESOURCE, this.element.TEMPLATE), |
|
133 |
'capacityTableHTML': CapacityTable.html() |
|
130 | 134 |
}); |
131 | 135 |
} |
132 | 136 |
|
... | ... | |
166 | 170 |
|
167 | 171 |
function _setup(context) { |
168 | 172 |
var that = this; |
169 |
|
|
173 |
$(".ec2",context).show(); |
|
170 | 174 |
RenameTr.setup(TAB_ID, RESOURCE, this.element.ID, context); |
171 | 175 |
ClusterTr.setup(RESOURCE, this.element.ID, this.element.CLUSTER_ID, context); |
172 | 176 |
|
... | ... | |
207 | 211 |
changeColorInputMEM(that.element.HOST_SHARE.TOTAL_MEM); |
208 | 212 |
document.getElementById('textInput_reserved_mem_hosts').value = Humanize.size(parseInt(document.getElementById('change_bar_mem_hosts').value)); |
209 | 213 |
}); |
214 |
|
|
210 | 215 |
document.getElementById("textInput_reserved_mem_hosts").addEventListener("input", function(){ |
211 | 216 |
changeInputMEM(that.element.HOST_SHARE.TOTAL_MEM); |
212 | 217 |
}); |
218 |
|
|
219 |
CapacityTable.setup(context, true, RESOURCE, this.element.TEMPLATE, this.element.ID); |
|
220 |
EC2Tr.setup(RESOURCE, this.element.ID, context); |
|
221 |
CapacityTable.fill(context, this.element.TEMPLATE.CAPACITY); |
|
222 |
$(".change_to_vector_attribute", context).hide(); |
|
223 |
$(".custom_tag_value",context).focusout(function(){ |
|
224 |
var key = $(".custom_tag_key",this.parentElement.parentElement).val(); |
|
225 |
if(!that.element.TEMPLATE.CAPACITY){ |
|
226 |
that.element.TEMPLATE.CAPACITY = {}; |
|
227 |
} |
|
228 |
that.element.TEMPLATE.CAPACITY[key] = this.value; |
|
229 |
Sunstone.runAction(RESOURCE+".update_template",that.element.ID, TemplateUtils.templateToString(that.element.TEMPLATE)); |
|
230 |
}); |
|
231 |
if (this.element.TEMPLATE.IM_MAD != "ec2"){ |
|
232 |
$(".ec2",context).hide(); |
|
233 |
} |
|
213 | 234 |
} |
214 | 235 |
}); |
src/sunstone/public/app/tabs/hosts-tab/panels/info/html.hbs | ||
---|---|---|
113 | 113 |
<div class="row"> |
114 | 114 |
<div class="large-9 columns">{{{templateTableHTML}}}</div> |
115 | 115 |
</div> |
116 |
<div class="row ec2"> |
|
117 |
<div class="large-12 columns"> |
|
118 |
<table class="dataTable"> |
|
119 |
<thead> |
|
120 |
<tr> |
|
121 |
<th colspan="3">{{tr "EC2"}}</th> |
|
122 |
</tr> |
|
123 |
</thead> |
|
124 |
<tbody> |
|
125 |
<tr> |
|
126 |
<div id="host_ec2_div" class="row"> |
|
127 |
<div class="row"> |
|
128 |
<div class="large12 columns"> |
|
129 |
<table class="dataTable"> |
|
130 |
<thead> |
|
131 |
<tr> |
|
132 |
<th colspan="3">{{tr "Attributes"}}</th> |
|
133 |
</tr> |
|
134 |
</thead> |
|
135 |
<tbody> |
|
136 |
{{{ec2_tr}}} |
|
137 |
</tbody> |
|
138 |
</table> |
|
139 |
</div> |
|
140 |
</div> |
|
141 |
<div class="row ec2"> |
|
142 |
<div class="large-12 columns" id="capacity"> |
|
143 |
<fieldset class="ec2_capacity"> |
|
144 |
<legend>{{tr "Capacity"}}</legend> |
|
145 |
{{{capacityTableHTML}}} |
|
146 |
</fieldset> |
|
147 |
</div> |
|
148 |
</div> |
|
149 |
</div> |
|
150 |
</tr> |
|
151 |
</tbody> |
|
152 |
</table> |
|
153 |
</div> |
|
154 |
</div> |
src/sunstone/public/app/utils/custom-tags-table.js | ||
---|---|---|
23 | 23 |
var VectorAttributeRowTemplateHTML = require('hbs!./custom-tags-table/vector-attribute-row'); |
24 | 24 |
var TemplateUtils = require('utils/template-utils'); |
25 | 25 |
var WizardFields = require('utils/wizard-fields'); |
26 |
var Sunstone = require('sunstone'); |
|
26 | 27 |
|
27 | 28 |
function _html(){ |
28 | 29 |
return TemplateHTML(); |
29 | 30 |
} |
30 | 31 |
|
31 |
function _setup(context){ |
|
32 |
function _setup(context, hide_vector_button = false, resourceType = undefined, element = undefined, elementID = undefined){
|
|
32 | 33 |
context.off("click", ".add_custom_tag"); |
33 | 34 |
context.on("click", ".add_custom_tag", function(){ |
34 | 35 |
$("tbody.custom_tags", context).append(RowTemplateHTML()); |
36 |
if(hide_vector_button){ |
|
37 |
$(".change_to_vector_attribute", context).hide(); |
|
38 |
$(".custom_tag_value",context).focusout(function(){ |
|
39 |
var key = $(".custom_tag_key",this.parentElement.parentElement).val(); |
|
40 |
if(!element.CAPACITY){ |
|
41 |
element.CAPACITY = {}; |
|
42 |
} |
|
43 |
element.CAPACITY[key] = this.value; |
|
44 |
Sunstone.runAction(resourceType+".update_template",elementID, TemplateUtils.templateToString(element)); |
|
45 |
}); |
|
46 |
} |
|
35 | 47 |
}); |
36 | 48 |
|
37 | 49 |
context.off("click", ".add_vector_attribute"); |
... | ... | |
58 | 70 |
context.on("click", "tbody.custom_tags i.remove-tab", function(){ |
59 | 71 |
var tr = $(this).closest('tr'); |
60 | 72 |
tr.remove(); |
73 |
if(hide_vector_button){ |
|
74 |
var key = $(".custom_tag_key",this.parentElement.parentElement.parentElement).val() |
|
75 |
if(element.CAPACITY && element.CAPACITY[key]){ |
|
76 |
delete element.CAPACITY[key]; |
|
77 |
Sunstone.runAction(resourceType+".update_template",elementID, TemplateUtils.templateToString(element)); |
|
78 |
} |
|
79 |
} |
|
61 | 80 |
}); |
62 | 81 |
} |
63 | 82 |
|
src/sunstone/public/app/utils/panel/ec2-tr.js | ||
---|---|---|
1 |
/* -------------------------------------------------------------------------- */ |
|
2 |
/* Copyright 2002-2017, OpenNebula Project, OpenNebula Systems */ |
|
3 |
/* */ |
|
4 |
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */ |
|
5 |
/* not use this file except in compliance with the License. You may obtain */ |
|
6 |
/* a copy of the License at */ |
|
7 |
/* */ |
|
8 |
/* http://www.apache.org/licenses/LICENSE-2.0 */ |
|
9 |
/* */ |
|
10 |
/* Unless required by applicable law or agreed to in writing, software */ |
|
11 |
/* distributed under the License is distributed on an "AS IS" BASIS, */ |
|
12 |
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ |
|
13 |
/* See the License for the specific language governing permissions and */ |
|
14 |
/* limitations under the License. */ |
|
15 |
/* -------------------------------------------------------------------------- */ |
|
16 |
define(function(require) { |
|
17 |
/* |
|
18 |
This module insert a row with the name of the resource. |
|
19 |
The row can be edited and a rename action will be sent |
|
20 |
*/ |
|
21 |
|
|
22 |
var TemplateEC2Tr = require('hbs!./ec2-tr/html'); |
|
23 |
var TemplateUtils = require('utils/template-utils'); |
|
24 |
var Sunstone = require('sunstone'); |
|
25 |
var Config = require('sunstone-config'); |
|
26 |
|
|
27 |
/* |
|
28 |
Generate the tr HTML with the name of the resource and an edit icon |
|
29 |
@param {String} tabName |
|
30 |
@param {String} resourceType Resource type (i.e: Zone, Host, Image...) |
|
31 |
@param {String} resourceName Name of the resource |
|
32 |
@returns {String} HTML row |
|
33 |
*/ |
|
34 |
var _html = function(resourceType, ec2_attributes) { |
|
35 |
this.element = ec2_attributes; |
|
36 |
this.valueRegion = ec2_attributes['REGION_NAME']? ec2_attributes['REGION_NAME']: ""; |
|
37 |
this.valueSecret = ec2_attributes['EC2_SECRET']? ec2_attributes['EC2_SECRET']: ""; |
|
38 |
this.valueAccess = ec2_attributes['EC2_ACCESS']? ec2_attributes['EC2_ACCESS']: ""; |
|
39 |
var renameTrHTML = TemplateEC2Tr({ |
|
40 |
'region_name': ec2_attributes['REGION_NAME'], |
|
41 |
'ec2_secret': ec2_attributes['EC2_SECRET'], |
|
42 |
'ec2_access': ec2_attributes['EC2_ACCESS'] |
|
43 |
}); |
|
44 |
|
|
45 |
return renameTrHTML; |
|
46 |
}; |
|
47 |
|
|
48 |
|
|
49 |
function _setup(resourceType, resourceId,context) { |
|
50 |
var that = this; |
|
51 |
context.off("click", "#div_edit_region_link"); |
|
52 |
context.on("click", "#div_edit_region_link", function() { |
|
53 |
var valueStr = $(".value_td_region", context).text(); |
|
54 |
$(".value_td_region", context).html('<input class="input_edit_value_region" id="input_edit_region" type="text" value="' + that.valueRegion + '"/>'); |
|
55 |
}); |
|
56 |
|
|
57 |
context.off("click", "#div_edit_access_link"); |
|
58 |
context.on("click", "#div_edit_access_link", function() { |
|
59 |
var valueStr = $(".value_td_access", context).text(); |
|
60 |
$(".value_td_access", context).html('<input class="input_edit_value_access" id="input_edit_access" type="text" value="' + that.valueAccess + '"/>'); |
|
61 |
}); |
|
62 |
|
|
63 |
context.off("click", "#div_edit_secret_link"); |
|
64 |
context.on("click", "#div_edit_secret_link", function() { |
|
65 |
var valueStr = $(".input_edit_value_secret", context).text(); |
|
66 |
$(".value_td_secret", context).html('<input class="input_edit_value_secret" id="input_edit_secret" type="text" value="' + that.valueSecret + '"/>'); |
|
67 |
}); |
|
68 |
|
|
69 |
context.off("change", ".input_edit_value_region"); |
|
70 |
context.on("change", ".input_edit_value_region", function() { |
|
71 |
var valueRegion = $(".input_edit_value_region").val(); |
|
72 |
that.element["REGION_NAME"] = valueRegion; |
|
73 |
Sunstone.runAction(resourceType+".update_template",resourceId, TemplateUtils.templateToString(that.element)); |
|
74 |
}); |
|
75 |
|
|
76 |
context.off("change", ".input_edit_value_access"); |
|
77 |
context.on("change", ".input_edit_value_access", function() { |
|
78 |
var valueAccess = $(".input_edit_value_access").val(); |
|
79 |
that.element["EC2_ACCESS"] = valueAccess; |
|
80 |
Sunstone.runAction(resourceType+".update_template",resourceId, TemplateUtils.templateToString(that.element)); |
|
81 |
}); |
|
82 |
|
|
83 |
context.off("change", ".input_edit_value_secret"); |
|
84 |
context.on("change", ".input_edit_value_secret", function() { |
|
85 |
var valueSecret = $(".input_edit_value_secret").val(); |
|
86 |
that.element["EC2_SECRET"] = valueSecret; |
|
87 |
Sunstone.runAction(resourceType+".update_template",resourceId, TemplateUtils.templateToString(that.element)); |
|
88 |
}); |
|
89 |
} |
|
90 |
|
|
91 |
return { |
|
92 |
'html': _html, |
|
93 |
'setup': _setup |
|
94 |
} |
|
95 |
}); |
src/sunstone/public/app/utils/panel/ec2-tr/html.hbs | ||
---|---|---|
1 |
<tr class="region_name"> |
|
2 |
<td class="key_td">{{tr "Region name"}}</td> |
|
3 |
<td class="value_td_region">{{region_name}}</td> |
|
4 |
<td> |
|
5 |
<div id="div_edit_region"> |
|
6 |
<a id="div_edit_region_link" class="edit_e" href="#"> <i class="fa fa-pencil-square-o right"/></a> |
|
7 |
</div> |
|
8 |
</td> |
|
9 |
</tr> |
|
10 |
<tr class="ec2_access"> |
|
11 |
<td class="key_td">{{tr "EC2 Access"}}</td> |
|
12 |
<td class="value_td_access">{{ec2_access}}</td> |
|
13 |
<td> |
|
14 |
<div id="div_edit_access"> |
|
15 |
<a id="div_edit_access_link" class="edit_e" href="#"> <i class="fa fa-pencil-square-o right"/></a> |
|
16 |
</div> |
|
17 |
</td> |
|
18 |
</tr> |
|
19 |
<tr class="ec2_secret"> |
|
20 |
<td class="key_td">{{tr "EC2 Secret"}}</td> |
|
21 |
<td class="value_td_secret">{{ec2_secret}}</td> |
|
22 |
<td> |
|
23 |
<div id="div_edit_secret"> |
|
24 |
<a id="div_edit_secret_link" class="edit_e" href="#"> <i class="fa fa-pencil-square-o right"/></a> |
|
25 |
</div> |
|
26 |
</td> |
|
27 |
</tr> |
Also available in: Unified diff