Revision 1e882dd7

b/snf-cyclades-app/synnefo/ui/static/snf/css/main.css
827 827
    vertical-align: top;
828 828
}
829 829

  
830
.icon .build-progress {
831
    margin-left: -3px;
832
}
833

  
834
#machinesview .build-progress .message {
835
  padding: 3px;
836
}
837

  
838
#machinesview .build-progress .btn:hover {
839
  background-color: #387693;
840
}
841

  
842
#machinesview .column1 .build-progress .btn {
843
    margin-top: 5px;
844
    display: inline-block;
845
}
846

  
847
#machinesview .build-progress .btn {
848
    cursor: pointer;
849
    background-color: #5e1616;
850
    color: #FFF;
851
    padding: 3px;
852
}
853

  
830 854
.ip-version-label {
831 855
    font-size: 0.8em;
832 856
    padding: 0.3em;
......
1106 1130
    font-size: 75%;
1107 1131
}
1108 1132

  
1133
.icon div.indicators {
1134
    float: right;
1135
}
1136

  
1109 1137
div.indicators {
1110 1138
    margin-right: 2px !important;
1111 1139
}
......
4518 4546
    color: #387693;
4519 4547
}
4520 4548

  
4549
.overlay .diagnostics-list {
4550
    height: 400px;
4551
    overflow: scroll;
4552
    overflow-y: scroll;
4553
    overflow-x: hidden;
4554
}
4521 4555
.overlay .overlay-content .description.subinfo {
4522 4556
    margin-bottom:0;
4523 4557
    border-bottom: none;
......
4704 4738
    margin-bottom: 2px;
4705 4739
}
4706 4740

  
4741
.overlay-info .msg-log-entry .src {
4742
    color: #4085A5;
4743
    float: right;
4744
    font-size: 0.9em;
4745
}
4746

  
4747
.overlay-info .msg-log-entry .date {
4748
    font-style: italic;
4749
}
4750

  
4751
.overlay-info .msg-log-entry pre {
4752
    color: #333;
4753
    width: 100%;
4754
    display: none;
4755
    margin: 10px 0;
4756
}
4757

  
4758
.msg-log-entry.warning {
4759
    color: #E57F01;
4760
}
4761

  
4762
.overlay-info .msg-log-entry.with-details .src {
4763
    margin-right: 15px;
4764
}
4765

  
4766
.overlay-info .msg-log-entry.with-details {
4767
    cursor: pointer;
4768
}
4769

  
4770
.overlay-info .msg-log-entry.with-details {
4771
    background-image: url("../images/plus-8-dark.png");
4772
    background-repeat: no-repeat;
4773
    background-position: 99% 9px;
4774
}
4775

  
4776
.overlay-info .msg-log-entry.with-details.expanded {
4777
    background-image: url("../images/minus-8-dark.png");
4778
}
4779

  
4780
.overlay-info .msg-log-entry .msg {
4781
    display: inline-block;
4782
    margin-left: 10px;
4783
}
4784

  
4785
.overlay-info .msg-log-entry {
4786
    border-bottom: 1px solid #aaa;
4787
    padding: 5px;
4788
}
4789

  
4790
.overlay-info .description.subinfo {
4791
    border: none !important;
4792
    padding-top: 0 !important;
4793
}
4794

  
4707 4795
.overlay-info .content {
4708 4796
    background-repeat: no-repeat;
4709 4797
    background-position: 110% 110%;
b/snf-cyclades-app/synnefo/ui/static/snf/js/models.js
123 123
        changedKeys: function() {
124 124
            return _.keys(this.changedAttributes() || {});
125 125
        },
126
            
127
        // return list of changed attributes that included in passed list
128
        // argument
129
        getKeysChanged: function(keys) {
130
            return _.intersection(keys, this.changedKeys());
131
        },
132
        
133
        // boolean check of keys changed
134
        keysChanged: function(keys) {
135
            return this.getKeysChanged(keys).length > 0;
136
        },
126 137

  
138
        // check if any of the passed attribues has changed
127 139
        hasOnlyChange: function(keys) {
128 140
            var ret = false;
129 141
            _.each(keys, _.bind(function(key) {
......
680 692
            // initialize interval
681 693
            this.init_stats_intervals(this.stats_update_interval);
682 694
            
683
            this.bind("change:progress", _.bind(this.update_building_progress, this));
684
            this.update_building_progress();
685

  
695
            // handle progress message on instance change
696
            this.bind("change", _.bind(this.update_status_message, this));
697
            // force update of progress message
698
            this.update_status_message(true);
699
            
686 700
            // default values
687 701
            this.bind("change:state", _.bind(function(){
688 702
                if (this.state() == "DESTROY") { 
......
717 731
            };
718 732
            return st;
719 733
        },
734
            
735
        get_diagnostics: function(success) {
736
            this.__make_api_call(this.get_diagnostics_url(),
737
                                 "read", // create so that sync later uses POST to make the call
738
                                 null, // payload
739
                                 function(data) {
740
                                     success(data);
741
                                 },  
742
                                 null, 'diagnostics');
743
        },
720 744

  
721
        update_building_progress: function() {
722
            if (this.is_building()) {
723
                var progress = this.get("progress");
724
                if (progress == 0) {
725
                    this.state("BUILD_INIT");
726
                    this.set({progress_message: BUILDING_MESSAGES['INIT']});
745
        has_diagnostics: function() {
746
            return this.get("diagnostics") && this.get("diagnostics").length;
747
        },
748

  
749
        get_progress_info: function() {
750
            // details about progress message
751
            // contains a list of diagnostic messages
752
            return this.get("status_messages");
753
        },
754

  
755
        get_status_message: function() {
756
            return this.get('status_message');
757
        },
758
        
759
        // extract status message from diagnostics
760
        status_message_from_diagnostics: function(diagnostics) {
761
            var valid_sources_map = synnefo.config.diagnostics_status_messages_map;
762
            var valid_sources = valid_sources_map[this.get('status')];
763
            if (!valid_sources) { return null };
764
            
765
            // filter messsages based on diagnostic source
766
            var messages = _.filter(diagnostics, function(diag) {
767
                return valid_sources.indexOf(diag.source) > -1;
768
            });
769

  
770
            var msg = messages[0];
771
            if (msg) {
772
              var message = msg.message;
773
              var message_tpl = snf.config.diagnostic_messages_tpls[msg.source];
774

  
775
              if (message_tpl) {
776
                  message = message_tpl.replace('MESSAGE', msg.message);
777
              }
778
              return message;
779
            }
780
            
781
            // no message to display, but vm in build state, display
782
            // finalizing message.
783
            if (this.is_building() == 'BUILD') {
784
                return synnefo.config.BUILDING_MESSAGES['FINAL'];
785
            }
786
            return null;
787
        },
788

  
789
        update_status_message: function(force) {
790
            // update only if one of the specified attributes has changed
791
            if (
792
              !this.keysChanged(['diagnostics', 'progress', 'status', 'state'])
793
                && !force
794
            ) { return };
795
            
796
            // if user requested to destroy the vm set the appropriate 
797
            // message.
798
            if (this.get('state') == "DESTROY") { 
799
                message = "Terminating..."
800
                this.set({status_message: message})
801
                return;
802
            }
803
            
804
            // set error message, if vm has diagnostic message display it as
805
            // progress message
806
            if (this.in_error_state()) {
807
                var d = this.get('diagnostics');
808
                if (d && d.length) {
809
                    var message = this.status_message_from_diagnostics(d);
810
                    this.set({status_message: message});
811
                } else {
812
                    this.set({status_message: null});
727 813
                }
728
                if (progress > 0 && progress < 99) {
729
                    this.state("BUILD_COPY");
730
                    this.get_copy_details(true, undefined, _.bind(function(details){
731
                        this.set({
732
                            progress_message: BUILDING_MESSAGES['COPY'].format(details.copy, 
733
                                                                               details.size, 
734
                                                                               details.progress)
735
                        });
736
                    }, this));
814
                return;
815
            }
816
            
817
            // identify building status message
818
            if (this.is_building()) {
819
                var self = this;
820
                var success = function(msg) {
821
                    self.set({status_message: msg});
737 822
                }
738
                if (progress == 100) {
739
                    this.state("BUILD_FINAL");
740
                    this.set({progress_message: BUILDING_MESSAGES['FINAL']});
823
                this.get_building_status_message(success);
824
                return;
825
            }
826

  
827
            this.set({status_message:null});
828
        },
829
            
830
        // get building status message. Asynchronous function since it requires
831
        // access to vm image.
832
        get_building_status_message: function(callback) {
833
            // no progress is set, vm is in initial build status
834
            var progress = this.get("progress");
835
            if (progress == 0 || !progress) {
836
                return callback(BUILDING_MESSAGES['INIT']);
837
            }
838
            
839
            // vm has copy progress, display copy percentage
840
            if (progress > 0 && progress <= 99) {
841
                this.get_copy_details(true, undefined, _.bind(
842
                    function(details){
843
                        callback(BUILDING_MESSAGES['COPY'].format(details.copy, 
844
                                                           details.size, 
845
                                                           details.progress));
846
                }, this));
847
                return;
848
            }
849

  
850
            // copy finished display FINAL message or identify status message
851
            // from diagnostics.
852
            if (progress >= 100) {
853
                if (!this.has_diagnostics()) {
854
                        callback(BUILDING_MESSAGES['FINAL']);
855
                } else {
856
                        var d = this.get("diagnostics");
857
                        var msg = this.status_message_from_diagnostics(d);
858
                        if (msg) {
859
                              callback(msg);
860
                        }
741 861
                }
742
            } else {
743 862
            }
744 863
        },
745 864

  
......
1094 1213
        },
1095 1214

  
1096 1215
        get_public_nic: function() {
1097
            return this.get_nics(function(n){ return n.get('network_id') == 'public'})[0];
1216
            return this.get_nics(function(n){ return n.get_network().is_public() })[0];
1098 1217
        },
1099 1218

  
1100 1219
        get_nic: function(net_id) {
......
1272 1391
            return this.url() + "/action";
1273 1392
        },
1274 1393

  
1394
        get_diagnostics_url: function() {
1395
            return this.url() + "/diagnostics";
1396
        },
1397

  
1275 1398
        get_connection_info: function(host_os, success, error) {
1276 1399
            var url = "/machines/connect";
1277 1400
            params = {
......
1689 1812
            return data;
1690 1813
        },
1691 1814

  
1815
        parse_vm_api_data: function(data) {
1816
            // do not add non existing DELETED entries
1817
            if (data.status && data.status == "DELETED") {
1818
                if (!this.get(data.id)) {
1819
                    return false;
1820
                }
1821
            }
1822

  
1823
            // OS attribute
1824
            if (this.has_meta(data)) {
1825
                data['OS'] = data.metadata.values.OS || "okeanos";
1826
            }
1827
            
1828
            if (!data.diagnostics) {
1829
                data.diagnostics = [];
1830
            }
1831

  
1832
            // network metadata
1833
            data['firewalls'] = {};
1834
            data['nics'] = {};
1835
            data['linked_to'] = [];
1836

  
1837
            if (data['attachments'] && data['attachments'].values) {
1838
                var nics = data['attachments'].values;
1839
                _.each(nics, function(nic) {
1840
                    var net_id = nic.network_id;
1841
                    var index = parseInt(NIC_REGEX.exec(nic.id)[2]);
1842
                    if (data['linked_to'].indexOf(net_id) == -1) {
1843
                        data['linked_to'].push(net_id);
1844
                    }
1845

  
1846
                    data['nics'][nic.id] = nic;
1847
                })
1848
            }
1849
            
1850
            // if vm has no metadata, no metadata object
1851
            // is in json response, reset it to force
1852
            // value update
1853
            if (!data['metadata']) {
1854
                data['metadata'] = {values:{}};
1855
            }
1856

  
1857
            return data;
1858
        },
1859

  
1692 1860
        add: function() {
1693 1861
            ret = models.VMS.__super__.add.apply(this, arguments);
1694 1862
            ret.each(function(r){
......
1751 1919
            return vm_data.metadata && vm_data.metadata.values
1752 1920
        },
1753 1921

  
1754
        parse_vm_api_data: function(data) {
1755
            // do not add non existing DELETED entries
1756
            if (data.status && data.status == "DELETED") {
1757
                if (!this.get(data.id)) {
1758
                    return false;
1759
                }
1760
            }
1761

  
1762
            // OS attribute
1763
            if (this.has_meta(data)) {
1764
                data['OS'] = data.metadata.values.OS || "okeanos";
1765
            }
1766
            
1767

  
1768
            // network metadata
1769
            data['firewalls'] = {};
1770
            data['nics'] = {};
1771
            data['linked_to'] = [];
1772

  
1773
            if (data['attachments'] && data['attachments'].values) {
1774
                var nics = data['attachments'].values;
1775
                _.each(nics, function(nic) {
1776
                    var net_id = nic.network_id;
1777
                    var index = parseInt(NIC_REGEX.exec(nic.id)[2]);
1778
                    if (data['linked_to'].indexOf(net_id) == -1) {
1779
                        data['linked_to'].push(net_id);
1780
                    }
1781

  
1782
                    data['nics'][nic.id] = nic;
1783
                })
1784
            }
1785
            
1786
            // if vm has no metadata, no metadata object
1787
            // is in json response, reset it to force
1788
            // value update
1789
            if (!data['metadata']) {
1790
                data['metadata'] = {values:{}};
1791
            }
1792

  
1793
            return data;
1794
        },
1795

  
1796 1922
        create: function (name, image, flavor, meta, extra, callback) {
1797 1923
            if (this.copy_image_meta) {
1798 1924
                if (image.get("OS")) {
......
1818 1944
                nic.get_network().update_state();
1819 1945
            });
1820 1946
            this.get_vm().bind("remove", function(){
1947
              try {
1821 1948
                this.collection.remove(this);
1949
              } catch (err) {};
1822 1950
            }, this);
1823 1951
            this.get_network().bind("remove", function(){
1824 1952
                try {
b/snf-cyclades-app/synnefo/ui/static/snf/js/tests.js
47 47
        'compute': '/api/v1.1', 
48 48
        'glance':'/images/v1.1'
49 49
    };
50
    
51
    // what messages to display based on vm status
52
    synnefo.config.diagnostics_status_messages_map = {
53
        'BUILD': ['image-helper-task-start', 'image-info'],
54
        'ERROR': ['image-error']
55
    };
56
    synnefo.config.diagnostic_messages_tpls = {
57
      'image-helper-task-start': "Running 'MESSAGE'"
58
    };
50 59

  
51 60
    snf.user = {'token': 'TESTTOKEN'}
52 61

  
53 62
    module("VM Model")
63
    
64
    test("vm diagnostics", function() {
65
        synnefo.storage.images.add({
66
          id:1, 
67
          size: 100,
68
          metadata:{
69
            values: {
70
              size: 100
71
            }
72
          }
73
        });
74
        synnefo.storage.vms.add({id:1, imageRef:1});
75
        var vm = synnefo.storage.vms.at(0);
76
        var diagnostics = [{
77
          source:'image-helper-task-start', 
78
          message:'SSHCopyKey'
79
        }];
80
        synnefo.storage.vms.update([{id:1, 
81
                                     progress: 0,
82
                                     status: "BUILD"}], 
83
                                    {silent:false});
84
        equal(vm.get('status_message'), "init", 
85
              "Show initial progress message");
86
        synnefo.storage.vms.update([{id:1, 
87
                                     progress: 10,
88
                                     status: "BUILD"}], 
89
                                    {silent:false});
90
        equal(vm.get('status_message'), "10.00 MB, 100.00 MB, 10", 
91
              "Construct message based on image size");
92
        synnefo.storage.vms.update([{id:1, 
93
                                     progress: 99,
94
                                     status: "BUILD"}], 
95
                                    {silent:false});
96
        equal(vm.get('status_message'), "99.00 MB, 100.00 MB, 99", 
97
              "Calculate 99% of image size and display image");
98
        synnefo.storage.vms.update([{id:1, 
99
                                     progress: 99,
100
                                     status: "ERROR"}], 
101
                                    {silent:false});
102
        equal(vm.get('status_message'), null);
103
        synnefo.storage.vms.update([{id:1, 
104
                                     progress: 100,
105
                                     status: "BUILD"}], 
106
                                    {silent:false});
107
        equal(vm.get('status_message'), "final", 
108
              "Progress is 100, show finializing progress message");
109
        synnefo.storage.vms.update([{id:1, 
110
                                     progress: 100,
111
                                     diagnostics: _.clone(diagnostics),
112
                                     status: "BUILD"}], 
113
                                    {silent:false});
114

  
115
        synnefo.storage.vms.update([{id:1, 
116
                                     progress: 100,
117
                                     diagnostics: _.clone(diagnostics),
118
                                     status: "BUILD"}], 
119
                                    {silent:false});
120
        equal(vm.get('status_message'), "Running 'SSHCopyKey'", 
121
              "Diagnostics added, show first building diagnostic message");
122

  
123
        diagnostics.unshift({source:'unknown-source', message:'H&^^ACJJ'});
124
        synnefo.storage.vms.update([{id:1, 
125
                                     progress: 100,
126
                                     diagnostics: _.clone(diagnostics),
127
                                     status: "BUILD"}], 
128
                                    {silent:false});
129
        equal(vm.get('status_message'), "Running 'SSHCopyKey'", 
130
              "Unwanted diagnostics get filtered out, progress message remains");
131

  
132
        synnefo.storage.vms.update([{id:1, 
133
                                     progress: 100,
134
                                     diagnostics: _.clone(diagnostics),
135
                                     status: "ERROR"}], 
136
                                    {silent:false});
137
        equal(vm.get('status_message'), null, 
138
              "In error state since no error diagnostic is set we get null");
139

  
140
        diagnostics.unshift({source:'image-error', message:'Image error'});
141
        synnefo.storage.vms.update([{id:1, 
142
                                     progress: 100,
143
                                     diagnostics: _.clone(diagnostics),
144
                                     status: "BUILD"}], 
145
                                    {silent:false});
146
        equal(vm.get('status_message'), "Running 'SSHCopyKey'");
147

  
148
        diagnostics.unshift({source:'image-error', message:'Image error'});
149
        synnefo.storage.vms.update([{id:1, 
150
                                     progress: 100,
151
                                     diagnostics: _.clone(diagnostics),
152
                                     status: "ERROR"}], 
153
                                    {silent:false});
154
        equal(vm.get('status_message'), "Image error");
155

  
156
        synnefo.storage.vms.update([{id:1, 
157
                                     progress: 100,
158
                                     diagnostics: _.clone(diagnostics),
159
                                     status: "ACTIVE"}], 
160
                                    {silent:false});
161
        equal(vm.get('status_message'), null);
162

  
163
    });
164

  
54 165

  
55 166
    test("model change events", function(){
56 167
        expect(8);
......
358 469

  
359 470
    module("network vm connections")
360 471
    test("network vm connections", function() {
361

  
362 472
        function _net(id, ip) {
363 473
            return {
364 474
                id: id,
b/snf-cyclades-app/synnefo/ui/static/snf/js/ui/web/ui_icon_view.js
721 721
        
722 722
        // vm specific event handlers
723 723
        set_vm_handlers: function(vm) {
724
            var el = this.vm(vm);
725

  
726 724
        },
727 725

  
728 726
        check_terminated_is_empty: function() {
......
753 751
            this.$("div.machine-container:last-child").find("div.separator").hide();
754 752
            fix_v6_addresses();
755 753
        },
754
  
755
        update_status_message: function(vm) {
756
            var el = this.vm(vm);
757
            var message = vm.get_status_message();
758
            if (message) {
759
                // update bulding progress
760
                el.find("div.machine-ips").hide();
761
                el.find("div.build-progress").show();
762
                el.find("div.build-progress .message").text(message);
763
                if (vm.in_error_state()) {
764
                    el.find("div.build-progress .btn").show();
765
                } else {
766
                    el.find("div.build-progress .btn").hide();
767
                }
768
            } else {
769
                // hide building progress
770
                el.find("div.machine-ips").show()
771
                el.find("div.build-progress").hide();
772
                el.find("div.build-progress .btn").hide();
773
            }
774
        },
756 775

  
757 776
        // update vm details
758 777
        update_details: function(vm) {
......
777 796
            
778 797
            var status = vm.get("status");
779 798
            var state = vm.get("state");
780

  
781
            if (status == 'BUILD') {
782
                // update bulding progress
783
                el.find("div.machine-ips").hide();
784
                el.find("div.build-progress").show().text(vm.get('progress_message'));
785
            } else {
786
                // hide building progress
787
                el.find("div.machine-ips").show()
788
                el.find("div.build-progress").hide();
789
            }
790

  
791
            if (state == "DESTROY") {
792
                el.find("div.machine-ips").hide();
793
                el.find("div.build-progress").show().text("Terminating...");
794
            }
799
            
800
            this.update_status_message(vm);
795 801

  
796 802
            icon_state = vm.is_active() ? "on" : "off";
797 803
            set_machine_os_image(el, "icon", icon_state, this.get_vm_icon_os(vm));
b/snf-cyclades-app/synnefo/ui/static/snf/js/ui/web/ui_main_view.js
49 49
    var bb = root.Backbone;
50 50
    var util = snf.util;
51 51
    
52
    // generic details overlay view.
53
    views.DetailsView = views.Overlay.extend({
54
        view_id: "details_view",
55
        
56
        content_selector: "#details-overlay",
57
        css_class: 'overlay-api-info overlay-info',
58
        overlay_id: "overlay-details",
59

  
60
        subtitle: "",
61
        title: "Details",
62
        
63
        show: function(title, msg, content) {
64
            this.title = title;
65
            this.msg = msg;
66
            this.content = content;
67
            views.DetailsView.__super__.show.apply(this);
68
        },
69

  
70
        beforeOpen: function() {
71
            this.set_title(this.title);
72
            if (!this.msg) { 
73
                this.$(".description.intro").hide() 
74
            } else {
75
                this.$(".description.intro").html(this.msg).show();
76
            }
77

  
78
            if (!this.content) { 
79
                this.$(".description.subinfo").hide() 
80
            } else {
81
                this.$(".description.subinfo").html(this.content).show(); 
82
            };
83
        }
84

  
85
    });
86

  
52 87
    views.ApiInfoView = views.Overlay.extend({
53 88
        view_id: "api_info_view",
54 89
        
......
513 548
        init_overlays: function() {
514 549
            this.create_vm_view = new views.CreateVMView();
515 550
            this.api_info_view = new views.ApiInfoView();
551
            this.details_view = new views.DetailsView();
516 552
            //this.notice_view = new views.NoticeView();
517 553
        },
518 554
        
......
663 699
        },
664 700

  
665 701
        update_status: function(msg) {
666
            this.log.debug(msg)
702
            //this.log.debug(msg)
667 703
            this.status = msg;
668 704
            $("#loading-view .info").removeClass("hidden")
669 705
            $("#loading-view .info").text(this.status);
b/snf-cyclades-app/synnefo/ui/static/snf/js/ui/web/ui_single_view.js
226 226
        
227 227
        // vm specific event handlers
228 228
        set_vm_handlers: function(vm) {
229
            var el = this.vm(vm);
230 229
        },
231 230
        
232 231
        // handle selected vm
......
292 291
            fix_v6_addresses();
293 292
        },
294 293

  
294
        update_status_message: function(vm) {
295
            var el = this.vm(vm);
296
            var message = vm.get_status_message();
297
            if (message) {
298
                // update bulding progress
299
                el.find("div.machine-ips").hide();
300
                el.find("div.build-progress").show();
301
                el.find("div.build-progress .message").text(message);
302
                if (vm.in_error_state()) {
303
                    el.find("div.build-progress .btn").show();
304
                } else {
305
                    el.find("div.build-progress .btn").hide();
306
                }
307
            } else {
308
                // hide building progress
309
                el.find("div.machine-ips").show()
310
                el.find("div.build-progress").hide();
311
                el.find("div.build-progress .btn").hide();
312
            }
313
        },
314

  
295 315
        // update vm details
296 316
        update_details: function(vm) {
297 317
            var el = this.vm(vm);
......
315 335
                el.addClass("connectable");
316 336
            }
317 337

  
318
            if (vm.get('status') == 'BUILD') {
319
                // update bulding progress
320
                el.find("span.build-progress").show().text(vm.get("progress_message"));
321
            } else {
322
                // hide building progress
323
                el.find("span.build-progress").hide();
324
            }
325

  
326
            if (vm.state() == "DESTROY") {
327
                el.find("span.build-progress").show().text("Terminating...");
328
            }
338
            this.update_status_message(vm);
329 339

  
330 340
            icon_state = vm.is_active() ? "on" : "off";
331 341
            set_machine_os_image(el, "single", icon_state, this.get_vm_icon_os(vm));
b/snf-cyclades-app/synnefo/ui/static/snf/js/ui/web/ui_vms_base_view.js
75 75
            this.connect_overlay = new views.VMConnectView();
76 76
        },
77 77

  
78
        // display vm diagnostics detail overlay view, update based on
79
        // diagnostics_update_interval config value.
80
        show_build_details_for_vm: function(vm) {
81
            var cont = null;
82
            var success = function(data) {
83
                var message = "";
84
                var title = vm.get('name');
85
                var info = '<em>Status log messages:</em>';
86

  
87
                var list_el = $('<div class="diagnostics-list">');
88
                list_el.append('<div class="empty">No data available</div>');
89

  
90
                cont = list_el;
91
                var messages = _.clone(data);
92

  
93
                update_overlay_diagnostics(data, cont);
94
                synnefo.ui.main.details_view.show(title, info, cont);
95
            }
96

  
97
            var update_overlay_diagnostics = function(data) {
98
                var existing = cont.find(".msg-log-entry");
99
                var messages = _.clone(data);
100
                
101
                var to_append = messages.slice(0, messages.length - existing.length);
102
                to_append.reverse();
103

  
104
                var appending = to_append.length != messages.length;
105
                
106
                if (to_append.length) { cont.find(".empty").hide() } else {
107
                    if (!messages.length) { cont.find(".empty").show() }
108
                }
109
                _.each(to_append, function(msg){
110
                    var el = $('<div class="clearfix msg-log-entry ' + msg.level.toLowerCase() + '">');
111
                    if (msg.details) {
112
                      el.addClass("with-details");
113
                    }
114
                    var display_source_date = synnefo.config.diagnostics_display_source_date;
115
                    var source_date = "";
116
                    if (display_source_date) {
117
                        source_date = '('+synnefo.util.formatDate(new Date(msg.source_date))+')';
118
                    }
119

  
120
                    el.append('<span class="date">' + synnefo.util.formatDate(new Date(msg.created)) + source_date + '</span>');
121
                    el.append('<span class="src">' + _.escape(msg.source) + '</span>');
122
                    el.append('<span class="msg">' + _.escape(msg.message) + '</span>');
123
                    if (msg.details) {
124
                        el.append('<pre class="details">' + _.escape(msg.details) + '</pre>');
125
                    }
126
                    if (appending) { el.hide(0); el.css({'display':'none'})}
127
                    cont.prepend(el);
128
                    el.click(function(el) {
129
                        $(this).find(".details").slideToggle();
130
                        $(this).toggleClass("expanded");
131
                    });
132

  
133
                    if (appending) { el.fadeIn(800); }
134
                });
135
                
136
                window.setTimeout(function(){
137
                    if (cont.is(":visible")) {
138
                        vm.get_diagnostics(update_overlay_diagnostics);
139
                    }
140
                }, synnefo.config.diagnostics_update_interval);
141
            }
142

  
143
            vm.get_diagnostics(success);
144
        },
145

  
78 146
        // Helpers
79 147
        //
80 148
        // get element based on this.selectors key/value pairs
......
166 234
                var el = this.create_vm(vm);
167 235
                el.show();
168 236
                this.post_add(vm);
237
                this.init_vm_view_handlers(vm);
169 238
            }
170 239

  
171 240
            return this.vm(vm);
172 241
        },
242

  
243
        init_vm_view_handlers: function(vm) {
244
            var self = this;
245
            var el = this.vm(vm);
246

  
247
            // hidden feature, double click on indicators to display 
248
            // vm diagnostics.
249
            el.find(".indicators").bind("dblclick", function(){
250
                self.show_build_details_for_vm(vm);
251
            });
252

  
253
            // this button gets visible if vm creation failed.
254
            el.find("div.build-progress .btn").click(function(){
255
                self.show_build_details_for_vm(vm);
256
            });
257
        },
173 258
        
174 259
        // helpers for VMListView descendants
175 260
        post_add: function(vm) { throw "Not implemented" },
......
246 331
        // container (e.g. some views might have different
247 332
        // containers for terminated or running machines
248 333
        check_vm_container: function(vm){
249
            if (vm.state() == "DESTROY") { return };
250 334
            var el = this.vm(vm);
251 335
            if (!el.length) { return };
252 336
            var self = this;
......
281 365
        
282 366
        // is vm in transition ??? show the progress spinner
283 367
        update_transition_state: function(vm) {
284
            if (vm.in_transition() && !vm.pending_action){
368
            if (vm.in_transition() && !vm.has_pending_action()){
285 369
                this.sel('vm_spinner', vm.id).show();
286 370
            } else {
287 371
                this.sel('vm_spinner', vm.id).hide();
b/snf-cyclades-app/synnefo/ui/static/snf/js/utils.js
132 132
    
133 133
    // Util namespace
134 134
    synnefo.util = synnefo.util || {};
135
    
136
    synnefo.util.FormatDigits = function(num, length) {
137
        var r = "" + num;
138
        while (r.length < length) {
139
            r = "0" + r;
140
        }
141
        return r;
142
    }
143

  
144
    synnefo.util.formatDate = function(d) {
145
        var dt = synnefo.util.FormatDigits(d.getDate()) + '/';
146
        dt += synnefo.util.FormatDigits(d.getMonth(), 2);
147
        dt += '/' + d.getFullYear();
148
        dt += ' ' + synnefo.util.FormatDigits(d.getHours(), 2) + ':';
149
        dt += synnefo.util.FormatDigits(d.getMinutes(), 2) + ':';
150
        dt += synnefo.util.FormatDigits(d.getSeconds(), 2);
151
        return dt;
152
    },
135 153

  
136 154
    // Extensions and Utility functions
137 155
    synnefo.util.ISODateString = function(d){
b/snf-cyclades-app/synnefo/ui/templates/home.html
132 132
        var BUILDING_MESSAGES = {
133 133
            'INIT': '{% trans "Initializing..." %}',
134 134
            'COPY': '{% trans "{0} of {1} ({2}%)" %}',
135
            'FINAL': '{% trans "Finalizing..." %}'
135
            'FINAL': '{% trans "Bulding image..." %}'
136 136
        }
137 137

  
138 138
        var ERROR_OVERRIDES = {
......
491 491
        </div>
492 492
    </div>
493 493
    
494
    <div id="details-overlay" class="overlay-content overlay-info hidden">
495
        <div class="description intro">
496
            <p></p>
497
        </div>
498
        <div class="description subinfo">
499
            <p></p>
500
        </div>
501
    </div>
502

  
494 503
    <div id="api-info-overlay" class="overlay-content overlay-info hidden">
495 504
        <div class="description">
496 505
            <p>{% blocktrans with EXTERNAL_CLIENT_URL as EXTERNAL_CLIENT_URL %}Use the following API key along with the <a
......
567 576
            synnefo.config.machines_icons_url = '{{ SYNNEFO_IMAGES_URL }}icons/machines/';
568 577
            synnefo.config.vm_name_template = {{ vm_name_template|safe }};
569 578
            synnefo.config.flavors_disk_templates_info = {{ flavors_disk_templates_info|safe }};
579

  
580
            synnefo.config.diagnostics_update_interval = {{ diagnostics_update_interval }};
581
            // override diagnostic messages display
582
            synnefo.config.diagnostic_messages_tpls = {
583
              'image-helper-task-start': "{% trans "Running task 'MESSAGE'" %}"
584
            };
585
            // what messages to display based on vm status
586
            synnefo.config.diagnostics_status_messages_map = {
587
                'BUILD': ['image-helper-task-start', 'image-info'],
588
                'ERROR': ['image-error']
589
            };
590

  
570 591
   			// TODO: make it dynamic
571 592
            synnefo.config.api_urls = {
572 593
                'compute':  {{ compute_api_url|safe }}, 
......
620 641
            synnefo.config.system_images_owners = {{ system_images_owners|safe }};
621 642
            synnefo.ui.init();
622 643
            synnefo.ui.main.bind("ready", function(){
644
              vms = synnefo.storage.vms;
645
              vm = vms.at(0);
623 646
            });
647

  
624 648
        })
625 649
    </script>
626 650
</body>
b/snf-cyclades-app/synnefo/ui/templates/partials/machines_icon.html
26 26
                            <span class="ip-version-label">IPv4</span> <span class="public ipv4-text"></span>
27 27
                            <span class="ip-version-label">IPv6</span> <span class="public ipv6-text"></span>
28 28
                        </div>
29
                        <div class="build-progress subtitle"></div>
29
                        <div class="build-progress subtitle">
30
                          <span class="message"></span>
31
                          <span class="btn">details</span>
32
                        </div>
30 33
                        <div class="info">
31 34
                            <span class="info-header cont-toggler toggler">
32 35
                                <span class="label">{% trans "info" %}</span>
b/snf-cyclades-app/synnefo/ui/templates/partials/machines_single.html
19 19
                    <img class="spinner" style="display:none" src="{{ SYNNEFO_IMAGES_URL }}icons/indicators/medium/progress.gif" />
20 20
                    <img class="wave" style="display:none" src="{{ SYNNEFO_IMAGES_URL }}icons/indicators/medium/wave.gif" />
21 21
                </div>
22
                <div class="progress-message"><span class="build-progress"></span></div>
22
                <div class="progress-message">
23
                        <div class="build-progress">
24
                          <span class="message"></span>
25
                          <span class="btn">details</span>
26
                        </div>
27
                </div>
23 28
            </div>
24 29
            <div class="title machine-detail name">My Desktop</div>
25 30
            <div class="column2">
b/snf-cyclades-app/synnefo/ui/views.py
119 119
GLANCE_API_URL = getattr(settings, 'UI_GLANCE_API_URL', '/glance')
120 120
FEEDBACK_CONTACTS = getattr(settings, "FEEDBACK_CONTACTS", [])
121 121
FEEDBACK_EMAIL_FROM = settings.FEEDBACK_EMAIL_FROM
122
DIAGNOSTICS_UPDATE_INTERVAL = getattr(settings,
123
                'UI_DIAGNOSTICS_UPDATE_INTERVAL', 2000)
122 124

  
123 125
# network settings
124 126
DEFAULT_NETWORK_TYPES = {'PRIVATE_FILTERED': 'mac-filtering'}
......
190 192
               'network_available_types': json.dumps(NETWORK_TYPES),
191 193
               'network_allow_duplicate_vm_nics': json.dumps(NETWORK_DUPLICATE_NICS),
192 194
               'network_strict_destroy': json.dumps(NETWORK_STRICT_DESTROY),
193
               'network_allow_multiple_destroy': json.dumps(NETWORK_ALLOW_MULTIPLE_DESTROY)
195
               'network_allow_multiple_destroy': json.dumps(NETWORK_ALLOW_MULTIPLE_DESTROY),
196
               'diagnostics_update_interval': json.dumps(DIAGNOSTICS_UPDATE_INTERVAL)
194 197
    }
195 198
    return template('home', request, context)
196 199

  

Also available in: Unified diff