Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
J
jumpserver
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ops
jumpserver
Commits
08e17884
Commit
08e17884
authored
Dec 14, 2017
by
ibuler
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Feature] 打算拆分下载和上传为独立模块,时间有限暂时放弃
parent
160b01ec
Show whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
452 additions
and
105 deletions
+452
-105
forms.py
apps/assets/forms.py
+5
-2
user.py
apps/assets/models/user.py
+23
-5
serializers.py
apps/assets/serializers.py
+1
-1
_system_user.html
apps/assets/templates/assets/_system_user.html
+1
-1
asset_update.html
apps/assets/templates/assets/asset_update.html
+3
-1
admin_user.py
apps/assets/views/admin_user.py
+1
-1
asset.py
apps/assets/views/asset.py
+10
-42
cluster.py
apps/assets/views/cluster.py
+1
-1
group.py
apps/assets/views/group.py
+1
-1
system_user.py
apps/assets/views/system_user.py
+1
-1
imexp.py
apps/common/imexp.py
+331
-0
django.po
apps/locale/zh/LC_MESSAGES/django.po
+3
-3
views.py
apps/perms/views.py
+11
-11
delete_confirm.html
apps/templates/delete_confirm.html
+16
-0
command_list.html
apps/terminal/templates/terminal/command_list.html
+6
-2
session_list.html
apps/terminal/templates/terminal/session_list.html
+17
-11
terminal_list.html
apps/terminal/templates/terminal/terminal_list.html
+8
-9
terminal_modal_accept.html
apps/terminal/templates/terminal/terminal_modal_accept.html
+3
-3
api_urls.py
apps/terminal/urls/api_urls.py
+2
-2
command.py
apps/terminal/views/command.py
+1
-1
session.py
apps/terminal/views/session.py
+2
-1
terminal.py
apps/terminal/views/terminal.py
+2
-2
authentication.py
apps/users/models/authentication.py
+0
-1
user.py
apps/users/models/user.py
+3
-3
No files found.
apps/assets/forms.py
View file @
08e17884
...
...
@@ -9,6 +9,7 @@ from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen,
logger
=
get_logger
(
__file__
)
from
rest_framework
import
serializers
class
AssetCreateForm
(
forms
.
ModelForm
):
...
...
@@ -240,7 +241,7 @@ class SystemUserForm(forms.ModelForm):
fields
=
[
'name'
,
'username'
,
'protocol'
,
'auto_generate_key'
,
'password'
,
'private_key_file'
,
'auto_push'
,
'sudo'
,
'comment'
,
'shell'
,
'cluster'
'comment'
,
'shell'
,
'cluster'
,
'priority'
,
]
widgets
=
{
'name'
:
forms
.
TextInput
(
attrs
=
{
'placeholder'
:
_
(
'Name'
)}),
...
...
@@ -254,6 +255,7 @@ class SystemUserForm(forms.ModelForm):
'username'
:
'* required'
,
'cluster'
:
'If auto push checked, system user will be create at cluster assets'
,
'auto_push'
:
'Auto push system user to asset'
,
'priority'
:
'High level will be using login asset as default, if user was granted more than 2 system user'
,
}
...
...
@@ -261,7 +263,7 @@ class SystemUserUpdateForm(forms.ModelForm):
class
Meta
:
model
=
SystemUser
fields
=
[
'name'
,
'username'
,
'protocol'
,
'name'
,
'username'
,
'protocol'
,
'priority'
,
'sudo'
,
'comment'
,
'shell'
,
'cluster'
]
widgets
=
{
...
...
@@ -275,6 +277,7 @@ class SystemUserUpdateForm(forms.ModelForm):
'name'
:
'* required'
,
'username'
:
'* required'
,
'cluster'
:
'If auto push checked, then push system user to that cluster assets'
,
'priority'
:
'High level will be using login asset as default, if user was granted more than 2 system user'
,
}
...
...
apps/assets/models/user.py
View file @
08e17884
...
...
@@ -7,6 +7,7 @@ import logging
import
uuid
from
hashlib
import
md5
import
sshpubkeys
from
django.db
import
models
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.conf
import
settings
...
...
@@ -27,7 +28,8 @@ class AssetUser(models.Model):
_private_key
=
models
.
TextField
(
max_length
=
4096
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'SSH private key'
),
validators
=
[
private_key_validator
,
])
_public_key
=
models
.
TextField
(
max_length
=
4096
,
blank
=
True
,
verbose_name
=
_
(
'SSH public key'
))
comment
=
models
.
TextField
(
blank
=
True
,
verbose_name
=
_
(
'Comment'
))
date_created
=
models
.
DateTimeField
(
auto_now_add
=
True
,
null
=
True
)
date_created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
date_updated
=
models
.
DateTimeField
(
auto_now
=
True
)
created_by
=
models
.
CharField
(
max_length
=
32
,
null
=
True
,
verbose_name
=
_
(
'Created by'
))
@property
...
...
@@ -45,16 +47,21 @@ class AssetUser(models.Model):
@property
def
private_key
(
self
):
if
self
.
_private_key
:
key_str
=
signer
.
unsign
(
self
.
_private_key
)
return
ssh_key_string_to_obj
(
key_str
,
password
=
self
.
password
)
else
:
return
None
return
signer
.
unsign
(
self
.
_private_key
)
@private_key.setter
def
private_key
(
self
,
private_key_raw
):
raise
AttributeError
(
"Using set_auth do that"
)
# self._private_key = signer.sign(private_key_raw)
@property
def
private_key_obj
(
self
):
if
self
.
_private_key
:
key_str
=
signer
.
unsign
(
self
.
_private_key
)
return
ssh_key_string_to_obj
(
key_str
,
password
=
self
.
password
)
else
:
return
None
@property
def
private_key_file
(
self
):
if
not
self
.
private_key
:
...
...
@@ -74,6 +81,15 @@ class AssetUser(models.Model):
def
public_key
(
self
):
return
signer
.
unsign
(
self
.
_public_key
)
@property
def
public_key_obj
(
self
):
if
self
.
public_key
:
try
:
return
sshpubkeys
.
SSHKey
(
self
.
public_key
)
except
TabError
:
pass
return
None
def
set_auth
(
self
,
password
=
None
,
private_key
=
None
,
public_key
=
None
):
update_fields
=
[]
if
password
:
...
...
@@ -170,6 +186,7 @@ class SystemUser(AssetUser):
(
'K'
,
'Public key'
),
)
cluster
=
models
.
ManyToManyField
(
'assets.Cluster'
,
verbose_name
=
_
(
"Cluster"
))
priority
=
models
.
IntegerField
(
default
=
10
,
verbose_name
=
_
(
"Priority"
))
# Todo: If user granted more priority user, default will be login as the hign
protocol
=
models
.
CharField
(
max_length
=
16
,
choices
=
PROTOCOL_CHOICES
,
default
=
'ssh'
,
verbose_name
=
_
(
'Protocol'
))
auto_push
=
models
.
BooleanField
(
default
=
True
,
verbose_name
=
_
(
'Auto push'
))
sudo
=
models
.
TextField
(
default
=
'/sbin/ifconfig'
,
verbose_name
=
_
(
'Sudo'
))
...
...
@@ -205,6 +222,7 @@ class SystemUser(AssetUser):
'name'
:
self
.
name
,
'username'
:
self
.
username
,
'protocol'
:
self
.
protocol
,
'priority'
:
self
.
priority
,
'auto_push'
:
self
.
auto_push
,
}
...
...
apps/assets/serializers.py
View file @
08e17884
...
...
@@ -115,7 +115,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
class
AssetSystemUserSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
SystemUser
fields
=
(
'id'
,
'name'
,
'username'
,
'pr
otocol'
,
'comment'
)
fields
=
(
'id'
,
'name'
,
'username'
,
'pr
iority'
,
'protocol'
,
'comment'
,
)
class
SystemUserUpdateAssetsSerializer
(
serializers
.
ModelSerializer
):
...
...
apps/assets/templates/assets/_system_user.html
View file @
08e17884
...
...
@@ -37,6 +37,7 @@
<h3>
{% trans 'Basic' %}
</h3>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.username layout="horizontal" %}
{% bootstrap_field form.priority layout="horizontal" %}
{% bootstrap_field form.protocol layout="horizontal" %}
{% bootstrap_field form.cluster layout="horizontal" %}
...
...
@@ -49,7 +50,6 @@
{{ form.auto_generate_key}}
</div>
</div>
</div>
<div
class=
"auth-fields"
>
{% bootstrap_field form.private_key_file layout="horizontal" %}
...
...
apps/assets/templates/assets/asset_update.html
View file @
08e17884
...
...
@@ -64,7 +64,9 @@
{% block custom_foot_js %}
<script>
$
(
document
).
ready
(
function
()
{
$
(
'.select2'
).
select2
();
$
(
'.select2'
).
select2
({
allowClear
:
true
});
$
(
"#tags"
).
select2
({
tags
:
true
,
maximumSelectionLength
:
8
//最多能够选择的个数
...
...
apps/assets/views/admin_user.py
View file @
08e17884
...
...
@@ -123,7 +123,7 @@ class AdminUserAssetsView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
class
AdminUserDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
model
=
AdminUser
template_name
=
'
assets/
delete_confirm.html'
template_name
=
'delete_confirm.html'
success_url
=
reverse_lazy
(
'assets:admin-user-list'
)
apps/assets/views/asset.py
View file @
08e17884
...
...
@@ -9,12 +9,13 @@ import chardet
from
io
import
StringIO
from
django.conf
import
settings
from
django.core.exceptions
import
ImproperlyConfigured
,
FieldDoesNotExist
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.views.generic
import
TemplateView
,
ListView
,
View
from
django.views.generic.edit
import
CreateView
,
DeleteView
,
FormView
,
UpdateView
from
django.urls
import
reverse_lazy
from
django.views.generic.detail
import
DetailView
,
SingleObjectMixin
from
django.http
import
HttpResponse
,
JsonResponse
,
HttpResponseRedirect
from
django.http
import
HttpResponse
,
JsonResponse
,
HttpResponseRedirect
,
Http404
from
django.views.decorators.csrf
import
csrf_protect
,
csrf_exempt
from
django.utils.decorators
import
method_decorator
from
django.core.cache
import
cache
...
...
@@ -22,8 +23,10 @@ from django.utils import timezone
from
django.contrib.auth.mixins
import
LoginRequiredMixin
from
django.shortcuts
import
get_object_or_404
,
redirect
,
reverse
from
common.mixins
import
JSONResponseMixin
from
common.utils
import
get_object_or_none
from
common.imexp
import
ModelExportView
from
..
import
forms
from
..models
import
Asset
,
AssetGroup
,
AdminUser
,
Cluster
,
SystemUser
from
..hands
import
AdminUserRequiredMixin
...
...
@@ -169,7 +172,7 @@ class AssetUpdateView(AdminUserRequiredMixin, UpdateView):
class
AssetDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
model
=
Asset
template_name
=
'
assets/
delete_confirm.html'
template_name
=
'delete_confirm.html'
success_url
=
reverse_lazy
(
'assets:asset-list'
)
...
...
@@ -193,46 +196,11 @@ class AssetDetailView(DetailView):
@method_decorator
(
csrf_exempt
,
name
=
'dispatch'
)
class
AssetExportView
(
View
):
def
get
(
self
,
request
):
spm
=
request
.
GET
.
get
(
'spm'
,
''
)
assets_id_default
=
[
Asset
.
objects
.
first
()
.
id
]
if
Asset
.
objects
.
first
()
else
[
1
]
assets_id
=
cache
.
get
(
spm
,
assets_id_default
)
fields
=
[
field
for
field
in
Asset
.
_meta
.
fields
if
field
.
name
not
in
[
'date_created'
]
]
filename
=
'assets-{}.csv'
.
format
(
timezone
.
localtime
(
timezone
.
now
())
.
strftime
(
'
%
Y-
%
m-
%
d_
%
H-
%
M-
%
S'
))
response
=
HttpResponse
(
content_type
=
'text/csv'
)
response
[
'Content-Disposition'
]
=
'attachment; filename="
%
s"'
%
filename
response
.
write
(
codecs
.
BOM_UTF8
)
assets
=
Asset
.
objects
.
filter
(
id__in
=
assets_id
)
writer
=
csv
.
writer
(
response
,
dialect
=
'excel'
,
quoting
=
csv
.
QUOTE_MINIMAL
)
header
=
[
field
.
verbose_name
for
field
in
fields
]
header
.
append
(
_
(
'Asset groups'
))
writer
.
writerow
(
header
)
for
asset
in
assets
:
groups
=
','
.
join
([
group
.
name
for
group
in
asset
.
groups
.
all
()])
data
=
[
getattr
(
asset
,
field
.
name
)
for
field
in
fields
]
data
.
append
(
groups
)
writer
.
writerow
(
data
)
return
response
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
try
:
assets_id
=
json
.
loads
(
request
.
body
)
.
get
(
'assets_id'
,
[])
except
ValueError
:
return
HttpResponse
(
'Json object not valid'
,
status
=
400
)
spm
=
uuid
.
uuid4
()
.
hex
cache
.
set
(
spm
,
assets_id
,
300
)
url
=
reverse_lazy
(
'assets:asset-export'
)
+
'?spm=
%
s'
%
spm
return
JsonResponse
({
'redirect'
:
url
})
class
AssetExportView
(
ModelExportView
):
filename_prefix
=
'jumpserver'
redirect_url
=
reverse_lazy
(
'assets:asset-export'
)
model
=
Asset
fields
=
(
'hostname'
,
'ip'
)
class
BulkImportAssetView
(
AdminUserRequiredMixin
,
JSONResponseMixin
,
FormView
):
...
...
apps/assets/views/cluster.py
View file @
08e17884
...
...
@@ -97,5 +97,5 @@ class ClusterAssetsView(AdminUserRequiredMixin, DetailView):
class
ClusterDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
model
=
Cluster
template_name
=
'
assets/
delete_confirm.html'
template_name
=
'delete_confirm.html'
success_url
=
reverse_lazy
(
'assets:cluster-list'
)
apps/assets/views/group.py
View file @
08e17884
...
...
@@ -101,6 +101,6 @@ class AssetGroupUpdateView(AdminUserRequiredMixin, UpdateView):
class
AssetGroupDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
template_name
=
'
assets/
delete_confirm.html'
template_name
=
'delete_confirm.html'
model
=
AssetGroup
success_url
=
reverse_lazy
(
'assets:asset-group-list'
)
apps/assets/views/system_user.py
View file @
08e17884
...
...
@@ -99,7 +99,7 @@ class SystemUserDetailView(AdminUserRequiredMixin, DetailView):
class
SystemUserDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
model
=
SystemUser
template_name
=
'
assets/
delete_confirm.html'
template_name
=
'delete_confirm.html'
success_url
=
reverse_lazy
(
'assets:system-user-list'
)
...
...
apps/common/imexp.py
0 → 100644
View file @
08e17884
# -*- coding: utf-8 -*-
#
import
codecs
import
csv
import
uuid
import
json
from
io
import
StringIO
import
warnings
import
chardet
from
django
import
forms
from
django.utils
import
timezone
from
django.views
import
View
from
django.core.cache
import
cache
from
django.core.exceptions
import
ImproperlyConfigured
,
FieldDoesNotExist
from
django.utils.encoding
import
force_text
from
django.http
import
Http404
,
HttpResponseRedirect
,
HttpResponse
,
JsonResponse
class
ModelExportPostMixin
:
"""
将用户post上来的数据转存到cache, 生成一个uuid, redirect 到GET URL
"""
redirect_url
=
None
error_message
=
'Json object not valid'
keyword
=
'spm'
cache_key
=
None
request
=
None
def
get_redirect_url
(
self
):
if
self
.
redirect_url
:
# Forcing possible reverse_lazy evaluation
url
=
force_text
(
self
.
redirect_url
)
else
:
msg
=
"No URL to redirect to. Provide a redirect_url."
raise
ImproperlyConfigured
(
msg
)
sep
=
"?"
if
url
.
find
(
'?'
)
else
'&'
url
=
'{}{}{}={}'
.
format
(
url
,
sep
,
self
.
keyword
,
self
.
cache_key
)
return
url
def
save_objects_id_to_cache
(
self
,
objects_id
):
self
.
cache_key
=
uuid
.
uuid4
()
.
hex
cache
.
set
(
self
.
cache_key
,
objects_id
,
300
)
return
self
.
cache_key
def
get_objects_id_from_request
(
self
):
try
:
objects_id
=
json
.
loads
(
self
.
request
.
body
)
except
ValueError
:
raise
Http404
(
self
.
error_message
)
return
objects_id
def
get_redirect_response
(
self
):
objects_id
=
self
.
get_objects_id_from_request
()
self
.
save_objects_id_to_cache
(
objects_id
)
url
=
self
.
get_redirect_url
()
return
HttpResponseRedirect
(
redirect_to
=
url
)
# View need implement it
# def post(self, request, *args, **kwargs):
# self.request = request
# return self.get_redirect_response()
class
MethodField
:
def
__init__
(
self
,
name
,
verbose_name
=
None
):
self
.
name
=
name
self
.
verbose_name
=
verbose_name
if
self
.
verbose_name
is
None
:
self
.
verbose_name
=
name
class
FieldCheckMeta
(
type
):
def
__new__
(
cls
,
name
,
bases
,
attrs
):
error
=
cls
.
validate_fields
(
attrs
)
if
not
error
:
return
super
()
.
__new__
(
cls
,
name
,
bases
,
attrs
)
else
:
raise
AttributeError
(
error
)
@staticmethod
def
validate_fields
(
attrs
):
model
=
attrs
.
get
(
'model'
)
fields
=
attrs
.
get
(
'fields'
)
if
model
is
None
or
fields
in
(
'__all__'
,
None
):
return
None
all_attr
=
[
attr
for
attr
in
dir
(
model
)
if
not
attr
.
startswith
(
'_'
)]
invalid_fields
=
[]
for
field
in
fields
:
if
field
not
in
all_attr
:
invalid_fields
.
append
(
field
)
if
not
invalid_fields
:
return
None
error
=
'model {} is not have `{}` attr, check `fields` setting'
.
format
(
model
.
_meta
.
model_name
,
', '
.
join
(
invalid_fields
)
)
return
error
class
ModelFieldsMixin
(
metaclass
=
FieldCheckMeta
):
model
=
None
fields
=
None
exclude
=
None
errors
=
None
__cleaned_fields_name
=
None
__is_valid
=
False
__defined_fields_name
=
None
def
get_define_fields_name
(
self
):
"""
Calculate fields, fields may be `__all__`, `(field1, field2)` or
set `exclude` so do that
:return: => list
"""
if
self
.
__defined_fields_name
:
return
self
.
__defined_fields_name
all_fields
=
[
field
.
name
for
field
in
self
.
model
.
_meta
.
fields
]
if
self
.
fields
==
'__all__'
:
return
all_fields
elif
self
.
fields
:
return
self
.
fields
elif
self
.
exclude
:
return
list
(
set
(
all_fields
)
-
set
(
self
.
exclude
))
else
:
return
[]
def
get_field
(
self
,
field_name
):
try
:
return
self
.
model
.
_meta
.
get_field
(
field_name
)
except
FieldDoesNotExist
:
attr
=
getattr
(
self
.
model
,
field_name
)
if
hasattr
(
attr
,
'verbose_name'
):
verbose_name
=
getattr
(
attr
,
'verbose_name'
)
else
:
verbose_name
=
field_name
return
MethodField
(
field_name
,
verbose_name
)
def
get_fields
(
self
,
cleaned_fields_name
):
"""
Get fields by fields name
:param cleaned_fields_name:
:return:
"""
fields
=
[]
for
name
in
cleaned_fields_name
:
fields
.
append
(
self
.
get_field
(
name
))
return
fields
def
get_define_fields
(
self
):
fields_name
=
self
.
get_define_fields_name
()
return
self
.
get_fields
(
fields_name
)
def
valid_field_name
(
self
,
field_name
):
if
not
hasattr
(
self
.
model
,
field_name
):
msg
=
"{} not has `{}` attr"
.
format
(
self
.
model
.
_meta
.
model_name
,
field_name
)
raise
AttributeError
(
msg
)
elif
field_name
not
in
self
.
get_define_fields_name
():
msg
=
'{} not allowed by server'
.
format
(
field_name
)
raise
AttributeError
(
msg
)
def
is_valid
(
self
,
fields
,
ignore_exception
=
True
):
self
.
__cleaned_fields_name
=
[]
self
.
errors
=
{}
for
field_name
in
fields
:
try
:
self
.
valid_field_name
(
field_name
)
self
.
__cleaned_fields_name
.
append
(
field_name
)
except
AttributeError
as
e
:
if
not
ignore_exception
:
self
.
errors
[
field_name
]
=
str
(
e
)
if
self
.
errors
:
self
.
__is_valid
=
False
return
False
else
:
self
.
__is_valid
=
True
return
True
@property
def
field_verbose_name_mapping
(
self
):
mapping
=
{}
for
field
in
self
.
get_define_fields
():
mapping
[
field
.
verbose_name
]
=
field
.
name
return
mapping
@property
def
cleaned_fields
(
self
):
if
self
.
__cleaned_fields_name
is
None
:
raise
AttributeError
(
"Run `is_valid` first"
)
if
not
self
.
__is_valid
:
warnings
.
warn
(
"Is not valid, result may be not complete"
)
return
self
.
get_fields
(
self
.
__cleaned_fields_name
)
class
ModelExportGetMixin
(
ModelFieldsMixin
):
filename_prefix
=
'jumpserver'
response
=
None
writer
=
None
model
=
None
objects_id
=
None
queryset
=
None
keyword
=
'spm'
def
get_filename
(
self
):
now
=
timezone
.
localtime
(
timezone
.
now
())
.
strftime
(
'
%
Y-
%
m-
%
d_
%
H-
%
M-
%
S'
)
filename
=
'{}-{}-{}.csv'
.
format
(
self
.
filename_prefix
,
self
.
model
.
_meta
.
model_name
,
now
)
return
filename
def
get_objects_id
(
self
):
cache_key
=
self
.
request
.
GET
.
get
(
self
.
keyword
)
self
.
objects_id
=
cache
.
get
(
cache_key
,
[])
return
self
.
objects_id
def
get_queryset
(
self
):
queryset
=
None
if
self
.
queryset
:
queryset
=
self
.
queryset
elif
self
.
queryset
is
None
:
queryset
=
self
.
model
.
_meta
.
default_manager
.
all
()
if
queryset
is
None
:
raise
AttributeError
(
"Get queryset failed, set `queryset` or `model`"
)
objects_id
=
self
.
get_objects_id
()
queryset_filtered
=
queryset
.
filter
(
id__in
=
objects_id
)
return
queryset_filtered
def
initial_csv_response
(
self
):
filename
=
self
.
get_filename
()
self
.
response
=
HttpResponse
(
content_type
=
'text/csv'
)
self
.
response
[
'Content-Disposition'
]
=
'attachment; filename="{}"'
.
format
(
filename
)
self
.
response
.
write
(
codecs
.
BOM_UTF8
)
self
.
writer
=
csv
.
writer
(
self
.
response
,
dialect
=
'excel'
,
quoting
=
csv
.
QUOTE_MINIMAL
)
header
=
[]
for
field
in
self
.
get_define_fields
():
if
hasattr
(
field
,
'verbose_name'
):
header
.
append
(
getattr
(
field
,
'verbose_name'
))
else
:
header
.
append
(
getattr
(
field
,
'name'
))
self
.
writer
.
writerow
(
header
)
def
make_csv_response
(
self
):
self
.
initial_csv_response
()
queryset
=
self
.
get_queryset
()
for
instance
in
queryset
:
data
=
[
getattr
(
instance
,
field
.
name
)
for
field
in
self
.
get_define_fields
()]
self
.
writer
.
writerow
(
data
)
return
self
.
response
class
FileForm
(
forms
.
Form
):
file
=
forms
.
FileField
()
class
ModelImportPostMixin
(
ModelFieldsMixin
):
form_context
=
"file"
csv_data
=
None
form_class
=
FileForm
stream
=
None
def
get_form
(
self
):
form
=
self
.
form_class
(
self
.
request
.
POST
)
if
form
.
is_valid
():
raise
Http404
(
"Form is not valid"
)
return
form
def
get_stream
(
self
):
self
.
stream
=
self
.
get_form
()
.
cleaned_data
[
self
.
form_context
]
return
self
.
stream
def
get_csv_data
(
self
,
stream
=
None
):
if
stream
is
None
:
stream
=
self
.
stream
result
=
chardet
.
detect
(
stream
.
read
())
stream
.
seek
(
0
)
raw_data
=
stream
.
read
()
.
decode
(
result
[
'encoding'
])
\
.
strip
(
codecs
.
BOM_UTF8
.
decode
())
csv_file
=
StringIO
(
raw_data
)
reader
=
csv
.
reader
(
csv_file
)
csv_data
=
[
row
for
row
in
reader
]
self
.
csv_data
=
csv_data
return
csv_data
def
cleaned_post_fields
(
self
):
fields
=
[]
header
=
self
.
csv_data
[
0
]
fields_name
=
[
self
.
field_verbose_name_mapping
.
get
(
v
)
for
v
in
header
]
for
name
in
fields_name
:
if
name
in
self
.
get_define_fields
():
fields
.
append
(
self
.
get_field
(
name
))
else
:
fields
.
append
(
None
)
return
fields
def
create_or_update
(
self
):
stream
=
self
.
get_stream
()
csv_data
=
self
.
get_csv_data
(
stream
)
cleaned_fields
=
self
.
cleaned_post_fields
()
class
ModelImportView
(
ModelImportPostMixin
):
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
return
self
.
create_or_update
()
class
ModelExportView
(
ModelExportPostMixin
,
ModelExportGetMixin
,
View
):
model
=
None
filename_prefix
=
'jumpserver'
def
post
(
self
,
request
,
*
args
,
**
kwargs
):
return
self
.
get_redirect_response
()
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
request
=
request
response
=
self
.
make_csv_response
()
return
response
apps/locale/zh/LC_MESSAGES/django.po
View file @
08e17884
...
...
@@ -1067,13 +1067,13 @@ msgstr "配置"
msgid "Location"
msgstr "位置"
#: assets/templates/
assets/
delete_confirm.html:6
#: perms/templates/
perms/
delete_confirm.html:6
#: assets/templates/delete_confirm.html:6
#: perms/templates/delete_confirm.html:6
#: users/templates/users/user_delete_confirm.html:6
msgid "Confirm delete"
msgstr "确认删除"
#: assets/templates/
assets/
delete_confirm.html:11
#: assets/templates/delete_confirm.html:11
msgid "Are you sure delete"
msgstr "您确定删除吗?"
...
...
apps/perms/views.py
View file @
08e17884
...
...
@@ -19,7 +19,7 @@ from .hands import AdminUserRequiredMixin, User, UserGroup, SystemUser, \
Asset
,
AssetGroup
from
.models
import
AssetPermission
from
.forms
import
AssetPermissionForm
from
.utils
import
associate_system_users_and_assets
#
from .utils import associate_system_users_and_assets
class
AssetPermissionListView
(
AdminUserRequiredMixin
,
ListView
):
...
...
@@ -87,15 +87,15 @@ class AssetPermissionCreateView(AdminUserRequiredMixin,
'successfully.'
.
format
(
url
=
url
,
name
=
self
.
object
.
name
))
return
success_message
def
form_valid
(
self
,
form
):
assets
=
form
.
cleaned_data
[
'assets'
]
asset_groups
=
form
.
cleaned_data
[
'asset_group
s'
]
system_users
=
form
.
cleaned_data
[
'system_user
s'
]
associate_system_users_and_assets
(
system_users
,
assets
,
asset_groups
)
response
=
super
(
AssetPermissionCreateView
,
self
)
.
form_valid
(
form
)
self
.
object
.
created_by
=
self
.
request
.
user
.
name
self
.
object
.
save
()
return
response
# Todo: When create push system user
# def form_valid(self, form):
# assets = form.cleaned_data['asset
s']
# asset_groups = form.cleaned_data['asset_group
s']
# system_users = form.cleaned_data['system_users']
#
response = super(AssetPermissionCreateView, self).form_valid(form)
#
self.object.created_by = self.request.user.name
#
self.object.save()
#
return response
class
AssetPermissionUpdateView
(
AdminUserRequiredMixin
,
UpdateView
):
...
...
@@ -150,7 +150,7 @@ class AssetPermissionDetailView(AdminUserRequiredMixin, DetailView):
class
AssetPermissionDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
model
=
AssetPermission
template_name
=
'
perms/
delete_confirm.html'
template_name
=
'delete_confirm.html'
success_url
=
reverse_lazy
(
'perms:asset-permission-list'
)
...
...
apps/templates/delete_confirm.html
0 → 100644
View file @
08e17884
{% load i18n %}
<!DOCTYPE html>
<html
lang=
"en"
>
<head>
<meta
charset=
"UTF-8"
>
<title>
{% trans 'Confirm delete' %}
</title>
</head>
<body>
<form
action=
""
method=
"post"
>
{% csrf_token %}
<p>
{% trans 'Are you sure delete' %}
<b>
{{ object.name }}
</b>
?
</p>
<input
type=
"submit"
value=
"Confirm"
/>
</form>
</body>
</html>
\ No newline at end of file
apps/terminal/templates/terminal/command_list.html
View file @
08e17884
...
...
@@ -2,7 +2,7 @@
{% load i18n %}
{% load static %}
{% load common_tags %}
{% block c
ontent_left_head
%}
{% block c
ustom_head_css_js
%}
<link
href=
"{% static "
css
/
plugins
/
footable
/
footable
.
core
.
css
"
%}"
rel=
"stylesheet"
>
<link
href=
"{% static 'css/plugins/datepicker/datepicker3.css' %}"
rel=
"stylesheet"
>
<style>
...
...
@@ -12,6 +12,10 @@
</style>
{% endblock %}
{% block content_left_head %}
123
{% endblock %}
{% block table_search %}
<form
id=
"search_form"
method=
"get"
action=
""
class=
"pull-right form-inline"
>
<div
class=
"form-group"
id=
"date"
>
...
...
@@ -26,7 +30,7 @@
<select
class=
"select2 form-control"
name=
"username"
>
<option
value=
""
>
{% trans 'User' %}
</option>
{% for u in user_list %}
<option
value=
"{{ u }}"
{%
if
u
ser =
=
u
%}
selected
{%
endif
%}
>
{{ u }}
</option>
<option
value=
"{{ u }}"
{%
if
u
=
=
username
%}
selected
{%
endif
%}
>
{{ u }}
</option>
{% endfor %}
</select>
</div>
...
...
apps/terminal/templates/terminal/session_list.html
View file @
08e17884
...
...
@@ -2,7 +2,7 @@
{% load i18n %}
{% load static %}
{% load terminal_tags %}
{% block c
ontent_left_head
%}
{% block c
ustom_head_css_js
%}
<link
href=
"{% static 'css/plugins/datepicker/datepicker3.css' %}"
rel=
"stylesheet"
>
<style>
#search_btn
{
...
...
@@ -11,6 +11,9 @@
</style>
{% endblock %}
{% block content_left_head %}
{% endblock %}
{% block table_search %}
<form
id=
"search_form"
method=
"get"
action=
""
class=
"pull-right form-inline"
>
...
...
@@ -26,7 +29,7 @@
<select
class=
"select2 form-control"
name=
"user"
>
<option
value=
""
>
{% trans 'User' %}
</option>
{% for u in user_list %}
<option
value=
"{{ u }}"
{%
if
u =
=
user
%}
selected
{%
endif
%}
>
{{ u }}
</option>
<option
value=
"{{ u }}"
{%
if
u =
=
user
name
%}
selected
{%
endif
%}
>
{{ u }}
</option>
{% endfor %}
</select>
</div>
...
...
@@ -106,10 +109,17 @@
function
success
()
{
window
.
setTimeout
(
function
()
{
window
.
location
.
reload
()
},
3
00
)
},
10
00
)
}
var
success_message
=
'{% trans "Terminate task send, waiting ..." %}'
;
var
the_url
=
"{% url 'api-terminal:tasks-list' %}"
;
APIUpdateAttr
({
url
:
the_url
,
method
:
'POST'
,
body
:
JSON
.
stringify
(
data
),
success
:
success
,
success_message
:
'Terminate success'
});
APIUpdateAttr
({
url
:
the_url
,
method
:
'POST'
,
body
:
JSON
.
stringify
(
data
),
success
:
success
,
success_message
:
success_message
});
}
$
(
document
).
ready
(
function
()
{
$
(
'table'
).
DataTable
({
...
...
@@ -118,7 +128,9 @@
"bInfo"
:
false
,
"order"
:
[]
});
$
(
'.select2'
).
select2
();
$
(
'.select2'
).
select2
({
dropdownAutoWidth
:
true
});
$
(
'#date .input-daterange'
).
datepicker
({
dateFormat
:
'mm/dd/yy'
,
keyboardNavigation
:
false
,
...
...
@@ -135,12 +147,6 @@
terminal
:
terminal_id
};
terminateSession
(
data
)
}).
on
(
'click'
,
'#btn_bulk_update'
,
function
()
{
var
data
=
[];
$
(
'.cbx-term:checked'
).
each
(
function
()
{
data
.
push
({
proxy_log_id
:
$
(
this
).
attr
(
'value'
)})
});
terminateSession
(
data
)
})
</script>
{% endblock %}
...
...
apps/terminal/templates/terminal/terminal_list.html
View file @
08e17884
...
...
@@ -2,7 +2,6 @@
{% load i18n static %}
{% block custom_head_css_js %}
{{ block.super }}
<style>
div
.dataTables_wrapper
div
.dataTables_filter
,
.dataTables_length
{
...
...
@@ -15,9 +14,10 @@
#modal
.modal-body
{
max-height
:
200px
;
}
</style>
{% endblock %}
{% block table_search %}{% endblock %}
{% block table_container %}
{#
<div
class=
"uc pull-left m-l-5 m-r-5"
><a
href=
"{% url "
users:user-create
"
%}"
class=
"btn btn-sm btn-primary"
>
{% trans "Create user" %}
</a></div>
#}
<table
class=
"table table-striped table-bordered table-hover "
id=
"terminal_list_table"
>
<thead>
<tr>
...
...
@@ -40,12 +40,11 @@
</tbody>
</table>
{% include 'terminal/terminal_modal_accept.html' %}
{% endblock %}
{% block custom_foot_js %}
<script
src=
"{% static 'js/jquery.form.min.js' %}"
></script>
<script>
$
(
document
).
ready
(
function
()
{
function
initTable
()
{
var
options
=
{
ele
:
$
(
'#terminal_list_table'
),
buttons
:
[],
...
...
@@ -100,8 +99,11 @@ $(document).ready(function(){
op_html
:
$
(
'#actions'
).
html
()
};
jumpserver
.
initDataTable
(
options
);
}
$
(
document
).
ready
(
function
(){
initTable
();
$
(
'#btn_terminal_accept'
).
click
(
function
()
{
}).
on
(
'click'
,
'#btn-confirm'
,
function
()
{
var
$form
=
$
(
'#form_terminal_accept'
);
function
success
(
data
,
textStatus
,
jqXHR
)
{
if
(
data
.
success
===
true
)
{
...
...
@@ -111,8 +113,6 @@ $(document).ready(function(){
}
}
$form
.
ajaxSubmit
({
success
:
success
});
})
}).
on
(
'click'
,
'.btn-del'
,
function
(){
var
$this
=
$
(
this
);
var
id
=
$this
.
data
(
'id'
);
...
...
@@ -124,8 +124,7 @@ $(document).ready(function(){
var
$this
=
$
(
this
);
var
terminal_id
=
$this
.
data
(
'id'
);
var
the_url
=
"{% url 'api-terminal:terminal-detail' pk=DEFAULT_PK %}"
.
replace
(
'{{ DEFAULT_PK }}'
,
terminal_id
);
var
post_url
=
$
(
'#form_terminal_accept'
).
attr
(
'action'
).
replace
(
'{{ DEFAULT_PK }}'
,
terminal_id
);
console
.
log
(
post_url
);
var
post_url
=
"{% url 'terminal:terminal-accept' pk=DEFAULT_PK %}"
.
replace
(
'{{ DEFAULT_PK }}'
,
terminal_id
);
$
.
ajax
({
url
:
the_url
,
method
:
'GET'
,
...
...
apps/terminal/templates/terminal/terminal_modal_accept.html
View file @
08e17884
...
...
@@ -5,7 +5,7 @@
{% block modal_title%}{% trans "Accept terminal registration" %}{% endblock %}
{% block modal_body %}
{% load bootstrap3 %}
<form
action=
"
{% url 'terminal:terminal-accept' pk="
{{
DEFAULT_PK
}}"
%}
"
method=
"post"
class=
"form-horizontal"
id=
"form_terminal_accept"
enctype=
"multipart/form-data"
>
<form
action=
""
method=
"post"
class=
"form-horizontal"
id=
"form_terminal_accept"
enctype=
"multipart/form-data"
>
{% csrf_token %}
<p
class=
"alert alert-danger"
id=
"modal-error"
style=
"display: none"
></p>
{% bootstrap_field form.name layout="horizontal" %}
...
...
@@ -16,4 +16,4 @@
</form>
{% endblock %}
{% block modal_confirm_id %}btn_terminal_accept{% endblock %}
\ No newline at end of file
{% block modal_confirm_id %}btn-confirm{% endblock %}
\ No newline at end of file
apps/terminal/urls/api_urls.py
View file @
08e17884
...
...
@@ -10,8 +10,8 @@ from .. import api
app_name
=
'terminal'
router
=
routers
.
DefaultRouter
()
router
.
register
(
r'v1/terminal/(?P<terminal>[
0-9]+
)?/?status'
,
api
.
StatusViewSet
,
'terminal-status'
)
router
.
register
(
r'v1/terminal/(?P<terminal>
[0-9]+
)?/?sessions'
,
api
.
SessionViewSet
,
'terminal-sessions'
)
router
.
register
(
r'v1/terminal/(?P<terminal>[
a-zA-Z0-9\-]{36}
)?/?status'
,
api
.
StatusViewSet
,
'terminal-status'
)
router
.
register
(
r'v1/terminal/(?P<terminal>
a-zA-Z0-9\-]{36}
)?/?sessions'
,
api
.
SessionViewSet
,
'terminal-sessions'
)
router
.
register
(
r'v1/tasks'
,
api
.
TaskViewSet
,
'tasks'
)
router
.
register
(
r'v1/terminal'
,
api
.
TerminalViewSet
,
'terminal'
)
router
.
register
(
r'v1/command'
,
api
.
CommandViewSet
,
'command'
)
...
...
apps/terminal/views/command.py
View file @
08e17884
...
...
@@ -70,7 +70,7 @@ class CommandListView(ListView):
'command'
:
self
.
command
,
'date_from'
:
self
.
date_from_s
,
'date_to'
:
self
.
date_to_s
,
'user'
:
self
.
user
,
'user
name
'
:
self
.
user
,
'asset'
:
self
.
asset
,
'system_user'
:
self
.
system_user
,
}
...
...
apps/terminal/views/session.py
View file @
08e17884
...
...
@@ -78,7 +78,7 @@ class SessionListView(AdminUserRequiredMixin, ListView):
'system_user_list'
:
utils
.
get_system_user_list_from_cache
(),
'date_from'
:
self
.
date_from_s
,
'date_to'
:
self
.
date_to_s
,
'user'
:
self
.
user
,
'user
name
'
:
self
.
user
,
'asset'
:
self
.
asset
,
'system_user'
:
self
.
system_user
,
}
...
...
@@ -122,6 +122,7 @@ class SessionOfflineListView(SessionListView):
class
SessionDetailView
(
SingleObjectMixin
,
ListView
):
template_name
=
'terminal/session_detail.html'
model
=
Session
object
=
None
def
get_queryset
(
self
):
self
.
object
=
self
.
get_object
()
...
...
apps/terminal/views/terminal.py
View file @
08e17884
...
...
@@ -62,14 +62,14 @@ class TerminalDetailView(LoginRequiredMixin, DetailView):
class
TerminalDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
model
=
Terminal
template_name
=
'
assets/
delete_confirm.html'
template_name
=
'delete_confirm.html'
success_url
=
reverse_lazy
(
'terminal:terminal-list'
)
class
TerminalAcceptView
(
AdminUserRequiredMixin
,
JSONResponseMixin
,
UpdateView
):
model
=
Terminal
form_class
=
TerminalForm
template_name
=
'
Terminal/terminal_modal_tes
t.html'
template_name
=
'
terminal/terminal_modal_accep
t.html'
def
form_valid
(
self
,
form
):
terminal
=
form
.
save
()
...
...
apps/users/models/authentication.py
View file @
08e17884
...
...
@@ -49,5 +49,4 @@ class LoginLog(models.Model):
datetime
=
models
.
DateTimeField
(
auto_now_add
=
True
,
verbose_name
=
_
(
'Date login'
))
class
Meta
:
db_table
=
'login_log'
ordering
=
[
'-datetime'
,
'username'
]
apps/users/models/user.py
View file @
08e17884
...
...
@@ -38,9 +38,9 @@ class User(AbstractUser):
phone
=
models
.
CharField
(
max_length
=
20
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Phone'
))
enable_otp
=
models
.
BooleanField
(
default
=
False
,
verbose_name
=
_
(
'Enable OTP'
))
secret_key_otp
=
models
.
CharField
(
max_length
=
16
,
blank
=
True
)
# Todo:
private_key may be not use
d
_private_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'
ssh p
rivate key'
))
_public_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'
ssh p
ublic key'
))
# Todo:
Auto generate key, let user downloa
d
_private_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'
P
rivate key'
))
_public_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'
P
ublic key'
))
comment
=
models
.
TextField
(
max_length
=
200
,
blank
=
True
,
verbose_name
=
_
(
'Comment'
))
is_first_login
=
models
.
BooleanField
(
default
=
False
)
date_expired
=
models
.
DateTimeField
(
default
=
date_expired_default
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Date expired'
))
...
...
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