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