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
Show whitespace changes
Inline
Side-by-side
Showing
57 changed files
with
1302 additions
and
628 deletions
+1302
-628
.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
+14
-36
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
+20
-18
circle/dashboard/templates/dashboard/_vm-mass-migrate.html
+5
-3
circle/dashboard/templates/dashboard/_vm-migrate.html
+13
-9
circle/dashboard/templates/dashboard/base.html
+1
-1
circle/dashboard/templates/dashboard/group-detail.html
+30
-0
circle/dashboard/templates/dashboard/index-nodes.html
+37
-31
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
+38
-7
circle/dashboard/templates/dashboard/vm-detail/network.html
+2
-0
circle/dashboard/templates/dashboard/vm-list.html
+12
-1
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
+80
-85
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
+3
-19
circle/vm/models/instance.py
+24
-174
circle/vm/models/network.py
+2
-3
circle/vm/models/node.py
+6
-5
circle/vm/operations.py
+322
-125
circle/vm/tasks/agent_tasks.py
+11
-1
circle/vm/tasks/local_agent_tasks.py
+73
-16
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
.coverage
*,cover
coverage.xml
.noseids
# Gettext object file:
*.mo
...
...
circle/acl/models.py
View file @
bf5b78a0
...
...
@@ -229,7 +229,7 @@ class AclBase(Model):
levelfilter
,
content_type
=
ct
,
level__weight__gte
=
level
.
weight
)
.
distinct
()
clsfilter
=
Q
(
object_level_set__in
=
ols
.
all
())
return
cls
.
objects
.
filter
(
clsfilter
)
return
cls
.
objects
.
filter
(
clsfilter
)
.
distinct
()
def
save
(
self
,
*
args
,
**
kwargs
):
super
(
AclBase
,
self
)
.
save
(
*
args
,
**
kwargs
)
...
...
circle/circle/settings/base.py
View file @
bf5b78a0
...
...
@@ -431,9 +431,18 @@ LOGIN_REDIRECT_URL = "/"
AGENT_DIR
=
get_env_variable
(
'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
:
git_env
=
{
'GIT_DIR'
:
join
(
AGENT_DIR
,
'.git'
)}
git_env
=
{
'GIT_DIR'
:
join
(
join
(
AGENT_DIR
,
"agent-linux"
)
,
'.git'
)}
AGENT_VERSION
=
check_output
(
(
'git'
,
'log'
,
'-1'
,
r'--pretty=format:
%
h'
,
'HEAD'
),
env
=
git_env
)
except
:
...
...
circle/common/operations.py
View file @
bf5b78a0
...
...
@@ -26,6 +26,17 @@ from .models import activity_context, has_suffix, humanize_exception
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
):
"""Base class for VM operations.
"""
...
...
@@ -36,6 +47,10 @@ class Operation(object):
abortable
=
False
has_percentage
=
False
@classmethod
def
get_activity_code_suffix
(
cls
):
return
cls
.
id
def
__call__
(
self
,
**
kwargs
):
return
self
.
call
(
**
kwargs
)
...
...
@@ -62,6 +77,8 @@ class Operation(object):
parent_activity
=
auxargs
.
pop
(
'parent_activity'
)
if
parent_activity
and
user
is
None
and
not
skip_auth_check
:
user
=
parent_activity
.
user
if
user
is
None
:
# parent was a system call
skip_auth_check
=
True
# check for unexpected keyword arguments
argspec
=
getargspec
(
self
.
_operation
)
...
...
@@ -232,7 +249,7 @@ class OperatedMixin(object):
operation could be found.
"""
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
)
else
:
return
None
...
...
circle/common/tests/test_operations.py
View file @
bf5b78a0
...
...
@@ -27,9 +27,7 @@ class OperationTestCase(TestCase):
class
AbortEx
(
Exception
):
pass
op
=
Operation
(
MagicMock
())
op
.
activity_code_suffix
=
'test'
op
.
id
=
'test'
op
=
TestOp
(
MagicMock
())
op
.
async_operation
=
MagicMock
(
apply_async
=
MagicMock
(
side_effect
=
AbortEx
))
...
...
@@ -44,9 +42,7 @@ class OperationTestCase(TestCase):
class
AbortEx
(
Exception
):
pass
op
=
Operation
(
MagicMock
())
op
.
activity_code_suffix
=
'test'
op
.
id
=
'test'
op
=
TestOp
(
MagicMock
())
with
patch
.
object
(
Operation
,
'create_activity'
,
side_effect
=
AbortEx
):
with
patch
.
object
(
Operation
,
'check_precond'
)
as
chk_pre
:
try
:
...
...
@@ -55,9 +51,7 @@ class OperationTestCase(TestCase):
self
.
assertTrue
(
chk_pre
.
called
)
def
test_auth_check_on_non_system_call
(
self
):
op
=
Operation
(
MagicMock
())
op
.
activity_code_suffix
=
'test'
op
.
id
=
'test'
op
=
TestOp
(
MagicMock
())
user
=
MagicMock
()
with
patch
.
object
(
Operation
,
'check_auth'
)
as
check_auth
:
with
patch
.
object
(
Operation
,
'check_precond'
),
\
...
...
@@ -67,9 +61,7 @@ class OperationTestCase(TestCase):
check_auth
.
assert_called_with
(
user
)
def
test_no_auth_check_on_system_call
(
self
):
op
=
Operation
(
MagicMock
())
op
.
activity_code_suffix
=
'test'
op
.
id
=
'test'
op
=
TestOp
(
MagicMock
())
with
patch
.
object
(
Operation
,
'check_auth'
,
side_effect
=
AssertionError
):
with
patch
.
object
(
Operation
,
'check_precond'
),
\
patch
.
object
(
Operation
,
'create_activity'
),
\
...
...
@@ -77,39 +69,25 @@ class OperationTestCase(TestCase):
op
.
call
(
system
=
True
)
def
test_no_exception_for_more_arguments_when_operation_takes_kwargs
(
self
):
class
KwargOp
(
Operation
):
activity_code_suffix
=
'test'
id
=
'test'
def
_operation
(
self
,
**
kwargs
):
pass
op
=
KwargOp
(
MagicMock
())
with
patch
.
object
(
KwargOp
,
'create_activity'
),
\
patch
.
object
(
KwargOp
,
'_exec_op'
):
op
=
TestOp
(
MagicMock
())
with
patch
.
object
(
TestOp
,
'create_activity'
),
\
patch
.
object
(
TestOp
,
'_exec_op'
):
op
.
call
(
system
=
True
,
foo
=
42
)
def
test_exception_for_unexpected_arguments
(
self
):
class
TestOp
(
Operation
):
activity_code_suffix
=
'test'
id
=
'test'
def
_operation
(
self
):
pass
op
=
TestOp
(
MagicMock
())
with
patch
.
object
(
TestOp
,
'create_activity'
),
\
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
):
class
TestOp
(
Operation
):
activity_code_suffix
=
'test'
op
=
TestOp
(
MagicMock
())
with
patch
.
object
(
TestOp
,
'create_activity'
):
self
.
assertRaises
(
TypeError
,
op
.
call
,
system
=
True
)
class
TestOp
(
Operation
):
id
=
'test'
def
_operation
(
self
,
foo
):
pass
op
=
TestOp
(
MagicMock
())
with
patch
.
object
(
TestOp
,
'create_activity'
):
self
.
assertRaises
(
TypeError
,
op
.
call
,
system
=
True
)
circle/dashboard/forms.py
View file @
bf5b78a0
...
...
@@ -18,6 +18,7 @@
from
__future__
import
absolute_import
from
datetime
import
timedelta
from
urlparse
import
urlparse
from
django.contrib.auth.forms
import
(
AuthenticationForm
,
PasswordResetForm
,
SetPasswordForm
,
...
...
@@ -39,6 +40,7 @@ from django.contrib.auth.forms import UserCreationForm as OrgUserCreationForm
from
django.forms.widgets
import
TextInput
,
HiddenInput
from
django.template
import
Context
from
django.template.loader
import
render_to_string
from
django.utils.html
import
escape
from
django.utils.translation
import
ugettext_lazy
as
_
from
sizefield.widgets
import
FileSizeWidget
from
django.core.urlresolvers
import
reverse_lazy
...
...
@@ -79,6 +81,12 @@ class VmSaveForm(forms.Form):
helper
.
form_tag
=
False
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
):
name
=
forms
.
CharField
(
widget
=
forms
.
TextInput
(
attrs
=
{
...
...
@@ -744,6 +752,20 @@ class VmRenewForm(forms.Form):
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
):
interrupt
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
...
...
@@ -752,6 +774,7 @@ class VmStateChangeForm(forms.Form):
"but don't interrupt any tasks."
))
new_state
=
forms
.
ChoiceField
(
Instance
.
STATUS
,
label
=
_
(
"New status"
))
reset_node
=
forms
.
BooleanField
(
required
=
False
,
label
=
_
(
"Reset node"
))
def
__init__
(
self
,
*
args
,
**
kwargs
):
show_interrupt
=
kwargs
.
pop
(
'show_interrupt'
)
...
...
@@ -769,6 +792,17 @@ class VmStateChangeForm(forms.Form):
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
):
name
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
"Name"
))
size
=
forms
.
CharField
(
...
...
@@ -776,6 +810,12 @@ class VmCreateDiskForm(forms.Form):
help_text
=
_
(
'Size of disk to create in bytes or with units '
'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
):
size_in_bytes
=
self
.
cleaned_data
.
get
(
"size"
)
if
not
size_in_bytes
.
isdigit
()
and
len
(
size_in_bytes
)
>
0
:
...
...
@@ -827,13 +867,42 @@ class VmDiskResizeForm(forms.Form):
helper
.
form_tag
=
False
if
self
.
disk
:
helper
.
layout
=
Layout
(
HTML
(
_
(
"<label>Disk:</label>
%
s"
)
%
self
.
disk
),
HTML
(
_
(
"<label>Disk:</label>
%
s"
)
%
escape
(
self
.
disk
)
),
Field
(
'disk'
),
Field
(
'size'
))
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
):
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
(),
])
@property
...
...
@@ -842,6 +911,18 @@ class VmDownloadDiskForm(forms.Form):
helper
.
form_tag
=
False
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
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
...
...
circle/dashboard/models.py
View file @
bf5b78a0
...
...
@@ -236,6 +236,9 @@ class GroupProfile(AclBase):
help_text
=
_
(
'Unique identifier of the group at the organization.'
))
description
=
TextField
(
blank
=
True
)
def
__unicode__
(
self
):
return
self
.
group
.
name
def
save
(
self
,
*
args
,
**
kwargs
):
if
not
self
.
org_id
:
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 {
}
#dashboard-template-list
a
small
{
max-width
:
50
%
;
max-width
:
45
%
;
float
:
left
;
padding-top
:
2px
;
text-overflow
:
ellipsis
;
...
...
@@ -974,6 +974,10 @@ textarea[name="new_members"] {
color
:
orange
;
}
#vm-info-pane
{
margin-bottom
:
20px
;
}
.node-list-table
tbody
>
tr
>
td
,
.node-list-table
thead
>
tr
>
th
{
vertical-align
:
middle
;
}
...
...
@@ -996,10 +1000,19 @@ textarea[name="new_members"] {
max-width
:
100%
;
}
#vm-list-table
tbody
td
:nth-child
(
3
)
{
#vm-list-table
td
.state
,
#vm-list-table
td
.memory
{
white-space
:
nowrap
;
}
#vm-list-table
td
{
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 () {
$
(
this
).
removeClass
(
"btn-default"
).
addClass
(
"btn-primary"
);
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
)
{
...
...
@@ -445,7 +456,7 @@ function generateNodeHTML(name, icon, _status, url, is_last) {
function
generateNodeTagHTML
(
name
,
icon
,
_status
,
label
,
url
)
{
return
'<a href="'
+
url
+
'" class="label '
+
label
+
'" >'
+
'<i class="'
+
icon
+
'" title="'
+
_status
+
'"></i> '
+
name
+
'<i class="
fa
'
+
icon
+
'" title="'
+
_status
+
'"></i> '
+
name
+
'</a> '
;
}
...
...
@@ -618,7 +629,7 @@ function addModalConfirmation(func, data) {
}
function
clientInstalledAction
(
location
)
{
setCookie
(
'downloaded_client'
,
true
,
365
*
24
*
60
*
60
,
"/"
);
setCookie
(
'downloaded_client'
,
true
,
365
*
24
*
60
*
60
*
1000
,
"/"
);
window
.
location
.
href
=
location
;
$
(
'#confirmation-modal'
).
modal
(
"hide"
);
}
...
...
circle/dashboard/static/dashboard/vm-common.js
View file @
bf5b78a0
...
...
@@ -16,15 +16,6 @@ $(function() {
$
(
'#confirmation-modal'
).
on
(
'hidden.bs.modal'
,
function
()
{
$
(
'#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'
);
}
});
...
...
@@ -51,7 +42,8 @@ $(function() {
if
(
data
.
success
)
{
$
(
'a[href="#activity"]'
).
trigger
(
"click"
);
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 */
...
...
circle/dashboard/static/dashboard/vm-details.js
View file @
bf5b78a0
var
show_all
=
false
;
var
in_progress
=
false
;
var
activity_hash
=
5
;
var
reload_vm_detail
=
false
;
$
(
function
()
{
/* do we need to check for new activities */
...
...
@@ -404,6 +405,7 @@ function checkNewActivity(runs) {
);
}
else
{
in_progress
=
false
;
if
(
reload_vm_detail
)
location
.
reload
();
}
$
(
'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 sizefieldtags %}
<i
class=
"fa {% if d.is_downloading %}fa-refresh fa-spin{% else %}fa-file{% if d.failed %}"
style=
"color: #d9534f;{% endif %}{% endif %}"
></i>
{{ d.name }} (#{{ d.id }}) -
{% if not d.is_downloading %}
{% if not d.failed %}
{% if d.size %}{{ d.size|filesize }}{% endif %}
{% else %}
<div
class=
"label label-danger"
{%
if
user
.
is_superuser
%}
title=
"{{ d.get_latest_activity_result }}"
{%
endif
%}
>
{% trans "failed" %}
</div>
{% endif %}
{% else %}
<span
class=
"disk-list-disk-percentage"
data-disk-pk=
"{{ d.pk }}"
>
{{ d.get_download_percentage }}
</span>
%{% endif %}
{% if is_owner != False %}
<a
href=
"{% url "
dashboard
.
views
.
disk-remove
"
pk=
d.pk
%}?
next=
{{
request
.
path
}}"
data-disk-pk=
"{{ d.pk }}"
class=
"btn btn-xs btn-danger pull-right disk-remove"
{%
if
not
long_remove
%}
title=
"{% trans "
Remove
"
%}"{%
endif
%}
>
<i
class=
"fa fa-times"
></i>
{% if long_remove %} {% trans "Remove" %}{% endif %}
<i
class=
"fa fa-file"
></i>
{{ d.name }} (#{{ d.id }}) - {{ d.size|filesize }}
{% if op.remove_disk %}
<span
class=
"operation-wrapper"
>
<a
href=
"{{ op.remove_disk.get_url }}?disk={{d.pk}}"
class=
"btn btn-xs btn-{{ op.remove_disk.effect}} pull-right operation disk-remove-btn
{% if op.resize_disk.disabled %}disabled{% endif %}"
>
<i
class=
"fa fa-{{ op.remove_disk.icon }}"
></i>
{% trans "Remove" %}
</a>
{% if op.resize_disk %}
</span>
{% endif %}
{% if op.resize_disk %}
<span
class=
"operation-wrapper"
>
<a
href=
"{{ op.resize_disk.get_url }}?disk={{d.pk}}"
class=
"btn btn-xs btn-warning pull-right operation"
>
<i
class=
"fa fa-arrows-alt"
></i>
{% trans "Resize" %}
<a
href=
"{{ op.resize_disk.get_url }}?disk={{d.pk}}"
class=
"btn btn-xs btn-{{ op.resize_disk.effect }} pull-right operation disk-resize-btn
{% if op.resize_disk.disabled %}disabled{% endif %}"
>
<i
class=
"fa fa-{{ op.resize_disk.icon }}"
></i>
{% trans "Resize" %}
</a>
</span>
{% endif %}
{% endif %}
<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" %}
{% load i18n %}
{% load sizefieldtags %}
{% load crispy_forms_tags %}
{% block formfields %}
...
...
@@ -11,20 +12,20 @@
<label
for=
"migrate-to-none"
>
<strong>
{% trans "Reschedule" %}
</strong>
</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"
>
{% trans "This option will reschedule each virtual machine to the optimal node." %}
</span>
<div
style=
"clear: both;"
></div>
</div>
</li>
{% for n in
nodes
%}
{% for n in
form.fields.to_node.queryset.all
%}
<li
class=
"panel panel-default mass-migrate-node"
>
<div
class=
"panel-body"
>
<label
for=
"migrate-to-{{n.pk}}"
>
<strong>
{{ n }}
</strong>
</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 "RAM usage" %}: {{ n.byte_ram_usage|filesize }}/{{ n.ram_size|filesize }}
</span>
<div
style=
"clear: both;"
></div>
...
...
@@ -32,5 +33,6 @@
</li>
{% endfor %}
</ul>
{{ form.live_migration|as_crispy_field }}
<hr
/>
{% endblock %}
circle/dashboard/templates/dashboard/_vm-migrate.html
View file @
bf5b78a0
{% extends "dashboard/operate.html" %}
{% load i18n %}
{% load sizefieldtags %}
{% load crispy_forms_tags %}
{% block question %}
<p>
...
...
@@ -13,24 +14,27 @@ Choose a compute node to migrate {{obj}} to.
{% block formfields %}
<ul
id=
"vm-migrate-node-list"
class=
"list-unstyled"
>
{% with current=object.node.pk
selected=object.select_node
.pk %}
{% for n in
nodes
%}
{% with current=object.node.pk
recommended=form.fields.to_node.initial
.pk %}
{% for n in
form.fields.to_node.queryset.all
%}
<li
class=
"panel panel-default"
><div
class=
"panel-body"
>
<label
for=
"migrate-to-{{n.pk}}"
>
<strong>
{{ n }}
</strong>
<div
class=
"label label-primary"
>
<i
class=
"fa {{n.get_status_icon}}"
></i>
{{n.get_status_display}}
</div>
<div
class=
"label label-primary"
>
<i
class=
"fa {{n.get_status_icon}}"
></i>
{{n.get_status_display}}
</div>
{% if current == n.pk %}
<div
class=
"label label-info"
>
{% trans "current" %}
</div>
{% endif %}
{% if
select
ed == n.pk %}
<div
class=
"label label-success"
>
{% trans "recommended" %}
</div>
{% endif %}
{% if
recommend
ed == n.pk %}
<div
class=
"label label-success"
>
{% trans "recommended" %}
</div>
{% endif %}
</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;"
{%
if
current =
=
n
.
pk
%}
disabled=
"disabled"
{%
endif
%}
{%
if
selected =
=
n
.
pk
%}
checked=
"checked"
{%
endif
%}
/>
{%
if
recommended =
=
n
.
pk
%}
checked=
"checked"
{%
endif
%}
/>
<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></
li>
</li>
{% endfor %}
{% endwith %}
</ul>
{{ form.live_migration|as_crispy_field }}
{% endblock %}
circle/dashboard/templates/dashboard/base.html
View file @
bf5b78a0
...
...
@@ -13,7 +13,7 @@
{% block navbar-brand %}
<a
class=
"navbar-brand"
href=
"{% url "
dashboard
.
index
"
%}"
style=
"padding: 10px 15px;"
>
<img
src=
"{{ STATIC_URL}}dashboard/img/logo.png"
style=
"height: 25px;"
/>
{% include "branding.html" %}
</a>
{% endblock %}
...
...
circle/dashboard/templates/dashboard/group-detail.html
View file @
bf5b78a0
...
...
@@ -52,6 +52,36 @@
<hr
/>
<h3>
{% trans "Available objects for this group" %}
</h3>
<ul
class=
"dashboard-profile-vm-list fa-ul"
>
{% for i in vm_objects %}
<li>
<a
href=
"{{ i.get_absolute_url }}"
>
<i
class=
"fa fa-li {{ i.get_status_icon }}"
></i>
{{ i }}
</a>
</li>
{% endfor %}
{% for t in template_objects %}
<li>
<a
href=
"{{ t.get_absolute_url }}"
>
<i
class=
"fa fa-li fa-puzzle-piece"
></i>
{{ t }}
</a>
</li>
{% endfor %}
{% for g in group_objects %}
<li>
<a
href=
"{{ g.get_absolute_url }}"
>
<i
class=
"fa fa-li fa-users"
></i>
{{ g }}
</a>
</li>
{% endfor %}
</ul>
<hr
/>
<h3>
{% trans "User list"|capfirst %}
{% if perms.auth.add_user %}
<a
href=
"{% url "
dashboard
.
views
.
create-user
"
group
.
pk
%}"
class=
"btn btn-success pull-right"
>
{% trans "Create user" %}
</a>
...
...
circle/dashboard/templates/dashboard/index-nodes.html
View file @
bf5b78a0
{% load i18n %}
<div
class=
"panel panel-default"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<div
class=
"pull-right toolbar"
>
<div
class=
"btn-group"
>
...
...
@@ -7,9 +7,10 @@
data-container=
"body"
><i
class=
"fa fa-dashboard"
></i></a>
<a
href=
"#index-list-view"
data-index-box=
"node"
class=
"btn btn-default btn-xs disabled"
data-container=
"body"
><i
class=
"fa fa-list"
></i></a>
</div>
<span
class=
"btn btn-default btn-xs infobtn"
title=
"{% trans "
List
of
compute
nodes
,
also
called
worker
nodes
or
hypervisors
,
which
run
the
virtual
machines
."
%}"
><i
class=
"fa fa-info-circle"
></i></span>
<span
class=
"btn btn-default btn-xs infobtn"
title=
"{% trans "
List
of
compute
nodes
,
also
called
worker
nodes
or
hypervisors
,
which
run
the
virtual
machines
."
%}"
>
<i
class=
"fa fa-info-circle"
></i>
</span>
</div>
<h3
class=
"no-margin"
>
<i
class=
"fa fa-sitemap"
></i>
{% trans "Nodes" %}
...
...
@@ -28,12 +29,40 @@
</a>
{% endfor %}
</div>
</div>
<!-- #node-list-view -->
<div
class=
"panel-body"
id=
"node-graph-view"
style=
"display: none; min-height: 204px;"
>
<p
class=
"pull-right"
>
<input
class=
"knob"
data-fgColor=
"chartreuse"
data-thickness=
".4"
data-width=
"60"
data-height=
"60"
data-readOnly=
"true"
value=
"{% widthratio node_num.running sum_node_num 100 %}"
>
</p>
<p>
<span
class=
"big"
>
<big>
{{ node_num.running }}
</big>
running
</span>
+
<big>
{{ node_num.missing }}
</big>
missing +
<br><big>
{{ node_num.disabled }}
</big>
disabled +
<big>
{{ node_num.offline }}
</big>
offline
</p>
<ul
class=
"list-inline"
id=
"dashboard-node-taglist"
>
{% for i in nodes %}
<a
href=
"{{ i.get_absolute_url }}"
class=
"label {{i.get_status_label}}"
>
<i
class=
"fa {{ i.get_status_icon }}"
title=
"{{ i.get_status_display }}"
></i>
{{ i.name }}
</a>
{% endfor %}
</ul>
<div
class=
"clearfix"
></div>
</div>
<div
href=
"#"
class=
"list-group-item list-group-footer"
>
<div
class=
"row"
>
<div
class=
"col-sm-6 col-xs-6 input-group input-group-sm"
>
<input
id=
"dashboard-node-search-input"
type=
"text"
class=
"form-control"
placeholder=
"{% trans "
Search
..."
%}"
/>
<input
id=
"dashboard-node-search-input"
type=
"text"
class=
"form-control"
placeholder=
"{% trans "
Search
..."
%}"
/>
<div
class=
"input-group-btn"
>
<button
type=
"submit"
class=
"form-control btn btn-primary"
title=
"search"
><i
class=
"fa fa-search"
></i></button>
<button
type=
"submit"
class=
"btn btn-primary"
title=
"{% trans "
Search
"
%}"
data-container=
"body"
>
<i
class=
"fa fa-search"
></i>
</button>
</div>
</div>
<div
class=
"col-sm-6 text-right"
>
...
...
@@ -45,33 +74,10 @@
{% trans "list" %}
{% endif %}
</a>
<a
class=
"btn btn-success btn-xs node-create"
href=
"{% url "
dashboard
.
views
.
node-create
"
%}"
><i
class=
"fa fa-plus-circle"
></i>
{% trans "new" %}
</a
>
</div>
</div
>
<a
class=
"btn btn-success btn-xs node-create"
href=
"{% url "
dashboard
.
views
.
node-create
"
%}"
>
<i
class=
"fa fa-plus-circle"
></i>
{% trans "new" %}
</a
>
</div>
</div>
<div
class=
"panel-body"
id=
"node-graph-view"
style=
"display: none"
>
<p
class=
"pull-right"
>
<input
class=
"knob"
data-fgColor=
"chartreuse"
data-thickness=
".4"
data-width=
"60"
data-height=
"60"
data-readOnly=
"true"
value=
"{% widthratio node_num.running sum_node_num 100 %}"
></p>
<p><span
class=
"big"
><big>
{{ node_num.running }}
</big>
running
</span>
+
<big>
{{ node_num.missing }}
</big>
missing +
<br><big>
{{ node_num.disabled }}
</big>
disabled +
<big>
{{ node_num.offline }}
</big>
offline
</p>
<ul
class=
"list-inline"
id=
"dashboard-node-taglist"
>
{% for i in nodes %}
<a
href=
"{{ i.get_absolute_url }}"
class=
"label {{i.get_status_label}}"
>
<i
class=
"fa {{ i.get_status_icon }}"
title=
"{{ i.get_status_display }}"
></i>
{{ i.name }}
</a>
{% endfor %}
</ul>