Revision 48c07635

b/invitations/templates/invitations.html
10 10
    {% csrf_token %}
11 11
    
12 12
    {% if errors %}
13
    <div id="errors">
13
    <div id="errors" class="error-msg">
14 14
        <p>{% trans "Invite error(s) occured" %}</p>
15 15

  
16 16
        <ul>
......
65 65
    </div>
66 66
    
67 67
    <div>
68
        <ul>
68
        <ul class="invsent-list">
69 69
        {% for inv in invitations %}
70 70
        <li class="clearfix {% if inv.accepted %}accepted{% endif %}">
71 71
                {% if inv.accepted %}
......
78 78
                <span class="name">{{ inv.targetname }}</span> <span class="email">{{ inv.target }}</span>
79 79
            </li>
80 80
        {% endfor %}
81
        </ul>
81
    </ul>
82

  
83
    <div class="pages"></div>
82 84
    </div>
83 85
</div>
b/ui/static/jquery.pagination.js
1
/**
2
 * This jQuery plugin displays pagination links inside the selected elements.
3
 * 
4
 * This plugin needs at least jQuery 1.4.2
5
 *
6
 * @author Gabriel Birke (birke *at* d-scribe *dot* de)
7
 * @version 2.2
8
 * @param {int} maxentries Number of entries to paginate
9
 * @param {Object} opts Several options (see README for documentation)
10
 * @return {Object} jQuery Object
11
 */
