Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
CIRCLE
/
cloud
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
94
Merge Requests
10
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
db31f971
authored
Sep 01, 2014
by
Kálmán Viktor
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into custom-connect-command
Conflicts: circle/dashboard/static/dashboard/dashboard.css
parents
d0ae4fc4
4268ac9d
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
37 changed files
with
671 additions
and
435 deletions
+671
-435
circle/circle/settings/base.py
+3
-3
circle/common/models.py
+36
-4
circle/common/operations.py
+25
-8
circle/dashboard/forms.py
+35
-15
circle/dashboard/static/dashboard/dashboard.css
+72
-0
circle/dashboard/static/dashboard/dashboard.js
+8
-3
circle/dashboard/static/dashboard/group-list.js
+0
-126
circle/dashboard/static/dashboard/vm-create.js
+5
-0
circle/dashboard/static/dashboard/vm-list.js
+113
-79
circle/dashboard/templates/dashboard/_notifications-timeline.html
+4
-3
circle/dashboard/templates/dashboard/_vm-create-2.html
+3
-1
circle/dashboard/templates/dashboard/_vm-mass-migrate.html
+36
-0
circle/dashboard/templates/dashboard/group-list.html
+6
-45
circle/dashboard/templates/dashboard/group-list/column-actions.html
+5
-1
circle/dashboard/templates/dashboard/index-nodes.html
+4
-2
circle/dashboard/templates/dashboard/index-vm.html
+6
-4
circle/dashboard/templates/dashboard/instanceactivity_detail.html
+4
-8
circle/dashboard/templates/dashboard/lease-create.html
+1
-1
circle/dashboard/templates/dashboard/lease-edit.html
+1
-1
circle/dashboard/templates/dashboard/mass-operate.html
+38
-0
circle/dashboard/templates/dashboard/node-detail/_activity-timeline.html
+2
-4
circle/dashboard/templates/dashboard/template-list.html
+1
-1
circle/dashboard/templates/dashboard/vm-detail/_activity-timeline.html
+6
-6
circle/dashboard/templates/dashboard/vm-detail/_operations.html
+1
-1
circle/dashboard/templates/dashboard/vm-list.html
+40
-37
circle/dashboard/templatetags/hro.py
+12
-0
circle/dashboard/tests/test_mockedviews.py
+109
-1
circle/dashboard/tests/test_views.py
+0
-14
circle/dashboard/urls.py
+2
-5
circle/dashboard/views.py
+0
-0
circle/dashboard/vm/urls.py
+12
-4
circle/locale/hu/LC_MESSAGES/django.po
+0
-0
circle/locale/hu/LC_MESSAGES/djangojs.po
+12
-11
circle/vm/models/activity.py
+13
-6
circle/vm/models/instance.py
+18
-3
circle/vm/models/node.py
+1
-1
circle/vm/operations.py
+37
-37
No files found.
circle/circle/settings/base.py
View file @
db31f971
...
...
@@ -368,9 +368,9 @@ if get_env_variable('DJANGO_SAML', 'FALSE') == 'TRUE':
from
shutilwhich
import
which
from
saml2
import
BINDING_HTTP_POST
,
BINDING_HTTP_REDIRECT
# INSTALLED_APPS += ( # needed only for testing djangosaml2
# 'djangosaml
',
#
)
INSTALLED_APPS
+=
(
'djangosaml2
'
,
)
AUTHENTICATION_BACKENDS
=
(
'django.contrib.auth.backends.ModelBackend'
,
'djangosaml2.backends.Saml2Backend'
,
...
...
circle/common/models.py
View file @
db31f971
...
...
@@ -27,6 +27,7 @@ from warnings import warn
from
django.contrib
import
messages
from
django.contrib.auth.models
import
User
from
django.core.cache
import
cache
from
django.core.exceptions
import
PermissionDenied
from
django.core.serializers.json
import
DjangoJSONEncoder
from
django.db.models
import
(
CharField
,
DateTimeField
,
ForeignKey
,
NullBooleanField
...
...
@@ -413,6 +414,10 @@ class HumanReadableObject(object):
self
.
_set_values
(
user_text_template
,
admin_text_template
,
params
)
def
_set_values
(
self
,
user_text_template
,
admin_text_template
,
params
):
if
isinstance
(
user_text_template
,
Promise
):
user_text_template
=
user_text_template
.
_proxy____args
[
0
]
if
isinstance
(
admin_text_template
,
Promise
):
admin_text_template
=
admin_text_template
.
_proxy____args
[
0
]
self
.
user_text_template
=
user_text_template
self
.
admin_text_template
=
admin_text_template
self
.
params
=
params
...
...
@@ -451,6 +456,12 @@ class HumanReadableObject(object):
self
.
user_text_template
,
unicode
(
self
.
params
))
return
self
.
user_text_template
def
get_text
(
self
,
user
):
if
user
and
user
.
is_superuser
:
return
self
.
get_admin_text
()
else
:
return
self
.
get_user_text
()
def
to_dict
(
self
):
return
{
"user_text_template"
:
self
.
user_text_template
,
"admin_text_template"
:
self
.
admin_text_template
,
...
...
@@ -481,13 +492,34 @@ class HumanReadableException(HumanReadableObject, Exception):
self
.
level
=
"error"
def
send_message
(
self
,
request
,
level
=
None
):
if
request
.
user
and
request
.
user
.
is_superuser
:
msg
=
self
.
get_admin_text
()
else
:
msg
=
self
.
get_user_text
()
msg
=
self
.
get_text
(
request
.
user
)
getattr
(
messages
,
level
or
self
.
level
)(
request
,
msg
)
def
fetch_human_exception
(
exception
,
user
=
None
):
"""Fetch user readable message from exception.
>>> r = humanize_exception("foo", Exception())
>>> fetch_human_exception(r, User())
u'foo'
>>> fetch_human_exception(r).get_text(User())
u'foo'
>>> fetch_human_exception(Exception(), User())
u'Unknown error'
>>> fetch_human_exception(PermissionDenied(), User())
u'Permission Denied'
"""
if
not
isinstance
(
exception
,
HumanReadableException
):
if
isinstance
(
exception
,
PermissionDenied
):
exception
=
create_readable
(
ugettext_noop
(
"Permission Denied"
))
else
:
exception
=
create_readable
(
ugettext_noop
(
"Unknown error"
),
ugettext_noop
(
"Unknown error:
%(ex)
s"
),
ex
=
unicode
(
exception
))
return
exception
.
get_text
(
user
)
if
user
else
exception
def
humanize_exception
(
message
,
exception
=
None
,
level
=
None
,
**
params
):
"""Return new dynamic-class exception which is based on
HumanReadableException and the original class with the dict of exception.
...
...
circle/common/operations.py
View file @
db31f971
...
...
@@ -18,10 +18,10 @@
from
inspect
import
getargspec
from
logging
import
getLogger
from
.models
import
activity_context
,
has_suffix
from
django.core.exceptions
import
PermissionDenied
,
ImproperlyConfigured
from
django.utils.translation
import
ugettext_noop
from
.models
import
activity_context
,
has_suffix
,
humanize_exception
logger
=
getLogger
(
__name__
)
...
...
@@ -31,6 +31,7 @@ class Operation(object):
"""
async_queue
=
'localhost.man'
required_perms
=
None
superuser_required
=
False
do_not_call_in_templates
=
True
abortable
=
False
has_percentage
=
False
...
...
@@ -143,13 +144,26 @@ class Operation(object):
def
check_precond
(
self
):
pass
def
check_auth
(
self
,
user
):
if
self
.
required_perms
is
None
:
@classmethod
def
check_perms
(
cls
,
user
):
"""Check if user is permitted to run this operation on any instance
"""
if
cls
.
required_perms
is
None
:
raise
ImproperlyConfigured
(
"Set required_perms to () if none needed."
)
if
not
user
.
has_perms
(
self
.
required_perms
):
if
not
user
.
has_perms
(
cls
.
required_perms
):
raise
PermissionDenied
(
"
%
s doesn't have the required permissions."
%
user
)
if
cls
.
superuser_required
and
not
user
.
is_superuser
:
raise
humanize_exception
(
ugettext_noop
(
"Superuser privileges are required."
),
PermissionDenied
())
def
check_auth
(
self
,
user
):
"""Check if user is permitted to run this operation on this instance
"""
self
.
check_perms
(
user
)
def
create_activity
(
self
,
parent
,
user
,
kwargs
):
raise
NotImplementedError
...
...
@@ -185,14 +199,17 @@ class OperatedMixin(object):
def
__getattr__
(
self
,
name
):
# NOTE: __getattr__ is only called if the attribute doesn't already
# exist in your __dict__
cls
=
self
.
__class__
return
self
.
get_operation_class
(
name
)(
self
)
@classmethod
def
get_operation_class
(
cls
,
name
):
ops
=
getattr
(
cls
,
operation_registry_name
,
{})
op
=
ops
.
get
(
name
)
if
op
:
return
op
(
self
)
return
op
else
:
raise
AttributeError
(
"
%
r object has no attribute
%
r"
%
(
self
.
__class__
.
__name__
,
name
))
(
cls
.
__name__
,
name
))
def
get_available_operations
(
self
,
user
):
"""Yield Operations that match permissions of user and preconditions.
...
...
circle/dashboard/forms.py
View file @
db31f971
...
...
@@ -636,12 +636,8 @@ class LeaseForm(forms.ModelForm):
Field
(
'name'
),
Field
(
"suspend_interval_seconds"
,
type
=
"hidden"
,
value
=
"0"
),
Field
(
"delete_interval_seconds"
,
type
=
"hidden"
,
value
=
"0"
),
HTML
(
string_concat
(
"<label>"
,
_
(
"Suspend in"
),
"</label>"
)),
Div
(
Div
(
HTML
(
_
(
"Suspend in"
)),
css_class
=
"input-group-addon"
,
style
=
"width: 100px;"
,
),
NumberField
(
"suspend_hours"
,
css_class
=
"form-control"
),
Div
(
HTML
(
_
(
"hours"
)),
...
...
@@ -664,12 +660,8 @@ class LeaseForm(forms.ModelForm):
),
css_class
=
"input-group interval-input"
,
),
HTML
(
string_concat
(
"<label>"
,
_
(
"Delete in"
),
"</label>"
)),
Div
(
Div
(
HTML
(
_
(
"Delete in"
)),
css_class
=
"input-group-addon"
,
style
=
"width: 100px;"
,
),
NumberField
(
"delete_hours"
,
css_class
=
"form-control"
),
Div
(
HTML
(
_
(
"hours"
)),
...
...
@@ -693,7 +685,7 @@ class LeaseForm(forms.ModelForm):
css_class
=
"input-group interval-input"
,
)
)
helper
.
add_input
(
Submit
(
"submit"
,
"Save changes"
))
helper
.
add_input
(
Submit
(
"submit"
,
_
(
"Save changes"
)
))
return
helper
class
Meta
:
...
...
@@ -705,6 +697,8 @@ class VmRenewForm(forms.Form):
force
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
"Set expiration times even if they are shorter than "
"the current value."
))
save
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
"Save selected lease."
))
def
__init__
(
self
,
*
args
,
**
kwargs
):
choices
=
kwargs
.
pop
(
'choices'
)
...
...
@@ -716,6 +710,32 @@ class VmRenewForm(forms.Form):
empty_label
=
None
,
label
=
_
(
'Length'
)))
if
len
(
choices
)
<
2
:
self
.
fields
[
'lease'
]
.
widget
=
HiddenInput
()
self
.
fields
[
'save'
]
.
widget
=
HiddenInput
()
@property
def
helper
(
self
):
helper
=
FormHelper
(
self
)
helper
.
form_tag
=
False
return
helper
class
VmStateChangeForm
(
forms
.
Form
):
interrupt
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
"Forcibly interrupt all running activities."
),
help_text
=
_
(
"Set all activities to finished state, "
"but don't interrupt any tasks."
))
new_state
=
forms
.
ChoiceField
(
Instance
.
STATUS
,
label
=
_
(
"New status"
))
def
__init__
(
self
,
*
args
,
**
kwargs
):
show_interrupt
=
kwargs
.
pop
(
'show_interrupt'
)
status
=
kwargs
.
pop
(
'status'
)
super
(
VmStateChangeForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
if
not
show_interrupt
:
self
.
fields
[
'interrupt'
]
.
widget
=
HiddenInput
()
self
.
fields
[
'new_state'
]
.
initial
=
status
@property
def
helper
(
self
):
...
...
@@ -1164,9 +1184,9 @@ class VmResourcesForm(forms.ModelForm):
vm_search_choices
=
(
(
0
,
_
(
"owned"
)),
(
1
,
_
(
"shared"
)),
(
2
,
_
(
"all"
)),
(
"owned"
,
_
(
"owned"
)),
(
"shared"
,
_
(
"shared"
)),
(
"all"
,
_
(
"all"
)),
)
...
...
@@ -1185,5 +1205,5 @@ class VmListSearchForm(forms.Form):
# set initial value, otherwise it would be overwritten by request.GET
if
not
self
.
data
.
get
(
"stype"
):
data
=
self
.
data
.
copy
()
data
[
'stype'
]
=
2
data
[
'stype'
]
=
"all"
self
.
data
=
data
circle/dashboard/static/dashboard/dashboard.css
View file @
db31f971
...
...
@@ -874,3 +874,75 @@ textarea[name="list-new-namelist"] {
text-align
:
center
;
vertical-align
:
middle
;
}
#vm-list-table
.migrating-icon
{
-webkit-animation
:
passing
2s
linear
infinite
;
animation
:
passing
2s
linear
infinite
;
}
@-webkit-keyframes
passing
{
0
%
{
-webkit-transform
:
translateX
(
50%
);
transform
:
translateX
(
50%
);
opacity
:
0
;
}
50
%
{
-webkit-transform
:
translateX
(
0%
);
transform
:
translateX
(
0%
);
opacity
:
1
;
}
100
%
{
-webkit-transform
:
translateX
(
-50%
);
transform
:
translateX
(
-50%
);
opacity
:
0
;
}
}
@keyframes
passing
{
0
%
{
-webkit-transform
:
translateX
(
50%
);
-ms-transform
:
translateX
(
50%
);
transform
:
translateX
(
50%
);
opacity
:
0
;
}
50
%
{
-webkit-transform
:
translateX
(
0%
);
-ms-transform
:
translateX
(
0%
);
transform
:
translateX
(
0%
);
opacity
:
1
;
}
100
%
{
-webkit-transform
:
translateX
(
-50%
);
-ms-transform
:
translateX
(
-50%
);
transform
:
translateX
(
-50%
);
opacity
:
0
;
}
}
.mass-migrate-node
{
cursor
:
pointer
;
}
.mass-op-panel
{
padding
:
6px
10px
;
}
.mass-op-panel
.check
{
color
:
#449d44
;
}
.mass-op-panel
.minus
{
color
:
#d9534f
;
}
.mass-op-panel
.status-icon
{
font-size
:
.8em
;
}
#vm-list-search
,
#vm-mass-ops
{
margin-top
:
8px
;
}
circle/dashboard/static/dashboard/dashboard.js
View file @
db31f971
...
...
@@ -262,7 +262,7 @@ $(function () {
$
(
"#dashboard-vm-search-form"
).
submit
(
function
()
{
var
vm_list_items
=
$
(
"#dashboard-vm-list .list-group-item"
);
if
(
vm_list_items
.
length
==
1
)
{
if
(
vm_list_items
.
length
==
1
&&
vm_list_items
.
first
().
prop
(
"href"
)
)
{
window
.
location
.
href
=
vm_list_items
.
first
().
prop
(
"href"
);
return
false
;
}
...
...
@@ -488,14 +488,19 @@ function addSliderMiscs() {
ram_fire
=
true
;
$
(
".ram-slider"
).
simpleSlider
(
"setValue"
,
parseInt
(
val
));
});
$
(
".cpu-priority-input"
).
trigger
(
"change"
);
$
(
".cpu-count-input, .ram-input"
).
trigger
(
"input"
);
setDefaultSliderValues
(
);
$
(
".cpu-priority-slider"
).
simpleSlider
(
"setDisabled"
,
$
(
".cpu-priority-input"
).
prop
(
"disabled"
));
$
(
".cpu-count-slider"
).
simpleSlider
(
"setDisabled"
,
$
(
".cpu-count-input"
).
prop
(
"disabled"
));
$
(
".ram-slider"
).
simpleSlider
(
"setDisabled"
,
$
(
".ram-input"
).
prop
(
"disabled"
));
}
function
setDefaultSliderValues
()
{
$
(
".cpu-priority-input"
).
trigger
(
"change"
);
$
(
".ram-input, .cpu-count-input"
).
trigger
(
"input"
);
}
/* deletes the VM with the pk
* if dir is true, then redirect to the dashboard landing page
...
...
circle/dashboard/static/dashboard/group-list.js
View file @
db31f971
var
ctrlDown
,
shiftDown
=
false
;
var
ctrlKey
=
17
;
var
shiftKey
=
16
;
var
selected
=
[];
$
(
function
()
{
$
(
document
).
keydown
(
function
(
e
)
{
if
(
e
.
keyCode
==
ctrlKey
)
ctrlDown
=
true
;
if
(
e
.
keyCode
==
shiftKey
)
shiftDown
=
true
;
}).
keyup
(
function
(
e
)
{
if
(
e
.
keyCode
==
ctrlKey
)
ctrlDown
=
false
;
if
(
e
.
keyCode
==
shiftKey
)
shiftDown
=
false
;
});
$
(
'.group-list-table tbody'
).
find
(
'tr'
).
mousedown
(
function
()
{
var
retval
=
true
;
if
(
ctrlDown
)
{
setRowColor
(
$
(
this
));
if
(
!
$
(
this
).
hasClass
(
'group-list-selected'
))
{
selected
.
splice
(
selected
.
indexOf
(
$
(
this
).
index
()),
1
);
}
else
{
selected
.
push
(
$
(
this
).
index
());
}
retval
=
false
;
}
else
if
(
shiftDown
)
{
if
(
selected
.
length
>
0
)
{
start
=
selected
[
selected
.
length
-
1
]
+
1
;
end
=
$
(
this
).
index
();
if
(
start
>
end
)
{
var
tmp
=
start
-
1
;
start
=
end
;
end
=
tmp
-
1
;
}
for
(
var
i
=
start
;
i
<=
end
;
i
++
)
{
if
(
selected
.
indexOf
(
i
)
<
0
)
{
selected
.
push
(
i
);
setRowColor
(
$
(
'.group-list-table tbody tr'
).
eq
(
i
));
}
}
}
retval
=
false
;
}
else
{
$
(
'.group-list-selected'
).
removeClass
(
'group-list-selected'
);
$
(
this
).
addClass
(
'group-list-selected'
);
selected
=
[
$
(
this
).
index
()];
}
// reset btn disables
$
(
'.group-list-table tbody tr .btn'
).
attr
(
'disabled'
,
false
);
// show/hide group controls
if
(
selected
.
length
>
1
)
{
$
(
'.group-list-group-control a'
).
attr
(
'disabled'
,
false
);
for
(
var
i
=
0
;
i
<
selected
.
length
;
i
++
)
{
$
(
'.group-list-table tbody tr'
).
eq
(
selected
[
i
]).
find
(
'.btn'
).
attr
(
'disabled'
,
true
);
}
}
else
{
$
(
'.group-list-group-control a'
).
attr
(
'disabled'
,
true
);
}
return
retval
;
});
$
(
'#group-list-group-migrate'
).
click
(
function
()
{
console
.
log
(
collectIds
(
selected
));
});
$
(
'tbody a'
).
mousedown
(
function
(
e
)
{
// parent tr doesn't get selected when clicked
e
.
stopPropagation
();
});
$
(
'tbody a'
).
click
(
function
(
e
)
{
// browser doesn't jump to top when clicked the buttons
if
(
!
$
(
this
).
hasClass
(
'real-link'
))
{
return
false
;
}
});
/* rename */
$
(
"#group-list-rename-button, .group-details-rename-button"
).
click
(
function
()
{
$
(
"#group-list-column-name"
,
$
(
this
).
closest
(
"tr"
)).
hide
();
...
...
@@ -113,51 +34,4 @@ $(function() {
return
false
;
});
/* group actions */
/* select all */
$
(
'#group-list-group-select-all'
).
click
(
function
()
{
$
(
'.group-list-table tbody tr'
).
each
(
function
()
{
var
index
=
$
(
this
).
index
();
if
(
selected
.
indexOf
(
index
)
<
0
)
{
selected
.
push
(
index
);
$
(
this
).
addClass
(
'group-list-selected'
);
}
});
if
(
selected
.
length
>
0
)
$
(
'.group-list-group-control a'
).
attr
(
'disabled'
,
false
);
return
false
;
});
/* mass vm delete */
$
(
'#group-list-group-delete'
).
click
(
function
()
{
addModalConfirmation
(
massDeleteVm
,
{
'url'
:
'/dashboard/group/mass-delete/'
,
'data'
:
{
'selected'
:
selected
,
'v'
:
collectIds
(
selected
)
}
}
);
return
false
;
});
});
function
collectIds
(
rows
)
{
var
ids
=
[];
for
(
var
i
=
0
;
i
<
rows
.
length
;
i
++
)
{
var
div
=
$
(
'td:first-child div'
,
$
(
'.group-list-table tbody tr'
).
eq
(
rows
[
i
]));
ids
.
push
(
div
.
prop
(
'id'
).
replace
(
'node-'
,
''
));
}
return
ids
;
}
function
setRowColor
(
row
)
{
if
(
!
row
.
hasClass
(
'group-list-selected'
))
{
row
.
addClass
(
'group-list-selected'
);
}
else
{
row
.
removeClass
(
'group-list-selected'
);
}
}
circle/dashboard/static/dashboard/vm-create.js
View file @
db31f971
...
...
@@ -28,6 +28,9 @@ function vmCreateLoaded() {
$
(
'#create-modal'
).
on
(
'hidden.bs.modal'
,
function
()
{
$
(
'#create-modal'
).
remove
();
});
$
(
"#create-modal"
).
on
(
"shown.bs.modal"
,
function
()
{
setDefaultSliderValues
();
});
});
return
false
;
});
...
...
@@ -217,6 +220,8 @@ function vmCustomizeLoaded() {
});
if
(
error
)
return
true
;
$
(
this
).
find
(
"i"
).
prop
(
"class"
,
"fa fa-spinner fa-spin"
);
$
.
ajax
({
url
:
'/dashboard/vm/create/'
,
headers
:
{
"X-CSRFToken"
:
getCookie
(
'csrftoken'
)},
...
...
circle/dashboard/static/dashboard/vm-list.js
View file @
db31f971
...
...
@@ -14,6 +14,7 @@ $(function() {
$
(
'.vm-list-table tbody'
).
find
(
'tr'
).
mousedown
(
function
()
{
var
retval
=
true
;
if
(
!
$
(
this
).
data
(
"vm-pk"
))
return
;
if
(
ctrlDown
)
{
setRowColor
(
$
(
this
));
if
(
!
$
(
this
).
hasClass
(
'vm-list-selected'
))
{
...
...
@@ -46,86 +47,20 @@ $(function() {
selected
=
[{
'index'
:
$
(
this
).
index
(),
'vm'
:
$
(
this
).
data
(
"vm-pk"
)}];
}
// reset btn disables
$
(
'.vm-list-table tbody tr .btn'
).
attr
(
'disabled'
,
false
);
// show/hide group controls
if
(
selected
.
length
>
0
)
{
$
(
'.vm-list-group-control a'
).
attr
(
'disabled'
,
false
);
for
(
var
i
=
0
;
i
<
selected
.
length
;
i
++
)
{
$
(
'.vm-list-table tbody tr'
).
eq
(
selected
[
i
]).
find
(
'.btn'
).
attr
(
'disabled'
,
true
);
}
$
(
'#vm-mass-ops .mass-operation'
).
attr
(
'disabled'
,
false
);
}
else
{
$
(
'
.vm-list-group-control a
'
).
attr
(
'disabled'
,
true
);
$
(
'
#vm-mass-ops .mass-operation
'
).
attr
(
'disabled'
,
true
);
}
return
retval
;
});
$
(
'#vm-list-group-migrate'
).
click
(
function
()
{
// pass?
});
$
(
'.vm-list-details'
).
popover
({
'placement'
:
'auto'
,
'html'
:
true
,
'trigger'
:
'hover'
});
$
(
'.vm-list-connect'
).
popover
({
'placement'
:
'left'
,
'html'
:
true
,
'trigger'
:
'click'
});
$
(
'tbody a'
).
mousedown
(
function
(
e
)
{
// parent tr doesn't get selected when clicked
e
.
stopPropagation
();
});
$
(
'tbody a'
).
click
(
function
(
e
)
{
// browser doesn't jump to top when clicked the buttons
if
(
!
$
(
this
).
hasClass
(
'real-link'
))
{
return
false
;
}
});
/* rename */
$
(
"#vm-list-rename-button, .vm-details-rename-button"
).
click
(
function
()
{
$
(
"#vm-list-column-name"
,
$
(
this
).
closest
(
"tr"
)).
hide
();
$
(
"#vm-list-rename"
,
$
(
this
).
closest
(
"tr"
)).
css
(
'display'
,
'inline'
);
$
(
"#vm-list-rename-name"
,
$
(
this
).
closest
(
"tr"
)).
focus
();
});
/* rename ajax */
$
(
'.vm-list-rename-submit'
).
click
(
function
()
{
var
row
=
$
(
this
).
closest
(
"tr"
)
var
name
=
$
(
'#vm-list-rename-name'
,
row
).
val
();
var
url
=
'/dashboard/vm/'
+
row
.
children
(
"td:first-child"
).
text
().
replace
(
" "
,
""
)
+
'/'
;
$
.
ajax
({
method
:
'POST'
,
url
:
url
,
data
:
{
'new_name'
:
name
},
headers
:
{
"X-CSRFToken"
:
getCookie
(
'csrftoken'
)},
success
:
function
(
data
,
textStatus
,
xhr
)
{
$
(
"#vm-list-column-name"
,
row
).
html
(
$
(
"<a/>"
,
{
'class'
:
"real-link"
,
href
:
"/dashboard/vm/"
+
data
[
'vm_pk'
]
+
"/"
,
text
:
data
[
'new_name'
]
})
).
show
();
$
(
'#vm-list-rename'
,
row
).
hide
();
// addMessage(data['message'], "success");
},
error
:
function
(
xhr
,
textStatus
,
error
)
{
addMessage
(
"Error during renaming!"
,
"danger"
);
}
});
return
false
;
});
/* group actions */
/* select all */
...
...
@@ -133,27 +68,69 @@ $(function() {
$
(
'.vm-list-table tbody tr'
).
each
(
function
()
{
var
index
=
$
(
this
).
index
();
var
vm
=
$
(
this
).
data
(
"vm-pk"
);
if
(
!
isAlreadySelected
(
vm
))
{
if
(
vm
&&
!
isAlreadySelected
(
vm
))
{
selected
.
push
({
'index'
:
index
,
'vm'
:
vm
});
$
(
this
).
addClass
(
'vm-list-selected'
);
}
});
if
(
selected
.
length
>
0
)
$
(
'
.vm-list-group-control a
'
).
attr
(
'disabled'
,
false
);
$
(
'
#vm-mass-ops .mass-operation
'
).
attr
(
'disabled'
,
false
);
return
false
;
});
/* mass vm delete */
$
(
'#vm-list-group-delete'
).
click
(
function
()
{
addModalConfirmation
(
massDeleteVm
,
{
'url'
:
'/dashboard/vm/mass-delete/'
,
'data'
:
{
'selected'
:
selected
,
'v'
:
collectIds
(
selected
)
/* mass operations */
$
(
"#vm-mass-ops"
).
on
(
'click'
,
'.mass-operation'
,
function
(
e
)
{
var
icon
=
$
(
this
).
children
(
"i"
).
addClass
(
'fa-spinner fa-spin'
);
params
=
"?"
+
selected
.
map
(
function
(
a
){
return
"vm="
+
a
.
vm
}).
join
(
"&"
);
$
.
ajax
({
type
:
'GET'
,
url
:
$
(
this
).
attr
(
'href'
)
+
params
,
success
:
function
(
data
)
{
icon
.
removeClass
(
"fa-spinner fa-spin"
);
$
(
'body'
).
append
(
data
);
$
(
'#confirmation-modal'
).
modal
(
'show'
);
$
(
'#confirmation-modal'
).
on
(
'hidden.bs.modal'
,
function
()
{
$
(
'#confirmation-modal'
).
remove
();
});
$
(
"[title]"
).
tooltip
({
'placement'
:
"left"
});
}
});
return
false
;
});
$
(
"body"
).
on
(
"click"
,
"#op-form-send"
,
function
()
{
var
url
=
$
(
this
).
closest
(
"form"
).
prop
(
"action"
);
$
(
this
).
find
(
"i"
).
prop
(
"class"
,
"fa fa-fw fa-spinner fa-spin"
);
$
.
ajax
({
url
:
url
,
headers
:
{
"X-CSRFToken"
:
getCookie
(
'csrftoken'
)},
type
:
'POST'
,
data
:
$
(
this
).
closest
(
'form'
).
serialize
(),
success
:
function
(
data
,
textStatus
,
xhr
)
{
/* hide the modal we just submitted */
$
(
'#confirmation-modal'
).
modal
(
"hide"
);
updateStatuses
(
1
);
/* if there are messages display them */
if
(
data
.
messages
&&
data
.
messages
.
length
>
0
)
{
addMessage
(
data
.
messages
.
join
(
"<br />"
),
"danger"
);
}
},
error
:
function
(
xhr
,
textStatus
,
error
)
{
$
(
'#confirmation-modal'
).
modal
(
"hide"
);
if
(
xhr
.
status
==
500
)
{
addMessage
(
"500 Internal Server Error"
,
"danger"
);
}
else
{
addMessage
(
xhr
.
status
+
" "
+
xhr
.
statusText
,
"danger"
);
}
}
);
}
);
return
false
;
});
...
...
@@ -181,8 +158,65 @@ $(function() {
$
(
".vm-list-table th a"
).
on
(
"click"
,
function
(
event
)
{
event
.
preventDefault
();
});
$
(
document
).
on
(
"click"
,
".mass-migrate-node"
,
function
()
{
$
(
this
).
find
(
'input[type="radio"]'
).
prop
(
"checked"
,
true
);
});
if
(
checkStatusUpdate
())
{
updateStatuses
(
1
);
}
});
function
checkStatusUpdate
()
{
icons
=
$
(
"#vm-list-table tbody td.state i"
);
if
(
icons
.
hasClass
(
"fa-spin"
)
||
icons
.
hasClass
(
"migrating-icon"
))
{
return
true
;
}
}
function
updateStatuses
(
runs
)
{
$
.
get
(
"/dashboard/vm/list/?compact"
,
function
(
result
)
{
$
(
"#vm-list-table tbody tr"
).
each
(
function
()
{
vm
=
$
(
this
).
data
(
"vm-pk"
);
status_td
=
$
(
this
).
find
(
"td.state"
);
status_icon
=
status_td
.
find
(
"i"
);
status_text
=
status_td
.
find
(
"span"
);
if
(
vm
in
result
)
{
if
(
result
[
vm
].
in_status_change
)
{
if
(
!
status_icon
.
hasClass
(
"fa-spin"
))
{
status_icon
.
prop
(
"class"
,
"fa fa-fw fa-spinner fa-spin"
);
}