Revision 820ef2f0

b/db/fixtures/flavors.json
267 267
            "ram": 4096,
268 268
            "disk": 40
269 269
        }
270
    },
271

  
272
    {
273
        "model": "db.Flavor",
274
        "pk": 28,
275
        "fields": {
276
            "cpu": 2,
277
            "ram": 4096,
278
            "disk": 10
279
        }
280
    },
281

  
282
    {
283
        "model": "db.Flavor",
284
        "pk": 29,
285
        "fields": {
286
            "cpu": 1,
287
            "ram": 1024,
288
            "disk": 10
289
        }
270 290
    }
291

  
292

  
271 293
]
b/ui/static/main.css
1231 1231
    margin-top: 4px;
1232 1232
}
1233 1233

  
1234
div.image {
1234
.icon div.image {
1235 1235
    clear: both;
1236 1236
    display: block;
1237 1237
    margin-bottom: 3px;
......
4769 4769
    color: #fff;
4770 4770
}
4771 4771

  
4772
#createvm-overlay-content {
4773
    padding: 0;
4774
}
4775

  
4776
.create-vm .header-step.current {
4777
    font-weight: bold;
4778
}
4779

  
4780
.create-vm .create-step-cont {
4781
    margin: 20px;
4782
    min-height: 240px;
4783
}
4784
.create-vm .create-controls {
4785
    padding: 10px;
4786
}
4787

  
4788
.create-vm ul li {
4789
    cursor: pointer;
4790
    padding: 4px;
4791
}
4792

  
4793
.create-vm ul li.selected {
4794
    background-color: #aaa;
4795
}
4796 4772

  
4797 4773
.cont-toggler {
4798 4774
    background-image: url("./down-arrow.png");
......
4902 4878
    background-position: center center;
4903 4879
    color: transparent;
4904 4880
}
4881

  
4882
#createvm-overlay-content {
4883
    padding: 0;