12
 (function($){
13
	/**
14
	 * @class Class for calculating pagination values
15
	 */
16
	$.PaginationCalculator = function(maxentries, opts) {
17
		this.maxentries = maxentries;
18
		this.opts = opts;
19
	}
20
	
21
	$.extend($.PaginationCalculator.prototype, {
22
		/**
23
		 * Calculate the maximum number of pages
24
		 * @method
25
		 * @returns {Number}
26
		 */
27
		numPages:function() {
28
			return Math.ceil(this.maxentries/this.opts.items_per_page);
29
		},
30
		/**
31
		 * Calculate start and end point of pagination links depending on 
32
		 * current_page and num_display_entries.
33
		 * @returns {Array}
34
		 */
35
		getInterval:function(current_page)  {
36
			var ne_half = Math.floor(this.opts.num_display_entries/2);
37
			var np = this.numPages();
38
			var upper_limit = np - this.opts.num_display_entries;
39
			var start = current_page > ne_half ? Math.max( Math.min(current_page - ne_half, upper_limit), 0 ) : 0;
40
			var end = current_page > ne_half?Math.min(current_page+ne_half + (this.opts.num_display_entries % 2), np):Math.min(this.opts.num_display_entries, np);
41
			return {start:start, end:end};
42
		}
43
	});
44
	
45
	// Initialize jQuery object container for pagination renderers
46
	$.PaginationRenderers = {}
47
	
48
	/**
49
	 * @class Default renderer for rendering pagination links
50
	 */
51
	$.PaginationRenderers.defaultRenderer = function(maxentries, opts) {
52
		this.maxentries = maxentries;
53
		this.opts = opts;
54
		this.pc = new $.PaginationCalculator(maxentries, opts);
55
	}
56
	$.extend($.PaginationRenderers.defaultRenderer.prototype, {
57
		/**
58
		 * Helper function for generating a single link (or a span tag if it's the current page)
59
		 * @param {Number} page_id The page id for the new item
60
		 * @param {Number} current_page 
61
		 * @param {Object} appendopts Options for the new item: text and classes
62
		 * @returns {jQuery} jQuery object containing the link
63
		 */
64
		createLink:function(page_id, current_page, appendopts){
65
			var lnk, np = this.pc.numPages();
66
			page_id = page_id<0?0:(page_id<np?page_id:np-1); // Normalize page id to sane value
67
			appendopts = $.extend({text:page_id+1, classes:""}, appendopts||{});
68
			if(page_id == current_page){
69
				lnk = $("<span class='current'>" + appendopts.text + "</span>");
70
			}
71
			else
72
			{
73
				lnk = $("<a>" + appendopts.text + "</a>")
74
					.attr('href', this.opts.link_to.replace(/__id__/,page_id));
75
			}
76
			if(appendopts.classes){ lnk.addClass(appendopts.classes); }
77
			lnk.data('page_id', page_id);
78
			return lnk;
79
		},
80
		// Generate a range of numeric links 
81
		appendRange:function(container, current_page, start, end, opts) {
82
			var i;
83
			for(i=start; i<end; i++) {
84
				this.createLink(i, current_page, opts).appendTo(container);
85
			}
86
		},
87
		getLinks:function(current_page, eventHandler) {
88
			var begin, end,
89
				interval = this.pc.getInterval(current_page),
90
				np = this.pc.numPages(),
91
				fragment = $("<div class='pagination'></div>");
92
			
93
			// Generate "Previous"-Link
94
			if(this.opts.prev_text && (current_page > 0 || this.opts.prev_show_always)){
95
				fragment.append(this.createLink(current_page-1, current_page, {text:this.opts.prev_text, classes:"prev"}));
96
			}
97
			// Generate starting points
98
			if (interval.start > 0 && this.opts.num_edge_entries > 0)
99
			{
100
				end = Math.min(this.opts.num_edge_entries, interval.start);
101
				this.appendRange(fragment, current_page, 0, end, {classes:'sp'});
102
				if(this.opts.num_edge_entries < interval.start && this.opts.ellipse_text)
103
				{
104
					jQuery("<span>"+this.opts.ellipse_text+"</span>").appendTo(fragment);
105
				}
106
			}
107
			// Generate interval links
108
			this.appendRange(fragment, current_page, interval.start, interval.end);
109
			// Generate ending points
110
			if (interval.end < np && this.opts.num_edge_entries > 0)
111
			{
112
				if(np-this.opts.num_edge_entries > interval.end && this.opts.ellipse_text)
113
				{
114
					jQuery("<span>"+this.opts.ellipse_text+"</span>").appendTo(fragment);
115
				}
116
				begin = Math.max(np-this.opts.num_edge_entries, interval.end);
117
				this.appendRange(fragment, current_page, begin, np, {classes:'ep'});
118
				
119
			}
120
			// Generate "Next"-Link
121
			if(this.opts.next_text && (current_page < np-1 || this.opts.next_show_always)){
122
				fragment.append(this.createLink(current_page+1, current_page, {text:this.opts.next_text, classes:"next"}));
123
			}
124
			$('a', fragment).click(eventHandler);
125
			return fragment;
126
		}
127
	});
128
	
129
	// Extend jQuery
130
	$.fn.pagination = function(maxentries, opts){
131
		
132
		// Initialize options with default values
133
		opts = jQuery.extend({
134
			items_per_page:10,
135
			num_display_entries:11,
136
			current_page:0,
137
			num_edge_entries:0,
138
			link_to:"#",
139
			prev_text:"Prev",
140
			next_text:"Next",
141
			ellipse_text:"...",
142
			prev_show_always:true,
143
			next_show_always:true,
144
			renderer:"defaultRenderer",
145
			load_first_page:false,
146
			callback:function(){return false;}
147
		},opts||{});
148
		
149
		var containers = this,
150
			renderer, links, current_page;
151
		
152
		/**
153
		 * This is the event handling function for the pagination links. 
154
		 * @param {int} page_id The new page number
155
		 */
156
		function paginationClickHandler(evt){
157
			var links, 
158
				new_current_page = $(evt.target).data('page_id'),
159
				continuePropagation = selectPage(new_current_page);
160
			if (!continuePropagation) {
161
				evt.stopPropagation();
162
			}
163
			return continuePropagation;
164
		}
165
		
166
		/**
167
		 * This is a utility function for the internal event handlers. 
168
		 * It sets the new current page on the pagination container objects, 
169
		 * generates a new HTMl fragment for the pagination links and calls
170
		 * the callback function.
171
		 */
172
		function selectPage(new_current_page) {
173
			// update the link display of a all containers
174
			containers.data('current_page', new_current_page);
175
			links = renderer.getLinks(new_current_page, paginationClickHandler);
176
			containers.empty();
177
			links.appendTo(containers);
178
			// call the callback and propagate the event if it does not return false
179
			var continuePropagation = opts.callback(new_current_page, containers);
180
			return continuePropagation;
181
		}
182
		
183
		// -----------------------------------
184
		// Initialize containers
185
		// -----------------------------------
186
		current_page = opts.current_page;
187
		containers.data('current_page', current_page);
188
		// Create a sane value for maxentries and items_per_page
189
		maxentries = (!maxentries || maxentries < 0)?1:maxentries;
190
		opts.items_per_page = (!opts.items_per_page || opts.items_per_page < 0)?1:opts.items_per_page;
191
		
192
		if(!$.PaginationRenderers[opts.renderer])
193
		{
194
			throw new ReferenceError("Pagination renderer '" + opts.renderer + "' was not found in jQuery.PaginationRenderers object.");
195
		}
196
		renderer = new $.PaginationRenderers[opts.renderer](maxentries, opts);
197
		
198
		// Attach control events to the DOM elements
199
		var pc = new $.PaginationCalculator(maxentries, opts);
200
		var np = pc.numPages();
201
		containers.bind('setPage', {numPages:np}, function(evt, page_id) { 
202
				if(page_id >= 0 && page_id < evt.data.numPages) {
203
					selectPage(page_id); return false;
204
				}
205
		});
206
		containers.bind('prevPage', function(evt){
207
				var current_page = $(this).data('current_page');
208
				if (current_page > 0) {
209
					selectPage(current_page - 1);
210
				}
211
				return false;
212
		});
213
		containers.bind('nextPage', {numPages:np}, function(evt){
214
				var current_page = $(this).data('current_page');
215
				if(current_page < evt.data.numPages - 1) {
216
					selectPage(current_page + 1);
217
				}
218
				return false;
219
		});
220
		
221
		// When all initialisation is done, draw the links
222
		links = renderer.getLinks(current_page, paginationClickHandler);
223
		containers.empty();
224
		links.appendTo(containers);
225
		// call callback function
226
		if(opts.load_first_page) {
227
			opts.callback(current_page, containers);
228
		}
229
	} // End of $.fn.pagination block
230
	
231
})(jQuery);
b/ui/static/main.css
3918 3918
    margin-top: 5px;
3919 3919
}
3920 3920

  
3921
.invitations #errors {
3922
    padding: 5px;
3923
    background-color: #800000;
3924
}
3925

  
3921 3926
.invitations #errors p {
3922 3927
    margin-bottom: 10px;
3923 3928
    font-size: 0.95em;
3929
    padding-top:0;
3930
    margin-top:0;
3931
    color: #fff;
3924 3932
}
3925 3933

  
3926 3934
.invitations #errors li {
3927 3935
    font-size: 0.85em;
3928
    color: #800000;
3936
    color: #ddd;
3929 3937
}
3930 3938

  
3931 3939
.invitations #errors {
3932 3940
    font-size: 100%;
3933
    color: #f00;
3934
    margin-bottom: 20px;
3941
    margin-bottom: 10px;
3935 3942
}
3936 3943

  
3937 3944
#invsent .message {
3938 3945
    font-size: 0.9em;
3939
    padding: 5px;
3946
    padding: 5px 0;
3940 3947
    margin-top: 10px;
3948
    margin-bottom: -10px;
3941 3949
    color: #5CAD54;
3942 3950
}
3943 3951

  
......
3949 3957
    margin-bottom: 10px;   
3950 3958
}
3951 3959

  
3960
.remove-field-trigger, .add-field-trigger {
3961
    cursor: pointer;
3962
}
3963

  
3952 3964
.add-field-trigger img {
3953 3965
    vertical-align: middle;
3954 3966
}
......
4376 4388
#disks-pane {
4377 4389
    margin-top: 58px;
