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
bf5b78a0
authored
Oct 14, 2014
by
Csók Tamás
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into issue-218
parents
41d90c7f
345a3659
Hide whitespace changes
Inline
Side-by-side
Showing
57 changed files
with
1344 additions
and
670 deletions
+1344
-670
.gitignore
+1
-0
circle/acl/models.py
+1
-1
circle/circle/settings/base.py
+10
-1
circle/common/operations.py
+18
-1
circle/common/tests/test_operations.py
+15
-37
circle/dashboard/forms.py
+83
-2
circle/dashboard/models.py
+3
-0
circle/dashboard/static/dashboard/dashboard.css
+15
-2
circle/dashboard/static/dashboard/dashboard.js
+13
-2
circle/dashboard/static/dashboard/vm-common.js
+2
-10
circle/dashboard/static/dashboard/vm-details.js
+2
-0
circle/dashboard/static/local-logo.png
+0
-0
circle/dashboard/templates/branding.html
+2
-0
circle/dashboard/templates/dashboard/_disk-list-element.html
+24
-22
circle/dashboard/templates/dashboard/_vm-mass-migrate.html
+5
-3
circle/dashboard/templates/dashboard/_vm-migrate.html
+14
-10
circle/dashboard/templates/dashboard/base.html
+1
-1
circle/dashboard/templates/dashboard/group-detail.html
+30
-0
circle/dashboard/templates/dashboard/index-nodes.html
+51
-45
circle/dashboard/templates/dashboard/instanceactivity_detail.html
+20
-0
circle/dashboard/templates/dashboard/node-list/column-vm.html
+1
-1
circle/dashboard/templates/dashboard/template-edit.html
+7
-1
circle/dashboard/templates/dashboard/vm-detail.html
+4
-1
circle/dashboard/templates/dashboard/vm-detail/_network-port-add.html
+2
-1
circle/dashboard/templates/dashboard/vm-detail/home.html
+41
-10
circle/dashboard/templates/dashboard/vm-detail/network.html
+7
-5
circle/dashboard/templates/dashboard/vm-list.html
+13
-2
circle/dashboard/tests/test_mockedviews.py
+32
-15
circle/dashboard/tests/test_views.py
+10
-10
circle/dashboard/views/group.py
+11
-4
circle/dashboard/views/node.py
+2
-0
circle/dashboard/views/template.py
+52
-0
circle/dashboard/views/util.py
+1
-1
circle/dashboard/views/vm.py
+82
-87
circle/fabfile.py
+4
-0
circle/firewall/migrations/0052_auto__chg_field_record_address.py
+202
-0
circle/firewall/models.py
+1
-1
circle/firewall/tasks/local_tasks.py
+20
-13
circle/manager/mancelery.py
+10
-0
circle/manager/moncelery.py
+10
-1
circle/manager/slowcelery.py
+10
-1
circle/network/views.py
+1
-1
circle/storage/models.py
+3
-0
circle/vm/models/__init__.py
+4
-6
circle/vm/models/activity.py
+5
-21
circle/vm/models/instance.py
+29
-179
circle/vm/models/network.py
+2
-3
circle/vm/models/node.py
+6
-5
circle/vm/operations.py
+324
-127
circle/vm/tasks/agent_tasks.py
+11
-1
circle/vm/tasks/local_agent_tasks.py
+75
-18
circle/vm/tests/test_models.py
+15
-12
circle/vm/tests/test_operations.py
+18
-1
miscellaneous/mancelery.conf
+6
-1
miscellaneous/moncelery.conf
+4
-1
miscellaneous/slowcelery.conf
+8
-2
requirements/base.txt
+1
-1
No files found.
.gitignore
View file @
bf5b78a0
...
@@ -23,6 +23,7 @@ celerybeat-schedule
...
@@ -23,6 +23,7 @@ celerybeat-schedule
.coverage
.coverage
*,cover
*,cover
coverage.xml
coverage.xml
.noseids
# Gettext object file:
# Gettext object file:
*.mo
*.mo
...
...
circle/acl/models.py
View file @
bf5b78a0
...
@@ -229,7 +229,7 @@ class AclBase(Model):
...
@@ -229,7 +229,7 @@ class AclBase(Model):
levelfilter
,
levelfilter
,
content_type
=
ct
,
level__weight__gte
=
level
.
weight
)
.
distinct
()
content_type
=
ct
,
level__weight__gte
=
level
.
weight
)
.
distinct
()
clsfilter
=
Q
(
object_level_set__in
=
ols
.
all
())
clsfilter
=
Q
(
object_level_set__in
=
ols
.
all
())
return
cls
.
objects
.
filter
(
clsfilter
)
return
cls
.
objects
.
filter
(
clsfilter
)
.
distinct
()
def
save
(
self
,
*
args
,
**
kwargs
):
def
save
(
self
,
*
args
,
**
kwargs
):
super
(
AclBase
,
self
)
.
save
(
*
args
,
**
kwargs
)
super
(
AclBase
,
self
)
.
save
(
*
args
,
**
kwargs
)
...
...
circle/circle/settings/base.py
View file @
bf5b78a0
...
@@ -431,9 +431,18 @@ LOGIN_REDIRECT_URL = "/"
...
@@ -431,9 +431,18 @@ LOGIN_REDIRECT_URL = "/"
AGENT_DIR
=
get_env_variable
(
AGENT_DIR
=
get_env_variable
(
'DJANGO_AGENT_DIR'
,
join
(
unicode
(
expanduser
(
"~"
)),
'agent'
))
'DJANGO_AGENT_DIR'
,
join
(
unicode
(
expanduser
(
"~"
)),
'agent'
))
# AGENT_DIR is the root directory for the agent.
# The directory structure SHOULD be:
# /home/username/agent
# |-- agent-linux
# | |-- .git
# | +-- ...
# |-- agent-win
# | +-- agent-win-%(version).exe
#
try
:
try
:
git_env
=
{
'GIT_DIR'
:
join
(
AGENT_DIR
,
'.git'
)}
git_env
=
{
'GIT_DIR'
:
join
(
join
(
AGENT_DIR
,
"agent-linux"
)
,
'.git'
)}
AGENT_VERSION
=
check_output
(
AGENT_VERSION
=
check_output
(
(
'git'
,
'log'
,
'-1'
,
r'--pretty=format:
%
h'
,
'HEAD'
),
env
=
git_env
)
(
'git'
,
'log'
,
'-1'
,
r'--pretty=format:
%
h'
,
'HEAD'
),
env
=
git_env
)
except
:
except
:
...
...
circle/common/operations.py
View file @
bf5b78a0
...
@@ -26,6 +26,17 @@ from .models import activity_context, has_suffix, humanize_exception
...
@@ -26,6 +26,17 @@ from .models import activity_context, has_suffix, humanize_exception
logger
=
getLogger
(
__name__
)
logger
=
getLogger
(
__name__
)
class
SubOperationMixin
(
object
):
required_perms
=
()
def
create_activity
(
self
,
parent
,
user
,
kwargs
):
if
not
parent
:
raise
TypeError
(
"SubOperation can only be called with "
"parent_activity specified."
)
return
super
(
SubOperationMixin
,
self
)
.
create_activity
(
parent
,
user
,
kwargs
)
class
Operation
(
object
):
class
Operation
(
object
):
"""Base class for VM operations.
"""Base class for VM operations.
"""
"""
...
@@ -36,6 +47,10 @@ class Operation(object):
...
@@ -36,6 +47,10 @@ class Operation(object):
abortable
=
False
abortable
=
False
has_percentage
=
False
has_percentage
=
False
@classmethod
def
get_activity_code_suffix
(
cls
):
return
cls
.
id
def
__call__
(
self
,
**
kwargs
):
def
__call__
(
self
,
**
kwargs
):
return
self
.
call
(
**
kwargs
)
return
self
.
call
(
**
kwargs
)
...
@@ -62,6 +77,8 @@ class Operation(object):
...
@@ -62,6 +77,8 @@ class Operation(object):
parent_activity
=
auxargs
.
pop
(
'parent_activity'
)
parent_activity
=
auxargs
.
pop
(
'parent_activity'
)
if
parent_activity
and
user
is
None
and
not
skip_auth_check
:
if
parent_activity
and
user
is
None
and
not
skip_auth_check
:
user
=
parent_activity
.
user
user
=
parent_activity
.
user
if
user
is
None
:
# parent was a system call
skip_auth_check
=
True
# check for unexpected keyword arguments
# check for unexpected keyword arguments
argspec
=
getargspec
(
self
.
_operation
)
argspec
=
getargspec
(
self
.
_operation
)
...
@@ -232,7 +249,7 @@ class OperatedMixin(object):
...
@@ -232,7 +249,7 @@ class OperatedMixin(object):
operation could be found.
operation could be found.
"""
"""
for
op
in
getattr
(
self
,
operation_registry_name
,
{})
.
itervalues
():
for
op
in
getattr
(
self
,
operation_registry_name
,
{})
.
itervalues
():
if
has_suffix
(
activity_code
,
op
.
activity_code_suffix
):
if
has_suffix
(
activity_code
,
op
.
get_activity_code_suffix
()
):
return
op
(
self
)
return
op
(
self
)
else
:
else
:
return
None
return
None
...
...
circle/common/tests/test_operations.py
View file @
bf5b78a0
...
@@ -27,9 +27,7 @@ class OperationTestCase(TestCase):
...
@@ -27,9 +27,7 @@ class OperationTestCase(TestCase):
class
AbortEx
(
Exception
):
class
AbortEx
(
Exception
):
pass
pass
op
=
Operation
(
MagicMock
())
op
=
TestOp
(
MagicMock
())
op
.
activity_code_suffix
=
'test'
op
.
id
=
'test'
op
.
async_operation
=
MagicMock
(
op
.
async_operation
=
MagicMock
(
apply_async
=
MagicMock
(
side_effect
=
AbortEx
))
apply_async
=
MagicMock
(
side_effect
=
AbortEx
))
...
@@ -44,9 +42,7 @@ class OperationTestCase(TestCase):
...
@@ -44,9 +42,7 @@ class OperationTestCase(TestCase):
class
AbortEx
(
Exception
):
class
AbortEx
(
Exception
):
pass
pass
op
=
Operation
(
MagicMock
())
op
=
TestOp
(
MagicMock
())
op
.
activity_code_suffix
=
'test'
op
.
id
=
'test'
with
patch
.
object
(
Operation
,
'create_activity'
,
side_effect
=
AbortEx
):
with
patch
.
object
(
Operation
,
'create_activity'
,
side_effect
=
AbortEx
):
with
patch
.
object
(
Operation
,
'check_precond'
)
as
chk_pre
:
with
patch
.
object
(
Operation
,
'check_precond'
)
as
chk_pre
:
try
:
try
:
...
@@ -55,9 +51,7 @@ class OperationTestCase(TestCase):
...
@@ -55,9 +51,7 @@ class OperationTestCase(TestCase):
self
.
assertTrue
(
chk_pre
.
called
)
self
.
assertTrue
(
chk_pre
.
called
)
def
test_auth_check_on_non_system_call
(
self
):
def
test_auth_check_on_non_system_call
(
self
):
op
=
Operation
(
MagicMock
())
op
=
TestOp
(
MagicMock
())
op
.
activity_code_suffix
=
'test'
op
.
id
=
'test'
user
=
MagicMock
()
user
=
MagicMock
()
with
patch
.
object
(
Operation
,
'check_auth'
)
as
check_auth
:
with
patch
.
object
(
Operation
,
'check_auth'
)
as
check_auth
:
with
patch
.
object
(
Operation
,
'check_precond'
),
\
with
patch
.
object
(
Operation
,
'check_precond'
),
\
...
@@ -67,9 +61,7 @@ class OperationTestCase(TestCase):
...
@@ -67,9 +61,7 @@ class OperationTestCase(TestCase):
check_auth
.
assert_called_with
(
user
)
check_auth
.
assert_called_with
(
user
)
def
test_no_auth_check_on_system_call
(
self
):
def
test_no_auth_check_on_system_call
(
self
):
op
=
Operation
(
MagicMock
())
op
=
TestOp
(
MagicMock
())
op
.
activity_code_suffix
=
'test'
op
.
id
=
'test'
with
patch
.
object
(
Operation
,
'check_auth'
,
side_effect
=
AssertionError
):
with
patch
.
object
(
Operation
,
'check_auth'
,
side_effect
=
AssertionError
):
with
patch
.
object
(
Operation
,
'check_precond'
),
\
with
patch
.
object
(
Operation
,
'check_precond'
),
\
patch
.
object
(
Operation
,
'create_activity'
),
\
patch
.
object
(
Operation
,
'create_activity'
),
\
...
@@ -77,39 +69,25 @@ class OperationTestCase(TestCase):
...
@@ -77,39 +69,25 @@ class OperationTestCase(TestCase):
op
.
call
(
system
=
True
)
op
.
call
(
system
=
True
)
def
test_no_exception_for_more_arguments_when_operation_takes_kwargs
(
self
):
def
test_no_exception_for_more_arguments_when_operation_takes_kwargs
(
self
):
class
KwargOp
(
Operation
):
op
=
TestOp
(
MagicMock
())
activity_code_suffix
=
'test'
with
patch
.
object
(
TestOp
,
'create_activity'
),
\
id
=
'test'
patch
.
object
(
TestOp
,
'_exec_op'
):
def
_operation
(
self
,
**
kwargs
):
pass
op
=
KwargOp
(
MagicMock
())
with
patch
.
object
(
KwargOp
,
'create_activity'
),
\
patch
.
object
(
KwargOp
,
'_exec_op'
):
op
.
call
(
system
=
True
,
foo
=
42
)
op
.
call
(
system
=
True
,
foo
=
42
)
def
test_exception_for_unexpected_arguments
(
self
):
def
test_exception_for_unexpected_arguments
(
self
):
class
TestOp
(
Operation
):
activity_code_suffix
=
'test'
id
=
'test'
def
_operation
(
self
):
pass
op
=
TestOp
(
MagicMock
())
op
=
TestOp
(
MagicMock
())
with
patch
.
object
(
TestOp
,
'create_activity'
),
\
with
patch
.
object
(
TestOp
,
'create_activity'
),
\
patch
.
object
(
TestOp
,
'_exec_op'
):
patch
.
object
(
TestOp
,
'_exec_op'
):
self
.
assertRaises
(
TypeError
,
op
.
call
,
system
=
True
,
foo
=
42
)
self
.
assertRaises
(
TypeError
,
op
.
call
,
system
=
True
,
bar
=
42
)
def
test_exception_for_missing_arguments
(
self
):
def
test_exception_for_missing_arguments
(
self
):
class
TestOp
(
Operation
):
activity_code_suffix
=
'test'
id
=
'test'
def
_operation
(
self
,
foo
):
pass
op
=
TestOp
(
MagicMock
())
op
=
TestOp
(
MagicMock
())
with
patch
.
object
(
TestOp
,
'create_activity'
):
with
patch
.
object
(
TestOp
,
'create_activity'
):
self
.
assertRaises
(
TypeError
,
op
.
call
,
system
=
True
)
self
.
assertRaises
(
TypeError
,
op
.
call
,
system
=
True
)
class
TestOp
(
Operation
):
id
=
'test'
def
_operation
(
self
,
foo
):
pass
circle/dashboard/forms.py
View file @
bf5b78a0
...
@@ -18,6 +18,7 @@
...
@@ -18,6 +18,7 @@
from
__future__
import
absolute_import
from
__future__
import
absolute_import
from
datetime
import
timedelta
from
datetime
import
timedelta
from
urlparse
import
urlparse
from
django.contrib.auth.forms
import
(
from
django.contrib.auth.forms
import
(
AuthenticationForm
,
PasswordResetForm
,
SetPasswordForm
,
AuthenticationForm
,
PasswordResetForm
,
SetPasswordForm
,
...
@@ -39,6 +40,7 @@ from django.contrib.auth.forms import UserCreationForm as OrgUserCreationForm
...
@@ -39,6 +40,7 @@ from django.contrib.auth.forms import UserCreationForm as OrgUserCreationForm
from
django.forms.widgets
import
TextInput
,
HiddenInput
from
django.forms.widgets
import
TextInput
,
HiddenInput
from
django.template
import
Context
from
django.template
import
Context
from
django.template.loader
import
render_to_string
from
django.template.loader
import
render_to_string
from
django.utils.html
import
escape
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
sizefield.widgets
import
FileSizeWidget
from
sizefield.widgets
import
FileSizeWidget
from
django.core.urlresolvers
import
reverse_lazy
from
django.core.urlresolvers
import
reverse_lazy
...
@@ -79,6 +81,12 @@ class VmSaveForm(forms.Form):
...
@@ -79,6 +81,12 @@ class VmSaveForm(forms.Form):
helper
.
form_tag
=
False
helper
.
form_tag
=
False
return
helper
return
helper
def
__init__
(
self
,
*
args
,
**
kwargs
):
default
=
kwargs
.
pop
(
'default'
,
None
)
super
(
VmSaveForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
if
default
:
self
.
fields
[
'name'
]
.
initial
=
default
class
VmCustomizeForm
(
forms
.
Form
):
class
VmCustomizeForm
(
forms
.
Form
):
name
=
forms
.
CharField
(
widget
=
forms
.
TextInput
(
attrs
=
{
name
=
forms
.
CharField
(
widget
=
forms
.
TextInput
(
attrs
=
{
...
@@ -744,6 +752,20 @@ class VmRenewForm(forms.Form):
...
@@ -744,6 +752,20 @@ class VmRenewForm(forms.Form):
return
helper
return
helper
class
VmMigrateForm
(
forms
.
Form
):
live_migration
=
forms
.
BooleanField
(
required
=
False
,
initial
=
True
,
label
=
_
(
"live migration"
))
def
__init__
(
self
,
*
args
,
**
kwargs
):
choices
=
kwargs
.
pop
(
'choices'
)
default
=
kwargs
.
pop
(
'default'
)
super
(
VmMigrateForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
fields
.
insert
(
0
,
'to_node'
,
forms
.
ModelChoiceField
(
queryset
=
choices
,
initial
=
default
,
required
=
False
,
widget
=
forms
.
RadioSelect
(),
label
=
_
(
"Node"
)))
class
VmStateChangeForm
(
forms
.
Form
):
class
VmStateChangeForm
(
forms
.
Form
):
interrupt
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
interrupt
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
...
@@ -752,6 +774,7 @@ class VmStateChangeForm(forms.Form):
...
@@ -752,6 +774,7 @@ class VmStateChangeForm(forms.Form):
"but don't interrupt any tasks."
))
"but don't interrupt any tasks."
))
new_state
=
forms
.
ChoiceField
(
Instance
.
STATUS
,
label
=
_
(
new_state
=
forms
.
ChoiceField
(
Instance
.
STATUS
,
label
=
_
(
"New status"
))
"New status"
))
reset_node
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
"Reset node"
))
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
show_interrupt
=
kwargs
.
pop
(
'show_interrupt'
)
show_interrupt
=
kwargs
.
pop
(
'show_interrupt'
)
...
@@ -769,6 +792,17 @@ class VmStateChangeForm(forms.Form):
...
@@ -769,6 +792,17 @@ class VmStateChangeForm(forms.Form):
return
helper
return
helper
class
RedeployForm
(
forms
.
Form
):
with_emergency_change_state
=
forms
.
BooleanField
(
required
=
False
,
initial
=
True
,
label
=
_
(
"use emergency state change"
))
@property
def
helper
(
self
):
helper
=
FormHelper
(
self
)
helper
.
form_tag
=
False
return
helper
class
VmCreateDiskForm
(
forms
.
Form
):
class
VmCreateDiskForm
(
forms
.
Form
):
name
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
"Name"
))
name
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
"Name"
))
size
=
forms
.
CharField
(
size
=
forms
.
CharField
(
...
@@ -776,6 +810,12 @@ class VmCreateDiskForm(forms.Form):
...
@@ -776,6 +810,12 @@ class VmCreateDiskForm(forms.Form):
help_text
=
_
(
'Size of disk to create in bytes or with units '
help_text
=
_
(
'Size of disk to create in bytes or with units '
'like MB or GB.'
))
'like MB or GB.'
))
def
__init__
(
self
,
*
args
,
**
kwargs
):
default
=
kwargs
.
pop
(
'default'
,
None
)
super
(
VmCreateDiskForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
if
default
:
self
.
fields
[
'name'
]
.
initial
=
default
def
clean_size
(
self
):
def
clean_size
(
self
):
size_in_bytes
=
self
.
cleaned_data
.
get
(
"size"
)
size_in_bytes
=
self
.
cleaned_data
.
get
(
"size"
)
if
not
size_in_bytes
.
isdigit
()
and
len
(
size_in_bytes
)
>
0
:
if
not
size_in_bytes
.
isdigit
()
and
len
(
size_in_bytes
)
>
0
:
...
@@ -827,13 +867,42 @@ class VmDiskResizeForm(forms.Form):
...
@@ -827,13 +867,42 @@ class VmDiskResizeForm(forms.Form):
helper
.
form_tag
=
False
helper
.
form_tag
=
False
if
self
.
disk
:
if
self
.
disk
:
helper
.
layout
=
Layout
(
helper
.
layout
=
Layout
(
HTML
(
_
(
"<label>Disk:</label>
%
s"
)
%
self
.
disk
),
HTML
(
_
(
"<label>Disk:</label>
%
s"
)
%
escape
(
self
.
disk
)
),
Field
(
'disk'
),
Field
(
'size'
))
Field
(
'disk'
),
Field
(
'size'
))
return
helper
return
helper
class
VmDiskRemoveForm
(
forms
.
Form
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
choices
=
kwargs
.
pop
(
'choices'
)
self
.
disk
=
kwargs
.
pop
(
'default'
)
super
(
VmDiskRemoveForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
fields
.
insert
(
0
,
'disk'
,
forms
.
ModelChoiceField
(
queryset
=
choices
,
initial
=
self
.
disk
,
required
=
True
,
empty_label
=
None
,
label
=
_
(
'Disk'
)))
if
self
.
disk
:
self
.
fields
[
'disk'
]
.
widget
=
HiddenInput
()
@property
def
helper
(
self
):
helper
=
FormHelper
(
self
)
helper
.
form_tag
=
False
if
self
.
disk
:
helper
.
layout
=
Layout
(
AnyTag
(
"div"
,
HTML
(
_
(
"<label>Disk:</label>
%
s"
)
%
escape
(
self
.
disk
)),
css_class
=
"form-group"
,
),
Field
(
"disk"
),
)
return
helper
class
VmDownloadDiskForm
(
forms
.
Form
):
class
VmDownloadDiskForm
(
forms
.
Form
):
name
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
"Name"
))
name
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
"Name"
)
,
required
=
False
)
url
=
forms
.
CharField
(
label
=
_
(
'URL'
),
validators
=
[
URLValidator
(),
])
url
=
forms
.
CharField
(
label
=
_
(
'URL'
),
validators
=
[
URLValidator
(),
])
@property
@property
...
@@ -842,6 +911,18 @@ class VmDownloadDiskForm(forms.Form):
...
@@ -842,6 +911,18 @@ class VmDownloadDiskForm(forms.Form):
helper
.
form_tag
=
False
helper
.
form_tag
=
False
return
helper
return
helper
def
clean
(
self
):
cleaned_data
=
super
(
VmDownloadDiskForm
,
self
)
.
clean
()
if
not
cleaned_data
[
'name'
]:
if
cleaned_data
[
'url'
]:
cleaned_data
[
'name'
]
=
urlparse
(
cleaned_data
[
'url'
])
.
path
.
split
(
'/'
)[
-
1
]
if
not
cleaned_data
[
'name'
]:
raise
forms
.
ValidationError
(
_
(
"Could not find filename in URL, "
"please specify a name explicitly."
))
return
cleaned_data
class
VmAddInterfaceForm
(
forms
.
Form
):
class
VmAddInterfaceForm
(
forms
.
Form
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
...
...
circle/dashboard/models.py
View file @
bf5b78a0
...
@@ -236,6 +236,9 @@ class GroupProfile(AclBase):
...
@@ -236,6 +236,9 @@ class GroupProfile(AclBase):
help_text
=
_
(
'Unique identifier of the group at the organization.'
))
help_text
=
_
(
'Unique identifier of the group at the organization.'
))
description
=
TextField
(
blank
=
True
)
description
=
TextField
(
blank
=
True
)
def
__unicode__
(
self
):
return
self
.
group
.
name
def
save
(
self
,
*
args
,
**
kwargs
):
def
save
(
self
,
*
args
,
**
kwargs
):
if
not
self
.
org_id
:
if
not
self
.
org_id
:
self
.
org_id
=
None
self
.
org_id
=
None
...
...
circle/dashboard/static/dashboard/dashboard.css
View file @
bf5b78a0
...
@@ -528,7 +528,7 @@ footer a, footer a:hover, footer a:visited {
...
@@ -528,7 +528,7 @@ footer a, footer a:hover, footer a:visited {
}
}
#dashboard-template-list
a
small
{
#dashboard-template-list
a
small
{
max-width
:
50
%
;
max-width
:
45
%
;
float
:
left
;
float
:
left
;
padding-top
:
2px
;
padding-top
:
2px
;
text-overflow
:
ellipsis
;
text-overflow
:
ellipsis
;
...
@@ -974,6 +974,10 @@ textarea[name="new_members"] {
...
@@ -974,6 +974,10 @@ textarea[name="new_members"] {
color
:
orange
;
color
:
orange
;
}
}
#vm-info-pane
{
margin-bottom
:
20px
;
}
.node-list-table
tbody
>
tr
>
td
,
.node-list-table
thead
>
tr
>
th
{
.node-list-table
tbody
>
tr
>
td
,
.node-list-table
thead
>
tr
>
th
{
vertical-align
:
middle
;
vertical-align
:
middle
;
}
}
...
@@ -996,10 +1000,19 @@ textarea[name="new_members"] {
...
@@ -996,10 +1000,19 @@ textarea[name="new_members"] {
max-width
:
100%
;
max-width
:
100%
;
}
}
#vm-list-table
tbody
td
:nth-child
(
3
)
{
#vm-list-table
td
.state
,
#vm-list-table
td
.memory
{
white-space
:
nowrap
;
white-space
:
nowrap
;
}
}
#vm-list-table
td
{
#vm-list-table
td
{
vertical-align
:
middle
;
vertical-align
:
middle
;
}
}
.disk-resize-btn
{
margin-right
:
5px
;
}
#vm-migrate-node-list
li
{
cursor
:
pointer
;
}
circle/dashboard/static/dashboard/dashboard.js
View file @
bf5b78a0
...
@@ -411,6 +411,17 @@ $(function () {
...
@@ -411,6 +411,17 @@ $(function () {
$
(
this
).
removeClass
(
"btn-default"
).
addClass
(
"btn-primary"
);
$
(
this
).
removeClass
(
"btn-default"
).
addClass
(
"btn-primary"
);
return
false
;
return
false
;
});
});
// vm migrate select for node
$
(
document
).
on
(
"click"
,
"#vm-migrate-node-list li"
,
function
(
e
)
{
var
li
=
$
(
this
).
closest
(
'li'
);
if
(
li
.
find
(
'input'
).
attr
(
'disabled'
))
return
true
;
$
(
'#vm-migrate-node-list li'
).
removeClass
(
'panel-primary'
);
li
.
addClass
(
'panel-primary'
).
find
(
'input'
).
prop
(
"checked"
,
true
);
return
true
;
});
});
});
function
generateVmHTML
(
pk
,
name
,
host
,
icon
,
_status
,
fav
,
is_last
)
{
function
generateVmHTML
(
pk
,
name
,
host
,
icon
,
_status
,
fav
,
is_last
)
{
...
@@ -445,7 +456,7 @@ function generateNodeHTML(name, icon, _status, url, is_last) {
...
@@ -445,7 +456,7 @@ function generateNodeHTML(name, icon, _status, url, is_last) {
function
generateNodeTagHTML
(
name
,
icon
,
_status
,
label
,
url
)
{
function
generateNodeTagHTML
(
name
,
icon
,
_status
,
label
,
url
)
{
return
'<a href="'
+
url
+
'" class="label '
+
label
+
'" >'
+
return
'<a href="'
+
url
+
'" class="label '
+
label
+
'" >'
+
'<i class="'
+
icon
+
'" title="'
+
_status
+
'"></i> '
+
name
+
'<i class="
fa
'
+
icon
+
'" title="'
+
_status
+
'"></i> '
+
name
+
'</a> '
;
'</a> '
;
}
}
...
@@ -618,7 +629,7 @@ function addModalConfirmation(func, data) {
...
@@ -618,7 +629,7 @@ function addModalConfirmation(func, data) {
}
}
function
clientInstalledAction
(
location
)
{
function
clientInstalledAction
(
location
)
{
setCookie
(
'downloaded_client'
,
true
,
365
*
24
*
60
*
60
,
"/"
);
setCookie
(
'downloaded_client'
,
true
,
365
*
24
*
60
*
60
*
1000
,
"/"
);
window
.
location
.
href
=
location
;
window
.
location
.
href
=
location
;
$
(
'#confirmation-modal'
).
modal
(
"hide"
);
$
(
'#confirmation-modal'
).
modal
(
"hide"
);
}
}
...
...
circle/dashboard/static/dashboard/vm-common.js
View file @
bf5b78a0
...
@@ -16,15 +16,6 @@ $(function() {
...
@@ -16,15 +16,6 @@ $(function() {
$
(
'#confirmation-modal'
).
on
(
'hidden.bs.modal'
,
function
()
{
$
(
'#confirmation-modal'
).
on
(
'hidden.bs.modal'
,
function
()
{
$
(
'#confirmation-modal'
).
remove
();
$
(
'#confirmation-modal'
).
remove
();
});
});
$
(
'#vm-migrate-node-list li'
).
click
(
function
(
e
)
{
var
li
=
$
(
this
).
closest
(
'li'
);
if
(
li
.
find
(
'input'
).
attr
(
'disabled'
))
return
true
;
$
(
'#vm-migrate-node-list li'
).
removeClass
(
'panel-primary'
);
li
.
addClass
(
'panel-primary'
).
find
(
'input'
).
attr
(
'checked'
,
true
);
return
false
;
});
$
(
'#vm-migrate-node-list li input:checked'
).
closest
(
'li'
).
addClass
(
'panel-primary'
);
$
(
'#vm-migrate-node-list li input:checked'
).
closest
(
'li'
).
addClass
(
'panel-primary'
);
}
}
});
});
...
@@ -51,7 +42,8 @@ $(function() {
...
@@ -51,7 +42,8 @@ $(function() {
if
(
data
.
success
)
{
if
(
data
.
success
)
{
$
(
'a[href="#activity"]'
).
trigger
(
"click"
);
$
(
'a[href="#activity"]'
).
trigger
(
"click"
);
if
(
data
.
with_reload
)
{
if
(
data
.
with_reload
)
{
location
.
reload
();
// when the activity check stops the page will reload
reload_vm_detail
=
true
;
}
}
/* if there are messages display them */
/* if there are messages display them */
...
...
circle/dashboard/static/dashboard/vm-details.js
View file @
bf5b78a0
var
show_all
=
false
;
var
show_all
=
false
;
var
in_progress
=
false
;
var
in_progress
=
false
;
var
activity_hash
=
5
;
var
activity_hash
=
5
;
var
reload_vm_detail
=
false
;
$
(
function
()
{
$
(
function
()
{
/* do we need to check for new activities */
/* do we need to check for new activities */
...
@@ -404,6 +405,7 @@ function checkNewActivity(runs) {
...
@@ -404,6 +405,7 @@ function checkNewActivity(runs) {
);
);
}
else
{
}
else
{
in_progress
=
false
;
in_progress
=
false
;
if
(
reload_vm_detail
)
location
.
reload
();
}
}
$
(
'a[href="#activity"] i'
).
removeClass
(
'fa-spin'
);
$
(
'a[href="#activity"] i'
).
removeClass
(
'fa-spin'
);
},
},
...
...
circle/dashboard/static/local-logo.png
0 → 100644
View file @
bf5b78a0
5.65 KB
circle/dashboard/templates/branding.html
0 → 100644
View file @
bf5b78a0
<img
src=
"{{ STATIC_URL}}dashboard/img/logo.png"
style=
"height: 25px;"
/>
<img
src=
"{{ STATIC_URL}}local-logo.png"
style=
"padding-left: 2px; height: 25px;"
/>
circle/dashboard/templates/dashboard/_disk-list-element.html
View file @
bf5b78a0
{% load i18n %}
{% load i18n %}
{% load sizefieldtags %}
{% load sizefieldtags %}
<i
class=
"fa {% if d.is_downloading %}fa-refresh fa-spin{% else %}fa-file{% if d.failed %}"
style=
"color: #d9534f;{% endif %}{% endif %}"
></i>
<i
class=
"fa fa-file"
></i>
{{ d.name }} (#{{ d.id }}) -
{{ d.name }} (#{{ d.id }}) - {{ d.size|filesize }}
{% if not d.is_downloading %}
{% if not d.failed %}
{% if op.remove_disk %}
{% if d.size %}{{ d.size|filesize }}{% endif %}
<span
class=
"operation-wrapper"
>
{% else %}
<a
href=
"{{ op.remove_disk.get_url }}?disk={{d.pk}}"
<div
class=
"label label-danger"
{%
if
user
.
is_superuser
%}
title=
"{{ d.get_latest_activity_result }}"
{%
endif
%}
>
{% trans "failed" %}
</div>
class=
"btn btn-xs btn-{{ op.remove_disk.effect}} pull-right operation disk-remove-btn
{% endif %}
{% if op.resize_disk.disabled %}disabled{% endif %}"
>
{% else %}
<span
class=
"disk-list-disk-percentage"
data-disk-pk=
"{{ d.pk }}"
>
{{ d.get_download_percentage }}
</span>
%{% endif %}
<i
class=
"fa fa-{{ op.remove_disk.icon }}"
></i>
{% trans "Remove" %}
{% if is_owner != False %}
</a>
<a
href=
"{% url "
dashboard
.
views
.
disk-remove
"
pk=
d.pk
%}?
next=
{{
request
.
path
}}"
</span>
data-disk-pk=
"{{ d.pk }}"
class=
"btn btn-xs btn-danger pull-right disk-remove"
{% endif %}
{%
if
not
long_remove
%}
title=
"{% trans "
Remove
"
%}"{%
endif
%}
>
{% if op.resize_disk %}
<i
class=
"fa fa-times"
></i>
{% if long_remove %} {% trans "Remove" %}{% endif %}
<span
class=
"operation-wrapper"
>
</a>
<a
href=
"{{ op.resize_disk.get_url }}?disk={{d.pk}}"
{% if op.resize_disk %}
class=
"btn btn-xs btn-{{ op.resize_disk.effect }} pull-right operation disk-resize-btn
<span
class=
"operation-wrapper"
>
{% if op.resize_disk.disabled %}disabled{% endif %}"
>
<a
href=
"{{ op.resize_disk.get_url }}?disk={{d.pk}}"
class=
"btn btn-xs btn-warning pull-right operation"
>
<i
class=
"fa fa-{{ op.resize_disk.icon }}"
></i>
{% trans "Resize" %}
<i
class=
"fa fa-arrows-alt"
></i>
{% trans "Resize" %}
</a>
</a>
</span>
</span>
{% endif %}
{% endif %}
{% endif %}
<div
style=
"clear: both;"
></div>
<div
style=
"clear: both;"
></div>
{% if request.user.is_superuser %}
<small>
{% trans "File name" %}: {{ d.filename }}
</small>
{% endif %}
circle/dashboard/templates/dashboard/_vm-mass-migrate.html
View file @
bf5b78a0
{% extends "dashboard/mass-operate.html" %}
{% extends "dashboard/mass-operate.html" %}
{% load i18n %}
{% load i18n %}
{% load sizefieldtags %}
{% load sizefieldtags %}
{% load crispy_forms_tags %}
{% block formfields %}
{% block formfields %}
...
@@ -11,20 +12,20 @@
...
@@ -11,20 +12,20 @@
<label
for=
"migrate-to-none"
>
<label
for=
"migrate-to-none"
>
<strong>
{% trans "Reschedule" %}
</strong>
<strong>
{% trans "Reschedule" %}
</strong>
</label>
</label>
<input
id=
"migrate-to-none"
type=
"radio"
name=
"node"
value=
""
style=
"float: right;"
checked=
"checked"
>
<input
id=
"migrate-to-none"
type=
"radio"
name=
"
to_
node"
value=
""
style=
"float: right;"
checked=
"checked"
>
<span
class=
"vm-migrate-node-property"
>
<span
class=
"vm-migrate-node-property"
>
{% trans "This option will reschedule each virtual machine to the optimal node." %}
{% trans "This option will reschedule each virtual machine to the optimal node." %}
</span>
</span>
<div
style=
"clear: both;"
></div>
<div
style=
"clear: both;"
></div>
</div>
</div>
</li>
</li>
{% for n in
nodes
%}
{% for n in
form.fields.to_node.queryset.all
%}
<li
class=
"panel panel-default mass-migrate-node"
>
<li
class=
"panel panel-default mass-migrate-node"
>
<div
class=
"panel-body"
>
<div
class=
"panel-body"
>
<label
for=
"migrate-to-{{n.pk}}"
>
<label
for=
"migrate-to-{{n.pk}}"
>
<strong>
{{ n }}
</strong>
<strong>
{{ n }}
</strong>
</label>
</label>
<input
id=
"migrate-to-{{n.pk}}"
type=
"radio"
name=
"node"
value=
"{{ n.pk }}"
style=
"float: right;"
/>
<input
id=
"migrate-to-{{n.pk}}"
type=
"radio"
name=
"
to_
node"
value=
"{{ n.pk }}"
style=
"float: right;"
/>
<span
class=
"vm-migrate-node-property"
>
{% trans "CPU load" %}: {{ n.cpu_usage }}
</span>
<span
class=
"vm-migrate-node-property"
>
{% trans "CPU load" %}: {{ n.cpu_usage }}
</span>
<span
class=
"vm-migrate-node-property"
>
{% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}
</span>
<span
class=
"vm-migrate-node-property"
>
{% trans "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}
</span>
<div
style=
"clear: both;"
></div>
<div
style=
"clear: both;"
></div>
...
@@ -32,5 +33,6 @@
...
@@ -32,5 +33,6 @@
</li>
</li>
{% endfor %}
{% endfor %}
</ul>
</ul>
{{ form.live_migration|as_crispy_field }}
<hr
/>
<hr
/>
{% endblock %}
{% endblock %}
circle/dashboard/templates/dashboard/_vm-migrate.html
View file @
bf5b78a0
{% extends "dashboard/operate.html" %}