4884
}
4885

  
4886
.create-vm .image-details.selected .size {
4887
    color: #eee;
4888
}
4889

  
4890
.create-vm .image-details p {
4891
    font-size: 0.8em;
4892
    padding-left: 27px;
4893
    display:block;
4894
}
4895

  
4896
.create-vm .image-details .size {
4897
    margin-top: 2px;
4898
    font-size: 0.8em;
4899
    color: #aaa;
4900
    position: absolute;
4901
    right:5px;
4902
    top: 5px;
4903
}
4904

  
4905
.create-vm .step-cont {
4906
    margin: 15px;
4907
}
4908

  
4909
.create-vm .create-step-cont {
4910
    min-height: 250px;
4911
    float: left;
4912
    width: 584px;
4913
}
4914

  
4915
.create-vm .create-controls {
4916
    padding: 10px;
4917
    border-top: 1px solid #ddd;
4918
}
4919

  
4920
.create-vm .empty {
4921
    font-size: 0.8em;
4922
    color: #444;
4923
}
4924

  
4925
.create-vm h4 {
4926
    color: #5CA1C0;
4927
    margin-bottom: 0.5em;
4928
    font-family: arial;
4929
}
4930
.create-vm ul li {
4931
    cursor: pointer;
4932
    padding: 4px;
4933
    font-size: 0.9em;
4934
}
4935

  
4936
.create-vm ul li.selected {
4937
    background-color: #5CA1C0;
4938
    color: #fff;
4939
}
4940

  
4941
.create-vm .images-list-cont {
4942
    width: 40%;
4943
    float: left;
4944
    padding-left: 3%;
4945
    padding-right: 3%;
4946
}
4947

  
4948
.create-vm .images-filter-cont, .create-vm .flavors-predefined-cont {
4949
    width: 18%;
4950
    padding-right: 4%;
4951
    float:left;
4952
    border-right: 1px solid #A1C8DB;
4953
    overflow: auto;
4954
}
4955

  
4956
.create-vm .flavor-options-cont {
4957
    width: 74%;
4958
    float: left;
4959
    margin-left: 20px;
4960
}
4961

  
4962
.create-vm .flavor-options-cont .flavor-options li:hover {
4963
    background-image:-webkit-linear-gradient(top, #E8F4FA, #D1E7F0);
4964
    background-image:linear-gradient(top, #E8F4FA, #D1E7F0);
4965
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#E8F4FA', endColorstr='#D1E7F0', GradientType=0);
4966
}
4967

  
4968
.create-vm .flavor-options-cont .flavor-options li.disabled * {
4969
    color: #eee !important;
4970
}
4971

  
4972
.create-vm .flavor-options-cont .flavor-options li.disabled {
4973
    background-image:linear-gradient(top, #aaa, #ddd);
4974
    background-image:-webkit-linear-gradient(top, #aaa, #ddd);
4975
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#aaaaaa', endColorstr='#dddddd', GradientType=0);
4976
}
4977

  
4978
.create-vm .flavor-options-cont .flavor-options li.selected {
4979
    background-image:linear-gradient(top, #FF9955, #E88B4D);
4980
    background-image:-webkit-linear-gradient(top, #FF9955, #E88B4D);
4981
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FF9955', endColorstr='#5CA1C0', GradientType=0);
4982
    border: 1px solid #C97943;
4983
}
4984

  
4985
.create-vm .predefined-list li.disabled {
4986
    color: #ddd !important;
4987
}
4988

  
4989
.create-vm .flavor-options-cont .flavor-options li {
4990
    display: block;
4991
    float: left;
4992
    margin-right: 10px;
4993
    padding: 10px 15px;
4994
    border: 1px solid #aaa;
4995
    background-image:-webkit-linear-gradient(top, #D1E7F0, #E8F4FA);
4996
    background-image:linear-gradient(top, #D1E7F0, #E8F4FA);
4997
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#D1E7F0', endColorstr='#E8F4FA', GradientType=0);
4998
}
4999

  
5000
.create-vm .flavor-options-cont .flavor-options {
5001
    margin-bottom: 25px;
5002
}
5003

  
5004
.create-vm .flavor-options .metric {
5005
    font-size: 0.8em;
5006
    margin-left: 2px;
5007
}
5008

  
5009
.create-vm .flavor-options span.title {
5010
    color: #444;
5011
}
5012

  
5013
.create-vm .flavor-options span.desc {
5014
    display: block;
5015
    color: #aaa;
5016
    font-weight: normal;
5017
    font-size: 0.8em;
5018
    float: right;
5019
    margin-top: 4px;
5020
}
5021

  
5022
.create-vm .flavor-options .selected .value {
5023
    color: #FFF;
5024
}
5025

  
5026
.create-vm .flavor-options .value {
5027
    font-weight: bold;
5028
    color: #5CA1C0;
5029
}
5030

  
5031
.create-vm .flavor-options-cont h4 {
5032
    border-bottom: 1px solid #A1C8DB;
5033
    padding-bottom: 5px;
5034
}
5035

  
5036
.create-vm .images-info-cont {
5037
    width: 28%;
5038
    padding-left: 3%;
5039
    float: left;
5040
    border-left: 1px solid #A1C8DB;
5041
}
5042

  
5043
.create-vm .images-info-cont h4 {
5044
    color: #FF9955;
5045
    margin-bottom: 1em;
5046
    font-size: 1.2em;
5047
}
5048

  
5049
.create-vm .images-info-cont span.title {
5050
    color: #4085A5;
5051
    display: block;
5052
    margin-bottom: 2px;
5053
    font-size: 0.8em;
5054
}
5055

  
5056
.create-vm .type-filter li {
5057
    font-size: 0.8em;
5058
    font-weight: bold;
5059
    padding: 4px;
5060
    margin-bottom: 0px;
5061
    
5062
}
5063

  
5064
.create-vm .images-list .image-details:hover {
5065
    background-color: #eee;
5066
}
5067

  
5068
.create-vm .images-list .image-details.selected:hover {
5069
    background-color: #5CA1C0;
5070
}
5071

  
5072
.create-vm .images-list .image-details.selected {
5073
    /*font-weight: bold;*/
5074
}
5075

  
5076
.create-vm .images-list .image-details {
5077
    padding: 6px;
5078
    margin-bottom:4px;
5079
    position: relative;
5080
}
5081

  
5082
.create-vm .images-list .image-details img {
5083
    vertical-align: middle;
5084
    margin-right: 10px;
5085
}
5086

  
5087
.create-vm .images-info-cont .image-detail:last-child p {
5088
    border-bottom: none;
5089
}
5090

  
5091
.create-vm .images-info-cont h4 img {
5092
    vertical-align: middle;
5093
    margin-right: 7px;
5094
    margin-bottom: 5px;
5095
}
5096

  
5097
.create-vm .images-info-cont .description p {
5098
    font-size: 0.8em;
5099
}
5100

  
5101
.create-vm .images-info-cont p {
5102
    margin-bottom: 7px;
5103
    font-size: 0.9em;
5104
    border-bottom: 1px solid #A1C8DB;
5105
    padding-bottom: 7px;
5106
}
5107

  
5108
.create-vm .step-header {
5109
    padding: 10px;
5110
    padding-bottom:0;
5111
    margin-bottom: 10px;
5112
    background-color: #A1C8DB;
5113
    border-bottom: 1px solid #aaa;
5114
    position: relative;
5115
}
5116

  
5117
.create-vm .step-header .header-step .info span.subtitle {
5118
    font-size: 1.2em;
5119
    color: #fff;
5120
    font-weight: bold;
5121
}
5122

  
5123
.create-vm .step-header .header-step .info span {
5124
    float: none;
5125
    text-align: right;
5126
}
5127

  
5128
.create-vm .step-header .header-step .info {
5129
    position: absolute;
5130
    right: 15px;
5131
    top: 20px;
5132
    font-size: 0.8em;
5133
}
5134

  
5135
.create-vm .step-header .header-step span {
5136
    float: left;
5137
    display: block;
5138
}
5139

  
5140
.create-vm .steps-container {
5141
    width: 2000em;
5142
}
5143

  
5144
.create-vm .step-header .header-step .title {
5145
    margin-top: 20px;
5146
    font-size: 1em;
5147
    margin-left: 10px;
5148
}
5149

  
5150
#createvm-overlay-content {
5151
    width: 584px;
5152
    overflow: hidden;
5153
}
5154

  
5155
.create-vm .step-header .header-step .num {
5156
    font-size: 4em;
5157
    margin-bottom: -5px;
5158
    font-family: 'Ubuntu';
5159
    font-weight: normal !important;
5160
}
5161

  
5162
.create-vm .step-header .header-step {
5163
    color: #DCEBF1;
5164
    margin-bottom: -6px;
5165
    width: 25%;
5166
    padding-left: 0%;
5167
    display: block;
5168
    float: left;
5169
}
5170

  
5171
.create-vm .step-header .header-step.current {
5172
    color: #387693;
5173
}
5174

  
5175
.create-vm .image-filters-title {
5176
    margin-top: 1em;
5177
    margin-bottom: 0.5em;
5178
}
5179

  
5180
.create-vm .create-step-cont span.clear {
5181
    font-size: 0.8em;
5182
    font-weight: bold;
5183
    color: #A1C8DB;
5184
    display: block;
5185
}
5186

  
5187
.create-vm .category-filters li {
5188
    float:left;
5189
    display: block;
5190
    padding: 4px;
5191
    background-color: #eee;
5192
    margin-right: 5px;
5193
    font-size: 0.9em;
5194
    margin-bottom: 5px;
5195
}
5196

  
5197
.create-vm .vm-confirm .rename input {
5198
    font-size: 1.4em;
5199
    padding: 1%;
5200
    width: 93%;
5201
    padding-left: 30px;
5202
    background-position: 7px center;
5203
    background-repeat: no-repeat;
5204
}
5205

  
5206
.create-vm .vm-confirm .rename label {
5207
    display: block;
5208
}
5209

  
5210
.create-vm .vm-confirm .confirm-conts {
5211
    margin-top: 20px;
5212
}
5213

  
5214
.create-vm .vm-confirm .confirm-cont {
5215
    width: 30%;
5216
    margin-right: 2%;
5217
    border-right: 1px solid #A1C8DB;
5218
    float: left;
5219
}
5220

  
5221
.create-vm .vm-confirm .confirm-cont. ul li {
5222
    padding:0;
5223
    margin:0;
5224
    margin-bottom: 5px;
5225
}
5226

  
5227
.create-vm .vm-confirm .confirm-cont.meta {
5228
    margin-right:0;
5229
    border-right: none;
5230
}
b/ui/static/snf/js/models.js
128 128
        path: 'images',
129 129

  
130 130
        get_size: function() {
131
            return this.get('metadata') ? this.get('metadata').values.size : undefined;
131
            return parseInt(this.get('metadata') ? this.get('metadata').values.size : -1)
132
        },
133

  
134
        get_os: function() {
135
            return this.get("OS");
132 136
        }
133 137
    });
134 138

  
......
138 142

  
139 143
        details_string: function() {
140 144
            return "{0} CPU, {1}MB, {2}GB".format(this.get('cpu'), this.get('ram'), this.get('disk'));
141
        }
145
        },
146

  
147
        get_disk_size: function() {
148
            return parseInt(this.get("disk") * 1000)
149
        },
150

  
142 151
    });
143 152
    
144 153
    //network vms list helper
......
1039 1048

  
1040 1049
        parse: function (resp, xhr) {
1041 1050
            // FIXME: depricated global var
1042
            window.networks = resp.networks ? resp.networks.values : {}; 
1043
            if (window.update_networks_view) {
1044
                update_networks_view(undefined, resp);
1045
            }
1046

  
1047 1051
            if (!resp) { return []};
1048 1052
               
1049 1053
            var data = _.map(resp.networks.values, _.bind(this.parse_net_api_data, this));
......
1085 1089

  
1086 1090
        parse: function (resp, xhr) {
1087 1091
            // FIXME: depricated global var
1088
            window.images = resp.images.values;
1089 1092
            var data = _.map(resp.images.values, _.bind(this.parse_meta, this));
1090 1093
            return resp.images.values;
1091 1094
        },
......
1131 1134

  
1132 1135
        parse: function (resp, xhr) {
1133 1136
            // FIXME: depricated global var
1134
            window.flavors = resp.flavors.values;
1135 1137
            return resp.flavors.values;
1136 1138
        },
1137 1139

  
1138
        for_image: function(img) {
1139
            var size = parseInt(img.get("size"));
1140
            return _.select(this.active(), function(el) {
1141
                return parseInt(parseInt(el.get("disk")) * 1000) >= size;
1142
            })
1140
        unavailable_values_for_image: function(img, flavors) {
1141
            var flavors = flavors || this.active();
1142
            var size = img.get_size();
1143
            
1144
            var index = {cpu:[], disk:[], ram:[]};
1145

  
1146
            _.each(this.active(), function(el) {
1147
                var img_size = size;
1148
                var flv_size = el.get_disk_size();
1149
                if (flv_size < img_size) {
1150
                    if (index.disk.indexOf(flv_size) == -1) {
1151
                        index.disk.push(flv_size);
1152
                    }
1153
                };
1154
            });
1155
            
1156
            return index;
1143 1157
        },
1144
        
1158

  
1145 1159
        get_flavor: function(cpu, mem, disk, filter_list) {
1146 1160
            if (!filter_list) { filter_list = this.models };
1147 1161
            return this.select(function(flv){
1148 1162
                if (flv.get("cpu") == cpu + "" &&
1149 1163
                   flv.get("ram") == mem + "" &&
1150 1164
                   flv.get("disk") == disk + "" &&
1151
                   filter_list.indexOf(flv) > -1)
1152
                {
1153
                    return true;
1154
                }
1165
                   filter_list.indexOf(flv) > -1) { return true; }
1155 1166
            })[0]
1156 1167
        },
1157

  
1168
        
1158 1169
        get_data: function(lst) {
1159 1170
            var data = {'cpu': [], 'mem':[], 'disk':[]};
1171

  
1160 1172
            _.each(lst, function(flv) {
1161
                if (data.cpu.indexOf(flv.cpu) == -1) {
1162
                    data.cpu[data.cpu.length] = flv.cpu;
1173
                if (data.cpu.indexOf(flv.get("cpu")) == -1) {
1174
                    data.cpu.push(flv.get("cpu"));
1163 1175
                }
1164
                if (data.cpu.indexOf(flv.mem) == -1) {
1165
                    data.mem[data.mem.length] = flv.mem;
1176
                if (data.mem.indexOf(flv.get("ram")) == -1) {
1177
                    data.mem.push(flv.get("ram"));
1166 1178
                }
1167
                if (data.disk.indexOf(flv.disk) == -1) {
1168
                    data.disk[data.disk.length] = flv.disk;
1179
                if (data.disk.indexOf(flv.get("disk")) == -1) {
1180
                    data.disk.push(flv.get("disk"));
1169 1181
                }
1170 1182
            })
1171

  
1183
            
1172 1184
            return data;
1173 1185
        },
1174 1186

  
......
1187 1199
        parse: function (resp, xhr) {
1188 1200
            // FIXME: depricated after refactoring
1189 1201
            var data = resp;
1190
            if (data && data.servers && data.servers.values.length > 0) {
1191
                if (window.update_servers_data) {
1192
                    update_servers_data(data.servers.values, data)
1193
                }
1194
                if (window.update_machines_view) {
1195
                    update_machines_view(data);
1196
                    //update_network_names(data);
1197
                }
1198
            }
1199 1202
            if (!resp) { return [] };
1200
            data = _.map(resp.servers.values, _.bind(this.parse_vm_api_data, this));
1203
            data = _.filter(_.map(resp.servers.values, _.bind(this.parse_vm_api_data, this)), function(v){return v});
1201 1204
            return data;
1202 1205
        },
1203 1206
        
......
1224 1227
        },
1225 1228

  
1226 1229
        parse_vm_api_data: function(data) {
1230

  
1231
            // do not add non existing DELETED entries
1232
            if (data.status && data.status == "DELETED") {
1233
                if (!this.get(data.id)) {
1234
                    console.error("non exising deleted vm", data)
1235
                    return false;
1236
                }
1237
            }
1238

  
1227 1239
            // OS attribute
1228 1240
            if (this.has_meta(data)) {
1229 1241
                data['OS'] = data.metadata.values.OS || "undefined";
b/ui/static/snf/js/ui/web/ui_create_view.js
64 64
        initialize: function(view) {
65 65
            this.parent = view;
66 66
            this.el = view.$("div.create-step-cont.step-" + this.step);
67
            this.header = view.$(".subheader .step-" + this.step);
67
            this.header = this.$(".step-header .step-" + this.step);
68 68
            this.view_id = "create_step_" + this.step;
69 69

  
70 70
            views.CreateVMStepView.__super__.initialize.apply(this);
......
74 74
            // show current
75 75
            this.el.show();
76 76
            this.header.addClass("current");
77
            this.header.show();
77 78
            this.update_layout();
78 79
        },
79 80

  
......
86 87
        initialize: function() {
87 88
            views.CreateImageSelectView.__super__.initialize.apply(this, arguments);
88 89

  
89
            this.predefined = this.$(".predefined-images ul");
90
            this.custom = this.$(".predefined-images ul");
91
            this.selected_image = undefined;
92
            this.reset_images();
90
            // elements
91
            this.images_list = this.$(".images-list-cont ul");
92
            this.image_details = this.$(".images-info-cont");
93
            this.image_details_desc = this.$(".images-info-cont .description p");
94
            this.image_details_title = this.$(".images-info-cont h4");
95
            this.image_details_size = this.$(".images-info-cont .size p");
96
            this.image_details_os = this.$(".images-info-cont .os p");
97
            this.image_details_kernel = this.$(".images-info-cont .kernel p");
98
            this.image_details_gui = this.$(".images-info-cont .gui p");
99

  
100
            this.types = this.$(".type-filter li");
101
            this.categories_list = this.$(".category-filters");
102

  
103
            // params initialization
104
            this.type_selections = ["system", "custom"]
105
            this.selected_type = "system";
106
            this.selected_categories = [];
107
            this.images = [];
108

  
109
            // update
110
            this.update_images();
111

  
112
            // handlers initialization
113
            this.init_handlers();
114
            this.init_position();
115
        },
116

  
117
        init_position: function() {
118
            //this.el.css({position: "absolute"});
119
            //this.el.css({top:"10px"})
120
        },
121
        
122
        init_handlers: function() {
123
            var self = this;
124
            this.types.live("click", function() {
125
                self.select_type($(this).attr("id").replace("type-select-",""));
126
            })
127
        },
128

  
129
        update_images: function() {
130
            this.images = storage.images.active();
131
            this.images_ids = _.map(this.images, function(img){return img.id});
132
            if (this.selected_type == "custom") { this.images = []; this.images_ids = []; }
133

  
134
            return this.images;
93 135
        },
94 136

  
95 137
        update_layout: function() {
138
            this.select_type(this.selected_type);
139
        },
140
        
141
        get_categories: function(images) {
142
            return [];
143
            return ["Desktop", "Server", "Linux", "Windows"];
144
        },
145

  
146
        reset_categories: function() {
147
            var categories = this.get_categories(this.images);
148
            this.categories_list.find("li").remove();
149

  
150
            _.each(categories, _.bind(function(cat) {
151
                var el = $("<li />");
152
                el.text(cat);
153
                this.categories_list.append(el);
154
            }, this));
155

  
156
            if (!categories.length) { 
157
                this.categories_list.parent().find(".clear").hide();
158
                this.categories_list.parent().find(".empty").show();
159
            } else {
160
                this.categories_list.parent().find(".clear").show();
161
                this.categories_list.parent().find(".empty").hide();
162
            }
163
        },
164
        
165
        select_type: function(type) {
166
            this.selected_type = type;
167
            this.types.removeClass("selected");
168
            this.types.filter("#type-select-" + this.selected_type).addClass("selected");
169

  
170
            this.reset_categories();
171
            this.update_images();
172
            this.reset_images();
173
            this.select_image();
96 174
        },
97 175

  
98 176
        select_image: function(image) {
99
            if (!image) {
100
                image = storage.images.at(0);
177
            if (!image && this.images_ids.length) {
178
                if (this.selected_image && this.images_ids.indexOf(this.selected_image.id) > -1) {
179
                    image = this.selected_image;
180
                } else {
181
                    image = storage.images.get(this.images_ids[0]);
182
                }
101 183
            }
102 184

  
185
            if (!this.images_ids.length) { image = this.selected_image || undefined };
186
            
103 187
            this.selected_image = image;
104
            this.trigger("change", this.selected_image);
188
            this.trigger("change", image);
105 189
            
106
            this.predefined.find(".image-details").removeClass("selected");
107
            this.predefined.find(".image-details#create-vm-image-" + this.selected_image.id).addClass("selected")
190
            if (image) {
191
                this.image_details.show();
192
                this.images_list.find(".image-details").removeClass("selected");
193
                this.images_list.find(".image-details#create-vm-image-" + this.selected_image.id).addClass("selected");
194
                
195
                this.image_details_desc.text(image.get("description"));
196
                
197
                var img = snf.ui.helpers.os_icon_tag(image.get("OS"))
198
                this.image_details_title.html(img + image.get("name"));
199
                this.image_details_os.text(_(image.get("OS")).capitalize());
200
                this.image_details_kernel.text(image.get("kernel"));
201

  
202
                var size = util.readablizeBytes(parseInt(image.get("size")) * 1000000);
203
                this.image_details_size.text(size);
204
                this.image_details_gui.text(image.get("GUI"));
205

  
206
            } else {
207
                this.image_details.hide();
208
            }
108 209
        },
109 210

  
110 211
        reset_images: function() {
111
            this.$(".image-details").remove();
112
            _.each(storage.images.active(), _.bind(function(img){
212
            this.images_list.find("li").remove();
213
            _.each(this.images, _.bind(function(img){
113 214
                this.add_image(img);
114 215
            }, this))
216
            
217
            if (this.images.length) {
218
                this.images_list.parent().find(".empty").hide();
219
            } else {
220
                this.images_list.parent().find(".empty").show();
221
            }
115 222

  
116 223
            this.select_image();
117 224
            
118 225
            var self = this;
119
            this.predefined.find(".image-details").click(function(){
226
            this.images_list.find(".image-details").click(function(){
120 227
                self.select_image($(this).data("image"));
121
            })
228
            });
229
            
230
            util.equalHeights(this.$(".images-filter-cont"), this.$(".images-list-cont"), this.$(".images-info-cont"));
231
        },
232

  
233
        show: function() {
234
            views.CreateImageSelectView.__super__.show.apply(this, arguments);
235
            util.equalHeights(this.$(".images-filter-cont"), this.$(".images-list-cont"), this.$(".images-info-cont"));
122 236
        },
123 237

  
124 238
        add_image: function(img) {
125
            var image = $('<li id="create-vm-image-{1}" class="image-details">{0}</li>'.format(img.get("name"), img.id));
239
            var image = $(('<li id="create-vm-image-{1}"' +
240
                           'class="image-details clearfix">{2}{0}' +
241
                           '<p>{4}</p><span class="size">{3}' +
242
                           '</span></li>').format(img.get("name"), 
243
                                                  img.id, 
244
                                                  snf.ui.helpers.os_icon_tag(img.get("OS")),
245
                                                  util.readablizeBytes(parseInt(img.get("size"))*1000000),
246
                                                  util.truncate(img.get("description"),35)));
126 247
            image.data("image", img);
127 248
            image.data("image_id", img.id);
128
            this.predefined.append(image);
249
            this.images_list.append(image);
129 250
        },
130 251

  
131 252
        reset: function() {
......
144 265
            views.CreateFlavorSelectView.__super__.initialize.apply(this, arguments);
145 266
            this.parent.bind("image:change", _.bind(this.handle_image_change, this));
146 267

  
147
            this.cpus = this.$(".flavors-cpu-list")
148
            this.disks = this.$(".flavors-disk-list")
149
            this.mems = this.$(".flavors-mem-list")
268
            this.cpus = this.$(".flavors-cpu-list");
269
            this.disks = this.$(".flavors-disk-list");
270
            this.mems = this.$(".flavors-mem-list");
150 271

  
151
            this.reset();
152
            this.update_layout();
272
            this.predefined_flavors = SUGGESTED_FLAVORS;
273
            this.predefined = this.$(".predefined-list");
274
            this.update_predefined_flavors();
153 275
        },
154 276

  
155 277
        handle_image_change: function(data) {
156 278
            this.current_image = data;
279
            this.update_valid_predefined();
157 280
            this.update_flavors_data();
158 281
            this.reset_flavors();
159 282
            this.update_layout();
......
164 287
            this.create_flavors();
165 288
        },
166 289

  
290
        update_predefined_flavors: function() {
291
            this.predefined.find("li").remove();
292
            _.each(this.predefined_flavors, _.bind(function(val, key) {
293
                var el = $(('<li class="predefined-selection" id="predefined-flavor-{0}">' +
294
                           '{1}</li>').format(key, key));
295

  
296
                this.predefined.append(el);
297
                el.data({flavor: storage.flavors.get_flavor(val.cpu, val.ram, val.disk, this.flavors)})
298
                el.click(_.bind(function() {
299
                    this.handle_predefined_click(el);
300
                }, this))
301
            }, this));
302
            this.update_valid_predefined();
303
        },
304

  
305
        handle_predefined_click: function(el) {
306
            if (el.hasClass("disabled")) { return };
307
            this.set_current(el.data("flavor"))
308
        },
309

  
310
        update_valid_predefined: function() {
311
            this.update_unavailable_values();
312
            var self = this;
313
            this.valid_predefined = _.select(_.map(this.predefined_flavors, function(flv, key){
314
                var existing = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, self.flavors);
315
                // non existing
316
                if (!existing) {
317
                    return false;
318
                }
319
                
320
                // not available for image
321
                if (self.unavailable_values && self.unavailable_values.disk.indexOf(existing.get_disk_size()) > -1) {
322
                    return false
323
                }
324

  
325
                return key;
326
            }), function(ret) { return ret });
327
            
328
            $("li.predefined-selection").addClass("disabled");
329
            _.each(this.valid_predefined, function(key) {
330
                $("#predefined-flavor-" + key).removeClass("disabled");
331
            })
332
        },
333

  
334
        update_selected_predefined: function() {
335
            var self = this;
336
            this.predefined.find("li").removeClass("selected");
337

  
338
            _.each(this.valid_predefined, function(key){
339
                var flv = self.predefined_flavors[key];
340
                var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, self.flavors);
341

  
342
                if (exists && (exists.id == self.current_flavor.id)) {
343
                    $("#predefined-flavor-" + key).addClass("selected");
344
                }
345
            })
346
        },
347
        
167 348
        update_flavors_data: function() {
168
            this.flavors = storage.flavors.for_image(this.current_image);
349
            this.flavors = storage.flavors.active();
169 350
            this.flavors_data = storage.flavors.get_data(this.flavors);
170

  
171
            if (this.flavors.indexOf(this.current_flavor) == -1) {
172
                this.set_current(this.flavors[0]);
351
            
352
            var self = this;
353
            var set = false;
354
            
355
            // FIXME: validate current flavor
356
            
357
            if (!this.current_flavor) {
358
                _.each(this.valid_predefined, function(key) {
359
                    var flv = self.predefined_flavors[key];
360
                    var exists = storage.flavors.get_flavor(flv.cpu, flv.ram, flv.disk, self.flavors);
361
                    if (exists && !set) {
362
                        self.set_current(exists);
363
                        set = true;
364
                    }
365
                })
173 366
            }
367

  
368
            this.update_unavailable_values();
174 369
        },
175 370

  
371
        update_unavailable_values: function() {
372
            if (!this.current_image) { this.unavailable_values = {disk:[], ram:[], cpu:[]}; return };
373
            this.unavailable_values = storage.flavors.unavailable_values_for_image(this.current_image);
374
        },
375
        
376
        flavor_is_valid: function(flv) {
377
            if (!flv) { return false };
378
            var existing = storage.flavors.get_flavor(flv.get("cpu"), flv.get("ram"), flv.get("disk"), this.flavors);
379
            if (!existing) { return false };
380
            if (this.unavailable_values && this.unavailable_values.disk.indexOf(flv.get("disk") > -1)) {
381
                return false
382
            }
383
            return true;
384
        },
385
            
176 386
        set_current: function(flv) {
387
            //console.log(flv);
388
            //if (!this.flavor_is_valid(flv)) { flv = undefined };
389
            
390
            console.log(flv);
177 391
            this.current_flavor = flv;
178 392
            this.trigger("change");
179 393
            this.update_selected_flavor();
394
            this.update_selected_predefined();
395
        },
396
        
397
        select_default_flavor: function() {
398
               
399
        },
400

  
401
        update_selected_from_ui: function() {
402
            this.set_current(this.ui_selected());
403
        },
404
        
405
        update_disabled_flavors: function() {
406
            this.$(".flavor-options.disk li").removeClass("disabled");
407
            if (!this.unavailable_values) { return }
408

  
409
            this.$(".flavor-options.disk li").each(_.bind(function(i, el){
410
                var el_value = $(el).data("value") * 1000;
411
                if (this.unavailable_values.disk.indexOf(el_value) > -1) {
412
                    $(el).addClass("disabled");
413
                };
414
            }, this));
180 415
        },
181 416

  
182 417
        create_flavors: function() {
183
            var flavors = this.get_flavors();
418
            var flavors = this.get_active_flavors();
419
            var valid_flavors = this.get_valid_flavors();
420

  
184 421
            _.each(flavors, _.bind(function(flv){
185 422
                this.add_flavor(flv);
186 423
            }, this));
......
188 425
            var self = this;
189 426
            this.$(".flavor-options li.option").click(function(){
190 427
                var el = $(this);
428

  
429
                if (el.hasClass("disabled")) { return }
430

  
191 431
                el.parent().find(".option").removeClass("selected");
192 432
                el.addClass("selected");
433
                
434
                if (el.hasClass("mem")) { this.last_choice = "mem" }
435
                if (el.hasClass("cpu")) { this.last_choice = "cpu" }
436
                if (el.hasClass("disk")) { this.last_choice = "disk" }
437

  
193 438
                self.update_selected_from_ui();
194 439
            })
195 440
        },
196 441
        
197 442
        ui_selected: function() {
198
            args = [this.$(".option.cpu.selected").data("value"), 
443
            var args = [this.$(".option.cpu.selected").data("value"), 
199 444
                this.$(".option.mem.selected").data("value"), 
200 445
                this.$(".option.disk.selected").data("value"),
201 446
            this.flavors];
202 447

  
203
            storage.flavors.apply(storage.flavors, args)
448
            var flv = storage.flavors.get_flavor.apply(storage.flavors, args);
449
            return flv;
204 450
        },
205 451

  
206 452
        update_selected_flavor: function() {
453
            var flv = this.current_flavor;
454
            this.$(".option").removeClass("selected");
207 455

  
456
            this.$(".option.cpu.value-" + flv.get("cpu")).addClass("selected");
457
            this.$(".option.mem.value-" + flv.get("ram")).addClass("selected");
458
            this.$(".option.disk.value-" + flv.get("disk")).addClass("selected");
208 459
        },
209 460

  
210 461
        add_flavor: function(flv) {
211 462
            var values = {'cpu': flv.get('cpu'), 'mem': flv.get('ram'), 'disk': flv.get('disk')};
212
            
463

  
464
            disabled = "";
465

  
213 466
            if (this.$('li.option.cpu.value-{0}'.format(values.cpu)).length == 0) {
214
                var cpu = $('<li class="option cpu value-{0}">{0}</li>'.format(values.cpu)).data('value', values.cpu);
467
                var cpu = $(('<li class="option cpu value-{0} {1}">' + 
468
                             '<span class="value">{0}</span>' + 
469
                             '<span class="metric">x</span></li>').format(values.cpu, disabled)).data('value', values.cpu);
470

  
215 471
                this.cpus.append(cpu);
216 472
            }
217 473
            if (this.$('li.option.mem.value-{0}'.format(values.mem)).length == 0) {
218
                var mem = $('<li class="option mem value-{0}">{0}</li>'.format(values.mem)).data('value', values.mem);
474
                var mem = $(('<li class="option mem value-{0}">' + 
475
                             '<span class="value">{0}</span>' + 
476
                             '<span class="metric">MB</span></li>').format(values.mem)).data('value', values.mem);
477

  
219 478
                this.mems.append(mem);
220 479
            }
221 480
            if (this.$('li.option.disk.value-{0}'.format(values.disk)).length == 0) {
222
                var disk = $('<li class="option disk value-{0}">{0}</li>'.format(values.disk)).data('value', values.disk);
481
                var disk = $(('<li class="option disk value-{0}">' + 
482
                              '<span class="value">{0}</span>' + 
483
                              '<span class="metric">GB</span></li>').format(values.disk)).data('value', values.disk);
484

  
223 485
                this.disks.append(disk);
224 486
            }
225 487
            
226 488
        },
489
        
490
        get_active_flavors: function() {
491
            return storage.flavors.active();
492
        },
227 493

  
228
        get_flavors: function() {
494
        get_valid_flavors: function() {
229 495
            return this.flavors;
230 496
        },
231 497

  
232 498
        update_layout: function() {
233
            var flv = this.current_flavor;
234
            this.$(".option.value").removeClass("selected");
235

  
236
            this.$(".option.cpu.value-" + flv.get("cpu")).addClass("selected");
237
            this.$(".option.mem.value-" + flv.get("ram")).addClass("selected");
238
            this.$(".option.disk.value-" + flv.get("disk")).addClass("selected");
499
            this.update_selected_flavor();
500
            this.update_disabled_flavors();
239 501
        },
240 502

  
241 503
        reset: function() {
......
263 525
        },
264 526

  
265 527
        init_handlers: function() {
266
            this.name.bind("keypress", _.bind(function() {
528
            this.name.bind("keypress", _.bind(function(e) {
267 529
                this.name_changed = true;
530
                if (e.keyCode == 13) { this.parent.submit() };    
268 531
            }, this));
269 532

  
270 533
            this.name.bind("click", _.bind(function() {
......
278 541
            views.CreateSubmitView.__super__.show.apply(this, arguments);
279 542
            this.update_layout();
280 543
        },
544
        
545
        update_image_details: function() {
546
            var image = this.parent.get_params().image;
547

  
548
            function set_img_detail(sel, key) {
549
                if (!key) { key = image.get(key) };
550
                this.$(".confirm-cont.image .image-" + sel + " .value").text(key)
551
            }
552
            
553
            set_img_detail("description");
554
            set_img_detail("name");
555
            set_img_detail("os", image.get("OS"));
556
        },
281 557

  
282 558
        update_layout: function() {
283 559
            var params = this.parent.get_params();
560

  
561
            if (!params.image) { return }
284 562
            var vm_name = "My {0} server".format(params.image.get("name"));
285 563
            var orig_name = vm_name;
286 564
            
287 565
            var existing = true;
288 566
            var j = 0;
289
            while (existing) {
567
            while (existing && !this.name_changed) {
290 568
                var existing = storage.vms.select(function(vm){return vm.get("name") == vm_name}).length
291 569
                if (existing) {
292 570
                    j++;
293 571
                    vm_name = orig_name + " " + j;
294 572
                }
295 573
            }
296

  
297 574
            if (!_(this.name.val()).trim() || !this.name_changed) {
298 575
                this.name.val(vm_name);
299 576
            }
......
302 579
            this.confirm.find("li.cpu .value").text(params.flavor.get("cpu"));
303 580
            this.confirm.find("li.mem .value").text(params.flavor.get("ram"));
304 581
            this.confirm.find("li.disk .value").text(params.flavor.get("disk"));
582

  
583
            if (!this.name_changed) {
584
                this.name.select().focus();
585
            }
586
            
587
            var img = snf.ui.helpers.os_icon_path(params.image.get("OS"))
588
            this.name.css({backgroundImage:"url({0})".format(img)})
589

  
590
            this.update_image_details();
305 591
        },
306 592

  
307 593
        reset: function() {
594
            this.name_changed = false;
308 595
            this.update_layout();
309 596
        },
310 597

  
......
375 662
        },
376 663

  
377 664
        submit: function() {
665
            if (this.submiting) { return };
378 666
            var data = this.get_params();
379 667
            if (this.validate(data)) {
380 668
                this.submit_btn.addClass("in-progress");
669
                this.submiting = true;
381 670
                storage.vms.create(data.name, data.image, data.flavor, {}, {}, _.bind(function(data){
382 671
                    this.close_all();
383
                    this.password_view.show(data.server.adminPass);
672
                    this.password_view.show(data.server.adminPass, data.server.id);
673
                    this.submiting = false;
384 674
                }, this));
385 675
            }
386 676
        },
......
409 699
        },
410 700

  
411 701
        beforeOpen: function() {
702
            this.submiting = false;
412 703
            this.reset();
413 704
            this.current_step = 1;
705
            this.$(".steps-container").css({"margin-left":0 + "px"});
414 706
            this.show_step(1);
415 707
        },
416 708

  
......
423 715
            }
424 716
                
425 717
            // hide other
426
            this.$(".subheader .header-step").removeClass("current");
427
            this.$(".create-step-cont").hide();
718
            //this.$(".step-header .header-step").removeClass("current").hide();
719
            //this.$(".create-step-cont").hide();
720
            var width = this.el.find('.container').width();
721
            var left = (step -1) * width * -1;
722
            this.$(".steps-container").css({"margin-left":left + "px"});
428 723
            
429 724
            this.steps[step].show();
430 725
            this.current_step = step;
431 726
            this.current_view = this.steps[step];
432
            //
433 727
            this.update_controls();
434 728
        },
435 729

  
b/ui/static/snf/js/utils.js
131 131
        return uri;
132 132
    }
133 133

  
134
    synnefo.util.equalHeights = function() {
135
        var max_height = 0;
136
        var selectors = _.toArray(arguments);
137

  
138
        // TODO: implement me
139
    }
140

  
134 141
    synnefo.util.truncate = function(string, size, append, words) {
135 142
        if (string.length <= size) {
136 143
            return string;
......
224 231
        } catch (err) {}
225 232
        return "";
226 233
    },
234
    
235
    synnefo.util.array_combinations = function(arr) {
236
        if (arr.length == 1) {
237
            return arr[0];
238
        } else {
239
            var result = [];
240

  
241
            // recur with the rest of array
242
            var allCasesOfRest = synnefo.util.array_combinations(arr.slice(1));  
243
            for (var i = 0; i < allCasesOfRest.length; i++) {
244
                for (var j = 0; j < arr[0].length; j++) {
245
                    result.push(arr[0][j] + "-" + allCasesOfRest[i]);
246
                }
247
            }
248
            return result;
249
        }
250
    }
227 251

  
228 252
    synnefo.util.parse_api_error = function(arguments) {
229 253
        arguments = arguments[0];
b/ui/templates/partials/create_vm.html
1 1
{% load i18n %}
2
<div id="createvm-overlay-content" class="hidden vm-meta create-vm">
3
    <div class="subheader">
4
        <span class="header-step step-1">1 <span>{% trans "Image" %}</span></span>
5
        <span class="header-step step-2">2 <span>{% trans "Flavor" %}</span></span>
6
        <span class="header-step step-3">3 <span>{% trans "Name" %}</span></span>
7
        </div>
8

  
9
        <div class="steps-containter">
10
            <div class="step-1 select-image create-step-cont">
11
                <div class="predefined-images">
12
                    <ul class="images-list">
13
                    </ul>
2
<div id="createvm-overlay-content" class="hidden create-vm">
3
    <div class="steps-container clearfix">
4
        <div class="step-1 select-image create-step-cont clearfix">
5
            <div class="clearfix step-header">
6
                <span class="header-step step-1 clearfix">
7
                    <span class="num">1/3</span>
8
                    <span class="title">
9
                        {% trans "Image" %}
10
                    </span>
11
                    <div class="info">
12
                        <span class="subtitle">
13
                            {% trans "Select an OS" %}
14
                        </span>
15
                        <span class="description">
16
                            {% trans "Choose your preferred image" %}
17
                        </span>
18
                    </div>
19
                </span>
20
            </div>
21
            <div class="step-cont clearfix">
22
                <div class="images-filter-cont">
23
                    <div class="images-filters">
24
                        <div class="image-types-cont">
25
                            <h4>Image type</h4>
26
                            <ul class="type-filter">
27
                                <li id="type-select-system">System</li>
28
                                <li id="type-select-custom">Custom</li>
29
                            </ul>
30
                        </div>
31
                        <div class="categories-filters-cont">
32
                            <h4 class="image-filters-title">Categories </h4>
33
                            <ul class="category-filters clearfix"></ul>
34
                            <span class="clear">{% trans "clear categories" %}</span>
35
                            <span class="empty">{% trans "no categories available" %}</span>
36
                        </div>
37
                    </div>
14 38
                </div>
15
                <div class="custom-images">
16
                    <ul class="images-list">
17
                    </ul>
39
                <div class="images-list-cont">
40
                    <h4>{% trans "Available images" %}</h4>
41
                        <ul class="images-list">
42
                        </ul>
43
                        <span class="empty">{% trans "no images available" %}</span>
18 44
                </div>
19
            </div>
20
            <div class="step-2 select-flavor create-step-cont">
21
                <div class="flavor-options cpu">
22
                    <h4>{% trans "cpu" %}</h4>
23
                    <ul class="flavors-cpu-list flavor-opts-list">
24
                    </ul>
45
                <div class="images-info-cont">
46
                    <h4>Image title</h4>
47
                    <div class="image-detail description">
48
                        <span class="title">{% trans "Description" %}</span>
49
                        <p></p>
50
                    </div>
51
                    <div class="image-detail os">
52
                        <span class="title">{% trans "OS" %}</span>
53
                        <p></p>
54
                    </div>
55
                    <div class="image-detail size">
56
                        <span class="title">{% trans "Size" %}</span>
57
                        <p></p>
58
                    </div>
59
                    <div class="image-detail gui">
60
                        <span class="title">{% trans "GUI" %}</span>
61
                        <p></p>
62
                    </div>
63
                    <div class="image-detail kernel">
64
                        <span class="title">{% trans "Kernel" %}</span>
65
                        <p></p>
66
                    </div>
25 67
                </div>
26
                <div class="flavor-options mem">
27
                    <h4>{% trans "mem" %}</h4>
28
                    <ul class="flavors-mem-list flavor-opts-list">
29
                    </ul>
68
            </div>
69
        </div>
70
        <div class="step-2 select-flavor create-step-cont">
71
            <div class="clearfix step-header">
72
                <span class="header-step step-2 clearfix">
73
                    <span class="num">2/3</span>
74
                    <span class="title">
75
                        {% trans "Flavor" %}
76
                    </span>
77
                    <div class="info">
78
                        <span class="subtitle">
79
                            {% trans "Select CPUs, RAM and Disk Size" %}
80
                        </span>
81
                        <span class="description">
82
                            {% trans "Available options are filtered based on the selected image" %}
83
                        </span>
84
                    </div>
85
                </span>
86
            </div>
87
            <div class="step-cont clearfix">
88
                <div class="flavors-predefined-cont">
89
                    <div class="flavors-predefined">
90
                        <h4>{% trans "Predefined" %}</h4>
91
                        <ul class="predefined-list">
92
                        </ul>
93
                    </div>
30 94
                </div>
31
                <div class="flavor-options disk">
32
                    <h4>{% trans "disk" %}</h4>
33
                    <ul class="flavors-disk-list flavor-opts-list">
34
                    </ul>
95
                <div class="flavor-options-cont">
96
                    <div class="flavor-options-inner-cont clearfix">
97
                        <div class="flavor-options cpu clearfix">
98
                            <h4 class="clearfix"><span class="title">{% trans "CPUs" %}</span>
99
                                <span class="desc">{% trans "Choose number of CPU cores" %}</span>
100
                            </h4>
101
                            <ul class="flavors-cpu-list flavor-opts-list clearfix">
102
                            </ul>
103
                        </div>
104
                        <div class="flavor-options mem clearfix">
105
                            <h4 class="clearfix"><span class="title">{% trans "Memory size" %}</span>
106
                                <span class="desc">{% trans "Choose memory size" %}</span>
107
                            </h4>
108
                            <ul class="flavors-mem-list flavor-opts-list clearfix">
109
                            </ul>
110
                        </div>
111
                        <div class="flavor-options disk clearfix">
112
                            <h4 class="clearfix"><span class="title">{% trans "Disk size" %}</span>
113
                                <span class="desc">{% trans "Choose disk size" %}</span>
114
                            </h4>
115
                            <ul class="flavors-disk-list flavor-opts-list clearfix">
116
                            </ul>
117
                        </div>
118
                    </div>
35 119
                </div>
36 120
            </div>
37
            <div class="step-3 select-name create-step-cont">
121
        </div>
122
        <div class="step-3 vm-confirm create-step-cont">
123
            <div class="clearfix step-header">
124
                <span class="header-step step-3 clearfix">
125
                    <span class="num">3/3</span>
126
                    <span class="title">
127
                        {% trans "Name" %}
128
                    </span>
129
                    <div class="info">
130
                        <span class="subtitle">
131
                            {% trans "Confirm your settings" %}
132
                        </span>
133
                        <span class="description">
134
                            {% trans "Confirm that the options you have selected are correct" %}
135
                        </span>
136
                    </div>
137
                </span>
138
            </div>
139
            <div class="step-cont clearfix">
38 140
                <div class="rename">
39 141
                    <div class="form-field">
40
                        <label for="">{% trans "Machine name" %}</label>
41
                        <input type="text" name="create-vm-name" />
142
                        <h4>
143
                            <label for="create-vm-name">{% trans "Machine name" %}</label>
144
                        </h4>
145
                        <input type="text" name="create-vm-name" id="create-vm-name" />
42 146
                    </div>
43
                    <div class="confirm-params">
44
                        <ul>
45
                            <li class="param cpu">
46
                                <span class="key">{% trans "Image" %}</span>
47
                                <span class="value"></span>
48
                            </li>
49
                            <li class="param cpu">
50
                                <span class="key">{% trans "CPUs" %}</span>
51
                                <span class="value"></span>
52
                            </li>
53
                            <li class="param mem">
54
                                <span class="key">{% trans "RAM" %}</span>
55
                                <span class="value"></span>
56
                            </li>
57
                            <li class="param disk">
58
                                <span class="key">{% trans "System disk" %}</span>
59
                                <span class="value"></span>
60
                            </li>
61
                        </ul>
147
                    
148
                    <div class="confirm-conts clearfix">
149
                        <div class="confirm-cont image">
150
                            <h4>{% trans "Image" %}</h4>
151
                            <ul class="confirm-params">
152
                                <li class="param image-name">
153
                                    <h4 class="value"></h4>
154
                                </li>
155
                                <li class="param image-description">
156
                                    <span class="title">{% trans "Description" %}</span>
157
                                    <span class="value"></span>
158
                                </li>
159
                                <li class="param image-os">
160
                                    <span class="title">{% trans "OS" %}</span>
161
                                    <span class="value"></span>
162
                                </li>
163
                                <li class="param image-size">
164
                                    <span class="title">{% trans "Size" %}</span>
165
                                    <span class="value"></span>
166
                                </li>
167
                                <li class="param image-gui">
168
                                    <span class="title">{% trans "GUI" %}</span>
169
                                    <span class="value"></span>
170
                                </li>
171
                                <li class="param image-kernel">
172
                                    <span class="title">{% trans "Kernel" %}</span>
173
                                    <span class="value"></span>
174
                                </li>
175
                            </ul>
176
                        </div>
177
                        <div class="confirm-cont flavor">
178
                            <h4>{% trans "Flavor" %}</h4>
179
                            <ul class="confirm-params">
180
                                <li class="param cpu">
181
                                    <span class="key">{% trans "Image" %}</span>
182
                                    <span class="value"></span>
183
                                </li>
184
                            </ul>
185
                        </div>
186
                        <div class="confirm-cont meta">
187
                            <h4>{% trans "Metadata" %}</h4>
188
                            <ul class="confirm-params">
189
                                <li class="param cpu">
190
                                    <span class="key">{% trans "Image" %}</span>
191
                                    <span class="value"></span>
192
                                </li>
193
                            </ul>
194
                        </div>
62 195
                    </div>
63 196
                </div>
197
            </div>
64 198
    </div>
65 199
</div>
66 200
<div class="create-controls clearfix">

Also available in: Unified diff