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
6c7e5104
Commit
6c7e5104
authored
Sep 19, 2016
by
江世峰
Browse files
Options
Browse Files
Download
Plain Diff
update assets_group
parents
98757aa4
9a81057d
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
365 additions
and
73 deletions
+365
-73
mixins.py
apps/common/mixins.py
+38
-0
style.css
apps/static/css/style.css
+2
-3
toastr.js.map
apps/static/js/plugins/toastr/toastr.js.map
+0
-0
toastr.min.js
apps/static/js/plugins/toastr/toastr.min.js
+0
-1
base.html
apps/templates/base.html
+21
-3
api.py
apps/users/api.py
+18
-2
forms.py
apps/users/forms.py
+3
-1
models.py
apps/users/models.py
+24
-21
serializers.py
apps/users/serializers.py
+21
-8
_user_update_pk_modal.html
apps/users/templates/users/_user_update_pk_modal.html
+4
-4
first_login.html
apps/users/templates/users/first_login.html
+2
-0
user_detail.html
apps/users/templates/users/user_detail.html
+36
-0
user_group_detail.html
apps/users/templates/users/user_group_detail.html
+111
-0
user_group_list.html
apps/users/templates/users/user_group_list.html
+59
-19
user_update.html
apps/users/templates/users/user_update.html
+1
-1
urls.py
apps/users/urls.py
+3
-1
views.py
apps/users/views.py
+22
-9
No files found.
apps/common/mixins.py
0 → 100644
View file @
6c7e5104
# coding: utf-8
from
django.db
import
models
from
django.utils.timezone
import
now
from
django.utils.translation
import
ugettext_lazy
as
_
class
NoDeleteQuerySet
(
models
.
query
.
QuerySet
):
def
delete
(
self
):
return
self
.
update
(
is_discard
=
True
,
discard_time
=
now
())
class
NoDeleteManager
(
models
.
Manager
):
def
get_all
(
self
):
return
NoDeleteQuerySet
(
self
.
model
,
using
=
self
.
_db
)
def
get_queryset
(
self
):
return
NoDeleteQuerySet
(
self
.
model
,
using
=
self
.
_db
)
.
filter
(
is_discard
=
False
)
def
get_deleted
(
self
):
return
NoDeleteQuerySet
(
self
.
model
,
using
=
self
.
_db
)
.
filter
(
is_discard
=
True
)
class
NoDeleteModelMixin
(
models
.
Model
):
is_discard
=
models
.
BooleanField
(
verbose_name
=
_
(
"is discard"
),
default
=
False
)
discard_time
=
models
.
DateTimeField
(
verbose_name
=
_
(
"discard time"
),
null
=
True
,
blank
=
True
)
objects
=
NoDeleteManager
()
class
Meta
:
abstract
=
True
def
delete
(
self
):
self
.
is_discard
=
True
self
.
discard_time
=
now
()
return
self
.
save
()
apps/static/css/style.css
View file @
6c7e5104
/*
@import url("https://fonts.useso.com/css?family=Open+Sans:300,400,600,700");
@import url("https://fonts.useso.com/css?family=Roboto:400,300,500,700");
@import
url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700")
;
@import
url("https://fonts.googleapis.com/css?family=Roboto:400,300,500,700")
;
/*
*
* INSPINIA - Responsive Admin Theme
...
...
apps/static/js/plugins/toastr/toastr.js.map
deleted
100644 → 0
View file @
98757aa4
This diff is collapsed.
Click to expand it.
apps/static/js/plugins/toastr/toastr.min.js
View file @
6c7e5104
!
function
(
e
){
e
([
"jquery"
],
function
(
e
){
return
function
(){
function
t
(
e
,
t
,
n
){
return
g
({
type
:
O
.
error
,
iconClass
:
m
().
iconClasses
.
error
,
message
:
e
,
optionsOverride
:
n
,
title
:
t
})}
function
n
(
t
,
n
){
return
t
||
(
t
=
m
()),
v
=
e
(
"#"
+
t
.
containerId
),
v
.
length
?
v
:(
n
&&
(
v
=
d
(
t
)),
v
)}
function
o
(
e
,
t
,
n
){
return
g
({
type
:
O
.
info
,
iconClass
:
m
().
iconClasses
.
info
,
message
:
e
,
optionsOverride
:
n
,
title
:
t
})}
function
s
(
e
){
C
=
e
}
function
i
(
e
,
t
,
n
){
return
g
({
type
:
O
.
success
,
iconClass
:
m
().
iconClasses
.
success
,
message
:
e
,
optionsOverride
:
n
,
title
:
t
})}
function
a
(
e
,
t
,
n
){
return
g
({
type
:
O
.
warning
,
iconClass
:
m
().
iconClasses
.
warning
,
message
:
e
,
optionsOverride
:
n
,
title
:
t
})}
function
r
(
e
,
t
){
var
o
=
m
();
v
||
n
(
o
),
u
(
e
,
o
,
t
)
||
l
(
o
)}
function
c
(
t
){
var
o
=
m
();
return
v
||
n
(
o
),
t
&&
0
===
e
(
":focus"
,
t
).
length
?
void
h
(
t
):
void
(
v
.
children
().
length
&&
v
.
remove
())}
function
l
(
t
){
for
(
var
n
=
v
.
children
(),
o
=
n
.
length
-
1
;
o
>=
0
;
o
--
)
u
(
e
(
n
[
o
]),
t
)}
function
u
(
t
,
n
,
o
){
var
s
=!
(
!
o
||!
o
.
force
)
&&
o
.
force
;
return
!
(
!
t
||!
s
&&
0
!==
e
(
":focus"
,
t
).
length
)
&&
(
t
[
n
.
hideMethod
]({
duration
:
n
.
hideDuration
,
easing
:
n
.
hideEasing
,
complete
:
function
(){
h
(
t
)}}),
!
0
)}
function
d
(
t
){
return
v
=
e
(
"<div/>"
).
attr
(
"id"
,
t
.
containerId
).
addClass
(
t
.
positionClass
),
v
.
appendTo
(
e
(
t
.
target
)),
v
}
function
p
(){
return
{
tapToDismiss
:
!
0
,
toastClass
:
"toast"
,
containerId
:
"toast-container"
,
debug
:
!
1
,
showMethod
:
"fadeIn"
,
showDuration
:
300
,
showEasing
:
"swing"
,
onShown
:
void
0
,
hideMethod
:
"fadeOut"
,
hideDuration
:
1
e3
,
hideEasing
:
"swing"
,
onHidden
:
void
0
,
closeMethod
:
!
1
,
closeDuration
:
!
1
,
closeEasing
:
!
1
,
closeOnHover
:
!
0
,
extendedTimeOut
:
1
e3
,
iconClasses
:{
error
:
"toast-error"
,
info
:
"toast-info"
,
success
:
"toast-success"
,
warning
:
"toast-warning"
},
iconClass
:
"toast-info"
,
positionClass
:
"toast-top-right"
,
timeOut
:
5
e3
,
titleClass
:
"toast-title"
,
messageClass
:
"toast-message"
,
escapeHtml
:
!
1
,
target
:
"body"
,
closeHtml
:
'<button type="button">×</button>'
,
closeClass
:
"toast-close-button"
,
newestOnTop
:
!
0
,
preventDuplicates
:
!
1
,
progressBar
:
!
1
,
progressClass
:
"toast-progress"
,
rtl
:
!
1
}}
function
f
(
e
){
C
&&
C
(
e
)}
function
g
(
t
){
function
o
(
e
){
return
null
==
e
&&
(
e
=
""
),
e
.
replace
(
/&/g
,
"&"
).
replace
(
/"/g
,
"""
).
replace
(
/'/g
,
"'"
).
replace
(
/</g
,
"<"
).
replace
(
/>/g
,
">"
)}
function
s
(){
c
(),
u
(),
d
(),
p
(),
g
(),
C
(),
l
(),
i
()}
function
i
(){
var
e
=
""
;
switch
(
t
.
iconClass
){
case
"toast-success"
:
case
"toast-info"
:
e
=
"polite"
;
break
;
default
:
e
=
"assertive"
}
I
.
attr
(
"aria-live"
,
e
)}
function
a
(){
E
.
closeOnHover
&&
I
.
hover
(
H
,
D
),
!
E
.
onclick
&&
E
.
tapToDismiss
&&
I
.
click
(
b
),
E
.
closeButton
&&
j
&&
j
.
click
(
function
(
e
){
e
.
stopPropagation
?
e
.
stopPropagation
():
void
0
!==
e
.
cancelBubble
&&
e
.
cancelBubble
!==!
0
&&
(
e
.
cancelBubble
=!
0
),
E
.
onCloseClick
&&
E
.
onCloseClick
(
e
),
b
(
!
0
)}),
E
.
onclick
&&
I
.
click
(
function
(
e
){
E
.
onclick
(
e
),
b
()})}
function
r
(){
I
.
hide
(),
I
[
E
.
showMethod
]({
duration
:
E
.
showDuration
,
easing
:
E
.
showEasing
,
complete
:
E
.
onShown
}),
E
.
timeOut
>
0
&&
(
k
=
setTimeout
(
b
,
E
.
timeOut
),
F
.
maxHideTime
=
parseFloat
(
E
.
timeOut
),
F
.
hideEta
=
(
new
Date
).
getTime
()
+
F
.
maxHideTime
,
E
.
progressBar
&&
(
F
.
intervalId
=
setInterval
(
x
,
10
)))}
function
c
(){
t
.
iconClass
&&
I
.
addClass
(
E
.
toastClass
).
addClass
(
y
)}
function
l
(){
E
.
newestOnTop
?
v
.
prepend
(
I
):
v
.
append
(
I
)}
function
u
(){
if
(
t
.
title
){
var
e
=
t
.
title
;
E
.
escapeHtml
&&
(
e
=
o
(
t
.
title
)),
M
.
append
(
e
).
addClass
(
E
.
titleClass
),
I
.
append
(
M
)}}
function
d
(){
if
(
t
.
message
){
var
e
=
t
.
message
;
E
.
escapeHtml
&&
(
e
=
o
(
t
.
message
)),
B
.
append
(
e
).
addClass
(
E
.
messageClass
),
I
.
append
(
B
)}}
function
p
(){
E
.
closeButton
&&
(
j
.
addClass
(
E
.
closeClass
).
attr
(
"role"
,
"button"
),
I
.
prepend
(
j
))}
function
g
(){
E
.
progressBar
&&
(
q
.
addClass
(
E
.
progressClass
),
I
.
prepend
(
q
))}
function
C
(){
E
.
rtl
&&
I
.
addClass
(
"rtl"
)}
function
O
(
e
,
t
){
if
(
e
.
preventDuplicates
){
if
(
t
.
message
===
w
)
return
!
0
;
w
=
t
.
message
}
return
!
1
}
function
b
(
t
){
var
n
=
t
&&
E
.
closeMethod
!==!
1
?
E
.
closeMethod
:
E
.
hideMethod
,
o
=
t
&&
E
.
closeDuration
!==!
1
?
E
.
closeDuration
:
E
.
hideDuration
,
s
=
t
&&
E
.
closeEasing
!==!
1
?
E
.
closeEasing
:
E
.
hideEasing
;
if
(
!
e
(
":focus"
,
I
).
length
||
t
)
return
clearTimeout
(
F
.
intervalId
),
I
[
n
]({
duration
:
o
,
easing
:
s
,
complete
:
function
(){
h
(
I
),
clearTimeout
(
k
),
E
.
onHidden
&&
"hidden"
!==
P
.
state
&&
E
.
onHidden
(),
P
.
state
=
"hidden"
,
P
.
endTime
=
new
Date
,
f
(
P
)}})}
function
D
(){(
E
.
timeOut
>
0
||
E
.
extendedTimeOut
>
0
)
&&
(
k
=
setTimeout
(
b
,
E
.
extendedTimeOut
),
F
.
maxHideTime
=
parseFloat
(
E
.
extendedTimeOut
),
F
.
hideEta
=
(
new
Date
).
getTime
()
+
F
.
maxHideTime
)}
function
H
(){
clearTimeout
(
k
),
F
.
hideEta
=
0
,
I
.
stop
(
!
0
,
!
0
)[
E
.
showMethod
]({
duration
:
E
.
showDuration
,
easing
:
E
.
showEasing
})}
function
x
(){
var
e
=
(
F
.
hideEta
-
(
new
Date
).
getTime
())
/
F
.
maxHideTime
*
100
;
q
.
width
(
e
+
"%"
)}
var
E
=
m
(),
y
=
t
.
iconClass
||
E
.
iconClass
;
if
(
"undefined"
!=
typeof
t
.
optionsOverride
&&
(
E
=
e
.
extend
(
E
,
t
.
optionsOverride
),
y
=
t
.
optionsOverride
.
iconClass
||
y
),
!
O
(
E
,
t
)){
T
++
,
v
=
n
(
E
,
!
0
);
var
k
=
null
,
I
=
e
(
"<div/>"
),
M
=
e
(
"<div/>"
),
B
=
e
(
"<div/>"
),
q
=
e
(
"<div/>"
),
j
=
e
(
E
.
closeHtml
),
F
=
{
intervalId
:
null
,
hideEta
:
null
,
maxHideTime
:
null
},
P
=
{
toastId
:
T
,
state
:
"visible"
,
startTime
:
new
Date
,
options
:
E
,
map
:
t
};
return
s
(),
r
(),
a
(),
f
(
P
),
E
.
debug
&&
console
&&
console
.
log
(
P
),
I
}}
function
m
(){
return
e
.
extend
({},
p
(),
b
.
options
)}
function
h
(
e
){
v
||
(
v
=
n
()),
e
.
is
(
":visible"
)
||
(
e
.
remove
(),
e
=
null
,
0
===
v
.
children
().
length
&&
(
v
.
remove
(),
w
=
void
0
))}
var
v
,
C
,
w
,
T
=
0
,
O
=
{
error
:
"error"
,
info
:
"info"
,
success
:
"success"
,
warning
:
"warning"
},
b
=
{
clear
:
r
,
remove
:
c
,
error
:
t
,
getContainer
:
n
,
info
:
o
,
options
:{},
subscribe
:
s
,
success
:
i
,
version
:
"2.1.3"
,
warning
:
a
};
return
b
}()})}(
"function"
==
typeof
define
&&
define
.
amd
?
define
:
function
(
e
,
t
){
"undefined"
!=
typeof
module
&&
module
.
exports
?
module
.
exports
=
t
(
require
(
"jquery"
)):
window
.
toastr
=
t
(
window
.
jQuery
)});
//# sourceMappingURL=toastr.js.map
apps/templates/base.html
View file @
6c7e5104
{% load static %}
{% load static
i18n
%}
<!DOCTYPE html>
<html>
<head>
...
...
@@ -20,6 +20,25 @@
<div
id=
"page-wrapper"
class=
"gray-bg"
>
{% include '_header_bar.html' %}
{% include '_message.html' %}
{% block first_login_message %}
{% if user.is_authenticated and user.is_first_login %}
<div
class=
"alert alert-danger"
style=
"margin: 20px auto 0px"
>
{% url 'users:user-first-login' as the_url %}
{% blocktrans %}
Your information was incomplete. Please click
<a
href=
"{{ the_url }}"
>
this link
</a>
to complete your information.
{% endblocktrans %}
</div>
{% endif %}
{% endblock %}
{% block update_public_key_message %}
{% if user.is_authenticated and not user.is_public_key_valid %}
<div
class=
"alert alert-danger"
style=
"margin: 20px auto 0px"
>
{% blocktrans %}
Your ssh-public-key has been expired. Please click
<a
href=
"#"
>
this link
</a>
to update your ssh-public-key.
{% endblocktrans %}
</div>
{% endif %}
{% endblock %}
{% block content %}{% endblock %}
{% include '_footer.html' %}
</div>
...
...
@@ -28,4 +47,4 @@
</body>
{% include '_foot_js.html' %}
{% block custom_foot_js %} {% endblock %}
</html>
\ No newline at end of file
</html>
apps/users/api.py
View file @
6c7e5104
...
...
@@ -5,7 +5,8 @@ import logging
from
rest_framework
import
generics
from
.serializers
import
UserSerializer
,
UserGroupSerializer
,
UserAttributeSerializer
,
UserGroupEditSerializer
from
.serializers
import
UserSerializer
,
UserGroupSerializer
,
UserAttributeSerializer
,
UserGroupEditSerializer
,
\
GroupEditSerializer
,
UserPKUpdateSerializer
from
.models
import
User
,
UserGroup
...
...
@@ -72,7 +73,22 @@ class UserResetPKApi(generics.UpdateAPIView):
def
perform_update
(
self
,
serializer
):
user
=
self
.
get_object
()
user
.
_public_key
=
''
user
.
is_public_key_valid
=
False
user
.
save
()
from
.utils
import
send_reset_ssh_key_mail
send_reset_ssh_key_mail
(
user
)
class
UserUpdatePKApi
(
generics
.
UpdateAPIView
):
queryset
=
User
.
objects
.
all
()
serializer_class
=
UserPKUpdateSerializer
def
perform_update
(
self
,
serializer
):
user
=
self
.
get_object
()
user
.
private_key
=
serializer
.
validated_data
[
'_public_key'
]
user
.
save
()
class
GroupDeleteApi
(
generics
.
DestroyAPIView
):
queryset
=
UserGroup
.
objects
.
all
()
serializer_class
=
GroupEditSerializer
apps/users/forms.py
View file @
6c7e5104
...
...
@@ -79,9 +79,11 @@ class UserKeyForm(forms.Form):
help_text
=
_
(
'Paste your id_ras.pub here.'
))
def
clean_public_key
(
self
):
public_key
=
self
.
cleaned_data
[
'public_key'
]
if
self
.
user
.
_public_key
and
public_key
==
self
.
user
.
public_key
:
raise
forms
.
ValidationError
(
_
(
'Public key should not be the same as your old one.'
))
from
sshpubkeys
import
SSHKey
from
sshpubkeys.exceptions
import
InvalidKeyException
public_key
=
self
.
cleaned_data
[
'public_key'
]
ssh
=
SSHKey
(
public_key
)
try
:
ssh
.
parse
()
...
...
apps/users/models.py
View file @
6c7e5104
...
...
@@ -4,19 +4,22 @@ from __future__ import unicode_literals
from
django.conf
import
settings
from
django.contrib.auth.hashers
import
make_password
from
django.utils
import
timezone
from
django.db
import
models
from
django.contrib.auth.models
import
AbstractUser
from
django.core
import
signing
from
django.db
import
models
,
IntegrityError
from
django.db.models.signals
import
post_save
from
django.dispatch
import
receiver
from
django.
utils
import
timezone
from
django.
db
import
IntegrityError
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.core
import
signing
from
rest_framework.authtoken.models
import
Token
from
common.utils
import
encrypt
,
decrypt
,
date_expired_default
from
common.mixins
import
NoDeleteModelMixin
class
UserGroup
(
models
.
Model
):
class
UserGroup
(
NoDeleteModelMixin
):
name
=
models
.
CharField
(
max_length
=
100
,
unique
=
True
,
verbose_name
=
_
(
'Name'
))
comment
=
models
.
TextField
(
blank
=
True
,
verbose_name
=
_
(
'Comment'
))
date_created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
...
...
@@ -30,14 +33,20 @@ class UserGroup(models.Model):
return
True
return
False
def
delete
(
self
):
if
self
.
name
!=
'Default'
:
self
.
users
.
clear
()
return
super
(
UserGroup
,
self
)
.
delete
()
return
True
class
Meta
:
db_table
=
'user
_
group'
db_table
=
'user
-
group'
@classmethod
def
initial
(
cls
):
group
_or_create
=
cls
.
objects
.
get_or_create
(
name
=
'Default'
,
comment
=
'Default user group for all user'
,
created_by
=
'System'
)
return
group
_or_create
[
0
]
group
,
created
=
cls
.
objects
.
get_or_create
(
name
=
'Default'
,
comment
=
'Default user group for all user'
,
created_by
=
'System'
)
return
group
@classmethod
def
generate_fake
(
cls
,
count
=
100
):
...
...
@@ -48,8 +57,7 @@ class UserGroup(models.Model):
for
i
in
range
(
count
):
group
=
cls
(
name
=
forgery_py
.
name
.
full_name
(),
comment
=
forgery_py
.
lorem_ipsum
.
sentence
(),
created_by
=
choice
(
User
.
objects
.
all
())
.
username
)
created_by
=
choice
(
User
.
objects
.
all
())
.
username
)
try
:
group
.
save
()
except
IntegrityError
:
...
...
@@ -76,7 +84,7 @@ class User(AbstractUser):
_private_key
=
models
.
CharField
(
max_length
=
5000
,
blank
=
True
,
verbose_name
=
_
(
'ssh private key'
))
_public_key
=
models
.
CharField
(
max_length
=
1000
,
blank
=
True
,
verbose_name
=
_
(
'ssh public key'
))
comment
=
models
.
TextField
(
max_length
=
200
,
blank
=
True
,
verbose_name
=
_
(
'Comment'
))
is_first_login
=
models
.
BooleanField
(
default
=
Tru
e
)
is_first_login
=
models
.
BooleanField
(
default
=
Fals
e
)
date_expired
=
models
.
DateTimeField
(
default
=
date_expired_default
,
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Date expired'
))
created_by
=
models
.
CharField
(
max_length
=
30
,
default
=
''
,
verbose_name
=
_
(
'Created by'
))
...
...
@@ -143,17 +151,13 @@ class User(AbstractUser):
pass
def
save
(
self
,
*
args
,
**
kwargs
):
# If user not set name, it's default equal username
if
not
self
.
name
:
self
.
name
=
self
.
username
super
(
User
,
self
)
.
save
(
*
args
,
**
kwargs
)
# Set user default group 'All'
# Todo: It's have bug
# Add the current user to the default group.
group
=
UserGroup
.
initial
()
if
group
not
in
self
.
groups
.
all
():
self
.
groups
.
add
(
group
)
# super(User, self).save(*args, **kwargs)
self
.
groups
.
add
(
group
)
@property
def
private_token
(
self
):
...
...
@@ -226,8 +230,7 @@ class User(AbstractUser):
role
=
choice
(
dict
(
User
.
ROLE_CHOICES
)
.
keys
()),
wechat
=
forgery_py
.
internet
.
user_name
(
True
),
comment
=
forgery_py
.
lorem_ipsum
.
sentence
(),
created_by
=
choice
(
cls
.
objects
.
all
())
.
username
,
)
created_by
=
choice
(
cls
.
objects
.
all
())
.
username
)
try
:
user
.
save
()
except
IntegrityError
:
...
...
@@ -239,13 +242,13 @@ class User(AbstractUser):
def
init_all_models
():
for
model
in
(
UserGroup
,
User
):
if
hasattr
(
model
,
b
'initial'
):
if
hasattr
(
model
,
'initial'
):
model
.
initial
()
def
generate_fake
():
for
model
in
(
UserGroup
,
User
):
if
hasattr
(
model
,
b
'generate_fake'
):
if
hasattr
(
model
,
'generate_fake'
):
model
.
generate_fake
()
...
...
apps/users/serializers.py
View file @
6c7e5104
# -*- coding: utf-8 -*-
#
from
django.utils.translation
import
ugettext_lazy
as
_
from
rest_framework
import
serializers
...
...
@@ -27,6 +26,13 @@ class UserGroupSerializer(serializers.ModelSerializer):
fields
=
'__all__'
class
GroupEditSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
UserGroup
fields
=
[
'id'
,
'name'
,
'comment'
,
'date_created'
,
'created_by'
]
class
UserAttributeSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
...
...
@@ -46,11 +52,18 @@ class UserPKUpdateSerializer(serializers.ModelSerializer):
class
Meta
:
model
=
User
fields
=
[
'id'
,
'_private_key'
]
def
validate__private_key
(
self
,
value
):
from
users.utils
import
validate_ssh_pk
checked
,
reason
=
validate_ssh_pk
(
value
)
if
not
checked
:
raise
serializers
.
ValidationError
(
_
(
'Not a valid ssh private key.'
))
fields
=
[
'id'
,
'_public_key'
]
def
validate__public_key
(
self
,
value
):
from
sshpubkeys
import
SSHKey
from
sshpubkeys.exceptions
import
InvalidKeyException
ssh
=
SSHKey
(
value
)
try
:
ssh
.
parse
()
except
InvalidKeyException
as
e
:
print
e
raise
serializers
.
ValidationError
(
_
(
'Not a valid ssh public key'
))
except
NotImplementedError
as
e
:
print
e
raise
serializers
.
ValidationError
(
_
(
'Not a valid ssh public key'
))
return
value
apps/users/templates/users/_user_
reset
_pk_modal.html
→
apps/users/templates/users/_user_
update
_pk_modal.html
View file @
6c7e5104
{% extends '_modal.html' %}
{% load i18n %}
{% block modal_id %}user_
reset
_pk_modal{% endblock %}
{% block modal_title%}{% trans
'Reset User SSH Private Key'
%}{% endblock %}
{% block modal_id %}user_
update
_pk_modal{% endblock %}
{% block modal_title%}{% trans
"Update User SSH Public Key"
%}{% endblock %}
{% block modal_body %}
<textarea
id=
"txt_pk"
class=
"form-control"
cols=
"30"
rows=
"10"
placeholder=
"
-----BEGIN RSA PRIVATE KEY-----
"
></textarea>
<textarea
id=
"txt_pk"
class=
"form-control"
cols=
"30"
rows=
"10"
placeholder=
"
ssh-rsa AAAAB3NzaC1yc2EAA.....
"
></textarea>
{% endblock %}
{% block modal_confirm_id %}btn_user_
reset
_pk{% endblock %}
{% block modal_confirm_id %}btn_user_
update
_pk{% endblock %}
apps/users/templates/users/first_login.html
View file @
6c7e5104
...
...
@@ -3,10 +3,12 @@
{% load i18n %}
{% load bootstrap %}
{% block custom_head_css_js %}
{{ wizard.form.media }}
<link
href=
"{% static 'css/plugins/steps/jquery.steps.css' %}"
rel=
"stylesheet"
>
{% endblock %}
{% block first_login_message %}{% endblock %}
{% block content %}
<div
class=
"wrapper wrapper-content animated fadeInRight"
>
<div
class=
"row"
>
...
...
apps/users/templates/users/user_detail.html
View file @
6c7e5104
...
...
@@ -160,6 +160,14 @@
</span>
</td>
</tr>
<tr>
<td>
{% trans 'Update ssh key' %}:
</td>
<td>
<span
class=
"pull-right"
>
<button
type=
"button"
class=
"btn btn-primary btn-xs"
id=
"btn_update_pk"
style=
"width: 54px;"
data-toggle=
"modal"
data-target=
"#user_update_pk_modal"
>
{% trans 'Update' %}
</button>
</span>
</td>
</tr>
</tbody>
</table>
</div>
...
...
@@ -207,6 +215,7 @@
</div>
</div>
</div>
{% include 'users/_user_update_pk_modal.html' %}
{% endblock %}
{% block custom_foot_js %}
<script>
...
...
@@ -352,6 +361,33 @@ $(document).ready(function() {
},
function
()
{
doReset
();
});
}).
on
(
'click'
,
'#btn_user_update_pk'
,
function
(){
var
$this
=
$
(
this
);
var
pk
=
$
(
'#txt_pk'
).
val
();
var
the_url
=
'{% url "users:user-update-pk-api" pk=user_object.id %}'
;
var
body
=
{
'_public_key'
:
pk
};
var
success
=
function
()
{
$
(
'#txt_pk'
).
val
(
''
);
$this
.
closest
(
'.modal'
).
modal
(
'hide'
);
var
msg
=
"{% trans 'Successfully updated the SSH public key.' %}"
;
swal
(
"{% trans 'User SSH Public Key Update' %}"
,
msg
,
"success"
);
};
var
fail
=
function
()
{
var
msg
=
"{% trans 'Failed to update the user
\
's SSH public key.' %}"
;
swal
({
title
:
"{% trans 'User SSH Public Key Update' %}"
,
text
:
msg
,
type
:
"error"
,
showCancelButton
:
false
,
confirmButtonColor
:
"#DD6B55"
,
confirmButtonText
:
"{% trans 'Confirm' %}"
,
closeOnConfirm
:
true
},
function
()
{
$
(
'#txt_pk'
).
focus
();
}
);
}
APIUpdateAttr
({
url
:
the_url
,
body
:
JSON
.
stringify
(
body
),
success
:
success
,
error
:
fail
});
});
</script>
{% endblock %}
apps/users/templates/users/user_group_detail.html
0 → 100644
View file @
6c7e5104
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link
href=
"{% static "
css
/
plugins
/
select2
/
select2
.
min
.
css
"
%}"
rel=
"stylesheet"
>
<link
href=
"{% static "
css
/
plugins
/
sweetalert
/
sweetalert
.
css
"
%}"
rel=
"stylesheet"
>
<script
src=
"{% static "
js
/
plugins
/
select2
/
select2
.
full
.
min
.
js
"
%}"
></script>
<script
src=
"{% static "
js
/
plugins
/
sweetalert
/
sweetalert
.
min
.
js
"
%}"
></script>
{% endblock %}
{% block content %}
<div
class=
"wrapper wrapper-content animated fadeInRight"
>
<div
class=
"row"
>
<div
class=
"col-sm-12"
>
<div
class=
"ibox float-e-margins"
>
<div
class=
"panel-options"
>
<ul
class=
"nav nav-tabs"
>
<li
class=
"active"
>
<a
href=
"{% url 'users:user-group-detail' pk=object.id %}"
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'User Group Detail' %}
</a>
</li>
</ul>
</div>
<div
class=
"tab-content"
>
<div
class=
"col-sm-7"
style=
"padding-left: 0"
>
<div
class=
"ibox float-e-margins"
>
<div
class=
"ibox-title"
>
<span
class=
"label"
><b>
{{ object.name }}
</b></span>
<div
class=
"ibox-tools"
>
<a
class=
"collapse-link"
>
<i
class=
"fa fa-chevron-up"
></i>
</a>
<a
class=
"dropdown-toggle"
data-toggle=
"dropdown"
href=
"#"
>
<i
class=
"fa fa-wrench"
></i>
</a>
</div>
</div>
<div
class=
"ibox-content"
>
<table
class=
"table"
>
<tbody>
<tr>
<td
width=
"20%"
>
{% trans 'Name' %}:
</td>
<td><b>
{{ object.name }}
</b></td>
</tr>
<tr>
<td>
{% trans 'Comment' %}:
</td>
<td><b>
{{ object.comment }}
</b></td>
</tr>
<tr>
<td>
{% trans 'Created at:' %}:
</td>
<td><b>
{{ object.date_created }}
</b></td>
</tr>
</table>
</div>
</div>
</div>
<div
class=
"col-sm-5"
style=
"padding-left: 0;padding-right: 0"
>
<div
class=
"panel panel-primary"
>
<div
class=
"panel-heading"
>
<i
class=
"fa fa-info-circle"
></i>
{% trans 'Quick modify' %}
</div>
<div
class=
"panel-body"
>
<table
class=
"table"
>
<tbody>
<tr
class=
"no-borders-tr"
>
<td
width=
"50%"
>
{% trans 'Active' %}:
</td>
<td><span
class=
"pull-right"
>
<div
class=
"switch"
>
<div
class=
"onoffswitch"
>
<input
type=
"checkbox"
{%
if
user_object
.
is_active
%}
checked
{%
endif
%}
class=
"onoffswitch-checkbox"
id=
"is_active"
>
<label
class=
"onoffswitch-label"
for=
"is_active"
>
<span
class=
"onoffswitch-inner"
></span>
<span
class=
"onoffswitch-switch"
></span>
</label>
</div>
</div>
</span></td>
</tr>
<tr>
<td>
{% trans 'Enable OTP' %}:
</td>
<td><span
class=
"pull-right"
>
<div
class=
"switch"
>
<div
class=
"onoffswitch"
>
<input
type=
"checkbox"
class=
"onoffswitch-checkbox"
{%
if
user_object
.
enable_otp
%}
checked
{%
endif
%}
id=
"enable_otp"
>
<label
class=
"onoffswitch-label"
for=
"enable_otp"
>
<span
class=
"onoffswitch-inner"
></span>
<span
class=
"onoffswitch-switch"
></span>
</label>
</div>
</div>
</span></td>
</tr>
<tr>
<td>
{% trans 'Reset password' %}:
</td>
<td>
<span
class=
"pull-right"
>
<button
type=
"button"
class=
"btn btn-primary btn-xs"
id=
"btn_reset_password"
style=
"width: 54px"
>
{% trans 'Reset' %}
</button>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
apps/users/templates/users/user_group_list.html
View file @
6c7e5104
{% extends '_list_base.html' %}
{% load i18n %}
{% load common_tags %}
{% load i18n static %}
{% block custom_head_css_js %}
<link
href=
"{% static "
css
/
plugins
/
sweetalert
/
sweetalert
.
css
"
%}"
rel=
"stylesheet"
>
<script
src=
"{% static "
js
/
plugins
/
sweetalert
/
sweetalert
.
min
.
js
"
%}"
></script>
{% endblock %}
{% block content_left_head %}
<a
href=
"{% url 'users:user-group-create' %}"
class=
"btn btn-sm btn-primary "
>
添加用户组
</a>
<a
href=
"{% url 'users:user-group-create' %}"
class=
"btn btn-sm btn-primary "
>
{% trans "Add User Group" %}
</a>
{% endblock %}
{% block table_head %}
<th
class=
"text-center"
>
<input
type=
"checkbox"
id=
"check_all"
onclick=
"checkAll('check_all', 'checked')"
>
</th>
<th
class=
"text-center"
><a
href=
"{% url 'users:user-group-list' %}?sort=name"
>
名称
</a></th>
<th
class=
"text-center"
>
用户数量
</th>
<th
class=
"text-center"
>
资产数量
</th>
<th
class=
"text-center"
>
描述
</th>
<th
class=
"text-center"
><a
href=
"{% url 'users:user-group-list' %}?sort=name"
>
{% trans "Name" %}
</a></th>
<th
class=
"text-center"
>
{% trans "User Amount" %}
</th>
<th
class=
"text-center"
>
{% trans "Asset Amount" %}
</th>
<th
class=
"text-center"
>
{% trans "Comment" %}
</th>
<th
class=
"text-center"
></th>
{% endblock %}
...
...
@@ -27,12 +31,13 @@
{{ user_group.name }}
</a>
</td>
<td
class=
"text-center"
>
{{ user_group.users.
all|length
}}
</td>
<td
class=
"text-center"
>
数量
</td>
<td
class=
"text-center"
>
{{ user_group.users.
count
}}
</td>
<td
class=
"text-center"
>
999
</td>
<th
class=
"text-center"
>
{{ user_group.comment|truncatewords:8 }}
</th>
<td
class=
"text-center"
>
<a
href=
"{% url 'users:user-group-update' pk=user_group.id %}"
class=
"btn btn-xs btn-info"
>
编辑
</a>
<a
href=
"{% url 'users:user-group-delete' pk=user_group.id %}"
class=
"btn btn-xs btn-danger del"
>
删除
</a>
<a
href=
"{% url 'users:user-group-update' pk=user_group.id %}"
class=
"btn btn-xs btn-info"
>
{% trans "Edit" %}
</a>
<a
href=
"javascript:void(0)"
data-gid=
"{{ user_group.id }}"
class=
"btn btn-xs btn-danger del {% ifequal user_group.name 'Default' %}disabled{% else %}btn_delete_user_group{% endifequal %}"
>
{% trans "Delete" %}
</a>
</td>
</tr>
{% endfor %}
...
...
@@ -42,18 +47,53 @@
<form
id=
""
method=
"get"
action=
""
class=
" mail-search"
>
<div
class=
"input-group"
>
<select
class=
"form-control m-b"
style=
"width: auto"
>
<option>
批量删除
</option>
<option>
批量更新
</option>
<option>
批量禁用
</option>
<option>
批量导出
</option>
<option>
{% trans "Bulk Update" %}
</option>
<option>
{% trans "Bulk Export" %}
</option>
<option>
{% trans "Bulk Update" %}
</option>
</select>
<div
class=
"input-group-btn pull-left"
style=
"padding-left: 5px;"
>
<button
id=
'search_btn'
type=
"submit"
style=
"height: 32px;"
class=
"btn btn-sm btn-primary"
>
确认
</button>
<button
id=
'search_btn'
type=
"submit"
style=
"height: 32px;"
class=
"btn btn-sm btn-primary"
>
{% trans "Confirm" %}
</button>
</div>
</div>
</form>
{% endblock %}
{% block custom_foot_js %}
<script>
$
(
document
).
on
(
'click'
,
'.btn_delete_user_group'
,
function
(){
var
$this
=
$
(
this
);
function
doDelete
()
{
var
group_id
=
$this
.
data
(
'gid'
);
var
the_url
=
"{% url 'users:user-group-delete-api' 99991937 %}"
.
replace
(
'99991937'
,
group_id
);
var
body
=
{};
var
success
=
function
()
{
var
msg
=
"{% trans 'Group Deleted.' %}"
;
swal
(
"{% trans 'Group Delete' %}"
,
msg
,
"success"
);
$this
.
closest
(
'tr.gradeX'
).
remove
();
};
var
fail
=
function
()
{
var
msg
=
"{% trans 'Group Deleting failed.' %}"
;
swal
(
"{% trans 'Group Delete' %}"
,
msg
,
"error"
);
}
APIUpdateAttr
({
url
:
the_url
,
body
:
JSON
.
stringify
(
body
),
method
:
'DELETE'
,
success
:
success
,
error
:
fail
});
}
swal
({
title
:
"{% trans 'Are you sure?' %}"
,
text
:
"{% trans 'This will delete the selected group, but will not remove any user in this group.' %}"
,
type
:
"warning"
,
showCancelButton
:
true
,
confirmButtonColor
:
"#DD6B55"
,
confirmButtonText
:
"{% trans 'Confirm' %}"
,
closeOnConfirm
:
false
},
function
()
{
doDelete
();
});
})
</script>
{% endblock %}
apps/users/templates/users/user_update.html
View file @
6c7e5104
...
...
@@ -5,7 +5,7 @@
<div
class=
"form-group"
>
<label
for=
"{{ form.username.id_for_label }}"
class=
"col-sm-2 control-label"
>
{% trans 'Username' %}
</label>
<div
class=
"col-sm-9 controls"
>
<input
id=
"{{ form.username.id_for_label }}"
name=
"{{ form.username.html_name }}"
type=
"text"
value=
"{{ user.username }}"
readonly
class=
"form-control"
>
<input
id=
"{{ form.username.id_for_label }}"
name=
"{{ form.username.html_name }}"
type=
"text"
value=
"{{ user
_object
.username }}"
readonly
class=
"form-control"
>
</div>
</div>
{% endblock %}
...
...
apps/users/urls.py
View file @
6c7e5104
...
...
@@ -31,7 +31,6 @@ urlpatterns = [
url
(
r'^user-group/(?P<pk>[0-9]+)$'
,
views
.
UserGroupDetailView
.
as_view
(),
name
=
'user-group-detail'
),
url
(
r'^user-group/create$'
,
views
.
UserGroupCreateView
.
as_view
(),
name
=
'user-group-create'
),
url
(
r'^user-group/(?P<pk>[0-9]+)/update$'
,
views
.
UserGroupUpdateView
.
as_view
(),
name
=
'user-group-update'
),
url
(
r'^user-group/(?P<pk>[0-9]+)/delete$'
,
views
.
UserGroupDeleteView
.
as_view
(),
name
=
'user-group-delete'
),
]
...
...
@@ -43,9 +42,12 @@ urlpatterns += [
api
.
UserAttributeApi
.
as_view
(),
name
=
'user-patch-api'
),
url
(
r'^v1/users/(?P<pk>\d+)/reset-password/$'
,
api
.
UserResetPasswordApi
.
as_view
(),
name
=
'user-reset-password-api'
),
url
(
r'^v1/users/(?P<pk>\d+)/reset-pk/$'
,
api
.
UserResetPKApi
.
as_view
(),
name
=
'user-reset-pk-api'
),
url
(
r'^v1/users/(?P<pk>\d+)/update-pk/$'
,
api
.
UserUpdatePKApi
.
as_view
(),
name
=
'user-update-pk-api'
),
url
(
r'^v1/user-groups$'
,
api
.
UserGroupListAddApi
.
as_view
(),
name
=
'user-group-list-api'
),
url
(
r'^v1/user-groups/(?P<pk>[0-9]+)$'
,
api
.
UserGroupDetailDeleteUpdateApi
.
as_view
(),
name
=
'user-group-detail-api'
),
url
(
r'^v1/user-groups/(?P<pk>[0-9]+)/edit$'
,
api
.
UserGroupEditApi
.
as_view
(),
name
=
'user-group-edit-api'
),
url
(
r'^v1/user-groups/(?P<pk>[0-9]+)/delete/$'
,
api
.
GroupDeleteApi
.
as_view
(),
name
=
'user-group-delete-api'
),
]
apps/users/views.py
View file @
6c7e5104
...
...
@@ -19,7 +19,7 @@ from django.views.decorators.debug import sensitive_post_parameters
from
django.views.generic.base
import
TemplateView
from
django.views.generic.list
import
ListView
from
django.views.generic.edit
import
CreateView
,
DeleteView
,
UpdateView
,
FormView
,
SingleObjectMixin
,
\
FormMixin
,
ModelFormMixin
,
ProcessFormView
,
BaseCreateView
FormMixin
from
django.views.generic.detail
import
DetailView
from
formtools.wizard.views
import
SessionWizardView
...
...
@@ -133,7 +133,7 @@ class UserUpdateView(AdminUserRequiredMixin, UpdateView):
model
=
User
form_class
=
UserUpdateForm
template_name
=
'users/user_update.html'
context_object_name
=
'user'
context_object_name
=
'user
_object
'
success_url
=
reverse_lazy
(
'users:user-list'
)
def
form_valid
(
self
,
form
):
...
...
@@ -146,10 +146,6 @@ class UserUpdateView(AdminUserRequiredMixin, UpdateView):
user
.
set_password
(
password
)
return
super
(
UserUpdateView
,
self
)
.
form_valid
(
form
)
def
form_invalid
(
self
,
form
):
print
(
form
.
errors
)
return
super
(
UserUpdateView
,
self
)
.
form_invalid
(
form
)
def
get_context_data
(
self
,
**
kwargs
):
context
=
super
(
UserUpdateView
,
self
)
.
get_context_data
(
**
kwargs
)
context
.
update
({
'app'
:
_
(
'Users'
),
'action'
:
_
(
'Update user'
)})
...
...
@@ -238,8 +234,14 @@ class UserGroupUpdateView(UpdateView):
pass
class
UserGroupDetailView
(
DetailView
):
pass
class
UserGroupDetailView
(
AdminUserRequiredMixin
,
DetailView
):
model
=
UserGroup
template_name
=
'users/user_group_detail.html'
def
get_context_data
(
self
,
**
kwargs
):
context
=
{
'app'
:
_
(
'Users'
),
'action'
:
_
(
'User Group Detail'
)}
kwargs
.
update
(
context
)
return
super
(
UserGroupDetailView
,
self
)
.
get_context_data
(
**
kwargs
)
class
UserGroupDeleteView
(
DeleteView
):
...
...
@@ -332,6 +334,7 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
if
field
.
name
==
'enable_otp'
:
user
.
enable_otp
=
field
.
value
()
user
.
is_first_login
=
False
user
.
is_public_key_valid
=
True
user
.
save
()
return
redirect
(
reverse
(
'index'
))
...
...
@@ -351,6 +354,16 @@ class UserFirstLoginView(LoginRequiredMixin, SessionWizardView):
}
return
super
(
UserFirstLoginView
,
self
)
.
get_form_initial
(
step
)
def
get_form
(
self
,
step
=
None
,
data
=
None
,
files
=
None
):
form
=
super
(
UserFirstLoginView
,
self
)
.
get_form
(
step
,
data
,
files
)
if
step
is
None
:
step
=
self
.
steps
.
current
if
step
==
'1'
:
form
.
user
=
self
.
request
.
user
return
form
class
UserAssetPermissionView
(
AdminUserRequiredMixin
,
FormMixin
,
SingleObjectMixin
,
ListView
):
paginate_by
=
settings
.
CONFIG
.
DISPLAY_PER_PAGE
...
...
@@ -376,7 +389,7 @@ class UserAssetPermissionView(AdminUserRequiredMixin, FormMixin, SingleObjectMix
def
get_queryset
(
self
):
asset_permissions
=
set
(
self
.
object
.
asset_permissions
.
all
())
\
|
self
.
get_asset_permission_inherit_from_user_group
()
|
self
.
get_asset_permission_inherit_from_user_group
()
return
list
(
asset_permissions
)
def
get_context_data
(
self
,
**
kwargs
):
...
...
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