4378 4390
}
4391

  
4392
span.resend-msg {
4393
    display: block;
4394
    margin-bottom: 5px;
4395
}
4396

  
4397
.success-msg {
4398
    background-color: #5CAD54;
4399
    color: #fff;
4400
    padding: 0.4em;
4401
    border: 1px solid #ddd;
4402
}
4403

  
4404
.error-msg {
4405
    background-color: #800000;
4406
    color: #fff;
4407
    padding: 0.4em;
4408
    border: 1px solid #ddd;
4409
}
4410

  
4411
.success-msg em, .error-msg em {
4412
    font-weight: bold;
4413
    font-style: normal;
4414
    font-size: 0.9em;
4415
}
4416

  
4417
.pagination {
4418
            font-size: 80%;
4419
        }
4420
        
4421
.pagination a {
4422
    text-decoration: none;
4423
	border: solid 1px #AAE;
4424
	color: #15B;
4425
}
4426

  
4427
.pagination a, .pagination span {
4428
    display: block;
4429
    float: left;
4430
    padding: 0.3em 0.5em;
4431
    margin-right: 5px;
4432
	margin-bottom: 5px;
4433
	min-width:1em;
4434
	text-align:center;
4435
}
4436

  
4437
.pagination .current {
4438
    background: #4085A5;
4439
    color: #fff;
4440
	border: solid 1px #AAE;
4441
}
4442

  
4443
.pagination .current.prev, .pagination .current.next{
4444
	color:#999;
4445
	border-color:#999;
4446
	background:#fff;
4447
}
4448

  
4449
#invsent .pagination {
4450
    margin-top: 10px;
4451
}
b/ui/static/synnefo.js
3090 3090

  
3091 3091
function show_invitations() {
3092 3092
    
3093
    function display_resend_success(msg) {
3094
        clear_resend_messages();
3095
        $("#invsent .message.success").text(msg).show();
3093
    var invsent_per_page = INVITATIONS_PER_PAGE;
3094
    function handle_pagination(p) {
3095
        show_sent_page(p, invsent_per_page);
3096
        return false;
3096 3097
    }
3098
    
3099
    function show_sent_page(i, per_page) {
3100
        $("#invsent ul li").hide();
3101
        start = i * per_page;
3102
        end = start + per_page;
3097 3103

  
3098
    function display_resend_error(msg) {
3099
        clear_resend_messages();
3100
        $("#invsent .message.errormsg").text(msg).show();
3104
        var i = start;
3105
        while(i < start + per_page) {
3106
            $($("#invsent ul li")[i]).show();
3107
            i++;
3108
        }
3109
    }
3110

  
3111
    function paginate_sent() {
3112
        var per_page = invsent_per_page;
3113
        var total = $(".invitations #invsent ul li").length;
3114
        $(".invitations .pages").pagination(total, {callback: handle_pagination, items_per_page: per_page});
3115
        show_sent_page(0, per_page);
3116
    }
3117

  
3118
    function display_resend_msg(msg, cls) {
3119
        el = $("<span class='resend-msg "+cls+"'>"+msg+"</span>");
3120
        
3121
        (function(el) {
3122
            var element = el;
3123
            window.setTimeout(function(){
3124
                element.fadeOut(1000).delay(2000).remove();
3125
            }, 4000);
3126
        })(el)
3127
        $("#invsent .message.success").append(el).show();
3101 3128
    }
3102 3129

  
3103 3130
    // clear resent messages
......
3125 3152
                type: "POST",
3126 3153
                url : "/invitations/resend",
3127 3154
                data : {invid : id},
3155
                invid: id,
3128 3156
                success: function(msg) {
3129
                    display_resend_success("Invitation has been resent");
3157
                    inv_email = $(".resend-invitation#inv-" + this.invid).parent().find(".email").text();
3158
                    display_resend_msg("Invitation to <em>'"+inv_email+"'</em> has been resent", "success-msg");
3130 3159
                    child.attr('src', '/static/resend.png');
3131 3160
                },
3132 3161
                error : function(xhr, status, error) {
3133
                    display_resend_error("Something seems to have gone wrong. " +
3134
                          "Please try again in a few minutes.");
3162
                    inv_email = $(".resend-invitation#inv-" + this.invid).parent().find(".email").text();
3163
                    display_resend_msg("Invitation to <em>'"+inv_email+"'</em> failed to send", "error-msg");
3135 3164
                    child.attr('src', '/static/resend.png');
3136 3165
                }
3137 3166
            });
......
3139 3168
    }
3140 3169

  
3141 3170
    handle_invitations = function(el) {
3142

  
3171
        
3172
        if ($("div.invitations").length > 1) {
3173
            $($("div.invitations")[0]).remove();
3174
        }
3143 3175
        // proper class to identify the overlay block
3144 3176
        el.addClass("invitations");
3145 3177

  
......
3167 3199
        $(".invitations .submit").show();
3168 3200
        $(".invitations #fieldheaders").show();
3169 3201
        $(".invitations #fields").show();
3202
        $("#fields input[name=name_1]").focus();
3170 3203

  
3171 3204
        // reset title
3172 3205
        $("#notification-box .header-box").html("");
3173 3206
        $("#notification-box .header-box").html(window.INVITATIONS_TITLE + " " + $($(".invitations-left")[0]).text());
3174
    
3207
        
3175 3208
        // resend buttons
3176 3209
        register_invitation_resends();
3177 3210
        clear_resend_messages();
3178 3211

  
3212
        paginate_sent();
3213

  
3179 3214
        // handle form submit
3180 3215
        form.submit(function(evn){
3181 3216
            evn.preventDefault();
b/ui/templates/home.html
55 55
    <script src="static/jquery.client.js"></script>
56 56
    <script src="static/json2.js"></script>
57 57
    <script src="static/jquery.dataTables.min.js"></script>
58
    <script src="static/jquery.pagination.js"></script>
58 59
    <script src="static/invitations.js"></script>
59 60
    <script src="static/synnefo.js"></script>
60 61

  
......
79 80
        var APP_DEBUG = {% if DEBUG %}true{% else %}false{% endif %};
80 81
        var FEEDBACK_URL = "{% url feedback %}";
81 82
        var FEEDBACK_TITLE = "{% trans "Send feedback" %}";
82

  
83 83
        var API_OVERLAY_TITLE = "{% trans "API access" %}";
84 84
        var API_OVERLAY_SUBCONTENT = "{% trans "The API key provides full access to your <em>~okeanos</em> account, so always keep it private." %}";
85
        var INVITATIONS_PER_PAGE = {% if invitations_per_page %} {{ invitations_per_page }} {% else %} 10 {% endif %};
85 86
        
86 87
        // building statuses
87 88
        var BUILDING_STATUSES = {
b/ui/views.py
52 52
LOGOUT_URL = getattr(settings, "LOGOUT_URL", settings.LOGIN_URL)
53 53
SUGGESTED_FLAVORS = getattr(settings, "SUGGESTED_FLAVORS", {})
54 54
VM_IMAGE_COMMON_METADATA = getattr(settings, "VM_IMAGE_COMMON_METADATA", ["OS"])
55
INVITATIONS_PER_PAGE = getattr(settings, "INVITATIONS_PER_PAGE", 10)
55 56

  
56 57
def template(name, context):
57 58
    template_path = os.path.join(os.path.dirname(__file__), "templates/")
......
69 70
               'logout_redirect': LOGOUT_URL,
70 71
               'suggested_flavors': json.dumps(SUGGESTED_FLAVORS),
71 72
               'vm_image_common_metadata': json.dumps(VM_IMAGE_COMMON_METADATA),
73
               'invitations_per_page': INVITATIONS_PER_PAGE,
72 74
               'DEBUG': settings.DEBUG}
73 75
    return template('home', context)
74 76

  

Also available in: Unified diff