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
2a3a6018
authored
Dec 01, 2021
by
Szeberényi Imre
1
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of
https://git.ik.bme.hu/circle/cloud
parents
6a7bb6c1
6f4187b1
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
369 additions
and
53 deletions
+369
-53
circle/dashboard/forms.py
+41
-1
circle/dashboard/models.py
+64
-11
circle/dashboard/static/dashboard/dashboard.js
+1
-1
circle/dashboard/static/dashboard/dashboard.less
+4
-0
circle/dashboard/store_api.py
+24
-7
circle/dashboard/templates/dashboard/group-detail.html
+6
-0
circle/dashboard/templates/dashboard/group-export.html
+11
-0
circle/dashboard/templates/dashboard/group-import.html
+11
-0
circle/dashboard/templates/dashboard/group-list/column-actions.html
+0
-0
circle/dashboard/templates/dashboard/index-groups.html
+2
-1
circle/dashboard/urls.py
+12
-3
circle/dashboard/views/group.py
+178
-13
circle/storage/models.py
+5
-6
circle/storage/tasks/storage_tasks.py
+2
-2
circle/templates/info/resize.html
+3
-2
circle/vm/operations.py
+5
-6
No files found.
circle/dashboard/forms.py
View file @
2a3a6018
...
@@ -255,6 +255,44 @@ class GroupCreateForm(NoFormTagMixin, forms.ModelForm):
...
@@ -255,6 +255,44 @@ class GroupCreateForm(NoFormTagMixin, forms.ModelForm):
fields
=
(
'name'
,)
fields
=
(
'name'
,)
class
GroupImportForm
(
NoFormTagMixin
,
forms
.
Form
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
user
=
kwargs
.
pop
(
"user"
)
super
(
GroupImportForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
exported_group_paths
=
Store
(
self
.
user
)
.
get_files_with_exts
([
"group"
])
exported_group_names
=
[
os
.
path
.
basename
(
item
)
for
item
in
exported_group_paths
]
self
.
choices
=
zip
(
exported_group_paths
,
exported_group_names
)
self
.
fields
[
"group_path"
]
=
forms
.
ChoiceField
(
label
=
_
(
"Group to import"
),
choices
=
self
.
choices
)
@property
def
helper
(
self
):
helper
=
super
(
GroupImportForm
,
self
)
.
helper
helper
.
add_input
(
Submit
(
"submit"
,
_
(
"Import"
)))
return
helper
class
GroupExportForm
(
NoFormTagMixin
,
forms
.
Form
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
default
=
kwargs
.
pop
(
"group_name"
)
super
(
GroupExportForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
fields
[
"exported_name"
]
=
forms
.
CharField
(
max_length
=
100
,
label
=
_
(
'Filename'
),
initial
=
default
)
@property
def
helper
(
self
):
helper
=
super
(
GroupExportForm
,
self
)
.
helper
helper
.
add_input
(
Submit
(
"submit"
,
_
(
"Export"
)))
return
helper
class
GroupProfileUpdateForm
(
NoFormTagMixin
,
forms
.
ModelForm
):
class
GroupProfileUpdateForm
(
NoFormTagMixin
,
forms
.
ModelForm
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
...
@@ -935,7 +973,9 @@ class VmImportDiskForm(OperationForm):
...
@@ -935,7 +973,9 @@ class VmImportDiskForm(OperationForm):
self
.
user
=
kwargs
.
pop
(
'user'
)
self
.
user
=
kwargs
.
pop
(
'user'
)
super
(
VmImportDiskForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
super
(
VmImportDiskForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
disk_paths
=
Store
(
self
.
user
)
.
get_disk_images
()
disk_paths
=
Store
(
self
.
user
)
.
get_files_with_exts
(
[
f
[
0
]
for
f
in
Disk
.
EXPORT_FORMATS
]
)
disk_filenames
=
[
os
.
path
.
basename
(
item
)
for
item
in
disk_paths
]
disk_filenames
=
[
os
.
path
.
basename
(
item
)
for
item
in
disk_paths
]
self
.
choices
=
zip
(
disk_paths
,
disk_filenames
)
self
.
choices
=
zip
(
disk_paths
,
disk_filenames
)
...
...
circle/dashboard/models.py
View file @
2a3a6018
...
@@ -17,14 +17,15 @@
...
@@ -17,14 +17,15 @@
from
__future__
import
absolute_import
from
__future__
import
absolute_import
from
datetime
import
timedelta
import
json
from
itertools
import
chain
from
hashlib
import
md5
from
hashlib
import
md5
from
logging
import
getLogger
from
logging
import
getLogger
from
datetime
import
timedelta
from
django.conf
import
settings
from
django.conf
import
settings
from
django.contrib.auth.models
import
User
,
Group
from
django.contrib.auth.models
import
User
,
Group
,
Permission
from
django.contrib.auth.signals
import
user_logged_in
from
django.contrib.auth.signals
import
user_logged_in
from
django.core.exceptions
import
ObjectDoesNotExist
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
django.db.models
import
(
from
django.db.models
import
(
Model
,
ForeignKey
,
OneToOneField
,
CharField
,
IntegerField
,
TextField
,
Model
,
ForeignKey
,
OneToOneField
,
CharField
,
IntegerField
,
TextField
,
...
@@ -36,20 +37,16 @@ from django.utils import timezone
...
@@ -36,20 +37,16 @@ from django.utils import timezone
from
django.utils.html
import
escape
from
django.utils.html
import
escape
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
django_sshkey.models
import
UserKey
from
django_sshkey.models
import
UserKey
from
django.core.exceptions
import
ObjectDoesNotExist
from
itertools
import
chain
from
sizefield.models
import
FileSizeField
from
jsonfield
import
JSONField
from
jsonfield
import
JSONField
from
model_utils.models
import
TimeFramedModel
,
TimeStampedModel
from
model_utils.fields
import
StatusField
from
model_utils
import
Choices
from
model_utils
import
Choices
from
model_utils.fields
import
StatusField
from
model_utils.models
import
TimeFramedModel
,
TimeStampedModel
from
sizefield.models
import
FileSizeField
from
acl.models
import
AclBase
from
acl.models
import
AclBase
from
common.models
import
HumanReadableObject
,
create_readable
,
Encoder
from
common.models
import
HumanReadableObject
,
create_readable
,
Encoder
from
vm.models.instance
import
ACCESS_METHODS
from
vm.models.instance
import
ACCESS_METHODS
from
.store_api
import
Store
,
NoStoreException
,
NotOkException
from
.store_api
import
Store
,
NoStoreException
,
NotOkException
from
.validators
import
connect_command_template_validator
from
.validators
import
connect_command_template_validator
...
@@ -323,6 +320,62 @@ class GroupProfile(AclBase):
...
@@ -323,6 +320,62 @@ class GroupProfile(AclBase):
return
reverse
(
'dashboard.views.group-detail'
,
return
reverse
(
'dashboard.views.group-detail'
,
kwargs
=
{
'pk'
:
self
.
group
.
pk
})
kwargs
=
{
'pk'
:
self
.
group
.
pk
})
@classmethod
def
create_from_json
(
cls
,
owner
,
json_data
):
group
=
Group
()
try
:
data
=
json
.
loads
(
json_data
)
group
.
name
=
data
[
"name"
]
group
.
save
()
profile
=
group
.
profile
profile
.
set_user_level
(
owner
,
"owner"
)
profile
.
description
=
data
[
"desc"
]
profile
.
org_id
=
data
[
"org_id"
]
profile
.
instance_limit
=
int
(
data
[
"instance_limit"
])
profile
.
template_instance_limit
=
int
(
data
[
"template_instance_limit"
])
profile
.
disk_quota
=
long
(
data
[
"disk_quota"
])
profile
.
save
()
for
org_id
in
data
[
"users"
]:
try
:
if
org_id
is
not
None
:
user
=
Profile
.
objects
.
get
(
org_id
=
org_id
)
.
user
user
.
groups
.
add
(
group
)
except
ObjectDoesNotExist
:
future_member
=
FutureMember
(
org_id
=
org_id
,
group
=
group
)
future_member
.
save
()
for
permission
in
data
[
"permissions"
]:
group
.
permissions
.
add
(
Permission
.
objects
.
get_by_natural_key
(
*
permission
)
)
return
group
.
profile
except
(
KeyError
,
ValueError
,
TypeError
):
if
group
.
id
is
not
None
:
group
.
delete
()
logger
.
error
(
"Invalid group JSON"
)
def
convert_to_json
(
self
):
json_group
=
{
"name"
:
self
.
group
.
name
,
"desc"
:
self
.
description
,
"org_id"
:
self
.
org_id
,
"instance_limit"
:
self
.
instance_limit
,
"template_instance_limit"
:
self
.
template_instance_limit
,
"disk_quota"
:
self
.
disk_quota
,
"users"
:
[
user
.
profile
.
org_id
for
user
in
self
.
group
.
user_set
.
all
()
],
"permissions"
:
[
permission
.
natural_key
()
for
permission
in
self
.
group
.
permissions
.
all
()
]
}
return
json
.
dumps
(
json_group
)
def
get_or_create_profile
(
self
):
def
get_or_create_profile
(
self
):
obj
,
created
=
GroupProfile
.
objects
.
get_or_create
(
group_id
=
self
.
pk
)
obj
,
created
=
GroupProfile
.
objects
.
get_or_create
(
group_id
=
self
.
pk
)
...
...
circle/dashboard/static/dashboard/dashboard.js
View file @
2a3a6018
...
@@ -29,7 +29,7 @@ $(function () {
...
@@ -29,7 +29,7 @@ $(function () {
return
false
;
return
false
;
});
});
$
(
'.group-create, .
node-create, .tx-tpl-ownership, .group-delete, .node-delete, .disk-remove, .template-delete, .delete-from-group
, .lease-delete'
).
click
(
function
(
e
)
{
$
(
'.group-create, .
group-import, .group-export, .node-create, .tx-tpl-ownership, .group-delete, .node-delete, .disk-remove, .template-delete, .delete-from-group, .group-remove-all-btn
, .lease-delete'
).
click
(
function
(
e
)
{
$
.
ajax
({
$
.
ajax
({
type
:
'GET'
,
type
:
'GET'
,
url
:
$
(
this
).
prop
(
'href'
),
url
:
$
(
this
).
prop
(
'href'
),
...
...
circle/dashboard/static/dashboard/dashboard.less
View file @
2a3a6018
...
@@ -806,6 +806,10 @@ textarea[name="new_members"] {
...
@@ -806,6 +806,10 @@ textarea[name="new_members"] {
margin-top: -6px;
margin-top: -6px;
}
}
.group-remove-all-btn {
margin-right: 5px;
}
.store-action-button {
.store-action-button {
margin-left: 5px;
margin-left: 5px;
}
}
...
...
circle/dashboard/store_api.py
View file @
2a3a6018
...
@@ -47,7 +47,7 @@ class NoStoreException(StoreApiException):
...
@@ -47,7 +47,7 @@ class NoStoreException(StoreApiException):
class
Store
(
object
):
class
Store
(
object
):
def
__init__
(
self
,
user
,
default_timeout
=
0.
5
):
def
__init__
(
self
,
user
,
default_timeout
=
5
):
self
.
store_url
=
settings
.
STORE_URL
self
.
store_url
=
settings
.
STORE_URL
if
not
self
.
store_url
:
if
not
self
.
store_url
:
raise
NoStoreException
raise
NoStoreException
...
@@ -110,14 +110,16 @@ class Store(object):
...
@@ -110,14 +110,16 @@ class Store(object):
else
:
else
:
return
result
return
result
def
get_disk_images
(
self
,
path
=
'/'
):
def
get_files_with_exts
(
self
,
exts
,
path
=
'/'
):
images
=
[]
"""
Get list of files from store with the given file extensions.
"""
matching_files
=
[]
file_list
=
self
.
list
(
path
,
process
=
False
)
file_list
=
self
.
list
(
path
,
process
=
False
)
export_formats
=
[
item
[
0
]
for
item
in
Disk
.
EXPORT_FORMATS
]
for
item
in
file_list
:
for
item
in
file_list
:
if
os
.
path
.
splitext
(
item
[
'NAME'
])[
1
]
.
strip
(
'.'
)
in
ex
port_forma
ts
:
if
os
.
path
.
splitext
(
item
[
'NAME'
])[
1
]
.
strip
(
'.'
)
in
exts
:
imag
es
.
append
(
os
.
path
.
join
(
path
,
item
[
'NAME'
]))
matching_fil
es
.
append
(
os
.
path
.
join
(
path
,
item
[
'NAME'
]))
return
imag
es
return
matching_fil
es
def
request_download
(
self
,
path
):
def
request_download
(
self
,
path
):
r
=
self
.
_request_cmd
(
"DOWNLOAD"
,
PATH
=
path
,
timeout
=
10
)
r
=
self
.
_request_cmd
(
"DOWNLOAD"
,
PATH
=
path
,
timeout
=
10
)
...
@@ -127,6 +129,21 @@ class Store(object):
...
@@ -127,6 +129,21 @@ class Store(object):
r
=
self
.
_request_cmd
(
"UPLOAD"
,
PATH
=
path
)
r
=
self
.
_request_cmd
(
"UPLOAD"
,
PATH
=
path
)
return
r
.
json
()[
'LINK'
]
return
r
.
json
()[
'LINK'
]
def
request_ssh_download
(
self
,
path
):
r
=
self
.
_request_cmd
(
"SSH_DOWNLOAD"
,
PATH
=
path
)
return
r
.
json
()[
'LINK'
],
r
.
json
()[
'PORT'
]
def
request_ssh_upload
(
self
):
r
=
self
.
_request_cmd
(
"SSH_UPLOAD"
)
return
r
.
json
()[
'LINK'
],
r
.
json
()[
'PORT'
]
def
ssh_upload_finished
(
self
,
uploaded_name
,
path
):
self
.
_request_cmd
(
"SSH_UPLOAD_FINISHED"
,
FILENAME
=
uploaded_name
,
PATH
=
path
,
)
def
remove
(
self
,
path
):
def
remove
(
self
,
path
):
self
.
_request_cmd
(
"REMOVE"
,
PATH
=
path
)
self
.
_request_cmd
(
"REMOVE"
,
PATH
=
path
)
...
...
circle/dashboard/templates/dashboard/group-detail.html
View file @
2a3a6018
...
@@ -12,6 +12,9 @@
...
@@ -12,6 +12,9 @@
<a
title=
"{% trans "
Rename
"
%}"
class=
"btn btn-default btn-xs group-details-rename-button"
>
<a
title=
"{% trans "
Rename
"
%}"
class=
"btn btn-default btn-xs group-details-rename-button"
>
<i
class=
"fa fa-pencil"
></i>
<i
class=
"fa fa-pencil"
></i>
</a>
</a>
<a
title=
"{% trans "
Export
"
%}"
data-group-pk=
"{{ group.pk }}"
class=
"btn btn-default btn-xs group-export"
href=
"{% url "
dashboard
.
views
.
group-export
"
group_pk=
group.pk
%}"
>
<i
class=
"fa fa-upload"
></i>
</a>
<a
title=
"{% trans "
Delete
"
%}"
data-group-pk=
"{{ group.pk }}"
class=
"btn btn-default btn-xs real-link group-delete"
href=
"{% url "
dashboard
.
views
.
delete-group
"
pk=
group.pk
%}"
>
<a
title=
"{% trans "
Delete
"
%}"
data-group-pk=
"{{ group.pk }}"
class=
"btn btn-default btn-xs real-link group-delete"
href=
"{% url "
dashboard
.
views
.
delete-group
"
pk=
group.pk
%}"
>
<i
class=
"fa fa-trash-o"
></i>
<i
class=
"fa fa-trash-o"
></i>
</a>
</a>
...
@@ -82,6 +85,9 @@
...
@@ -82,6 +85,9 @@
{% trans "Create user" %}
{% trans "Create user" %}
</a>
</a>
{% endif %}
{% endif %}
<a
data-group_pk=
"{{ group.pk }}"
href=
"{% url "
dashboard
.
views
.
remove-all-users
"
group_pk=
group.pk
%}"
class=
"btn btn-danger group-remove-all-btn pull-right"
>
{% trans "Remove all users" %}
</a>
</h3>
</h3>
<form
action=
""
method=
"post"
>
{% csrf_token %}
<form
action=
""
method=
"post"
>
{% csrf_token %}
<table
class=
"table table-striped table-with-form-fields table-bordered"
id=
"group-detail-user-table"
>
<table
class=
"table table-striped table-with-form-fields table-bordered"
id=
"group-detail-user-table"
>
...
...
circle/dashboard/templates/dashboard/group-export.html
0 → 100644
View file @
2a3a6018
{% load crispy_forms_tags %}
{% load i18n %}
<p
class=
"text-muted"
>
{% trans "Export a group to the user store with the given filename." %}
</p>
<form
method=
"POST"
data-group_pk=
"{{ group.pk }}"
action=
"{% url "
dashboard
.
views
.
group-export
"
group_pk=
group.pk
%}"
>
{% csrf_token %}
{% crispy form %}
</form>
circle/dashboard/templates/dashboard/group-import.html
0 → 100644
View file @
2a3a6018
{% load crispy_forms_tags %}
{% load i18n %}
<p
class=
"text-muted"
>
{% trans "Import a previously exported group from the user store." %}
</p>
<form
method=
"POST"
action=
"{% url "
dashboard
.
views
.
group-import
"
%}"
>
{% csrf_token %}
{% crispy form %}
</form>
circle/dashboard/templates/dashboard/group-list/column-actions.html
View file @
2a3a6018
circle/dashboard/templates/dashboard/index-groups.html
View file @
2a3a6018
...
@@ -40,7 +40,8 @@
...
@@ -40,7 +40,8 @@
{% trans "list" %}
{% trans "list" %}
{% endif %}
{% endif %}
</a>
</a>
<a
class=
"btn btn-success btn-xs group-create"
href=
"{% url "
dashboard
.
views
.
group-create
"
%}"
><i
class=
"fa fa-plus-circle"
></i>
{% trans "new" %}
</a>
<a
class=
"btn btn-success btn-xs group-create"
href=
"{% url "
dashboard
.
views
.
group-create
"
%}"
title=
"{% trans "
new
"
%}"
><i
class=
"fa fa-plus-circle"
></i></a>
<a
class=
"btn btn-success btn-xs group-import"
href=
"{% url "
dashboard
.
views
.
group-import
"
%}"
title=
"{% trans "
import
"
%}"
><i
class=
"fa fa-download"
></i></a>
</div>
</div>
</div>
</div>
</div>
</div>
...
...
circle/dashboard/urls.py
View file @
2a3a6018
...
@@ -16,6 +16,7 @@
...
@@ -16,6 +16,7 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from
__future__
import
absolute_import
from
__future__
import
absolute_import
from
django.conf.urls
import
url
from
django.conf.urls
import
url
from
vm.models
import
Instance
from
vm.models
import
Instance
...
@@ -32,6 +33,7 @@ from .views import (
...
@@ -32,6 +33,7 @@ from .views import (
DiskRemoveView
,
get_disk_download_status
,
DiskRemoveView
,
get_disk_download_status
,
GroupRemoveUserView
,
GroupRemoveUserView
,
GroupRemoveFutureUserView
,
GroupRemoveFutureUserView
,
GroupRemoveAllUsersView
,
GroupCreate
,
GroupProfileUpdate
,
GroupCreate
,
GroupProfileUpdate
,
TemplateChoose
,
TemplateChoose
,
UserCreationView
,
UserCreationView
,
...
@@ -56,11 +58,10 @@ from .views import (
...
@@ -56,11 +58,10 @@ from .views import (
MessageList
,
MessageDetail
,
MessageCreate
,
MessageDelete
,
MessageList
,
MessageDetail
,
MessageCreate
,
MessageDelete
,
EnableTwoFactorView
,
DisableTwoFactorView
,
EnableTwoFactorView
,
DisableTwoFactorView
,
AclUserGroupAutocomplete
,
AclUserAutocomplete
,
AclUserGroupAutocomplete
,
AclUserAutocomplete
,
RescheduleView
,
RescheduleView
,
GroupImportView
,
GroupExportView
)
)
from
.views.vm
import
vm_ops
,
vm_mass_ops
from
.views.node
import
node_ops
from
.views.node
import
node_ops
from
.views.vm
import
vm_ops
,
vm_mass_ops
urlpatterns
=
[
urlpatterns
=
[
url
(
r'^$'
,
IndexView
.
as_view
(),
name
=
"dashboard.index"
),
url
(
r'^$'
,
IndexView
.
as_view
(),
name
=
"dashboard.index"
),
...
@@ -192,8 +193,16 @@ urlpatterns = [
...
@@ -192,8 +193,16 @@ urlpatterns = [
url
(
r'^group/(?P<group_pk>\d+)/remove/futureuser/(?P<member_org_id>.+)/$'
,
url
(
r'^group/(?P<group_pk>\d+)/remove/futureuser/(?P<member_org_id>.+)/$'
,
GroupRemoveFutureUserView
.
as_view
(),
GroupRemoveFutureUserView
.
as_view
(),
name
=
"dashboard.views.remove-future-user"
),
name
=
"dashboard.views.remove-future-user"
),
url
(
r'^group/(?P<group_pk>\d+)/remove/user/all/$'
,
GroupRemoveAllUsersView
.
as_view
(),
name
=
"dashboard.views.remove-all-users"
),
url
(
r'^group/create/$'
,
GroupCreate
.
as_view
(),
url
(
r'^group/create/$'
,
GroupCreate
.
as_view
(),
name
=
'dashboard.views.group-create'
),
name
=
'dashboard.views.group-create'
),
url
(
r'^group/import/$'
,
GroupImportView
.
as_view
(),
name
=
"dashboard.views.group-import"
),
url
(
r'^group/(?P<group_pk>\d+)/export/$'
,
GroupExportView
.
as_view
(),
name
=
"dashboard.views.group-export"
),
url
(
r'^group/(?P<group_pk>\d+)/permissions/$'
,
url
(
r'^group/(?P<group_pk>\d+)/permissions/$'
,
GroupPermissionsView
.
as_view
(),
GroupPermissionsView
.
as_view
(),
name
=
"dashboard.views.group-permissions"
),
name
=
"dashboard.views.group-permissions"
),
...
...
circle/dashboard/views/group.py
View file @
2a3a6018
...
@@ -18,31 +18,33 @@ from __future__ import unicode_literals, absolute_import
...
@@ -18,31 +18,33 @@ from __future__ import unicode_literals, absolute_import
import
json
import
json
import
logging
import
logging
from
itertools
import
chain
import
requests
from
braces.views
import
SuperuserRequiredMixin
,
LoginRequiredMixin
from
django.conf
import
settings
from
django.conf
import
settings
from
django.contrib
import
messages
from
django.contrib
import
messages
from
django.contrib.auth.models
import
User
,
Group
from
django.contrib.auth.models
import
User
,
Group
from
django.contrib.messages.views
import
SuccessMessageMixin
from
django.contrib.messages.views
import
SuccessMessageMixin
from
django.core.exceptions
import
PermissionDenied
from
django.core.exceptions
import
PermissionDenied
,
SuspiciousOperation
from
django.core.urlresolvers
import
reverse
,
reverse_lazy
from
django.core.urlresolvers
import
reverse
,
reverse_lazy
from
django.http
import
HttpResponse
,
Http404
from
django.http
import
HttpResponse
,
Http404
from
django.shortcuts
import
redirect
from
django.shortcuts
import
redirect
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
django.views.generic
import
UpdateView
,
TemplateView
from
django.views.generic
import
UpdateView
,
TemplateView
from
django.views.generic.detail
import
SingleObjectMixin
from
braces.views
import
SuperuserRequiredMixin
,
LoginRequiredMixin
from
django_tables2
import
SingleTableView
from
django_tables2
import
SingleTableView
from
itertools
import
chain
from
vm.models
import
Instance
,
InstanceTemplate
from
.util
import
(
CheckedDetailView
,
AclUpdateView
,
search_user
,
saml_available
,
DeleteViewBase
)
from
..forms
import
(
from
..forms
import
(
AddGroupMemberForm
,
AclUserOrGroupAddForm
,
GroupPermissionForm
,
AddGroupMemberForm
,
AclUserOrGroupAddForm
,
GroupPermissionForm
,
GroupCreateForm
,
Group
ProfileUpdate
Form
,
GroupCreateForm
,
Group
ImportForm
,
GroupProfileUpdateForm
,
GroupExport
Form
,
)
)
from
..models
import
FutureMember
,
GroupProfile
from
..models
import
FutureMember
,
GroupProfile
from
vm.models
import
Instance
,
InstanceTemplate
from
..store_api
import
Store
,
NoStoreException
from
..tables
import
GroupListTable
from
..tables
import
GroupListTable
from
.util
import
(
CheckedDetailView
,
AclUpdateView
,
search_user
,
saml_available
,
DeleteViewBase
)
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
...
@@ -285,6 +287,33 @@ class GroupRemoveFutureUserView(GroupRemoveUserView):
...
@@ -285,6 +287,33 @@ class GroupRemoveFutureUserView(GroupRemoveUserView):
group
=
self
.
get_object
())
.
delete
()
group
=
self
.
get_object
())
.
delete
()
class
GroupRemoveAllUsersView
(
DeleteViewBase
):
model
=
Group
level
=
'operator'
slug_field
=
'pk'
slug_url_kwarg
=
'group_pk'
success_message
=
_
(
"All users successfully removed from group."
)
def
check_auth
(
self
):
if
not
self
.
get_object
()
.
profile
.
has_level
(
self
.
request
.
user
,
self
.
level
):
raise
PermissionDenied
()
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
(
GroupRemoveAllUsersView
,
self
)
.
get_context_data
(
**
kwargs
)
context
[
'member'
]
=
_
(
"all users"
)
return
context
def
get_success_url
(
self
):
return
reverse_lazy
(
"dashboard.views.group-detail"
,
kwargs
=
{
'pk'
:
self
.
get_object
()
.
pk
})
def
delete_obj
(
self
,
request
,
*
args
,
**
kwargs
):
container
=
self
.
get_object
()
container
.
user_set
.
clear
()
FutureMember
.
objects
.
filter
(
group
=
container
)
.
delete
()
class
GroupDelete
(
DeleteViewBase
):
class
GroupDelete
(
DeleteViewBase
):
model
=
Group
model
=
Group
success_message
=
_
(
"Group successfully deleted."
)
success_message
=
_
(
"Group successfully deleted."
)
...
@@ -298,7 +327,6 @@ class GroupDelete(DeleteViewBase):
...
@@ -298,7 +327,6 @@ class GroupDelete(DeleteViewBase):
class
GroupCreate
(
GroupCodeMixin
,
LoginRequiredMixin
,
TemplateView
):
class
GroupCreate
(
GroupCodeMixin
,
LoginRequiredMixin
,
TemplateView
):
form_class
=
GroupCreateForm
form_class
=
GroupCreateForm
def
get_template_names
(
self
):
def
get_template_names
(
self
):
...
@@ -334,16 +362,153 @@ class GroupCreate(GroupCodeMixin, LoginRequiredMixin, TemplateView):
...
@@ -334,16 +362,153 @@ class GroupCreate(GroupCodeMixin, LoginRequiredMixin, TemplateView):
savedform
.
profile
.
set_level
(
request
.
user
,
'owner'
)
savedform
.
profile
.
set_level
(
request
.
user
,
'owner'
)
messages
.
success
(
request
,
_
(
'Group successfully created.'
))
messages
.
success
(
request
,
_
(
'Group successfully created.'
))
if
request
.
is_ajax
():
if
request
.
is_ajax
():
return
HttpResponse
(
json
.
dumps
({
'redirect'
:
return
HttpResponse
(
savedform
.
profile
.
get_absolute_url
()}),
json
.
dumps
(
content_type
=
"application/json"
)
{
'redirect'
:
savedform
.
profile
.
get_absolute_url
()}
),
content_type
=
"application/json"
)
else
:
else
:
return
redirect
(
savedform
.
profile
.
get_absolute_url
())
return
redirect
(
savedform
.
profile
.
get_absolute_url
())
class
GroupImportView
(
LoginRequiredMixin
,
TemplateView
):
form_class
=
GroupImportForm
def
get_template_names
(
self
):
if
self
.
request
.
is_ajax
():
return
[
'dashboard/_modal.html'
]
else
:
return
[
'dashboard/nojs-wrapper.html'
]
def
get
(
self
,
request
,
form
=
None
,
*
args
,
**
kwargs
):
if
not
request
.
user
.
has_module_perms
(
'auth'
):
raise
PermissionDenied
()
try
:
Store
(
request
.
user
)
except
NoStoreException
:
raise
PermissionDenied
()
if
form
is
None
:
form
=
self
.
form_class
(
user
=
request
.
user
)
context
=
self
.
get_context_data
(
**
kwargs
)
context
.
update
({
'template'
:
'dashboard/group-import.html'
,
'box_title'
:
_
(
'Import a Group'
),
'form'
:
form
,
'ajax_title'
:
True
,
})
return
self
.
render_to_response
(
context
)
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
if
not
request
.
user
.
has_module_perms
(
'auth'
):
raise
PermissionDenied
()
try
:
Store
(
request
.
user
)
except
NoStoreException
:
raise
PermissionDenied
()
form
=
self
.
form_class
(
request
.
POST
,
user
=
request
.
user
)
if
form
.
is_valid
():
group_path
=
form
.
cleaned_data
[
"group_path"
]
url
=
Store
(
request
.
user
)
.
request_download
(
group_path
)
json_str
=
requests
.
get
(
url
)
.
content
profile
=
GroupProfile
.
create_from_json
(
request
.
user
,
json_str
)
if
profile
is
None
:
raise
SuspiciousOperation
()
success_message
=
_
(
"Group successfully imported."
)
if
request
.
is_ajax
():
response
=
{
'message'
:
success_message
,
'redirect'
:
profile
.
get_absolute_url
()
}
return
HttpResponse
(
json
.
dumps
(
response
),
content_type
=
"application/json"
)
else
:
messages
.
success
(
request
,
success_message
)
return
redirect
(
profile
.
get_absolute_url
())
else
:
return
self
.
get
(
request
,
form
,
*
args
,
**
kwargs
)
class
GroupExportView
(
LoginRequiredMixin
,
SingleObjectMixin
,
TemplateView
):
form_class
=
GroupExportForm
model
=
Group
pk_url_kwarg
=
"group_pk"
def
__init__
(
self
):
super
(
GroupExportView
,
self
)
.
__init__
()
self
.
object
=
None
def
get_template_names
(
self
):
if
self
.
request
.
is_ajax
():
return
[
'dashboard/_modal.html'
]
else
:
return
[
'dashboard/nojs-wrapper.html'
]
def
get
(
self
,
request
,
form
=
None
,
*
args
,
**
kwargs
):
self
.
object
=
self
.
get_object
()
if
not
self
.
object
.
profile
.
has_level
(
request
.
user
,
'operator'
):
raise
PermissionDenied
()
try
:
Store
(
request
.
user
)
except
NoStoreException
:
raise
PermissionDenied
()
if
form
is
None
:
form
=
self
.
form_class
(
group_name
=
self
.
object
.
name
)
context
=
self
.
get_context_data
(
**
kwargs
)
context
.
update
({
'group'
:
self
.
object
,
'template'
:
'dashboard/group-export.html'
,
'box_title'
:
_
(
'Export Group'
),
'form'
:
form
,
'ajax_title'
:
True
,
})
return
self
.
render_to_response
(
context
)
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
object
=
self
.
get_object
()
group
=
self
.
object
if
not
group
.
profile
.
has_level
(
request
.
user
,
'operator'
):
raise
PermissionDenied
()
try
:
Store
(
request
.
user
)
except
NoStoreException
:
raise
PermissionDenied
()
form
=
self
.
form_class
(
request
.
POST
,
group_name
=
self
.
object
.
name
)
if
form
.
is_valid
():
name
=
form
.
cleaned_data
[
"exported_name"
]
group_json
=
group
.
profile
.
convert_to_json
()
store
=
Store
(
request
.
user
)
url
=
store
.
request_upload
(
"/"
)
data
=
{
'data'
:
(
name
+
'.group'
,
group_json
)}
requests
.
post
(
url
,
files
=
data
)
success_message
=
_
(
"Group successfully exported."
)
if
request
.
is_ajax
():
response
=
{
'message'
:
success_message
,
'redirect'
:
group
.
profile
.
get_absolute_url
()
}
return
HttpResponse
(
json
.
dumps
(
response
),
content_type
=
"application/json"
)
else
:
messages
.
success
(
request
,
success_message
)
return
redirect
(
group
.
profile
.
get_absolute_url
())
else
:
return
self
.
get
(
request
,
form
,
*
args
,
**
kwargs
)
class
GroupProfileUpdate
(
SuccessMessageMixin
,
GroupCodeMixin
,
class
GroupProfileUpdate
(
SuccessMessageMixin
,
GroupCodeMixin
,
LoginRequiredMixin
,
UpdateView
):
LoginRequiredMixin
,
UpdateView
):
form_class
=
GroupProfileUpdateForm
form_class
=
GroupProfileUpdateForm
model
=
Group
model
=
Group
success_message
=
_
(
'Group is successfully updated.'
)
success_message
=
_
(
'Group is successfully updated.'
)
...
...
circle/storage/models.py
View file @
2a3a6018
...
@@ -477,7 +477,7 @@ class Disk(TimeStampedModel):
...
@@ -477,7 +477,7 @@ class Disk(TimeStampedModel):
return
disk
return
disk
@classmethod
@classmethod
def
import_disk
(
cls
,
user
,
name
,
download_link
,
task
):
def
import_disk
(
cls
,
user
,
name
,
download_link
,
port
,
task
):
params
=
{
'name'
:
name
,
params
=
{
'name'
:
name
,
'type'
:
'qcow2-norm'
}
'type'
:
'qcow2-norm'
}
disk
=
cls
.
__create
(
user
=
user
,
params
=
params
)
disk
=
cls
.
__create
(
user
=
user
,
params
=
params
)
...
@@ -486,7 +486,7 @@ class Disk(TimeStampedModel):
...
@@ -486,7 +486,7 @@ class Disk(TimeStampedModel):
kwargs
=
{
kwargs
=
{
"disk_desc"
:
disk
.
get_disk_desc
(),
"disk_desc"
:
disk
.
get_disk_desc
(),
"url"
:
download_link
,
"url"
:
download_link
,
"
task"
:
task
.
request
.
id
"
port"
:
port
},
},
queue
=
queue_name
queue
=
queue_name
)
)
...
@@ -497,18 +497,17 @@ class Disk(TimeStampedModel):
...
@@ -497,18 +497,17 @@ class Disk(TimeStampedModel):
disk
.
save
()
disk
.
save
()
return
disk
return
disk
def
export
(
self
,
exported_name
,
disk_format
,
upload_link
,
task
):
def
export
(
self
,
disk_format
,
upload_link
,
port
,
task
):
queue_name
=
self
.
get_remote_queue_name
(
'storage'
,
priority
=
'slow'
)
queue_name
=
self
.
get_remote_queue_name
(
'storage'
,
priority
=
'slow'
)
remote
=
storage_tasks
.
export_disk
.
apply_async
(
remote
=
storage_tasks
.
export_disk
.
apply_async
(
kwargs
=
{
kwargs
=
{
"disk_desc"
:
self
.
get_disk_desc
(),
"disk_desc"
:
self
.
get_disk_desc
(),
"disk_format"
:
disk_format
,
"disk_format"
:
disk_format
,
"exported_name"
:
exported_name
,
"upload_link"
:
upload_link
,
"upload_link"
:
upload_link
,
"
task"
:
task
.
request
.
id
"
port"
:
port
},
},
queue
=
queue_name
)
queue
=
queue_name
)
self
.
_run_abortable_task
(
remote
,
task
)
return
self
.
_run_abortable_task
(
remote
,
task
)
def
destroy
(
self
,
user
=
None
,
task_uuid
=
None
):
def
destroy
(
self
,
user
=
None
,
task_uuid
=
None
):
if
self
.
destroyed
:
if
self
.
destroyed
:
...
...
circle/storage/tasks/storage_tasks.py
View file @
2a3a6018
...
@@ -39,12 +39,12 @@ def download(disk_desc, url):
...
@@ -39,12 +39,12 @@ def download(disk_desc, url):
@celery.task
(
name
=
'storagedriver.import_disk'
)
@celery.task
(
name
=
'storagedriver.import_disk'
)
def
import_disk
(
disk_desc
,
url
):
def
import_disk
(
disk_desc
,
url
,
port
):
pass
pass
@celery.task
(
name
=
'storagedriver.export_disk'
)
@celery.task
(
name
=
'storagedriver.export_disk'
)
def
export_disk
(
disk_desc
,
forma
t
):
def
export_disk
(
disk_desc
,
disk_format
,
url
,
por
t
):
pass
pass
...
...
circle/templates/info/resize.html
View file @
2a3a6018
...
@@ -253,9 +253,10 @@
...
@@ -253,9 +253,10 @@
<p>
<p>
{% blocktrans %}
{% blocktrans %}
Press
<strong>
n
</strong>
to create new partition.
Press
<strong>
n
</strong>
to create new partition.
Type
<strong>
l
</strong>
to choose logical type.
If not selected automatically, type
<strong>
l
</strong>
to choose logical type, and
S
et partition number - the same as the Linux LVM (vda5) has above:
<strong>
5
</strong>
.
s
et partition number - the same as the Linux LVM (vda5) has above:
<strong>
5
</strong>
.
You can use the default starting and ending sector.
You can use the default starting and ending sector.
Do
<strong>
NOT
</strong>
remove the LVM2_member signature.
{% endblocktrans %}
{% endblocktrans %}
</p>
</p>
</li>
</li>
...
...
circle/vm/operations.py
View file @
2a3a6018
...
@@ -352,7 +352,6 @@ class ImportDiskOperation(InstanceOperation):
...
@@ -352,7 +352,6 @@ class ImportDiskOperation(InstanceOperation):
'from the user store. The disk image has to be in the '
'from the user store. The disk image has to be in the '
'root directory of the store.'
)
'root directory of the store.'
)
abortable
=
True
abortable
=
True
has_percentage
=
True
required_perms
=
(
'storage.import_disk'
,)
required_perms
=
(
'storage.import_disk'
,)
accept_states
=
(
'STOPPED'
,
'PENDING'
,
'RUNNING'
)
accept_states
=
(
'STOPPED'
,
'PENDING'
,
'RUNNING'
)
async_queue
=
'localhost.man.slow'
async_queue
=
'localhost.man.slow'
...
@@ -366,8 +365,8 @@ class ImportDiskOperation(InstanceOperation):
...
@@ -366,8 +365,8 @@ class ImportDiskOperation(InstanceOperation):
def
_operation
(
self
,
user
,
name
,
disk_path
,
task
):
def
_operation
(
self
,
user
,
name
,
disk_path
,
task
):
store
=
Store
(
user
)
store
=
Store
(
user
)
download_link
=
store
.
request
_download
(
disk_path
)
download_link
,
port
=
store
.
request_ssh
_download
(
disk_path
)
disk
=
Disk
.
import_disk
(
user
,
name
,
download_link
,
task
)
disk
=
Disk
.
import_disk
(
user
,
name
,
download_link
,
port
,
task
)
self
.
instance
.
disks
.
add
(
disk
)
self
.
instance
.
disks
.
add
(
disk
)
...
@@ -377,7 +376,6 @@ class ExportDiskOperation(InstanceOperation):
...
@@ -377,7 +376,6 @@ class ExportDiskOperation(InstanceOperation):
name
=
_
(
'export disk'
)
name
=
_
(
'export disk'
)
description
=
_
(
'Export disk to the selected format.'
)
description
=
_
(
'Export disk to the selected format.'
)
abortable
=
True
abortable
=
True
has_percentage
=
True
required_perms
=
(
'storage.export_disk'
,)
required_perms
=
(
'storage.export_disk'
,)
accept_states
=
(
'STOPPED'
,)
accept_states
=
(
'STOPPED'
,)
async_queue
=
'localhost.man.slow'
async_queue
=
'localhost.man.slow'
...
@@ -391,8 +389,9 @@ class ExportDiskOperation(InstanceOperation):
...
@@ -391,8 +389,9 @@ class ExportDiskOperation(InstanceOperation):
def
_operation
(
self
,
user
,
disk
,
exported_name
,
disk_format
,
task
):
def
_operation
(
self
,
user
,
disk
,
exported_name
,
disk_format
,
task
):
store
=
Store
(
user
)
store
=
Store
(
user
)
upload_link
=
store
.
request_upload
(
'/'
)
upload_link
,
port
=
store
.
request_ssh_upload
()
disk
.
export
(
exported_name
,
disk_format
,
upload_link
,
task
)
file_name
=
disk
.
export
(
disk_format
,
upload_link
,
port
,
task
)
store
.
ssh_upload_finished
(
file_name
,
exported_name
+
'.'
+
disk_format
)
@register_operation
@register_operation
...
...
Szeberényi Imre
@szebi
mentioned in commit
edd5e74d
Dec 01, 2021
mentioned in commit
edd5e74d
mentioned in commit edd5e74d06ad07d8cfcb2c142e9af1dd2c04de20
Toggle commit list
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment