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
76df1de6
Commit
76df1de6
authored
Dec 21, 2017
by
ibuler
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Bugfix] 修复一些明显的bug
parent
5a929721
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
36 changed files
with
253 additions
and
165 deletions
+253
-165
api.py
apps/assets/api.py
+2
-0
forms.py
apps/assets/forms.py
+8
-9
asset.py
apps/assets/models/asset.py
+1
-5
group.py
apps/assets/models/group.py
+0
-1
user.py
apps/assets/models/user.py
+9
-7
tasks.py
apps/assets/tasks.py
+0
-6
asset_detail.html
apps/assets/templates/assets/asset_detail.html
+15
-15
asset_list.html
apps/assets/templates/assets/asset_list.html
+41
-39
cluster_list.html
apps/assets/templates/assets/cluster_list.html
+1
-0
system_user_list.html
apps/assets/templates/assets/system_user_list.html
+7
-0
user_asset_list.html
apps/assets/templates/assets/user_asset_list.html
+0
-0
utils.py
apps/assets/utils.py
+10
-0
models.py
apps/common/models.py
+0
-3
utils.py
apps/common/utils.py
+4
-4
settings.py
apps/jumpserver/settings.py
+0
-1
callback.py
apps/ops/ansible/callback.py
+8
-11
inventory.py
apps/ops/ansible/inventory.py
+1
-0
task_list.html
apps/ops/templates/ops/task_list.html
+2
-2
utils.py
apps/ops/utils.py
+5
-2
forms.py
apps/perms/forms.py
+31
-0
asset_permission_create_update.html
...perms/templates/perms/asset_permission_create_update.html
+1
-1
asset_permission_list.html
apps/perms/templates/perms/asset_permission_list.html
+6
-0
views.py
apps/perms/views.py
+24
-4
admin.png
apps/static/img/avatar/admin.png
+0
-0
user.png
apps/static/img/avatar/user.png
+0
-0
jumpserver.js
apps/static/js/jumpserver.js
+15
-9
_left_side_bar.html
apps/templates/_left_side_bar.html
+1
-1
_nav.html
apps/templates/_nav.html
+6
-8
_user_profile.html
apps/templates/_user_profile.html
+19
-0
tasks.py
apps/terminal/tasks.py
+0
-6
command_list.html
apps/terminal/templates/terminal/command_list.html
+2
-3
terminal_list.html
apps/terminal/templates/terminal/terminal_list.html
+1
-7
forms.py
apps/users/forms.py
+21
-12
user.py
apps/users/models/user.py
+6
-5
login_log_list.html
apps/users/templates/users/login_log_list.html
+5
-3
user_list.html
apps/users/templates/users/user_list.html
+1
-1
No files found.
apps/assets/api.py
View file @
76df1de6
...
...
@@ -44,9 +44,11 @@ class AssetViewSet(IDInFilterMixin, BulkModelViewSet):
else
:
assets_granted
=
get_user_granted_assets
(
self
.
request
.
user
)
queryset
=
self
.
queryset
.
filter
(
id__in
=
[
asset
.
id
for
asset
in
assets_granted
])
cluster_id
=
self
.
request
.
query_params
.
get
(
'cluster_id'
)
asset_group_id
=
self
.
request
.
query_params
.
get
(
'asset_group_id'
)
admin_user_id
=
self
.
request
.
query_params
.
get
(
'admin_user_id'
)
if
cluster_id
:
queryset
=
queryset
.
filter
(
cluster__id
=
cluster_id
)
if
asset_group_id
:
...
...
apps/assets/forms.py
View file @
76df1de6
# coding:utf-8
import
uuid
from
django
import
forms
from
django.utils.translation
import
gettext_lazy
as
_
...
...
@@ -9,7 +8,6 @@ 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
):
...
...
@@ -57,11 +55,11 @@ class AssetUpdateForm(forms.ModelForm):
class
AssetBulkUpdateForm
(
forms
.
ModelForm
):
assets
=
forms
.
MultipleChoiceField
(
assets
=
forms
.
M
odelM
ultipleChoiceField
(
required
=
True
,
help_text
=
'* required'
,
label
=
_
(
'Select assets'
),
choices
=
[(
asset
.
id
,
asset
.
hostname
)
for
asset
in
Asset
.
objects
.
all
()]
,
queryset
=
Asset
.
objects
.
all
()
,
widget
=
forms
.
SelectMultiple
(
attrs
=
{
'class'
:
'select2'
,
...
...
@@ -94,10 +92,9 @@ class AssetBulkUpdateForm(forms.ModelForm):
cleaned_data
=
{
k
:
v
for
k
,
v
in
self
.
cleaned_data
.
items
()
if
k
in
changed_fields
}
print
(
cleaned_data
)
assets_id
=
cleaned_data
.
pop
(
'assets'
)
assets
=
cleaned_data
.
pop
(
'assets'
)
groups
=
cleaned_data
.
pop
(
'groups'
,
[])
assets
=
Asset
.
objects
.
filter
(
id__in
=
assets_id
)
assets
=
Asset
.
objects
.
filter
(
id__in
=
[
asset
.
id
for
asset
in
assets
]
)
assets
.
update
(
**
cleaned_data
)
if
groups
:
for
asset
in
assets
:
...
...
@@ -175,16 +172,18 @@ class AdminUserForm(forms.ModelForm):
password
=
None
if
private_key
:
public_key
=
ssh_pubkey_gen
(
private_key
)
public_key
=
ssh_pubkey_gen
(
private_key
,
password
=
password
)
admin_user
.
set_auth
(
password
=
password
,
public_key
=
public_key
,
private_key
=
private_key
)
return
admin_user
def
clean_private_key_file
(
self
):
private_key_file
=
self
.
cleaned_data
[
'private_key_file'
]
password
=
self
.
cleaned_data
[
'password'
]
if
private_key_file
:
private_key
=
private_key_file
.
read
()
if
not
validate_ssh_private_key
(
private_key
):
if
not
validate_ssh_private_key
(
private_key
,
password
):
raise
forms
.
ValidationError
(
_
(
'Invalid private key'
))
return
private_key
return
private_key_file
...
...
apps/assets/models/asset.py
View file @
76df1de6
...
...
@@ -18,10 +18,6 @@ __all__ = ['Asset']
logger
=
logging
.
getLogger
(
__name__
)
def
get_default_cluster
():
return
Cluster
.
initial
()
class
Asset
(
models
.
Model
):
# Todo: Move them to settings
STATUS_CHOICES
=
(
...
...
@@ -48,7 +44,7 @@ class Asset(models.Model):
hostname
=
models
.
CharField
(
max_length
=
128
,
unique
=
True
,
verbose_name
=
_
(
'Hostname'
))
port
=
models
.
IntegerField
(
default
=
22
,
verbose_name
=
_
(
'Port'
))
groups
=
models
.
ManyToManyField
(
AssetGroup
,
blank
=
True
,
related_name
=
'assets'
,
verbose_name
=
_
(
'Asset groups'
))
cluster
=
models
.
ForeignKey
(
Cluster
,
blank
=
True
,
null
=
True
,
related_name
=
'assets'
,
on_delete
=
models
.
SET_NULL
,
verbose_name
=
_
(
'Cluster'
)
,
)
cluster
=
models
.
ForeignKey
(
Cluster
,
blank
=
True
,
null
=
True
,
related_name
=
'assets'
,
on_delete
=
models
.
SET_NULL
,
verbose_name
=
_
(
'Cluster'
))
is_active
=
models
.
BooleanField
(
default
=
True
,
verbose_name
=
_
(
'Is active'
))
type
=
models
.
CharField
(
choices
=
TYPE_CHOICES
,
max_length
=
16
,
blank
=
True
,
null
=
True
,
default
=
'Server'
,
verbose_name
=
_
(
'Asset type'
),)
env
=
models
.
CharField
(
choices
=
ENV_CHOICES
,
max_length
=
8
,
blank
=
True
,
null
=
True
,
default
=
'Prod'
,
verbose_name
=
_
(
'Asset environment'
),)
...
...
apps/assets/models/group.py
View file @
76df1de6
...
...
@@ -10,7 +10,6 @@ from django.db import models
import
logging
from
django.utils.translation
import
ugettext_lazy
as
_
from
.user
import
SystemUser
__all__
=
[
'AssetGroup'
]
logger
=
logging
.
getLogger
(
__name__
)
...
...
apps/assets/models/user.py
View file @
76df1de6
...
...
@@ -66,16 +66,15 @@ class AssetUser(models.Model):
@property
def
private_key_file
(
self
):
if
not
self
.
private_key
:
if
not
self
.
private_key
_obj
:
return
None
project_dir
=
settings
.
PROJECT_DIR
tmp_dir
=
os
.
path
.
join
(
project_dir
,
'tmp'
)
key_str
=
signer
.
unsign
(
self
.
_private_key
)
key_name
=
md5
(
key_str
.
encode
(
'utf-8'
))
.
hexdigest
()
key_name
=
'.'
+
md5
(
key_str
.
encode
(
'utf-8'
))
.
hexdigest
()
key_path
=
os
.
path
.
join
(
tmp_dir
,
key_name
)
if
not
os
.
path
.
exists
(
key_path
):
with
open
(
key_path
,
'w'
)
as
f
:
f
.
write
(
key_str
)
self
.
private_key_obj
.
write_private_key_file
(
key_path
)
os
.
chmod
(
key_path
,
0
o400
)
return
key_path
...
...
@@ -105,7 +104,6 @@ class AssetUser(models.Model):
update_fields
.
append
(
'_public_key'
)
if
update_fields
:
print
(
update_fields
)
self
.
save
(
update_fields
=
update_fields
)
def
auto_gen_auth
(
self
):
...
...
@@ -149,7 +147,11 @@ class AdminUser(AssetUser):
@property
def
become_pass
(
self
):
return
signer
.
unsign
(
self
.
_become_pass
)
password
=
signer
.
unsign
(
self
.
_become_pass
)
if
password
:
return
password
else
:
return
""
@become_pass.setter
def
become_pass
(
self
,
password
):
...
...
@@ -199,7 +201,7 @@ class SystemUser(AssetUser):
(
'K'
,
'Public key'
),
)
cluster
=
models
.
ManyToManyField
(
'assets.Cluster'
,
blank
=
True
,
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
priority
=
models
.
IntegerField
(
default
=
10
,
verbose_name
=
_
(
"Priority"
))
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'
))
...
...
apps/assets/tasks.py
View file @
76df1de6
...
...
@@ -402,12 +402,6 @@ def push_system_user_on_auth_change(sender, instance=None, update_fields=None, *
push_system_user_to_cluster_assets
.
delay
(
instance
,
task_name
)
@receiver
(
on_app_ready
,
dispatch_uid
=
"my_unique_identifier"
)
def
test_admin_user_on_app_ready
(
sender
,
**
kwargs
):
logger
.
debug
(
"Receive app ready signal, test admin connectability"
)
test_admin_user_connectability_period
.
delay
()
celery_app
.
conf
[
'CELERYBEAT_SCHEDULE'
]
.
update
(
{
'update_assets_hardware_period'
:
{
...
...
apps/assets/templates/assets/asset_detail.html
View file @
76df1de6
...
...
@@ -63,7 +63,7 @@
</tr>
<tr>
<td>
{% trans 'Public IP' %}:
</td>
<td><b>
{{ asset.public_ip }}
</b></td>
<td><b>
{{ asset.public_ip
|default:""
}}
</b></td>
</tr>
<tr>
<td>
{% trans 'Port' %}:
</td>
...
...
@@ -74,12 +74,12 @@
{% if asset.admin_user_avail %}
<td><b>
{{ asset.admin_user_avail.name }}
</b></td>
{% else %}
<td><
b>
None
</b><
/td>
<td></td>
{% endif %}
</tr>
<tr>
<td>
{% trans 'Remote card IP' %}:
</td>
<td><b>
{{ asset.remote_card_ip }}
</b></td>
<td><b>
{{ asset.remote_card_ip
|default:""
}}
</b></td>
</tr>
<tr>
<td>
{% trans 'Cluster' %}:
</td>
...
...
@@ -87,39 +87,39 @@
</tr>
<tr>
<td>
{% trans 'Cabinet number' %}:
</td>
<td><b>
{{ asset.cabinet_no }}
</b></td>
<td><b>
{{ asset.cabinet_no
|default:""
}}
</b></td>
</tr>
<tr>
<td>
{% trans 'Cabinet position' %}:
</td>
<td><b>
{{ asset.cabinet_pos }}
</b></td>
<td><b>
{{ asset.cabinet_pos
|default:""
}}
</b></td>
</tr>
<tr>
<td>
{% trans 'Vendor' %}:
</td>
<td><b>
{{ asset.vendor }}
</b></td>
<td><b>
{{ asset.vendor
|default:""
}}
</b></td>
</tr>
<tr>
<td>
{% trans 'Model' %}:
</td>
<td><b>
{{ asset.model }}
</b></td>
<td><b>
{{ asset.model
|default:""
}}
</b></td>
</tr>
<tr>
<td>
{% trans 'CPU' %}:
</td>
<td><b>
{{ asset.cpu_model
}} {{ asset.cpu_count }}*{{ asset.cpu_cores
}}
</b></td>
<td><b>
{{ asset.cpu_model
|default:"" }} {{ asset.cpu_count|default:"" }}*{{ asset.cpu_cores|default:""
}}
</b></td>
</tr>
<tr>
<td>
{% trans 'Memory' %}:
</td>
<td><b>
{{ asset.memory }}
</b></td>
<td><b>
{{ asset.memory
|default:""
}}
</b></td>
</tr>
<tr>
<td>
{% trans 'Disk' %}:
</td>
<td><b>
{{ asset.disk_total }}
</b></td>
<td><b>
{{ asset.disk_total
|default:""
}}
</b></td>
</tr>
<tr>
<td>
{% trans 'Platform' %}:
</td>
<td><b>
{{ asset.platform }}
</b></td>
<td><b>
{{ asset.platform
|default:""
}}
</b></td>
</tr>
<tr>
<td>
{% trans 'OS' %}:
</td>
<td><b>
{{ asset.os
}} {{ asset.os_version }} {{ asset.os_arch
}}
</b></td>
<td><b>
{{ asset.os
|default:"" }} {{ asset.os_version|default:"" }} {{ asset.os_arch|default:""
}}
</b></td>
</tr>
<tr>
<td>
{% trans 'Asset status' %}:
</td>
...
...
@@ -127,7 +127,7 @@
</tr>
<tr>
<td>
{% trans 'Is active' %}:
</td>
<td><b>
{{ asset.is_active }}
</b></td>
<td><b>
{{ asset.is_active
|yesno:"Yes,No"
}}
</b></td>
</tr>
<tr>
<td>
{% trans 'Asset type' %}:
</td>
...
...
@@ -139,11 +139,11 @@
</tr>
<tr>
<td>
{% trans 'Serial number' %}:
</td>
<td><b>
{{ asset.sn }}
</b></td>
<td><b>
{{ asset.sn
|default:""
}}
</b></td>
</tr>
<tr>
<td>
{% trans 'Asset number' %}:
</td>
<td><b>
{{ asset.number }}
</b></td>
<td><b>
{{ asset.number
|default:""
}}
</b></td>
</tr>
<tr>
<td>
{% trans 'Created by' %}:
</td>
...
...
apps/assets/templates/assets/asset_list.html
View file @
76df1de6
...
...
@@ -64,8 +64,7 @@
<script
src=
"{% static 'js/jquery.form.min.js' %}"
></script>
<script
type=
"text/javascript"
>
$
(
document
).
ready
(
function
(){
function
initTable
()
{
var
options
=
{
ele
:
$
(
'#asset_list_table'
),
columnDefs
:
[
...
...
@@ -103,48 +102,51 @@ $(document).ready(function(){
{
data
:
"is_active"
},
{
data
:
"is_connective"
},
{
data
:
"id"
}],
op_html
:
$
(
'#actions'
).
html
()
};
var
table
=
jumpserver
.
initDataTable
(
options
);
return
jumpserver
.
initDataTable
(
options
);
}
$
(
'.btn_export'
).
click
(
function
()
{
var
assets
=
[];
var
rows
=
table
.
rows
(
'.selected'
).
data
();
$
.
each
(
rows
,
function
(
index
,
obj
)
{
assets
.
push
(
obj
.
id
)
});
console
.
log
(
assets
);
$
.
ajax
({
url
:
"{% url "
assets
:
asset
-
export
" %}"
,
method
:
'POST'
,
data
:
JSON
.
stringify
({
assets_id
:
assets
}),
dataType
:
"json"
,
success
:
function
(
data
,
textStatus
)
{
window
.
open
(
data
.
redirect
)
},
error
:
function
()
{
toastr
.
error
(
'Export failed'
);
}
})
$
(
document
).
ready
(
function
(){
initTable
();
})
.
on
(
'click'
,
'#btn_export'
,
function
()
{
var
$data_table
=
$
(
'#asset_list_table'
).
DataTable
();
var
rows
=
$data_table
.
rows
(
'.selected'
).
data
();
var
assets
=
[];
$
.
each
(
rows
,
function
(
index
,
obj
)
{
assets
.
push
(
obj
.
id
)
});
$
(
'#btn_asset_import'
).
click
(
function
()
{
var
$form
=
$
(
'#fm_asset_import'
);
$form
.
find
(
'.help-block'
).
remove
();
function
success
(
data
)
{
if
(
data
.
valid
===
false
)
{
$
(
'<span />'
,
{
class
:
'help-block text-danger'
}).
html
(
data
.
msg
).
insertAfter
(
$
(
'#id_assets'
));
}
else
{
$
(
'#id_created'
).
html
(
data
.
created_info
);
$
(
'#id_created_detail'
).
html
(
data
.
created
.
join
(
', '
));
$
(
'#id_updated'
).
html
(
data
.
updated_info
);
$
(
'#id_updated_detail'
).
html
(
data
.
updated
.
join
(
', '
));
$
(
'#id_failed'
).
html
(
data
.
failed_info
);
$
(
'#id_failed_detail'
).
html
(
data
.
failed
.
join
(
', '
));
var
$data_table
=
$
(
'#asset_list_table'
).
DataTable
();
$data_table
.
ajax
.
reload
();
}
$
.
ajax
({
url
:
"{% url "
assets
:
asset
-
export
" %}"
,
method
:
'POST'
,
data
:
JSON
.
stringify
({
assets_id
:
assets
}),
dataType
:
"json"
,
success
:
function
(
data
,
textStatus
)
{
window
.
open
(
data
.
redirect
)
},
error
:
function
()
{
toastr
.
error
(
'Export failed'
);
}
$form
.
ajaxSubmit
({
success
:
success
});
})
})
.
on
(
'click'
,
'#btn_import'
,
function
()
{
var
$form
=
$
(
'#fm_asset_import'
);
$form
.
find
(
'.help-block'
).
remove
();
function
success
(
data
)
{
if
(
data
.
valid
===
false
)
{
$
(
'<span />'
,
{
class
:
'help-block text-danger'
}).
html
(
data
.
msg
).
insertAfter
(
$
(
'#id_assets'
));
}
else
{
$
(
'#id_created'
).
html
(
data
.
created_info
);
$
(
'#id_created_detail'
).
html
(
data
.
created
.
join
(
', '
));
$
(
'#id_updated'
).
html
(
data
.
updated_info
);
$
(
'#id_updated_detail'
).
html
(
data
.
updated
.
join
(
', '
));
$
(
'#id_failed'
).
html
(
data
.
failed_info
);
$
(
'#id_failed_detail'
).
html
(
data
.
failed
.
join
(
', '
));
var
$data_table
=
$
(
'#asset_list_table'
).
DataTable
();
$data_table
.
ajax
.
reload
();
}
}
$form
.
ajaxSubmit
({
success
:
success
});
})
.
on
(
'click'
,
'.btn_asset_delete'
,
function
()
{
var
$this
=
$
(
this
);
...
...
apps/assets/templates/assets/cluster_list.html
View file @
76df1de6
...
...
@@ -4,6 +4,7 @@
<link
href=
"{% static 'css/plugins/select2/select2.min.css' %}"
rel=
"stylesheet"
>
<script
src=
"{% static 'js/plugins/select2/select2.full.min.js' %}"
></script>
{% endblock %}
{% block table_search %}{% endblock %}
{% block table_container %}
<div
class=
"uc pull-left m-r-5"
>
...
...
apps/assets/templates/assets/system_user_list.html
View file @
76df1de6
{% extends '_base_list.html' %}
{% load i18n %}
{% block help_message %}
<div
class=
"alert alert-info help-message"
>
系统用户是 用户登录资产(服务器)时使用的用户,如 web, sa, dba等具有特殊功能的用户。系统用户创建时,如果选择了自动推送
Jumpserver会使用ansible自动推送到系统用户所在集群的资产中,如果资产(交换机)不支持ansible, 请手动填写账号密码。
</div>
{% endblock %}
{% block table_search %}
{% endblock %}
...
...
apps/assets/templates/assets/user_asset_list.html
View file @
76df1de6
This diff is collapsed.
Click to expand it.
apps/assets/utils.py
View file @
76df1de6
# ~*~ coding: utf-8 ~*~
#
from
collections
import
defaultdict
from
common.utils
import
get_object_or_none
from
.models
import
Asset
,
SystemUser
...
...
@@ -17,3 +18,12 @@ def get_system_user_by_name(name):
return
system_user
def
check_assets_have_system_user
(
assets
,
system_users
):
errors
=
defaultdict
(
list
)
for
system_user
in
system_users
:
clusters
=
system_user
.
cluster
.
all
()
for
asset
in
assets
:
if
asset
.
cluster
not
in
clusters
:
errors
[
asset
]
.
append
(
system_user
)
return
errors
apps/common/models.py
View file @
76df1de6
from
__future__
import
unicode_literals
from
django.db
import
models
# Create your models here.
apps/common/utils.py
View file @
76df1de6
...
...
@@ -195,11 +195,11 @@ def ssh_key_string_to_obj(text, password=None):
return
key
def
ssh_pubkey_gen
(
private_key
=
None
,
username
=
'jumpserver'
,
hostname
=
'localhost'
):
def
ssh_pubkey_gen
(
private_key
=
None
,
username
=
'jumpserver'
,
hostname
=
'localhost'
,
password
=
None
):
if
isinstance
(
private_key
,
bytes
):
private_key
=
private_key
.
decode
(
"utf-8"
)
if
isinstance
(
private_key
,
string_types
):
private_key
=
ssh_key_string_to_obj
(
private_key
)
private_key
=
ssh_key_string_to_obj
(
private_key
,
password
=
password
)
if
not
isinstance
(
private_key
,
(
paramiko
.
RSAKey
,
paramiko
.
DSSKey
)):
raise
IOError
(
'Invalid private key'
)
...
...
@@ -238,14 +238,14 @@ def ssh_key_gen(length=2048, type='rsa', password=None, username='jumpserver', h
raise
IOError
(
'These is error when generate ssh key.'
)
def
validate_ssh_private_key
(
text
):
def
validate_ssh_private_key
(
text
,
password
=
None
):
if
isinstance
(
text
,
bytes
):
try
:
text
=
text
.
decode
(
"utf-8"
)
except
UnicodeDecodeError
:
return
False
key
=
ssh_key_string_to_obj
(
text
)
key
=
ssh_key_string_to_obj
(
text
,
password
=
password
)
if
key
is
None
:
return
False
else
:
...
...
apps/jumpserver/settings.py
View file @
76df1de6
...
...
@@ -59,7 +59,6 @@ INSTALLED_APPS = [
'assets.apps.AssetsConfig'
,
'perms.apps.PermsConfig'
,
'ops.apps.OpsConfig'
,
# 'audits.apps.AuditsConfig',
'common.apps.CommonConfig'
,
'terminal.apps.TerminalConfig'
,
'rest_framework'
,
...
...
apps/ops/ansible/callback.py
View file @
76df1de6
# ~*~ coding: utf-8 ~*~
from
collections
import
defaultdict
from
ansible.plugins.callback
import
CallbackBase
from
ansible.plugins.callback.default
import
CallbackModule
...
...
@@ -21,9 +19,8 @@ class AdHocResultCallback(CallbackModule):
# "contacted": {"hostname",...},
# "dark": {"hostname": {"task_name": {}, "task_name": {}},...,},
# }
self
.
results_raw
=
dict
(
ok
=
defaultdict
(
dict
),
failed
=
defaultdict
(
dict
),
unreachable
=
defaultdict
(
dict
),
skipped
=
defaultdict
(
dict
))
self
.
results_summary
=
dict
(
contacted
=
[],
dark
=
defaultdict
(
dict
))
self
.
results_raw
=
dict
(
ok
=
{},
failed
=
{},
unreachable
=
{},
skipped
=
{})
self
.
results_summary
=
dict
(
contacted
=
[],
dark
=
{})
super
()
.
__init__
()
def
gather_result
(
self
,
t
,
res
):
...
...
@@ -34,8 +31,8 @@ class AdHocResultCallback(CallbackModule):
if
self
.
results_raw
[
t
]
.
get
(
host
):
self
.
results_raw
[
t
][
host
][
task_name
]
=
task_result
#
else:
#
self.results_raw[t][host] = {task_name: task_result}
else
:
self
.
results_raw
[
t
][
host
]
=
{
task_name
:
task_result
}
self
.
clean_result
(
t
,
host
,
task_name
,
task_result
)
def
clean_result
(
self
,
t
,
host
,
task_name
,
task_result
):
...
...
@@ -45,10 +42,10 @@ class AdHocResultCallback(CallbackModule):
if
host
not
in
contacted
:
contacted
.
append
(
host
)
else
:
#
if dark.get(host):
dark
[
host
][
task_name
]
=
task_result
.
values
#
else:
#
dark[host] = {task_name: task_result}
if
dark
.
get
(
host
):
dark
[
host
][
task_name
]
=
task_result
.
values
else
:
dark
[
host
]
=
{
task_name
:
task_result
}
if
host
in
contacted
:
contacted
.
remove
(
host
)
...
...
apps/ops/ansible/inventory.py
View file @
76df1de6
...
...
@@ -32,6 +32,7 @@ class BaseHost(Host):
}
"""
self
.
host_data
=
host_data
print
(
host_data
)
hostname
=
host_data
.
get
(
'hostname'
)
or
host_data
.
get
(
'ip'
)
port
=
host_data
.
get
(
'port'
)
or
22
super
()
.
__init__
(
hostname
,
port
)
...
...
apps/ops/templates/ops/task_list.html
View file @
76df1de6
...
...
@@ -27,7 +27,7 @@
<div
class=
"input-group"
>
<div
class=
"input-group-btn"
>
<button
id=
'search_btn'
type=
"submit"
class=
"btn btn-sm btn-primary"
>
搜索
{% trans "Search" %}
</button>
</div>
</div>
...
...
@@ -37,7 +37,7 @@
{% block table_head %}
<th
class=
"text-center"
></th>
<th
class=
"text-center"
>
{% trans 'Name' %}
</th>
<th
class=
"text-center"
>
{% trans '
F/S/T
' %}
</th>
<th
class=
"text-center"
>
{% trans '
Run times
' %}
</th>
<th
class=
"text-center"
>
{% trans 'Versions' %}
</th>
<th
class=
"text-center"
>
{% trans 'Hosts' %}
</th>
<th
class=
"text-center"
>
{% trans 'Success' %}
</th>
...
...
apps/ops/utils.py
View file @
76df1de6
# ~*~ coding: utf-8 ~*~
import
re
import
time
from
django.utils
import
timezone
from
django.db
import
transaction
from
common.utils
import
get_logger
,
get_object_or_none
,
get_short_uuid_str
from
.ansible
import
AdHocRunner
,
CommandResultCallback
...
...
@@ -58,6 +58,7 @@ def get_inventory(hostname_list, run_as_admin=False, run_as=None, become_info=No
)
@record_adhoc
def
get_adhoc_runner
(
hostname_list
,
run_as_admin
=
False
,
run_as
=
None
,
become_info
=
None
):
inventory
=
get_inventory
(
hostname_list
,
run_as_admin
=
run_as_admin
,
...
...
@@ -67,7 +68,6 @@ def get_adhoc_runner(hostname_list, run_as_admin=False, run_as=None, become_info
return
runner
@record_adhoc
def
run_adhoc_object
(
adhoc
,
**
options
):
"""
:param adhoc: Instance of AdHoc
...
...
@@ -109,6 +109,8 @@ def create_or_update_task(
run_as_admin
=
False
,
run_as
=
""
,
become_info
=
None
,
created_by
=
None
):
print
(
options
)
print
(
task_name
)
task
=
get_object_or_none
(
Task
,
name
=
task_name
)
if
task
is
None
:
task
=
Task
(
name
=
task_name
,
created_by
=
created_by
)
...
...
@@ -125,6 +127,7 @@ def create_or_update_task(
if
not
adhoc
or
adhoc
!=
new_adhoc
:
new_adhoc
.
save
()
task
.
latest_adhoc
=
new_adhoc
print
(
"Return task"
)
return
task
...
...
apps/perms/forms.py
View file @
76df1de6
...
...
@@ -38,3 +38,34 @@ class AssetPermissionForm(forms.ModelForm):
'user_groups'
:
_
(
'User or user group at least one required'
),
'asset_groups'
:
_
(
'Asset or Asset group at least one required'
),
}
def
clean_system_users
(
self
):
from
assets.utils
import
check_assets_have_system_user
errors
=
[]
assets
=
self
.
cleaned_data
[
'assets'
]
asset_groups
=
self
.
cleaned_data
[
'asset_groups'
]
system_users
=
self
.
cleaned_data
[
'system_users'
]
error_data
=
check_assets_have_system_user
(
assets
,
system_users
)
if
error_data
:
for
asset
,
system_users
in
error_data
.
items
():
msg
=
_
(
"Asset {} not have [{}] system users, please check
\n
"
)
error
=
forms
.
ValidationError
(
msg
.
format
(
asset
.
hostname
,
", "
.
join
(
system_user
.
name
for
system_user
in
system_users
)
))
errors
.
append
(
error
)
for
group
in
asset_groups
:
msg
=
_
(
"Asset {}: {} not have [{}] system users, please check"
)
assets
=
group
.
assets
.
all
()
error_data
=
check_assets_have_system_user
(
assets
,
system_users
)
for
asset
,
system_users
in
error_data
.
items
():
errors
.
append
(
msg
.
format
(
group
.
name
,
asset
.
hostname
,
", "
.
join
(
system_user
.
name
for
system_user
in
system_users
)
))
if
errors
:
raise
forms
.
ValidationError
(
errors
)
return
self
.
cleaned_data
[
'system_users'
]
apps/perms/templates/perms/asset_permission_create_update.html
View file @
76df1de6
...
...
@@ -55,7 +55,7 @@
<div
class=
"col-sm-9"
>
<div
class=
"input-group date"
>
<span
class=
"input-group-addon"
><i
class=
"fa fa-calendar"
></i></span>
<input
id=
"{{ form.date_expired.id_for_label }}"
name=
"{{ form.date_expired.html_name }}"
type=
"text"
class=
"form-control"
value=
"{{ form.date_expired.value|date:'Y-m-d' }}"
>
<input
id=
"{{ form.date_expired.id_for_label }}"
name=
"{{ form.date_expired.html_name }}"
type=
"text"
class=
"form-control"
value=
"{{ form.date_expired.value|date:'Y-m-d'
|default:form.date_expired.value
}}"
>
</div>
<span
class=
"help-block "
>
{{ form.date_expired.errors }}
</span>
</div>
...
...
apps/perms/templates/perms/asset_permission_list.html
View file @
76df1de6
...
...
@@ -4,6 +4,12 @@
{% block table_search %}
{% endblock %}
{% block help_message %}
<div
class=
"alert alert-info help-message"
>
提前规划好集群中的系统用户,授权时选择的资产(组内资产)必须存在该系统用户,否则可能无法成功登录
</div>
{% endblock %}
{% block table_container %}
<div
class=
"uc pull-left m-r-5"
>
<a
href=
"{% url 'perms:asset-permission-create' %}"
class=
"btn btn-sm btn-primary "
>
...
...
apps/perms/views.py
View file @
76df1de6
...
...
@@ -66,9 +66,7 @@ class MessageMixin:
return
success_message
class
AssetPermissionCreateView
(
AdminUserRequiredMixin
,
MessageMixin
,
CreateView
):
class
AssetPermissionCreateView
(
AdminUserRequiredMixin
,
SuccessMessageMixin
,
CreateView
):
model
=
AssetPermission
form_class
=
AssetPermissionForm
template_name
=
'perms/asset_permission_create_update.html'
...
...
@@ -83,8 +81,19 @@ class AssetPermissionCreateView(AdminUserRequiredMixin,
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
def
get_success_message
(
self
,
cleaned_data
):
url
=
reverse_lazy
(
'perms:asset-permission-detail'
,
kwargs
=
{
'pk'
:
self
.
object
.
pk
}
)
success_message
=
_
(
'Create asset permission <a href="{url}"> {name} </a> '
'success.'
.
format
(
url
=
url
,
name
=
self
.
object
.
name
)
)
return
success_message
class
AssetPermissionUpdateView
(
AdminUserRequiredMixin
,
MessageMixin
,
UpdateView
):
class
AssetPermissionUpdateView
(
AdminUserRequiredMixin
,
Success
MessageMixin
,
UpdateView
):
model
=
AssetPermission
form_class
=
AssetPermissionForm
template_name
=
'perms/asset_permission_create_update.html'
...
...
@@ -98,6 +107,17 @@ class AssetPermissionUpdateView(AdminUserRequiredMixin, MessageMixin, UpdateView
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
def
get_success_message
(
self
,
cleaned_data
):
url
=
reverse_lazy
(
'perms:asset-permission-detail'
,
kwargs
=
{
'pk'
:
self
.
object
.
pk
}
)
success_message
=
_
(
'Update asset permission <a href="{url}"> {name} </a> '
'success.'
.
format
(
url
=
url
,
name
=
self
.
object
.
name
)
)
return
success_message
class
AssetPermissionDetailView
(
AdminUserRequiredMixin
,
DetailView
):
template_name
=
'perms/asset_permission_detail.html'
...
...
apps/
media/avatar/default
.png
→
apps/
static/img/avatar/admin
.png
View file @
76df1de6
File moved
apps/static/img/avatar/user.png
0 → 100644
View file @
76df1de6
61.3 KB
apps/static/js/jumpserver.js
View file @
76df1de6
...
...
@@ -156,8 +156,8 @@ function activeNav() {
function
APIUpdateAttr
(
props
)
{
// props = {url: .., body: , success: , error: , method: ,}
props
=
props
||
{};
var
success_message
=
props
.
success_message
||
'
Update successfully
!'
;
var
fail_message
=
props
.
fail_message
||
'
Error occurred while updating
.'
;
var
success_message
=
props
.
success_message
||
'
更新成功
!'
;
var
fail_message
=
props
.
fail_message
||
'
更新时发生未知错误
.'
;
$
.
ajax
({
url
:
props
.
url
,
type
:
props
.
method
||
"PATCH"
,
...
...
@@ -183,7 +183,7 @@ function objectDelete(obj, name, url, redirectTo) {
function
doDelete
()
{
var
body
=
{};
var
success
=
function
()
{
swal
(
'Deleted!'
,
"[ "
+
name
+
"]"
+
" has been deleted "
,
"success"
);
//
swal('Deleted!', "[ "+name+"]"+" has been deleted ", "success");
if
(
!
redirectTo
)
{
$
(
obj
).
parent
().
parent
().
remove
();
}
else
{
...
...
@@ -191,7 +191,7 @@ function objectDelete(obj, name, url, redirectTo) {
}
};
var
fail
=
function
()
{
swal
(
"
Failed"
,
"Delete"
+
"[ "
+
name
+
" ]"
+
"failed
"
,
"error"
);
swal
(
"
错误"
,
"删除"
+
"[ "
+
name
+
" ]"
+
"遇到错误
"
,
"error"
);
};
APIUpdateAttr
({
url
:
url
,
...
...
@@ -202,14 +202,14 @@ function objectDelete(obj, name, url, redirectTo) {
});
}
swal
({
title
:
'
Are you sure delete
?'
,
title
:
'
你确定删除吗
?'
,
text
:
" ["
+
name
+
"] "
,
type
:
"warning"
,
showCancelButton
:
true
,
cancelButtonText
:
'
Cancel
'
,
confirmButtonColor
:
"#
DD6B5
5"
,
confirmButtonText
:
'
Confirm
'
,
closeOnConfirm
:
false
cancelButtonText
:
'
取消
'
,
confirmButtonColor
:
"#
ed556
5"
,
confirmButtonText
:
'
确认
'
,
closeOnConfirm
:
true
,
},
function
()
{
doDelete
()
});
...
...
@@ -334,3 +334,9 @@ String.prototype.format = function(args) {
}
return
result
;
};
function
setCookie
(
key
,
value
)
{
var
expires
=
new
Date
();
expires
.
setTime
(
expires
.
getTime
()
+
(
24
*
60
*
60
*
1000
));
document
.
cookie
=
key
+
'='
+
value
+
';expires='
+
expires
.
toUTCString
();
}
apps/templates/_left_side_bar.html
View file @
76df1de6
...
...
@@ -2,7 +2,7 @@
<div
class=
"sidebar-collapse"
>
<ul
class=
"nav"
id=
"side-menu"
>
{% include '_user_profile.html' %}
{% if request.user.is_superuser %}
{% if request.user.is_superuser
and request.COOKIES.admin == "Yes"
%}
{% include '_nav.html' %}
{% else %}
{% include '_nav_user.html' %}
...
...
apps/templates/_nav.html
View file @
76df1de6
...
...
@@ -34,16 +34,14 @@
</li>
</ul>
</li>
<li
id=
"ops"
>
<a>
<i
class=
"fa fa-coffee"
></i>
<span
class=
"nav-label"
>
{% trans 'Job Center' %}
</span><span
class=
"fa arrow"
></span>
</a>
<ul
class=
"nav nav-second-level"
>
<li
id=
"task"
><a
href=
"{% url 'ops:task-list' %}"
>
{% trans 'Task
s
' %}
</a></li>
<li
id=
"task"
><a
href=
"{% url 'ops:task-list' %}"
>
{% trans 'Task' %}
</a></li>
</ul>
</li>
<li
id=
"terminal"
>
<a>
<i
class=
"fa fa-rocket"
></i>
<span
class=
"nav-label"
>
{% trans 'Terminal' %}
</span><span
class=
"fa arrow"
></span>
...
...
@@ -65,11 +63,11 @@
{#
<li
id=
"download"
><a
href=
""
>
{% trans 'File download' %}
</a></li>
#}
{#
</ul>
#}
{#
</li>
#}
<li
id=
""
>
<a
href=
""
>
<i
class=
"fa fa-gears"
></i>
<span
class=
"nav-label"
>
{% trans 'Settings' %}
</span><span
class=
"label label-info pull-right"
></span>
</a>
</li>
{#
<li
id=
""
>
#}
{#
<a
href=
""
>
#}
{#
<i
class=
"fa fa-gears"
></i>
<span
class=
"nav-label"
>
{% trans 'Settings' %}
</span><span
class=
"label label-info pull-right"
></span>
#}
{#
</a>
#}
{#
</li>
#}
<li
class=
"special_link"
>
<a
href=
"http://www.jumpserver.org"
target=
"_blank"
><i
class=
"fa fa-database"
></i>
<span
class=
"nav-label"
>
{% trans 'Visit us' %}
</span>
...
...
apps/templates/_user_profile.html
View file @
76df1de6
...
...
@@ -19,6 +19,13 @@
<li><a
href=
"{% url 'users:user-profile' %}"
>
{% trans 'Profile' %}
</a></li>
<li><a
href=
"{% url 'users:user-profile-update' %}"
>
{% trans 'Profile settings' %}
</a></li>
<li
class=
"divider"
></li>
{% if request.user.is_superuser %}
{% if request.COOKIES.admin == 'No' %}
<li><a
id=
"switch_admin"
>
{% trans 'Admin page' %}
</a></li>
{% else %}
<li><a
id=
"switch_user"
>
{% trans 'User page' %}
</a></li>
{% endif %}
{% endif %}
<li><a
href=
"{% url 'users:logout' %}"
>
{% trans 'Logout' %}
</a></li>
</ul>
</div>
...
...
@@ -26,3 +33,15 @@
JMS
</div>
</li>
<script>
$
(
document
).
ready
(
function
()
{
})
.
on
(
'click'
,
'#switch_admin'
,
function
()
{
setCookie
(
"admin"
,
"Yes"
);
window
.
location
=
"/"
})
.
on
(
'click'
,
'#switch_user'
,
function
()
{
setCookie
(
"admin"
,
"No"
);
window
.
location
=
"/"
})
</script>
apps/terminal/tasks.py
View file @
76df1de6
# -*- coding: utf-8 -*-
#
import
time
from
celery
import
shared_task
from
django.core.cache
import
cache
from
django.db.utils
import
ProgrammingError
,
OperationalError
from
.models
import
Session
CACHE_REFRESH_INTERVAL
=
10
...
...
apps/terminal/templates/terminal/command_list.html
View file @
76df1de6
...
...
@@ -13,7 +13,6 @@
{% endblock %}
{% block content_left_head %}
123
{% endblock %}
{% block table_search %}
...
...
@@ -51,12 +50,12 @@
</select>
</div>
<div
class=
"input-group"
>
<input
type=
"text"
class=
"form-control input-sm"
name=
"command"
placeholder=
"
Command
"
value=
"{{ command }}"
>
<input
type=
"text"
class=
"form-control input-sm"
name=
"command"
placeholder=
"
{% trans 'Command' %}
"
value=
"{{ command }}"
>
</div>
<div
class=
"input-group"
>
<div
class=
"input-group-btn"
>
<button
id=
'search_btn'
type=
"submit"
class=
"btn btn-sm btn-primary"
>
搜索
{% trans 'Search' %}
</button>
</div>
</div>
...
...
apps/terminal/templates/terminal/terminal_list.html
View file @
76df1de6
...
...
@@ -78,14 +78,8 @@ function initTable() {
var
reject_btn
=
'<a class="btn btn-xs btn-danger m-l-xs btn-del" data-id="{{ DEFAULT_PK }}" data-name="99991938">{% trans "Reject" %}</a>'
.
replace
(
'{{ DEFAULT_PK }}'
,
cellData
)
.
replace
(
'99991938'
,
rowData
.
name
);
var
connect_btn
=
'<a href="" class="btn btn-xs btn-warning btn-connect" >{% trans "Connect" %}</a> '
.
replace
(
'{{ DEFAULT_PK }}'
,
cellData
);
if
(
rowData
.
is_accepted
)
{
{
%
if
user
.
is_superuser
%
}
$
(
td
).
html
(
connect_btn
+
update_btn
+
delete_btn
);
{
%
else
%
}
$
(
td
).
html
(
connect_btn
);
{
%
endif
%
}
$
(
td
).
html
(
update_btn
+
delete_btn
);
}
else
{
{
%
if
user
.
is_superuser
%
}
$
(
td
).
html
(
accept_btn
+
reject_btn
);
...
...
apps/users/forms.py
View file @
76df1de6
...
...
@@ -67,15 +67,23 @@ class UserProfileForm(forms.ModelForm):
class
UserPasswordForm
(
forms
.
Form
):
old_password
=
forms
.
CharField
(
max_length
=
128
,
widget
=
forms
.
PasswordInput
)
max_length
=
128
,
widget
=
forms
.
PasswordInput
,
label
=
_
(
"Old password"
)
)
new_password
=
forms
.
CharField
(
min_length
=
5
,
max_length
=
128
,
widget
=
forms
.
PasswordInput
)
min_length
=
5
,
max_length
=
128
,
widget
=
forms
.
PasswordInput
,
label
=
_
(
"New password"
)
)
confirm_password
=
forms
.
CharField
(
min_length
=
5
,
max_length
=
128
,
widget
=
forms
.
PasswordInput
)
min_length
=
5
,
max_length
=
128
,
widget
=
forms
.
PasswordInput
,
label
=
_
(
"Confirm password"
)
)
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
instance
=
kwargs
.
pop
(
'instance'
)
super
(
UserPasswordForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
super
()
.
__init__
(
*
args
,
**
kwargs
)
def
clean_old_password
(
self
):
old_password
=
self
.
cleaned_data
[
'old_password'
]
...
...
@@ -102,20 +110,21 @@ class UserPublicKeyForm(forms.Form):
public_key
=
forms
.
CharField
(
label
=
_
(
'ssh public key'
),
max_length
=
5000
,
widget
=
forms
.
Textarea
(
attrs
=
{
'placeholder'
:
_
(
'ssh-rsa AAAA...'
)}),
help_text
=
_
(
'Paste your id_rsa.pub here.'
))
help_text
=
_
(
'Paste your id_rsa.pub here.'
)
)
def
__init__
(
self
,
*
args
,
**
kwargs
):
if
'instance'
in
kwargs
:
self
.
instance
=
kwargs
.
pop
(
'instance'
)
else
:
self
.
instance
=
None
super
(
UserPublicKeyForm
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
super
()
.
__init__
(
*
args
,
**
kwargs
)
def
clean_public_key
(
self
):
public_key
=
self
.
cleaned_data
[
'public_key'
]
if
self
.
instance
.
public_key
and
public_key
==
self
.
instance
.
public_key
:
raise
forms
.
ValidationError
(
_
(
'Public key should not be the '
'same as your old one.'
)
)
msg
=
_
(
'Public key should not be the same as your old one.'
)
raise
forms
.
ValidationError
(
msg
)
if
not
validate_ssh_public_key
(
public_key
):
raise
forms
.
ValidationError
(
_
(
'Not a valid ssh public key'
))
...
...
@@ -129,11 +138,11 @@ class UserPublicKeyForm(forms.Form):
class
UserBulkUpdateForm
(
forms
.
ModelForm
):
users
=
forms
.
MultipleChoiceField
(
users
=
forms
.
M
odelM
ultipleChoiceField
(
required
=
True
,
help_text
=
'* required'
,
label
=
_
(
'Select users'
),
choices
=
[(
user
.
id
,
user
.
name
)
for
user
in
User
.
objects
.
all
()]
,
queryset
=
User
.
objects
.
all
()
,
widget
=
forms
.
SelectMultiple
(
attrs
=
{
'class'
:
'select2'
,
...
...
@@ -162,9 +171,9 @@ class UserBulkUpdateForm(forms.ModelForm):
cleaned_data
=
{
k
:
v
for
k
,
v
in
self
.
cleaned_data
.
items
()
if
k
in
changed_fields
}
users
_id
=
cleaned_data
.
pop
(
'users'
,
''
)
users
=
cleaned_data
.
pop
(
'users'
,
''
)
groups
=
cleaned_data
.
pop
(
'groups'
,
[])
users
=
User
.
objects
.
filter
(
id__in
=
users_id
)
users
=
User
.
objects
.
filter
(
id__in
=
[
user
.
id
for
user
in
users
]
)
users
.
update
(
**
cleaned_data
)
if
groups
:
for
user
in
users
:
...
...
apps/users/models/user.py
View file @
76df1de6
...
...
@@ -146,7 +146,7 @@ class User(AbstractUser):
if
not
self
.
name
:
self
.
name
=
self
.
username
super
(
User
,
self
)
.
save
(
*
args
,
**
kwargs
)
super
()
.
save
(
*
args
,
**
kwargs
)
# Add the current user to the default group.
if
not
self
.
groups
.
count
():
group
=
UserGroup
.
initial
()
...
...
@@ -180,13 +180,14 @@ class User(AbstractUser):
return
False
def
avatar_url
(
self
):
admin_default
=
settings
.
STATIC_URL
+
"img/avatar/admin.png"
user_default
=
settings
.
STATIC_URL
+
"img/avatar/user.png"
if
self
.
avatar
:
return
self
.
avatar
.
url
if
self
.
is_superuser
:
return
admin_default
else
:
avatar_dir
=
os
.
path
.
join
(
settings
.
MEDIA_ROOT
,
'avatar'
)
if
os
.
path
.
isdir
(
avatar_dir
):
return
os
.
path
.
join
(
settings
.
MEDIA_URL
,
'avatar'
,
'default.png'
)
return
'https://www.gravatar.com/avatar/c6812ab450230979465d7bf288eadce2a?s=120&d=identicon'
return
user_default
def
generate_reset_token
(
self
):
return
signer
.
sign_t
({
'reset'
:
str
(
self
.
id
),
'email'
:
self
.
email
},
expires_in
=
3600
)
...
...
apps/users/templates/users/login_log_list.html
View file @
76df1de6
...
...
@@ -31,12 +31,12 @@
</select>
</div>
<div
class=
"input-group"
>
<input
type=
"text"
class=
"form-control input-sm"
name=
"keyword"
placeholder=
"
Search
"
value=
"{{ keyword }}"
>
<input
type=
"text"
class=
"form-control input-sm"
name=
"keyword"
placeholder=
"
{% trans "
Search
"
%}
"
value=
"{{ keyword }}"
>
</div>
<div
class=
"input-group"
>
<div
class=
"input-group-btn"
>
<button
id=
'search_btn'
type=
"submit"
class=
"btn btn-sm btn-primary"
>
搜索
{% trans "Search" %}
</button>
</div>
</div>
...
...
@@ -85,7 +85,9 @@
forceParse
:
false
,
autoclose
:
true
});
$
(
'.select2'
).
select2
();
$
(
'.select2'
).
select2
({
dropdownAutoWidth
:
true
});
})
</script>
{% endblock %}
...
...
apps/users/templates/users/user_list.html
View file @
76df1de6
...
...
@@ -73,7 +73,7 @@ function initTable() {
}
}},
{
targets
:
6
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
update_btn
=
'<a href="{% url "users:user-update" pk=
'
00000000
-
0000
-
0000
-
0000
-
000000000000
'
%}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'
.
replace
(
'00000000-0000-0000-0000-000000000000'
,
cellData
);
var
update_btn
=
'<a href="{% url "users:user-update" pk=
DEFAULT_PK
%}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'
.
replace
(
'00000000-0000-0000-0000-000000000000'
,
cellData
);
var
del_btn
=
""
;
if
(
rowData
.
id
===
1
||
rowData
.
username
===
"admin"
||
rowData
.
username
===
"{{ user.username }}"
)
{
...
...
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