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
986782b5
authored
Sep 03, 2014
by
Őry Máté
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into feature-template-list
Conflicts: circle/dashboard/views.py
parents
6ad5c00e
0963fa06
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
34 changed files
with
654 additions
and
93 deletions
+654
-93
circle/circle/settings/production.py
+3
-0
circle/dashboard/admin.py
+6
-2
circle/dashboard/forms.py
+27
-2
circle/dashboard/migrations/0015_auto__add_connectcommand.py
+0
-0
circle/dashboard/migrations/0016_auto__del_field_connectcommand_application__add_field_connectcommand_n.py
+0
-0
circle/dashboard/models.py
+40
-0
circle/dashboard/static/dashboard/dashboard.css
+9
-3
circle/dashboard/static/dashboard/vm-details.js
+10
-9
circle/dashboard/tables.py
+38
-1
circle/dashboard/templates/dashboard/connect-command-create.html
+44
-0
circle/dashboard/templates/dashboard/connect-command-edit.html
+44
-0
circle/dashboard/templates/dashboard/connect-command-list/column-command-actions.html
+7
-0
circle/dashboard/templates/dashboard/group-create.html
+6
-0
circle/dashboard/templates/dashboard/profile_form.html
+16
-0
circle/dashboard/templates/dashboard/vm-detail.html
+19
-5
circle/dashboard/templates/dashboard/vm-detail/home.html
+7
-1
circle/dashboard/urls.py
+11
-0
circle/dashboard/validators.py
+26
-0
circle/dashboard/views.py
+100
-12
circle/fabfile.py
+2
-2
circle/firewall/models.py
+35
-6
circle/manager/mancelery.py
+0
-31
circle/manager/moncelery.py
+66
-0
circle/manager/slowcelery.py
+50
-0
circle/storage/models.py
+1
-1
circle/vm/operations.py
+10
-4
circle/vm/tasks/agent_tasks.py
+5
-0
circle/vm/tasks/local_agent_tasks.py
+25
-6
miscellaneous/manager.conf
+16
-0
miscellaneous/mancelery.conf
+3
-6
miscellaneous/moncelery.conf
+14
-0
miscellaneous/portal-uwsgi.conf
+0
-1
miscellaneous/portal.conf
+0
-1
miscellaneous/slowcelery.conf
+14
-0
No files found.
circle/circle/settings/production.py
View file @
986782b5
...
...
@@ -20,9 +20,12 @@
from
os
import
environ
from
sys
import
argv
from
base
import
*
# noqa
if
'runserver'
in
argv
:
SECURE_PROXY_SSL_HEADER
=
(
'HTTP_X_FORWARDED_PROTOCOL'
,
'https'
)
########## HOST CONFIGURATION
# See: https://docs.djangoproject.com/en/1.5/releases/1.5/
...
...
circle/dashboard/admin.py
View file @
986782b5
...
...
@@ -21,18 +21,22 @@ from django import contrib
from
django.contrib.auth.admin
import
UserAdmin
,
GroupAdmin
from
django.contrib.auth.models
import
User
,
Group
from
dashboard.models
import
Profile
,
GroupProfile
from
dashboard.models
import
Profile
,
GroupProfile
,
ConnectCommand
class
ProfileInline
(
contrib
.
admin
.
TabularInline
):
model
=
Profile
class
CommandInline
(
contrib
.
admin
.
TabularInline
):
model
=
ConnectCommand
class
GroupProfileInline
(
contrib
.
admin
.
TabularInline
):
model
=
GroupProfile
UserAdmin
.
inlines
=
(
ProfileInline
,
)
UserAdmin
.
inlines
=
(
ProfileInline
,
CommandInline
,
)
GroupAdmin
.
inlines
=
(
GroupProfileInline
,
)
contrib
.
admin
.
site
.
unregister
(
User
)
...
...
circle/dashboard/forms.py
View file @
986782b5
...
...
@@ -54,7 +54,9 @@ from .models import Profile, GroupProfile
from
circle.settings.base
import
LANGUAGES
,
MAX_NODE_RAM
from
django.utils.translation
import
string_concat
from
.virtvalidator
import
domain_validator
from
.validators
import
domain_validator
from
dashboard.models
import
ConnectCommand
LANGUAGES_WITH_CODE
=
((
l
[
0
],
string_concat
(
l
[
1
],
" ("
,
l
[
0
],
")"
))
for
l
in
LANGUAGES
)
...
...
@@ -176,7 +178,14 @@ class GroupCreateForm(forms.ModelForm):
self
.
fields
[
'org_id'
]
=
forms
.
ChoiceField
(
# TRANSLATORS: directory like in LDAP
choices
=
choices
,
required
=
False
,
label
=
_
(
'Directory identifier'
))
if
not
new_groups
:
if
new_groups
:
self
.
fields
[
'org_id'
]
.
help_text
=
_
(
"If you select an item here, the members of this directory "
"group will be automatically added to the group at the time "
"they log in. Please note that other users (those with "
"permissions like yours) may also automatically become a "
"group co-owner)."
)
else
:
self
.
fields
[
'org_id'
]
.
widget
=
HiddenInput
()
def
save
(
self
,
commit
=
True
):
...
...
@@ -1057,6 +1066,22 @@ class UserKeyForm(forms.ModelForm):
return
super
(
UserKeyForm
,
self
)
.
clean
()
class
ConnectCommandForm
(
forms
.
ModelForm
):
class
Meta
:
fields
=
(
'name'
,
'access_method'
,
'template'
)
model
=
ConnectCommand
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
user
=
kwargs
.
pop
(
"user"
)
super
(
ConnectCommandForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
def
clean
(
self
):
if
self
.
user
:
self
.
instance
.
user
=
self
.
user
return
super
(
ConnectCommandForm
,
self
)
.
clean
()
class
TraitsForm
(
forms
.
ModelForm
):
class
Meta
:
...
...
circle/dashboard/migrations/0015_auto__add_connectcommand.py
0 → 100644
View file @
986782b5
This diff is collapsed.
Click to expand it.
circle/dashboard/migrations/0016_auto__del_field_connectcommand_application__add_field_connectcommand_n.py
0 → 100644
View file @
986782b5
This diff is collapsed.
Click to expand it.
circle/dashboard/models.py
View file @
986782b5
...
...
@@ -46,8 +46,10 @@ from acl.models import AclBase
from
common.models
import
HumanReadableObject
,
create_readable
,
Encoder
from
vm.tasks.agent_tasks
import
add_keys
,
del_keys
from
vm.models.instance
import
ACCESS_METHODS
from
.store_api
import
Store
,
NoStoreException
,
NotOkException
from
.validators
import
connect_command_template_validator
logger
=
getLogger
(
__name__
)
...
...
@@ -100,6 +102,25 @@ class Notification(TimeStampedModel):
self
.
message_data
=
None
if
value
is
None
else
value
.
to_dict
()
class
ConnectCommand
(
Model
):
user
=
ForeignKey
(
User
,
related_name
=
'command_set'
)
access_method
=
CharField
(
max_length
=
10
,
choices
=
ACCESS_METHODS
,
verbose_name
=
_
(
'access method'
),
help_text
=
_
(
'Type of the remote access method.'
))
name
=
CharField
(
max_length
=
"128"
,
verbose_name
=
_
(
'name'
),
blank
=
False
,
help_text
=
_
(
"Name of your custom command."
))
template
=
CharField
(
blank
=
True
,
null
=
True
,
max_length
=
256
,
verbose_name
=
_
(
'command template'
),
help_text
=
_
(
'Template for connection command string. '
'Available parameters are: '
'username, password, '
'host, port.'
),
validators
=
[
connect_command_template_validator
])
def
__unicode__
(
self
):
return
self
.
template
class
Profile
(
Model
):
user
=
OneToOneField
(
User
)
preferred_language
=
CharField
(
verbose_name
=
_
(
'preferred language'
),
...
...
@@ -129,6 +150,25 @@ class Profile(Model):
default
=
2048
*
1024
*
1024
,
help_text
=
_
(
'Disk quota in mebibytes.'
))
def
get_connect_commands
(
self
,
instance
,
use_ipv6
=
False
):
""" Generate connection command based on template."""
single_command
=
instance
.
get_connect_command
(
use_ipv6
)
if
single_command
:
# can we even connect to that VM
commands
=
self
.
user
.
command_set
.
filter
(
access_method
=
instance
.
access_method
)
if
commands
.
count
()
<
1
:
return
[
single_command
]
else
:
return
[
command
.
template
%
{
'port'
:
instance
.
get_connect_port
(
use_ipv6
=
use_ipv6
),
'host'
:
instance
.
get_connect_host
(
use_ipv6
=
use_ipv6
),
'password'
:
instance
.
pw
,
'username'
:
'cloud'
,
}
for
command
in
commands
]
else
:
return
[]
def
notify
(
self
,
subject
,
template
,
context
=
None
,
valid_until
=
None
,
**
kwargs
):
if
context
is
not
None
:
...
...
circle/dashboard/static/dashboard/dashboard.css
View file @
986782b5
...
...
@@ -654,7 +654,8 @@ textarea[name="list-new-namelist"] {
width
:
130px
;
}
#vm-details-connection-string-copy
{
.vm-details-connection-string-copy
,
#vm-details-pw-show
{
cursor
:
pointer
;
}
...
...
@@ -681,10 +682,9 @@ textarea[name="list-new-namelist"] {
max-width
:
200px
;
}
#
dashboard-vm-details-connect-command
{
.
dashboard-vm-details-connect-command
{
/* for mobile view */
margin-bottom
:
20px
;
}
#store-list-list
{
...
...
@@ -868,6 +868,12 @@ textarea[name="list-new-namelist"] {
padding
:
5px
0px
;
}
#profile-key-list-table
td
:last-child
,
#profile-key-list-table
th
:last-child
,
#profile-command-list-table
td
:last-child
,
#profile-command-list-table
th
:last-child
,
#profile-command-list-table
td
:nth-child
(
2
),
#profile-command-list-table
th
:nth-child
(
2
)
{
text-align
:
center
;
vertical-align
:
middle
;
}
#vm-list-table
.migrating-icon
{
-webkit-animation
:
passing
2s
linear
infinite
;
...
...
circle/dashboard/static/dashboard/vm-details.js
View file @
986782b5
...
...
@@ -105,19 +105,20 @@ $(function() {
$
(
"#vm-details-pw-show"
).
click
(
function
()
{
var
input
=
$
(
this
).
parent
(
"div"
).
children
(
"input"
);
var
eye
=
$
(
this
).
children
(
"#vm-details-pw-eye"
);
var
span
=
$
(
this
);
eye
.
tooltip
(
"destroy"
)
span
.
tooltip
(
"destroy"
)
if
(
eye
.
hasClass
(
"fa-eye"
))
{
eye
.
removeClass
(
"fa-eye"
).
addClass
(
"fa-eye-slash"
);
input
.
prop
(
"type"
,
"text"
);
input
.
focus
();
eye
.
prop
(
"title"
,
"Hide password"
);
input
.
select
();
span
.
prop
(
"title"
,
gettext
(
"Hide password"
)
);
}
else
{
eye
.
removeClass
(
"fa-eye-slash"
).
addClass
(
"fa-eye"
);
input
.
prop
(
"type"
,
"password"
);
eye
.
prop
(
"title"
,
"Show password"
);
span
.
prop
(
"title"
,
gettext
(
"Show password"
)
);
}
eye
.
tooltip
();
span
.
tooltip
();
});
/* change password confirmation */
...
...
@@ -198,7 +199,7 @@ $(function() {
$
(
"#vm-details-h1-name, .vm-details-rename-button"
).
click
(
function
()
{
$
(
"#vm-details-h1-name"
).
hide
();
$
(
"#vm-details-rename"
).
css
(
'display'
,
'inline'
);
$
(
"#vm-details-rename-name"
).
focus
();
$
(
"#vm-details-rename-name"
).
select
();
return
false
;
});
...
...
@@ -206,7 +207,7 @@ $(function() {
$
(
".vm-details-home-edit-name-click"
).
click
(
function
()
{
$
(
".vm-details-home-edit-name-click"
).
hide
();
$
(
"#vm-details-home-rename"
).
show
();
$
(
"input"
,
$
(
"#vm-details-home-rename"
)).
focus
();
$
(
"input"
,
$
(
"#vm-details-home-rename"
)).
select
();
return
false
;
});
...
...
@@ -306,8 +307,8 @@ $(function() {
});
// select connection string
$
(
"
#
vm-details-connection-string-copy"
).
click
(
function
()
{
$
(
"#vm-details-connection-string"
).
focus
();
$
(
"
.
vm-details-connection-string-copy"
).
click
(
function
()
{
$
(
this
).
parent
(
"div"
).
find
(
"input"
).
select
();
});
$
(
"a.operation-password_reset"
).
click
(
function
()
{
...
...
circle/dashboard/tables.py
View file @
986782b5
...
...
@@ -25,6 +25,7 @@ from django_tables2.columns import (TemplateColumn, Column, BooleanColumn,
from
vm.models
import
Node
,
InstanceTemplate
,
Lease
from
django.utils.translation
import
ugettext_lazy
as
_
from
django_sshkey.models
import
UserKey
from
dashboard.models
import
ConnectCommand
class
NodeListTable
(
Table
):
...
...
@@ -249,5 +250,41 @@ class UserKeyListTable(Table):
class
Meta
:
model
=
UserKey
attrs
=
{
'class'
:
(
'table table-bordered table-striped table-hover'
)}
attrs
=
{
'class'
:
(
'table table-bordered table-striped table-hover'
),
'id'
:
"profile-key-list-table"
}
fields
=
(
'name'
,
'fingerprint'
,
'created'
,
'actions'
)
prefix
=
"key-"
empty_text
=
_
(
"You haven't added any public keys yet."
)
class
ConnectCommandListTable
(
Table
):
name
=
LinkColumn
(
'dashboard.views.connect-command-detail'
,
args
=
[
A
(
'pk'
)],
attrs
=
{
'th'
:
{
'data-sort'
:
"string"
}}
)
access_method
=
Column
(
verbose_name
=
_
(
"Access method"
),
attrs
=
{
'th'
:
{
'data-sort'
:
"string"
}}
)
template
=
Column
(
verbose_name
=
_
(
"Template"
),
attrs
=
{
'th'
:
{
'data-sort'
:
"string"
}}
)
actions
=
TemplateColumn
(
verbose_name
=
_
(
"Actions"
),
template_name
=
(
"dashboard/connect-command-list/column-command"
"-actions.html"
),
orderable
=
False
,
)
class
Meta
:
model
=
ConnectCommand
attrs
=
{
'class'
:
(
'table table-bordered table-striped table-hover'
),
'id'
:
"profile-command-list-table"
}
fields
=
(
'name'
,
'access_method'
,
'template'
,
'actions'
)
prefix
=
"cmd-"
empty_text
=
_
(
"You don't have any custom connection commands yet. You can "
"specify commands to be displayed on VM detail pages instead of "
"the defaults."
)
circle/dashboard/templates/dashboard/connect-command-create.html
0 → 100644
View file @
986782b5
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title-page %}{% trans "Create command template" %}{% endblock %}
{% block content %}
<div
class=
"row"
>
<div
class=
"col-md-12"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<a
class=
"pull-right btn btn-default btn-xs"
href=
"{% url "
dashboard
.
views
.
profile-preferences
"
%}"
>
{% trans "Back" %}
</a>
<h3
class=
"no-margin"
><i
class=
"fa fa-code"
></i>
{% trans "Create new command template" %}
</h3>
</div>
<div
class=
"panel-body"
>
<form
method=
"POST"
>
{% csrf_token %}
{{ form.name|as_crispy_field }}
{{ form.access_method|as_crispy_field }}
{{ form.template|as_crispy_field }}
<p
class=
"text-muted"
>
{% trans "Examples" %}
</p>
<p>
<strong>
SSH:
</strong>
<span
class=
"text-muted"
>
sshpass -p %(password)s ssh -o StrictHostKeyChecking=no cloud@%(host)s -p %(port)d
</span>
</p>
<p>
<strong>
RDP:
</strong>
<span
class=
"text-muted"
>
rdesktop %(host)s:%(port)d -u cloud -p %(password)s
</span>
</p>
<input
type=
"submit"
class=
"btn btn-primary"
value=
"{% trans "
Save
"
%}"
>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
circle/dashboard/templates/dashboard/connect-command-edit.html
0 → 100644
View file @
986782b5
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title-page %}{% trans "Edit command template" %}{% endblock %}
{% block content %}
<div
class=
"row"
>
<div
class=
"col-md-12"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<a
class=
"pull-right btn btn-default btn-xs"
href=
"{% url "
dashboard
.
views
.
profile-preferences
"
%}"
>
{% trans "Back" %}
</a>
<h3
class=
"no-margin"
><i
class=
"fa fa-code"
></i>
{% trans "Edit command template" %}
</h3>
</div>
<div
class=
"panel-body"
>
<form
method=
"POST"
>
{% csrf_token %}
{{ form.name|as_crispy_field }}
{{ form.access_method|as_crispy_field }}
{{ form.template|as_crispy_field }}
<p
class=
"text-muted"
>
{% trans "Examples" %}
</p>
<p>
<strong>
SSH:
</strong>
<span
class=
"text-muted"
>
sshpass -p %(password)s ssh -o StrictHostKeyChecking=no cloud@%(host)s -p %(port)d
</span>
</p>
<p>
<strong>
RDP:
</strong>
<span
class=
"text-muted"
>
rdesktop %(host)s:%(port)d -u cloud -p %(password)s
</span>
</p>
<input
type=
"submit"
class=
"btn btn-primary"
value=
"{% trans "
Save
"
%}"
>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
circle/dashboard/templates/dashboard/connect-command-list/column-command-actions.html
0 → 100644
View file @
986782b5
{% load i18n %}
<a
href=
"{% url "
dashboard
.
views
.
connect-command-detail
"
pk=
record.pk%}"
id=
"template-list-edit-button"
class=
"btn btn-default btn-xs"
title=
"{% trans "
Edit
"
%}"
>
<i
class=
"fa fa-edit"
></i>
</a>
<a
data-template-pk=
"{{ record.pk }}"
href=
"{% url "
dashboard
.
views
.
connect-command-delete
"
pk=
record.pk
%}"
class=
"btn btn-danger btn-xs template-delete"
title=
"{% trans "
Delete
"
%}"
>
<i
class=
"fa fa-times"
></i>
</a>
circle/dashboard/templates/dashboard/group-create.html
View file @
986782b5
{% load crispy_forms_tags %}
{% load i18n %}
<p
class=
"text-muted"
>
{% trans "User groups allow sharing templates or other resources with multiple users at once." %}
</p>
<form
method=
"POST"
action=
"{% url "
dashboard
.
views
.
group-create
"
%}"
>
{% csrf_token %}
...
...
circle/dashboard/templates/dashboard/profile_form.html
View file @
986782b5
...
...
@@ -66,4 +66,20 @@
</div>
</div>
<div
class=
"row"
>
<div
class=
"col-md-12"
>
<div
class=
"panel panel-default"
>
<div
class=
"panel-heading"
>
<a
href=
"{% url "
dashboard
.
views
.
connect-command-create
"
%}"
class=
"pull-right btn btn-success btn-xs"
style=
"margin-right: 10px;"
>
<i
class=
"fa fa-plus"
></i>
{% trans "add command template" %}
</a>
<h3
class=
"no-margin"
><i
class=
"fa fa-code"
></i>
{% trans "Command templates" %}
</h3>
</div>
<div
class=
"panel-body"
>
{% render_table connectcommand_table %}
</div>
</div>
</div>
</div>
{% endblock %}
circle/dashboard/templates/dashboard/vm-detail.html
View file @
986782b5
...
...
@@ -98,8 +98,9 @@
<div
class=
"input-group"
>
<input
type=
"text"
id=
"vm-details-pw-input"
class=
"form-control input-sm input-tags"
value=
"{{ instance.pw }}"
spellcheck=
"false"
/>
<span
class=
"input-group-addon input-tags"
id=
"vm-details-pw-show"
>
<i
class=
"fa fa-eye"
id=
"vm-details-pw-eye"
title=
"Show password"
></i>
<span
class=
"input-group-addon input-tags"
id=
"vm-details-pw-show"
title=
"{% trans "
Show
password
"
%}"
data-container=
"body"
>
<i
class=
"fa fa-eye"
id=
"vm-details-pw-eye"
></i>
</span>
</div>
</dd>
...
...
@@ -111,16 +112,29 @@
</div>
</dd>
</dl>
<div
class=
"input-group
"
id=
"
dashboard-vm-details-connect-command"
>
{% for c in connect_commands %}
<div
class=
"input-group
dashboard-vm-details-connect-command"
>
<span
class=
"input-group-addon input-tags"
>
{% trans "Command" %}
</span>
<input
type=
"text"
spellcheck=
"false"
value=
"{% if instance.get_connect_command %}{{ instance.get_connect_command }}{% else %}{% trans "
Connection
is
not
possible
."
%}{%
endif
%}"
value=
"{{ c }}"
id=
"vm-details-connection-string"
class=
"form-control input-tags"
/>
<span
class=
"input-group-addon input-tags vm-details-connection-string-copy"
title=
"{% trans "
Select
all
"
%}"
data-container=
"body"
>
<i
class=
"fa fa-copy"
></i>
</span>
</div>
{% empty %}
<div
class=
"input-group dashboard-vm-details-connect-command"
>
<span
class=
"input-group-addon input-tags"
>
{% trans "Command" %}
</span>
<input
type=
"text"
spellcheck=
"false"
value=
"{% trans "
Connection
is
not
possible
."
%}"
id=
"vm-details-connection-string"
class=
"form-control input-tags"
/>
<span
class=
"input-group-addon input-tags"
id=
"vm-details-connection-string-copy"
>
<i
class=
"fa fa-copy"
title=
"{% trans "
Select
all
"
%}"
></i>
</span>
</div>
{% endfor %}
</div>
<div
class=
"col-md-8"
id=
"vm-detail-pane"
>
<div
class=
"panel panel-default"
id=
"vm-detail-panel"
>
...
...
circle/dashboard/templates/dashboard/vm-detail/home.html
View file @
986782b5
...
...
@@ -94,7 +94,13 @@
<dt>
{% trans "Template" %}:
</dt>
<dd>
{% if instance.template %}
{{ instance.template.name }}
{% if can_link_template %}
<a
href=
"{{ instance.template.get_absolute_url }}"
>
{{ instance.template.name }}
</a>
{% else %}
{{ instance.template.name }}
{% endif %}
{% else %}
-
{% endif %}
...
...
circle/dashboard/urls.py
View file @
986782b5
...
...
@@ -39,6 +39,7 @@ from .views import (
get_vm_screenshot
,
ProfileView
,
toggle_use_gravatar
,
UnsubscribeFormView
,
UserKeyDelete
,
UserKeyDetail
,
UserKeyCreate
,
ConnectCommandDelete
,
ConnectCommandDetail
,
ConnectCommandCreate
,
StoreList
,
store_download
,
store_upload
,
store_get_upload_url
,
StoreRemove
,
store_new_directory
,
store_refresh_toplist
,
VmTraitsUpdate
,
VmRawDataUpdate
,
...
...
@@ -177,6 +178,16 @@ urlpatterns = patterns(
UserKeyCreate
.
as_view
(),
name
=
"dashboard.views.userkey-create"
),
url
(
r'^conncmd/delete/(?P<pk>\d+)/$'
,
ConnectCommandDelete
.
as_view
(),
name
=
"dashboard.views.connect-command-delete"
),
url
(
r'^conncmd/(?P<pk>\d+)/$'
,
ConnectCommandDetail
.
as_view
(),
name
=
"dashboard.views.connect-command-detail"
),
url
(
r'^conncmd/create/$'
,
ConnectCommandCreate
.
as_view
(),
name
=
"dashboard.views.connect-command-create"
),
url
(
r'^autocomplete/'
,
include
(
'autocomplete_light.urls'
)),
url
(
r"^store/list/$"
,
StoreList
.
as_view
(),
...
...
circle/dashboard/v
irtvalidator
.py
→
circle/dashboard/v
alidators
.py
View file @
986782b5
from
django.core.exceptions
import
ValidationError
from
django.utils.translation
import
ugettext_lazy
as
_
from
lxml
import
etree
as
ET
import
logging
...
...
@@ -29,3 +31,27 @@ def domain_validator(value):
relaxng
.
assertValid
(
parsed_xml
)
except
Exception
as
e
:
raise
ValidationError
(
e
.
message
)
def
connect_command_template_validator
(
value
):
"""Validate value as a connect command template.
>>> try: connect_command_template_validator("
%(host)
s")
... except ValidationError as e: print e
...
>>> connect_command_template_validator("
%(host)
s")
>>> try: connect_command_template_validator("
%(host)
s
%
s")
... except ValidationError as e: print e
...
[u'Invalid template string.']
"""
try
:
value
%
{
'username'
:
"uname"
,
'password'
:
"pw"
,
'host'
:
"111.111.111.111"
,
'port'
:
12345
,
}
except
(
KeyError
,
TypeError
,
ValueError
):
raise
ValidationError
(
_
(
"Invalid template string."
))
circle/dashboard/views.py
View file @
986782b5
...
...
@@ -71,12 +71,12 @@ from .forms import (
CirclePasswordChangeForm
,
VmCreateDiskForm
,
VmDownloadDiskForm
,
TraitsForm
,
RawDataForm
,
GroupPermissionForm
,
AclUserAddForm
,
VmResourcesForm
,
VmAddInterfaceForm
,
VmListSearchForm
,
TemplateListSearchForm
,
TemplateListSearchForm
,
ConnectCommandForm
)
from
.tables
import
(
NodeListTable
,
TemplateListTable
,
LeaseListTable
,
GroupListTable
,
UserKeyListTable
GroupListTable
,
UserKeyListTable
,
ConnectCommandListTable
,
)
from
common.models
import
(
HumanReadableObject
,
HumanReadableException
,
fetch_human_exception
,
...
...
@@ -88,7 +88,8 @@ from vm.models import (
)
from
storage.models
import
Disk
from
firewall.models
import
Vlan
,
Host
,
Rule
from
.models
import
Favourite
,
Profile
,
GroupProfile
,
FutureMember
from
.models
import
(
Favourite
,
Profile
,
GroupProfile
,
FutureMember
,
ConnectCommand
)
from
.store_api
import
Store
,
NoStoreException
,
NotOkException
...
...
@@ -366,6 +367,7 @@ class VmDetailView(CheckedDetailView):
kwargs
=
{
'pk'
:
self
.
object
.
pk
}),
'ops'
:
ops
,
'op'
:
{
i
.
op
:
i
for
i
in
ops
},
'connect_commands'
:
user
.
profile
.
get_connect_commands
(
instance
)
})
# activity data
...
...
@@ -396,7 +398,7 @@ class VmDetailView(CheckedDetailView):
# resources forms
can_edit
=
(
instance
in
Instance
.
get_objects_with_level
(
"owner"
,
user
)
instance
.
has_level
(
user
,
"owner"
)
and
self
.
request
.
user
.
has_perm
(
"vm.change_resources"
))
context
[
'resources_form'
]
=
VmResourcesForm
(
can_edit
=
can_edit
,
instance
=
instance
)
...
...
@@ -409,6 +411,11 @@ class VmDetailView(CheckedDetailView):
context
[
'can_change_resources'
]
=
self
.
request
.
user
.
has_perm
(
"vm.change_resources"
)
# can link template
context
[
'can_link_template'
]
=
(
instance
.
template
and
instance
.
template
.
has_level
(
user
,
"operator"
)
)
return
context
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
...
...
@@ -1538,7 +1545,7 @@ class TemplateChoose(LoginRequiredMixin, TemplateView):
self
.
request
.
user
)
context
.
update
({
'box_title'
:
_
(
'Choose template'
),
'ajax_title'
:
Fals
e
,
'ajax_title'
:
Tru
e
,
'template'
:
"dashboard/_template-choose.html"
,
'templates'
:
templates
.
all
(),
})
...
...
@@ -2093,7 +2100,7 @@ class VmCreate(LoginRequiredMixin, TemplateView):
context
.
update
({
'template'
:
'dashboard/_vm-create-2.html'
,
'box_title'
:
_
(
'Customize VM'
),
'ajax_title'
:
Fals
e
,
'ajax_title'
:
Tru
e
,
'vm_create_form'
:
form
,
'template_o'
:
templates
.
get
(
pk
=
template
),
})
...
...
@@ -2101,7 +2108,7 @@ class VmCreate(LoginRequiredMixin, TemplateView):
context
.
update
({
'template'
:
'dashboard/_vm-create-1.html'
,
'box_title'
:
_
(
'Create a VM'
),
'ajax_title'
:
Fals
e
,
'ajax_title'
:
Tru
e
,
'templates'
:
templates
.
all
(),
})
return
self
.
render_to_response
(
context
)
...
...
@@ -2291,9 +2298,9 @@ class GroupCreate(GroupCodeMixin, LoginRequiredMixin, TemplateView):
context
=
self
.
get_context_data
(
**
kwargs
)
context
.
update
({
'template'
:
'dashboard/group-create.html'
,
'box_title'
:
'Create a Group'
,
'box_title'
:
_
(
'Create a Group'
)
,
'form'
:
form
,
'ajax_title'
:
True
,
})
return
self
.
render_to_response
(
context
)
...
...
@@ -2961,11 +2968,16 @@ class MyPreferencesView(UpdateView):
user
=
self
.
request
.
user
),
'change_language'
:
MyProfileForm
(
instance
=
self
.
get_object
()),
}
table
=
UserKeyListTable
(
key_
table
=
UserKeyListTable
(
UserKey
.
objects
.
filter
(
user
=
self
.
request
.
user
),
request
=
self
.
request
)
table
.
page
=
None
context
[
'userkey_table'
]
=
table
key_table
.
page
=
None
context
[
'userkey_table'
]
=
key_table
cmd_table
=
ConnectCommandListTable
(
self
.
request
.
user
.
command_set
.
all
(),
request
=
self
.
request
)
cmd_table
.
page
=
None
context
[
'connectcommand_table'
]
=
cmd_table
return
context
def
get_object
(
self
,
queryset
=
None
):
...
...
@@ -3370,6 +3382,82 @@ class UserKeyCreate(LoginRequiredMixin, SuccessMessageMixin, CreateView):
return
kwargs
class
ConnectCommandDetail
(
LoginRequiredMixin
,
SuccessMessageMixin
,
UpdateView
):
model
=
ConnectCommand
template_name
=
"dashboard/connect-command-edit.html"
form_class
=
ConnectCommandForm
success_message
=
_
(
"Successfully modified command template."
)
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
object
=
self
.
get_object
()
if
object
.
user
!=
request
.
user
:
raise
PermissionDenied
()
return
super
(
ConnectCommandDetail
,
self
)
.
get
(
request
,
*
args
,
**
kwargs
)
def
get_success_url
(
self
):
return
reverse_lazy
(
"dashboard.views.connect-command-detail"
,
kwargs
=
self
.
kwargs
)
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
object
=
self
.
get_object
()
if
object
.
user
!=
request
.
user
:
raise
PermissionDenied
()
return
super
(
ConnectCommandDetail
,
self
)
.
post
(
request
,
args
,
kwargs
)
def
get_form_kwargs
(
self
):
kwargs
=
super
(
ConnectCommandDetail
,
self
)
.
get_form_kwargs
()
kwargs
[
'user'
]
=
self
.
request
.
user
return
kwargs
class
ConnectCommandDelete
(
LoginRequiredMixin
,
DeleteView
):
model
=
ConnectCommand
def
get_success_url
(
self
):
return
reverse
(
"dashboard.views.profile-preferences"
)
def
get_template_names
(
self
):
if
self
.
request
.
is_ajax
():
return
[
'dashboard/confirm/ajax-delete.html'
]
else
:
return
[
'dashboard/confirm/base-delete.html'
]
def
delete
(
self
,
request
,
*
args
,
**
kwargs
):
object
=
self
.
get_object
()
if
object
.
user
!=
request
.
user
:
raise
PermissionDenied
()
object
.
delete
()
success_url
=
self
.
get_success_url
()
success_message
=
_
(
"Command template successfully deleted."
)
if
request
.
is_ajax
():
return
HttpResponse
(
json
.
dumps
({
'message'
:
success_message
}),
content_type
=
"application/json"
,
)
else
:
messages
.
success
(
request
,
success_message
)
return
HttpResponseRedirect
(
success_url
)
class
ConnectCommandCreate
(
LoginRequiredMixin
,
SuccessMessageMixin
,
CreateView
):
model
=
ConnectCommand
form_class
=
ConnectCommandForm
template_name
=
"dashboard/connect-command-create.html"
success_message
=
_
(
"Successfully created a new command template."
)
def
get_success_url
(
self
):
return
reverse_lazy
(
"dashboard.views.profile-preferences"
)
def
get_form_kwargs
(
self
):
kwargs
=
super
(
ConnectCommandCreate
,
self
)
.
get_form_kwargs
()
kwargs
[
'user'
]
=
self
.
request
.
user
return
kwargs