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
0c9e24dc
Commit
0c9e24dc
authored
Dec 10, 2017
by
ibuler
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Feture] 添加ops 页面
parent
18fd04d6
Hide whitespace changes
Inline
Side-by-side
Showing
41 changed files
with
1438 additions
and
565 deletions
+1438
-565
api.py
apps/assets/api.py
+5
-4
models.py
apps/assets/models.py
+0
-2
__init__.py
apps/assets/models/__init__.py
+0
-2
asset.py
apps/assets/models/asset.py
+5
-0
group.py
apps/assets/models/group.py
+0
-1
user.py
apps/assets/models/user.py
+0
-19
serializers.py
apps/assets/serializers.py
+23
-5
tasks.py
apps/assets/tasks.py
+213
-21
admin_user_assets.html
apps/assets/templates/assets/admin_user_assets.html
+387
-0
admin_user_detail.html
apps/assets/templates/assets/admin_user_detail.html
+8
-116
admin_user_list.html
apps/assets/templates/assets/admin_user_list.html
+3
-6
system_user_asset.html
apps/assets/templates/assets/system_user_asset.html
+2
-4
system_user_list.html
apps/assets/templates/assets/system_user_list.html
+1
-1
views_urls.py
apps/assets/urls/views_urls.py
+1
-0
utils.py
apps/assets/utils.py
+5
-38
admin_user.py
apps/assets/views/admin_user.py
+26
-1
system_user.py
apps/assets/views/system_user.py
+3
-10
celery.py
apps/common/celery.py
+2
-2
utils.py
apps/common/utils.py
+5
-0
settings.py
apps/jumpserver/settings.py
+1
-0
callback.py
apps/ops/ansible/callback.py
+14
-13
inventory.py
apps/ops/ansible/inventory.py
+36
-25
runner.py
apps/ops/ansible/runner.py
+23
-34
test_inventory.py
apps/ops/ansible/test_inventory.py
+2
-2
test_runner.py
apps/ops/ansible/test_runner.py
+5
-2
api.py
apps/ops/api.py
+30
-3
inventory.py
apps/ops/inventory.py
+46
-0
models.py
apps/ops/models.py
+107
-34
serializers.py
apps/ops/serializers.py
+33
-2
tasks.py
apps/ops/tasks.py
+6
-15
task_adhoc.html
apps/ops/templates/ops/task_adhoc.html
+117
-0
task_detail.html
apps/ops/templates/ops/task_detail.html
+29
-50
task_history.html
apps/ops/templates/ops/task_history.html
+123
-0
task_list.html
apps/ops/templates/ops/task_list.html
+13
-15
test_utils.py
apps/ops/test_utils.py
+4
-5
api_urls.py
apps/ops/urls/api_urls.py
+2
-0
view_urls.py
apps/ops/urls/view_urls.py
+3
-0
utils.py
apps/ops/utils.py
+112
-80
views.py
apps/ops/views.py
+41
-16
tasks.py
apps/perms/tasks.py
+1
-36
_nav.html
apps/templates/_nav.html
+1
-1
No files found.
apps/assets/api.py
View file @
0c9e24dc
...
...
@@ -26,12 +26,13 @@ from .hands import IsSuperUser, IsAppUser, IsValidUser, \
get_user_granted_assets
,
push_users
from
.models
import
AssetGroup
,
Asset
,
Cluster
,
SystemUser
,
AdminUser
from
.
import
serializers
from
.tasks
import
update_assets_hardware_info
from
.utils
import
test_admin_user_connective_manual
from
.tasks
import
update_assets_hardware_info
,
test_admin_user_connectability_manual
class
AssetViewSet
(
IDInFilterMixin
,
BulkModelViewSet
):
"""API endpoint that allows Asset to be viewed or edited."""
"""
API endpoint that allows Asset to be viewed or edited.
"""
queryset
=
Asset
.
objects
.
all
()
serializer_class
=
serializers
.
AssetSerializer
permission_classes
=
(
IsValidUser
,)
...
...
@@ -195,7 +196,7 @@ class AssetAdminUserTestView(AssetRefreshHardwareView):
def
retrieve
(
self
,
request
,
*
args
,
**
kwargs
):
asset_id
=
kwargs
.
get
(
'pk'
)
asset
=
get_object_or_404
(
Asset
,
pk
=
asset_id
)
result
=
test_admin_user_connect
ive_manual
([
asset
]
)
result
=
test_admin_user_connect
ability_manual
(
asset
)
if
result
:
return
Response
(
'1'
)
else
:
...
...
apps/assets/models.py
deleted
100644 → 0
View file @
18fd04d6
# -*- coding: utf-8 -*-
#
apps/assets/models/__init__.py
View file @
0c9e24dc
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
print
(
"Import assets model"
)
from
.user
import
AdminUser
,
SystemUser
from
.cluster
import
*
from
.group
import
*
...
...
apps/assets/models/asset.py
View file @
0c9e24dc
...
...
@@ -7,6 +7,7 @@ import uuid
from
django.db
import
models
import
logging
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.core.cache
import
cache
from
.cluster
import
Cluster
from
.group
import
AssetGroup
...
...
@@ -21,6 +22,7 @@ def get_default_cluster():
class
Asset
(
models
.
Model
):
# Todo: Move them to settings
STATUS_CHOICES
=
(
(
'In use'
,
_
(
'In use'
)),
(
'Out of use'
,
_
(
'Out of use'
)),
...
...
@@ -103,6 +105,9 @@ class Asset(models.Model):
'groups'
:
[
group
.
name
for
group
in
self
.
groups
.
all
()],
}
def
is_connective
(
self
):
return
cache
.
get
(
self
.
hostname
)
def
_to_secret_json
(
self
):
"""
Ansible use it create inventory
...
...
apps/assets/models/group.py
View file @
0c9e24dc
...
...
@@ -19,7 +19,6 @@ logger = logging.getLogger(__name__)
class
AssetGroup
(
models
.
Model
):
id
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
name
=
models
.
CharField
(
max_length
=
64
,
unique
=
True
,
verbose_name
=
_
(
'Name'
))
system_users
=
models
.
ManyToManyField
(
SystemUser
,
related_name
=
'asset_groups'
,
blank
=
True
)
created_by
=
models
.
CharField
(
max_length
=
32
,
blank
=
True
,
verbose_name
=
_
(
'Created by'
))
date_created
=
models
.
DateTimeField
(
auto_now_add
=
True
,
null
=
True
,
verbose_name
=
_
(
'Date created'
))
comment
=
models
.
TextField
(
blank
=
True
,
verbose_name
=
_
(
'Comment'
))
...
...
apps/assets/models/user.py
View file @
0c9e24dc
...
...
@@ -201,21 +201,6 @@ class SystemUser(models.Model):
def
public_key
(
self
,
public_key_raw
):
self
.
_public_key
=
signer
.
sign
(
public_key_raw
)
def
get_assets_inherit_from_asset_groups
(
self
):
assets
=
set
()
asset_groups
=
self
.
asset_groups
.
all
()
for
asset_group
in
asset_groups
:
for
asset
in
asset_group
.
assets
.
all
():
setattr
(
asset
,
'is_inherit_from_asset_groups'
,
True
)
setattr
(
asset
,
'inherit_from_asset_groups'
,
getattr
(
asset
,
'inherit_from_asset_groups'
,
set
())
.
add
(
asset_group
))
assets
.
add
(
asset
)
return
assets
def
get_assets
(
self
):
assets
=
set
(
self
.
assets
.
all
())
|
self
.
get_assets_inherit_from_asset_groups
()
return
list
(
assets
)
def
_to_secret_json
(
self
):
"""Push system user use it"""
return
{
...
...
@@ -232,10 +217,6 @@ class SystemUser(models.Model):
def
assets_amount
(
self
):
return
self
.
assets
.
count
()
@property
def
asset_group_amount
(
self
):
return
self
.
asset_groups
.
count
()
def
to_json
(
self
):
return
{
'id'
:
self
.
id
,
...
...
apps/assets/serializers.py
View file @
0c9e24dc
# -*- coding: utf-8 -*-
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.core.cache
import
cache
from
rest_framework
import
viewsets
,
serializers
,
generics
from
.models
import
AssetGroup
,
Asset
,
Cluster
,
AdminUser
,
SystemUser
from
common.mixins
import
IDInFilterMixin
from
rest_framework_bulk
import
BulkListSerializer
,
BulkSerializerMixin
from
.models
import
AssetGroup
,
Asset
,
Cluster
,
AdminUser
,
SystemUser
from
.tasks
import
SYSTEM_USER_CONN_CACHE_KEY_PREFIX
,
ADMIN_USER_CONN_CACHE_KEY_PREFIX
class
AssetGroupSerializer
(
BulkSerializerMixin
,
serializers
.
ModelSerializer
):
assets_amount
=
serializers
.
SerializerMethodField
()
...
...
@@ -64,11 +64,20 @@ class ClusterUpdateAssetsSerializer(serializers.ModelSerializer):
class
AdminUserSerializer
(
serializers
.
ModelSerializer
):
assets
=
serializers
.
PrimaryKeyRelatedField
(
many
=
True
,
queryset
=
Asset
.
objects
.
all
())
unreachable_amount
=
serializers
.
SerializerMethodField
()
class
Meta
:
model
=
AdminUser
fields
=
'__all__'
@staticmethod
def
get_unreachable_amount
(
obj
):
data
=
cache
.
get
(
ADMIN_USER_CONN_CACHE_KEY_PREFIX
+
obj
.
name
)
if
data
:
return
len
(
data
.
get
(
'dark'
))
else
:
return
'Unknown'
def
get_field_names
(
self
,
declared_fields
,
info
):
fields
=
super
(
AdminUserSerializer
,
self
)
.
get_field_names
(
declared_fields
,
info
)
fields
.
append
(
'assets_amount'
)
...
...
@@ -76,10 +85,20 @@ class AdminUserSerializer(serializers.ModelSerializer):
class
SystemUserSerializer
(
serializers
.
ModelSerializer
):
unreachable_amount
=
serializers
.
SerializerMethodField
()
class
Meta
:
model
=
SystemUser
exclude
=
(
'_password'
,
'_private_key'
,
'_public_key'
)
@staticmethod
def
get_unreachable_amount
(
obj
):
data
=
cache
.
get
(
SYSTEM_USER_CONN_CACHE_KEY_PREFIX
+
obj
.
name
)
if
data
:
return
len
(
data
.
get
(
'dark'
))
else
:
return
"Unknown"
def
get_field_names
(
self
,
declared_fields
,
info
):
fields
=
super
(
SystemUserSerializer
,
self
)
.
get_field_names
(
declared_fields
,
info
)
fields
.
extend
([
'assets_amount'
])
...
...
@@ -167,8 +186,7 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
@staticmethod
def
get_system_users_join
(
obj
):
return
', '
.
join
([
system_user
.
username
for
system_user
in
obj
.
system_users_granted
])
return
', '
.
join
([
system_user
.
username
for
system_user
in
obj
.
system_users_granted
])
class
MyAssetGrantedSerializer
(
AssetGrantedSerializer
):
...
...
apps/assets/tasks.py
View file @
0c9e24dc
# ~*~ coding: utf-8 ~*~
from
celery
import
shared_task
import
json
from
celery
import
shared_task
from
django.core.cache
import
cache
from
ops.tasks
import
run_AdHoc
from
common.utils
import
get_object_or_none
,
capacity_convert
,
sum_capacity
from
assets.models
import
SystemUser
,
AdminUser
from
common.utils
import
get_object_or_none
,
capacity_convert
,
sum_capacity
,
encrypt_password
,
get_logger
from
.models
import
Asset
FORKS
=
10
TIMEOUT
=
60
logger
=
get_logger
(
__file__
)
ADMIN_USER_CONN_CACHE_KEY_PREFIX
=
"ADMIN_USER_CONN_"
SYSTEM_USER_CONN_CACHE_KEY_PREFIX
=
'SYSTEM_USER_CONN_'
@shared_task
def
update_assets_hardware_info
(
assets
):
task_tuple
=
(
(
'setup'
,
''
),
)
summary
,
result
=
run_AdHoc
(
task_tuple
,
assets
,
record
=
False
)
for
hostname
,
info
in
result
[
'contacted'
]
.
items
():
"""
Using ansible api to update asset hardware info
:param assets: asset seq
:return: result summary ['contacted': {}, 'dark': {}]
"""
from
ops.utils
import
run_adhoc
name
=
"GET_ASSETS_HARDWARE_INFO"
tasks
=
[
{
'name'
:
name
,
'action'
:
{
'module'
:
'setup'
}
}
]
hostname_list
=
[
asset
.
hostname
for
asset
in
assets
]
result
=
run_adhoc
(
hostname_list
,
pattern
=
'all'
,
tasks
=
tasks
,
name
=
name
,
run_as_admin
=
True
)
summary
,
result_raw
=
result
.
results_summary
,
result
.
results_raw
for
hostname
,
info
in
result_raw
[
'ok'
]
.
items
():
if
info
:
info
=
info
[
0
][
'ansible_facts'
]
info
=
info
[
name
][
'ansible_facts'
]
else
:
continue
asset
=
get_object_or_none
(
Asset
,
hostname
=
hostname
)
...
...
@@ -58,23 +80,193 @@ def update_assets_hardware_info(assets):
@shared_task
def
update_assets_hardware_period
():
"""
Update asset hardware period task
:return:
"""
assets
=
Asset
.
objects
.
filter
(
type__in
=
[
'Server'
,
'VM'
])
update_assets_hardware_info
(
assets
)
@shared_task
def
test_admin_user_connective_period
():
assets
=
Asset
.
objects
.
filter
(
type__in
=
[
'Server'
,
'VM'
])
task_tuple
=
(
(
'ping'
,
''
),
)
summary
,
_
=
run_AdHoc
(
task_tuple
,
assets
,
record
=
False
)
for
i
in
summary
[
'success'
]:
cache
.
set
(
i
,
'1'
,
2
*
60
*
60
*
60
)
for
i
,
msg
in
summary
[
'failed'
]:
cache
.
set
(
i
,
'0'
,
60
*
60
*
60
)
return
summary
def
test_admin_user_connectability
(
admin_user
):
"""
Test asset admin user can connect or not. Using ansible api do that
:param admin_user:
:return:
"""
from
ops.utils
import
run_adhoc
assets
=
admin_user
.
assets
.
all
()
# assets = Asset.objects.filter(type__in=['Server', 'VM'])
hosts
=
[
asset
.
hostname
for
asset
in
assets
]
tasks
=
[
{
"name"
:
"TEST_ADMIN_CONNECTIVE"
,
"action"
:
{
"module"
:
"ping"
,
}
}
]
result
=
run_adhoc
(
hosts
,
tasks
=
tasks
,
pattern
=
"all"
,
run_as_admin
=
True
)
return
result
.
results_summary
@shared_task
def
test_admin_user_connectability_period
():
# assets = Asset.objects.filter(type__in=['Server', 'VM'])
admin_users
=
AdminUser
.
objects
.
all
()
for
admin_user
in
admin_users
:
summary
=
test_admin_user_connectability
(
admin_user
)
cache
.
set
(
ADMIN_USER_CONN_CACHE_KEY_PREFIX
+
admin_user
.
name
,
summary
,
60
*
60
*
60
)
for
i
in
summary
[
'contacted'
]:
cache
.
set
(
ADMIN_USER_CONN_CACHE_KEY_PREFIX
+
i
,
1
,
60
*
60
*
60
)
for
i
in
summary
[
'dark'
]:
cache
.
set
(
ADMIN_USER_CONN_CACHE_KEY_PREFIX
+
i
,
0
,
60
*
60
*
60
)
def
test_admin_user_connectability_manual
(
asset
):
from
ops.utils
import
run_adhoc
# assets = Asset.objects.filter(type__in=['Server', 'VM'])
hosts
=
[
asset
.
hostname
]
tasks
=
[
{
"name"
:
"TEST_ADMIN_CONNECTIVE"
,
"action"
:
{
"module"
:
"ping"
,
}
}
]
result
=
run_adhoc
(
hosts
,
tasks
=
tasks
,
pattern
=
"all"
,
run_as_admin
=
True
)
if
result
.
results_summary
[
'dark'
]:
return
False
else
:
return
True
@shared_task
def
test_system_user_connectability
(
system_user
):
"""
Test system cant connect his assets or not.
:param system_user:
:return:
"""
from
ops.utils
import
run_adhoc
assets
=
system_user
.
assets
.
all
()
hosts
=
[
asset
.
hostname
for
asset
in
assets
]
tasks
=
[
{
"name"
:
"TEST_SYSTEM_USER_CONNECTIVE"
,
"action"
:
{
"module"
:
"ping"
,
}
}
]
result
=
run_adhoc
(
hosts
,
tasks
=
tasks
,
pattern
=
"all"
,
run_as
=
system_user
.
name
)
return
result
.
results_summary
@shared_task
def
test_system_user_connectability_period
():
for
system_user
in
SystemUser
.
objects
.
all
():
summary
=
test_system_user_connectability
(
system_user
)
cache
.
set
(
SYSTEM_USER_CONN_CACHE_KEY_PREFIX
+
system_user
.
name
,
summary
,
60
*
60
*
60
)
def
get_push_system_user_tasks
(
system_user
):
tasks
=
[
{
'name'
:
'Add user'
,
'action'
:
{
'module'
:
'user'
,
'args'
:
'name={} shell={} state=present password={}'
.
format
(
system_user
.
username
,
system_user
.
shell
,
encrypt_password
(
system_user
.
password
),
),
}
},
{
'name'
:
'Set authorized key'
,
'action'
:
{
'module'
:
'authorized_key'
,
'args'
:
"user={} state=present key='{}'"
.
format
(
system_user
.
username
,
system_user
.
public_key
)
}
},
{
'name'
:
'Set sudoers'
,
'action'
:
{
'module'
:
'lineinfile'
,
'args'
:
"dest=/etc/sudoers state=present regexp='^{0} ALL=' "
"line='{0} ALL=(ALL) NOPASSWD: {1}' "
"validate='visudo -cf
%
s'"
.
format
(
system_user
.
username
,
system_user
.
sudo
,
)
}
}
]
return
tasks
PUSH_SYSTEM_USER_PERIOD_TASK_NAME
=
'PUSH SYSTEM USER {} PERIOD...'
PUSH_SYSTEM_USER_TASK_NAME
=
'PUSH SYSTEM USER {} ASSETS'
def
get_push_system_user_task
(
system_user
):
from
ops.utils
import
get_task_by_name
task
=
get_task_by_name
(
PUSH_SYSTEM_USER_PERIOD_TASK_NAME
.
format
(
system_user
.
name
))
return
task
def
push_system_user
(
system_user
,
assets
,
name
):
from
ops.utils
import
get_task_by_name
,
run_adhoc_object
,
\
create_task
,
create_adhoc
if
system_user
.
auto_push
and
assets
:
task
=
get_task_by_name
(
name
)
if
not
task
:
task
=
create_task
(
name
,
created_by
=
"System"
)
task
.
save
()
tasks
=
get_push_system_user_tasks
(
system_user
)
hosts
=
[
asset
.
hostname
for
asset
in
assets
]
options
=
{
'forks'
:
FORKS
,
'timeout'
:
TIMEOUT
}
adhoc
=
task
.
get_latest_adhoc
()
if
not
adhoc
or
adhoc
.
task
!=
tasks
or
adhoc
.
hosts
!=
hosts
:
adhoc
=
create_adhoc
(
task
=
task
,
tasks
=
tasks
,
pattern
=
'all'
,
options
=
options
,
hosts
=
hosts
,
run_as_admin
=
True
)
return
run_adhoc_object
(
adhoc
)
@shared_task
def
push_system_user_period
():
logger
.
debug
(
"Push system user period"
)
for
s
in
SystemUser
.
objects
.
filter
(
auto_push
=
True
):
assets
=
s
.
assets
.
all
()
name
=
PUSH_SYSTEM_USER_PERIOD_TASK_NAME
.
format
(
s
.
name
)
push_system_user
(
s
,
assets
,
name
)
def
push_system_user_to_assets_if_need
(
system_user
,
assets
=
None
,
asset_groups
=
None
):
assets_to_push
=
[]
system_user_assets
=
system_user
.
assets
.
all
()
if
assets
:
assets_to_push
.
extend
(
assets
)
if
asset_groups
:
for
group
in
asset_groups
:
assets_to_push
.
extend
(
group
.
assets
.
all
())
assets_need_push
=
set
(
assets_to_push
)
-
set
(
system_user_assets
)
if
not
assets_need_push
:
return
logger
.
debug
(
"Push system user {} to {} assets"
.
format
(
system_user
.
name
,
', '
.
join
([
asset
.
hostname
for
asset
in
assets_need_push
])
))
result
=
push_system_user
(
system_user
,
assets_need_push
,
PUSH_SYSTEM_USER_TASK_NAME
)
system_user
.
assets
.
add
(
*
tuple
(
assets_need_push
))
return
result
apps/assets/templates/assets/admin_user_assets.html
0 → 100644
View file @
0c9e24dc
{% extends 'base.html' %}
{% load static %}
{% load i18n %}
{% block custom_head_css_js %}
<link
href=
"{% static 'css/plugins/select2/select2.min.css' %}"
rel=
"stylesheet"
>
<script
src=
"{% static 'js/plugins/select2/select2.full.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>
<a
href=
"{% url 'assets:admin-user-detail' pk=admin_user.pk %}"
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Detail' %}
</a>
</li>
<li
class=
"active"
>
<a
href=
"{% url 'assets:admin-user-assets' pk=admin_user.pk %}"
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Assets list' %}
</a>
</li>
<li
class=
"pull-right"
>
<a
class=
"btn btn-outline btn-default"
href=
"{% url 'assets:admin-user-update' pk=admin_user.id %}"
><i
class=
"fa fa-edit"
></i>
Update
</a>
</li>
<li
class=
"pull-right"
>
<a
class=
"btn btn-outline btn-danger btn-delete-admin-user"
>
<i
class=
"fa fa-edit"
></i>
Delete
</a>
</li>
</ul>
</div>
<div
class=
"tab-content"
>
<div
class=
"col-sm-8"
style=
"padding-left: 0;"
>
<div
class=
"ibox float-e-margins"
>
<div
class=
"ibox-title"
>
<span
style=
"float: left"
>
{% trans 'Asset list of ' %}
<b>
{{ admin_user.name }}
</b>
<span
class=
"badge"
>
{{ total_amount }}
</span>
<span
class=
"badge badge-danger"
>
{{ unreachable_amount }}
</span></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>
<ul
class=
"dropdown-menu dropdown-user"
>
</ul>
<a
class=
"close-link"
>
<i
class=
"fa fa-times"
></i>
</a>
</div>
</div>
<div
class=
"ibox-content"
>
<table
class=
"table table-hover"
id=
"system_user_liste"
>
<thead>
<tr>
<th>
{% trans 'Hostname' %}
</th>
<th>
{% trans 'IP' %}
</th>
<th>
{% trans 'Port' %}
</th>
<th>
{% trans 'Alive' %}
</th>
</tr>
</thead>
<tbody>
{% for asset in page_obj %}
<tr>
<td>
{{ asset.hostname }}
</td>
<td>
{{ asset.ip }}
</td>
<td>
{{ asset.port }}
</td>
{% if asset.is_connective == '1' %}
<td>
<i
class=
"fa fa-check text-navy"
></i>
</td>
{% else %}
<td>
<i
class=
"fa fa-times text-danger"
></i>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
<div
class=
"row"
>
{% include '_pagination.html' %}
</div>
</div>
</div>
</div>
<div
class=
"col-sm-4"
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 update' %}
</div>
<div
class=
"panel-body"
>
<table
class=
"table"
>
<tbody>
<tr
class=
"no-borders-tr"
>
<td
width=
"50%"
>
{% trans 'Retest connectivity' %}:
</td>
<td>
<span
style=
"float: right"
>
<button
type=
"button"
class=
"btn btn-primary btn-xs"
style=
"width: 54px"
>
{% trans 'Start' %}
</button>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div
class=
"panel panel-info"
>
<div
class=
"panel-heading"
>
<i
class=
"fa fa-info-circle"
></i>
{% trans 'Replace asset admin user with this' %}
</div>
<div
class=
"panel-body"
>
<table
class=
"table"
>
<tbody>
<form>
<tr
class=
"no-borders-tr"
>
<td
colspan=
"2"
>
<select
data-placeholder=
"{% trans 'Select asset' %}"
class=
"select2"
style=
"width: 100%"
multiple=
""
tabindex=
"4"
>
{% for asset in assets_remain %}
<option
value=
"{{ asset.id }}"
>
{{ asset.ip }}:{{ asset.port }}
</option>
{% endfor %}
</select>
</td>
</tr>
<tr
class=
"no-borders-tr"
>
<td
colspan=
"2"
>
<button
type=
"button"
class=
"btn btn-info btn-sm btn-replace-asset-admin_user"
>
{% trans 'Replace' %}
</button>
</td>
</tr>
</form>
</tbody>
</table>
</div>
</div>
<div
class=
"panel panel-warning"
>
<div
class=
"panel-heading"
>
<i
class=
"fa fa-info-circle"
></i>
{% trans 'Replace asset group admin user with this' %}
</div>
<div
class=
"panel-body"
>
<table
class=
"table"
>
<tbody>
<form>
<tr
class=
"no-borders-tr"
>
<td
colspan=
"2"
>
<select
data-placeholder=
"{% trans 'Select asset groups' %}"
class=
"select2"
style=
"width: 100%"
multiple=
""
tabindex=
"4"
>
{% for asset_group in asset_groups %}
<option
value=
"{{ asset_group.id }}"
>
{{ asset_group.name }}
</option>
{% endfor %}
</select>
</td>
</tr>
<tr
class=
"no-borders-tr"
>
<td
colspan=
"2"
>
<button
type=
"button"
class=
"btn btn-warning btn-sm btn-replace-asset_groups-admin_user"
>
{% trans 'Replace' %}
</button>
</td>
</tr>
</form>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
Array
.
prototype
.
remove
=
function
(
val
)
{
var
index
=
this
.
indexOf
(
val
);
if
(
index
>
-
1
)
{
this
.
splice
(
index
,
1
);
}
};
Array
.
prototype
.
unique
=
function
(){
var
res
=
[];
var
json
=
{};
for
(
var
i
=
0
;
i
<
this
.
length
;
i
++
){
if
(
!
json
[
this
[
i
]]){
res
.
push
(
this
[
i
]);
json
[
this
[
i
]]
=
1
;
}
}
return
res
;
};
function
objectRemove
(
obj
,
name
,
url
,
data
)
{
function
doRemove
()
{
var
body
=
data
;
var
success
=
function
()
{
swal
(
'Remove!'
,
"[ "
+
name
+
"]"
+
" has been deleted "
,
"success"
);
$
(
obj
).
parent
().
parent
().
remove
();
};
var
fail
=
function
()
{
swal
(
"Failed"
,
"Remove"
+
"[ "
+
name
+
" ]"
+
"failed"
,
"error"
);
};
APIUpdateAttr
({
url
:
url
,
body
:
JSON
.
stringify
(
body
),
method
:
'PATCH'
,
success
:
success
,
error
:
fail
});
}
swal
({
title
:
'Are you sure remove ?'
,
text
:
" ["
+
name
+
"] "
,
type
:
"warning"
,
showCancelButton
:
true
,
cancelButtonText
:
'Cancel'
,
confirmButtonColor
:
"#DD6B55"
,
confirmButtonText
:
'Confirm'
,
closeOnConfirm
:
false
},
function
()
{
doRemove
()
});
}
jumpserver
.
assets_selected
=
{};
jumpserver
.
asset_groups_selected
=
{};
$
(
document
).
ready
(
function
()
{
$
(
'.select2'
).
select2
()
.
on
(
"select2:select"
,
function
(
evt
)
{
var
data
=
evt
.
params
.
data
;
jumpserver
.
assets_selected
[
data
.
id
]
=
data
.
text
;
jumpserver
.
asset_groups_selected
[
data
.
id
]
=
data
.
text
;
})
.
on
(
'select2:unselect'
,
function
(
evt
)
{
var
data
=
evt
.
params
.
data
;
delete
jumpserver
.
assets_selected
[
data
.
id
];
delete
jumpserver
.
asset_groups_selected
[
data
.
id
]
});
var
options
=
{
ele
:
$
(
'#system_user_assets_table'
),
buttons
:
[],
order
:
[],
columnDefs
:
[
{
targets
:
0
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
detail_btn
=
'<a href="{% url "assets:asset-detail" pk=99991937 %}" data-aid="'
+
rowData
.
id
+
'">'
+
cellData
+
'</a>'
;
$
(
td
).
html
(
detail_btn
.
replace
(
'99991937'
,
rowData
.
id
));
}},
{
targets
:
3
,
createdCell
:
function
(
td
,
cellData
)
{
if
(
!
cellData
)
{
$
(
td
).
html
(
'<i class="fa fa-times text-danger"></i>'
)
}
else
{
$
(
td
).
html
(
'<i class="fa fa-check text-navy"></i>'
)
}
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
update_btn
=
'<a href="{% url "assets:asset-update" pk=99991937 %}" class="btn btn-xs btn-info">{% trans "Update" %}</a>'
.
replace
(
'99991937'
,
rowData
.
id
);
var
del_btn
=
'<a class="btn btn-xs btn-danger m-l-xs btn_asset_remove" data-aid="99991937">{% trans "Remove" %}</a>'
.
replace
(
'99991937'
,
rowData
.
id
);
$
(
td
).
html
(
update_btn
+
del_btn
)
}}
],
ajax_url
:
'{% url "api-assets:asset-list" %}?admin_user_id={{ admin_user.id }}'
,
columns
:
[{
data
:
"hostname"
},
{
data
:
"ip"
},
{
data
:
"port"
},
{
data
:
"is_active"
},
{
data
:
"id"
}],
op_html
:
$
(
'#actions'
).
html
()
};
jumpserver
.
initDataTable
(
options
);
function
adminUserDelete
(
name
,
url
)
{
function
doDelete
()
{
var
body
=
{};
var
success
=
function
()
{
swal
(
'Deleted!'
,
"[ "
+
name
+
"]"
+
" has been deleted "
,
"success"
);
window
.
location
.
href
=
"{% url 'assets:cluster-list' %}"
;
};
var
fail
=
function
()
{
swal
(
"Failed"
,
"Delete"
+
"[ "
+
name
+
" ]"
+
"failed"
,
"error"
);
};
APIUpdateAttr
({
url
:
url
,
body
:
JSON
.
stringify
(
body
),
method
:
'DELETE'
,
success
:
success
,
error
:
fail
});
}
swal
({
title
:
'Are you sure delete ?'
,
text
:
" ["
+
name
+
"] "
,
type
:
"warning"
,
showCancelButton
:
true
,
cancelButtonText
:
'Cancel'
,
confirmButtonColor
:
"#DD6B55"
,
confirmButtonText
:
'Confirm'
,
closeOnConfirm
:
false
},
function
()
{
doDelete
()
});
}
})
.
on
(
'click'
,
'.btn-replace-asset-admin_user'
,
function
()
{
if
(
Object
.
keys
(
jumpserver
.
assets_selected
).
length
===
0
)
{
return
false
;
}
jumpserver
.
asset_groups_selected
=
{};
var
$data_table
=
$
(
"#system_user_assets_table"
).
DataTable
();
var
assets
=
[];
$
.
map
(
jumpserver
.
assets_selected
,
function
(
value
,
index
)
{
assets
.
push
(
parseInt
(
index
));
});
assets
.
unique
();
var
data
=
[];
var
admin_user_id
=
"{{ admin_user.id }}"
;
var
the_url
=
'{% url "api-assets:asset-list" %}'
;
for
(
var
i
=
0
;
i
<
assets
.
length
;
i
++
)
{
data
.
push
({
"id"
:
assets
[
i
],
"admin_user"
:
admin_user_id
});
}
APIUpdateAttr
({
url
:
the_url
,
body
:
JSON
.
stringify
(
data
),
method
:
'PATCH'
});
$data_table
.
ajax
.
reload
();
})
.
on
(
'click'
,
'.btn-replace-asset_groups-admin_user'
,
function
()
{
if
(
Object
.
keys
(
jumpserver
.
asset_groups_selected
).
length
===
0
)
{
return
false
;
}
jumpserver
.
assets_selected
=
{};
var
$data_table
=
$
(
"#system_user_assets_table"
).
DataTable
();
var
asset_groups
=
[];
var
assets
=
[];
var
data
=
[];
var
the_url
=
'{% url "api-assets:asset-list" %}'
;
$
.
map
(
jumpserver
.
asset_groups_selected
,
function
(
value
,
index
)
{
asset_groups
.
push
(
parseInt
(
index
));
});
$
.
ajax
({
url
:
'{% url "api-assets:asset-group-list" %}?id__in=['
+
asset_groups
.
join
(
','
)
+
']'
,
method
:
'GET'
,
dataType
:
'json'
,
success
:
function
(
result
)
{
for
(
var
i
=
0
;
i
<
result
.
length
;
i
++
)
{
for
(
var
j
=
0
;
j
<
result
[
i
][
'assets'
].
length
;
j
++
)
{
assets
.
push
(
result
[
i
][
'assets'
][
j
])
}
}
for
(
var
z
=
0
;
z
<
assets
.
length
;
z
++
)
{
data
.
push
({
"id"
:
assets
[
z
],
"admin_user"
:
"{{admin_user.id}}"
});
}
APIUpdateAttr
({
url
:
the_url
,
body
:
JSON
.
stringify
(
data
),
method
:
'PATCH'
});
$data_table
.
ajax
.
reload
();
}
});
})
.
on
(
'click'
,
'.btn_asset_remove'
,
function
()
{
var
$this
=
$
(
this
);
var
the_url
=
"{% url 'api-assets:admin-user-detail' pk=admin_user.id %}"
;
var
name
=
$
(
this
).
closest
(
"tr"
).
find
(
":nth-child(1) > a"
).
html
();
var
assets
=
[];
var
delete_asset_id
=
$
(
this
).
data
(
'aid'
);
$
.
ajax
({
url
:
the_url
,
method
:
'GET'
,
dataType
:
'json'
,
success
:
function
(
result
)
{
for
(
var
i
=
0
;
i
<
result
[
'assets'
].
length
;
i
++
)
{
assets
.
push
(
result
[
'assets'
][
i
])
}
assets
.
remove
(
delete_asset_id
);
var
data
=
{
"assets"
:
assets
};
objectRemove
(
$this
,
name
,
the_url
,
data
);
}
})
}).
on
(
'click'
,
'.btn-delete-admin-user'
,
function
()
{
var
$this
=
$
(
this
);
var
name
=
"{{ admin_user.name }}"
;
var
uid
=
"{{ admin_user.id }}"
;
var
the_url
=
'{% url "api-assets:admin-user-detail" pk=99991937 %}'
.
replace
(
'99991937'
,
uid
);
var
redirect_url
=
"{% url 'assets:admin-user-list' %}"
;
objectDelete
(
$this
,
name
,
the_url
,
redirect_url
);
})
</script>
{% endblock %}
apps/assets/templates/assets/admin_user_detail.html
View file @
0c9e24dc
...
...
@@ -15,7 +15,10 @@
<div
class=
"panel-options"
>
<ul
class=
"nav nav-tabs"
>
<li
class=
"active"
>
<a
href=
""
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Detail' %}
</a>
<a
href=
"{% url 'assets:admin-user-detail' pk=admin_user.pk %}"
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Detail' %}
</a>
</li>
<li>
<a
href=
"{% url 'assets:admin-user-assets' pk=admin_user.pk %}"
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Assets list' %}
</a>
</li>
<li
class=
"pull-right"
>
<a
class=
"btn btn-outline btn-default"
href=
"{% url 'assets:admin-user-update' pk=admin_user.id %}"
><i
class=
"fa fa-edit"
></i>
Update
</a>
...
...
@@ -73,51 +76,6 @@
</table>
</div>
</div>
<div
class=
"ibox float-e-margins"
>
<div
class=
"ibox-title"
>
<span
style=
"float: left"
>
{% trans 'Asset list of ' %}
<b>
{{ admin_user.name }}
</b>
<span
class=
"badge"
>
{{ paginator.count }}
</span></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>
<ul
class=
"dropdown-menu dropdown-user"
>
</ul>
<a
class=
"close-link"
>
<i
class=
"fa fa-times"
></i>
</a>
</div>
</div>
<div
class=
"ibox-content"
>
<table
class=
"table table-hover"
id=
"system_user_assets_table"
>
<thead>
<tr>
<th>
{% trans 'Hostname' %}
</th>
<th>
{% trans 'IP' %}
</th>
<th>
{% trans 'Port' %}
</th>
<th>
{% trans 'Alive' %}
</th>
<th>
{% trans 'Action' %}
</th>
</tr>
</thead>
<tbody>
{# {% for asset in page_obj %}#}
{#
<tr>
#}
{#
<td>
{{ asset.hostname }}
</td>
#}
{#
<td>
{{ asset.ip }}
</td>
#}
{#
<td>
{{ asset.port }}
</td>
#}
{#
<td>
Alive
</td>
#}
{#
</tr>
#}
{# {% endfor %}#}
</tbody>
</table>
{#
<div
class=
"row"
>
#}
{# {% include '_pagination.html' %}#}
{#
</div>
#}
</div>
</div>
</div>
<div
class=
"col-sm-5"
style=
"padding-left: 0;padding-right: 0"
>
<div
class=
"panel panel-primary"
>
...
...
@@ -128,25 +86,15 @@
<table
class=
"table"
>
<tbody>
<tr
class=
"no-borders-tr"
>
<td
width=
"50%"
>
{% trans 'Get install script' %}:
</td>
<td>
<span
style=
"float: right"
>
<button
type=
"button"
class=
"btn btn-primary btn-xs"
style=
"width: 54px"
>
{% trans 'Get' %}
</button>
</span>
</td>
</tr>
<tr>
<td
width=
"50%"
>
{% trans 'Retest asset connectivity' %}:
</td>
<td
width=
"50%"
>
{% trans 'Reset private key' %}:
</td>
<td>
<span
style=
"float: right"
>
<button
type=
"button"
class=
"btn btn-primary btn-xs"
style=
"width: 54px"
>
{% trans '
Star
t' %}
</button>
<button
type=
"button"
class=
"btn btn-primary btn-xs"
style=
"width: 54px"
>
{% trans '
Rese
t' %}
</button>
</span>
</td>
</tr>
<tr>
<td
width=
"50%"
>
{% trans 'Reset p
rivate key
' %}:
</td>
<td
width=
"50%"
>
{% trans 'Reset p
assword
' %}:
</td>
<td>
<span
style=
"float: right"
>
<button
type=
"button"
class=
"btn btn-primary btn-xs"
style=
"width: 54px"
>
{% trans 'Reset' %}
</button>
...
...
@@ -157,62 +105,6 @@
</table>
</div>
</div>
<div
class=
"panel panel-info"
>
<div
class=
"panel-heading"
>
<i
class=
"fa fa-info-circle"
></i>
{% trans 'Replace asset admin user with this' %}
</div>
<div
class=
"panel-body"
>
<table
class=
"table"
>
<tbody>
<form>
<tr
class=
"no-borders-tr"
>
<td
colspan=
"2"
>
<select
data-placeholder=
"{% trans 'Select asset' %}"
class=
"select2"
style=
"width: 100%"
multiple=
""
tabindex=
"4"
>
{% for asset in assets_remain %}
<option
value=
"{{ asset.id }}"
>
{{ asset.ip }}:{{ asset.port }}
</option>
{% endfor %}
</select>
</td>
</tr>
<tr
class=
"no-borders-tr"
>
<td
colspan=
"2"
>
<button
type=
"button"
class=
"btn btn-info btn-sm btn-replace-asset-admin_user"
>
{% trans 'Replace' %}
</button>
</td>
</tr>
</form>
</tbody>
</table>
</div>
</div>
<div
class=
"panel panel-warning"
>
<div
class=
"panel-heading"
>
<i
class=
"fa fa-info-circle"
></i>
{% trans 'Replace asset admin user with this admin user' %}
</div>
<div
class=
"panel-body"
>
<table
class=
"table"
>
<tbody>
<form>
<tr
class=
"no-borders-tr"
>
<td
colspan=
"2"
>
<select
data-placeholder=
"{% trans 'Select asset groups' %}"
class=
"select2"
style=
"width: 100%"
multiple=
""
tabindex=
"4"
>
{% for asset_group in asset_groups %}
<option
value=
"{{ asset_group.id }}"
>
{{ asset_group.name }}
</option>
{% endfor %}
</select>
</td>
</tr>
<tr
class=
"no-borders-tr"
>
<td
colspan=
"2"
>
<button
type=
"button"
class=
"btn btn-warning btn-sm btn-replace-asset_groups-admin_user"
>
{% trans 'Replace' %}
</button>
</td>
</tr>
</form>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
...
...
@@ -361,7 +253,7 @@ $(document).ready(function () {
});
assets
.
unique
();
var
data
=
[];
var
admin_user_id
=
{{
admin_user
.
id
}}
;
var
admin_user_id
=
"{{ admin_user.id }}"
;
var
the_url
=
'{% url "api-assets:asset-list" %}'
;
for
(
var
i
=
0
;
i
<
assets
.
length
;
i
++
)
{
data
.
push
({
"id"
:
assets
[
i
],
"admin_user"
:
admin_user_id
});
...
...
apps/assets/templates/assets/admin_user_list.html
View file @
0c9e24dc
...
...
@@ -15,6 +15,7 @@
<th
class=
"text-center"
>
{% trans 'Name' %}
</th>
<th
class=
"text-center"
>
{% trans 'Username' %}
</th>
<th
class=
"text-center"
>
{% trans 'Asset num' %}
</th>
<th
class=
"text-center"
>
{% trans 'Unreachable' %}
</th>
<th
class=
"text-center"
>
{% trans 'Comment' %}
</th>
<th
class=
"text-center"
>
{% trans 'Action' %}
</th>
</tr>
...
...
@@ -34,11 +35,7 @@ $(document).ready(function(){
var
detail_btn
=
'<a href="{% url "assets:admin-user-detail" pk=99991937 %}">'
+
cellData
+
'</a>'
;
$
(
td
).
html
(
detail_btn
.
replace
(
'99991937'
,
rowData
.
id
));
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
var
innerHtml
=
cellData
.
length
>
8
?
cellData
.
substring
(
0
,
24
)
+
'...'
:
cellData
;
$
(
td
).
html
(
'<a href="javascript:void(0);" data-toggle="tooltip" title="'
+
cellData
+
'">'
+
innerHtml
+
'</a>'
);
}},
{
targets
:
5
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
{
targets
:
6
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
{
#
var
script_btn
=
'<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs btn-primary">{% trans "Script" %}</a>'
.
replace
(
'99991937'
,
cellData
);
#
}
var
update_btn
=
'<a href="{% url "assets:admin-user-update" pk=99991937 %}" class="btn btn-xs m-l-xs btn-info">{% trans "Update" %}</a>'
.
replace
(
'99991937'
,
cellData
);
var
del_btn
=
'<a class="btn btn-xs btn-danger m-l-xs btn_admin_user_delete" data-uid="99991937">{% trans "Delete" %}</a>'
.
replace
(
'99991937'
,
cellData
);
...
...
@@ -47,7 +44,7 @@ $(document).ready(function(){
}}],
ajax_url
:
'{% url "api-assets:admin-user-list" %}'
,
columns
:
[{
data
:
function
(){
return
""
}},
{
data
:
"name"
},
{
data
:
"username"
},
{
data
:
"assets_amount"
},
{
data
:
"
comment"
},
{
data
:
"id"
}],
{
data
:
"
unreachable_amount"
},
{
data
:
"comment"
},
{
data
:
"id"
}]
};
jumpserver
.
initDataTable
(
options
);
})
...
...
apps/assets/templates/assets/system_user_asset.html
View file @
0c9e24dc
...
...
@@ -58,18 +58,16 @@
<td>
{{ asset.ip }}
</td>
<td>
{{ asset.port }}
</td>
<td>
<i
class=
"fa fa-check text-navy"
></i>
</td>
<td>
<button
class=
"btn btn-danger pull-right btn-xs
{% if asset.is_inherit_from_asset_groups %} disabled {% endif %}
"
type=
"button"
><i
class=
"fa fa-minus"
></i></button>
<button
class=
"btn btn-danger pull-right btn-xs"
type=
"button"
><i
class=
"fa fa-minus"
></i></button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{#
<div
class=
"row"
>
#}
{# {% include '_pagination.html' %}#}
{#
</div>
#}
</div>
</div>
</div>
...
...
apps/assets/templates/assets/system_user_list.html
View file @
0c9e24dc
...
...
@@ -60,7 +60,7 @@ $(document).ready(function(){
$
(
td
).
html
(
update_btn
+
del_btn
)
}}],
ajax_url
:
'{% url "api-assets:system-user-list" %}'
,
columns
:
[{
data
:
"id"
},
{
data
:
"name"
},
{
data
:
"username"
},
{
data
:
"assets_amount"
},
{
data
:
function
()
{
return
"3"
}
},
columns
:
[{
data
:
"id"
},
{
data
:
"name"
},
{
data
:
"username"
},
{
data
:
"assets_amount"
},
{
data
:
"unreachable_amount"
},
{
data
:
"comment"
},
{
data
:
"id"
}],
op_html
:
$
(
'#actions'
).
html
()
};
...
...
apps/assets/urls/views_urls.py
View file @
0c9e24dc
...
...
@@ -41,6 +41,7 @@ urlpatterns = [
url
(
r'^admin-user/(?P<pk>[0-9a-zA-Z\-]+)/$'
,
views
.
AdminUserDetailView
.
as_view
(),
name
=
'admin-user-detail'
),
url
(
r'^admin-user/(?P<pk>[0-9a-zA-Z\-]+)/update/$'
,
views
.
AdminUserUpdateView
.
as_view
(),
name
=
'admin-user-update'
),
url
(
r'^admin-user/(?P<pk>[0-9a-zA-Z\-]+)/delete/$'
,
views
.
AdminUserDeleteView
.
as_view
(),
name
=
'admin-user-delete'
),
url
(
r'^admin-user/(?P<pk>[0-9a-zA-Z\-]+)/assets/$'
,
views
.
AdminUserAssetsView
.
as_view
(),
name
=
'admin-user-assets'
),
# Resource system user url
url
(
r'^system-user/$'
,
views
.
SystemUserListView
.
as_view
(),
name
=
'system-user-list'
),
...
...
apps/assets/utils.py
View file @
0c9e24dc
# ~*~ coding: utf-8 ~*~
#
from
.models
import
Asset
def
test_admin_user_connective_manual
(
asset
):
from
ops.utils
import
run_AdHoc
if
not
isinstance
(
asset
,
list
):
asset
=
[
asset
]
task_tuple
=
(
(
'ping'
,
''
),
)
summary
,
_
=
run_AdHoc
(
task_tuple
,
asset
,
record
=
False
)
if
len
(
summary
[
'failed'
])
!=
0
:
return
False
else
:
return
True
from
common.utils
import
get_object_or_none
from
.models
import
Asset
,
SystemUser
def
get_assets_by_id_list
(
id_list
):
...
...
@@ -25,26 +12,6 @@ def get_assets_by_hostname_list(hostname_list):
return
Asset
.
objects
.
filter
(
hostname__in
=
hostname_list
)
def
get_asset_admin_user
(
user
,
asset
):
if
user
.
is_superuser
:
return
asset
.
admin_user
else
:
msg
=
"{} have no permission for admin user"
.
format
(
user
.
username
)
raise
PermissionError
(
msg
)
def
get_asset_system_user
(
user
,
asset
,
system_user_name
):
from
perms.utils
import
get_user_granted_assets
assets
=
get_user_granted_assets
(
user
)
system_users
=
{
system_user
.
name
:
system_user
for
system_user
in
assets
.
get
(
asset
)}
if
system_user_name
in
system_users
:
return
system_users
[
system_user_name
]
else
:
msg
=
"{} have no permission for {}"
.
format
(
user
.
name
,
system_user_name
)
raise
PermissionError
(
msg
)
def
get_assets_with_admin_by_hostname_list
(
hostname_list
):
assets
=
Asset
.
objects
.
filter
(
hostname__in
=
hostname_list
)
return
[(
asset
,
asset
.
admin_user
)
for
asset
in
assets
]
def
get_system_user_by_name
(
name
):
system_user
=
get_object_or_none
(
SystemUser
,
name
=
name
)
return
system_user
apps/assets/views/admin_user.py
View file @
0c9e24dc
...
...
@@ -14,7 +14,7 @@ from ..hands import AdminUserRequiredMixin
__all__
=
[
'AdminUserCreateView'
,
'AdminUserDetailView'
,
'AdminUserDeleteView'
,
'AdminUserListView'
,
'AdminUserUpdateView'
,
'AdminUserUpdateView'
,
'AdminUserAssetsView'
,
]
...
...
@@ -104,6 +104,31 @@ class AdminUserDetailView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
return
super
(
AdminUserDetailView
,
self
)
.
get_context_data
(
**
kwargs
)
class
AdminUserAssetsView
(
AdminUserRequiredMixin
,
SingleObjectMixin
,
ListView
):
paginate_by
=
settings
.
CONFIG
.
DISPLAY_PER_PAGE
template_name
=
'assets/admin_user_assets.html'
context_object_name
=
'admin_user'
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
object
=
self
.
get_object
(
queryset
=
AdminUser
.
objects
.
all
())
return
super
()
.
get
(
request
,
*
args
,
**
kwargs
)
def
get_queryset
(
self
):
self
.
queryset
=
self
.
object
.
assets
.
all
()
sorted
(
self
.
queryset
,
key
=
lambda
x
:
x
.
is_connective
()
is
False
)
return
self
.
queryset
def
get_context_data
(
self
,
**
kwargs
):
context
=
{
'app'
:
'assets'
,
'action'
:
'Admin user detail'
,
"total_amount"
:
len
(
self
.
queryset
),
'unreachable_amount'
:
len
([
asset
for
asset
in
self
.
queryset
if
asset
.
is_connective
()
is
False
])
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
class
AdminUserDeleteView
(
AdminUserRequiredMixin
,
DeleteView
):
model
=
AdminUser
template_name
=
'assets/delete_confirm.html'
...
...
apps/assets/views/system_user.py
View file @
0c9e24dc
...
...
@@ -117,25 +117,18 @@ class SystemUserAssetView(AdminUserRequiredMixin, SingleObjectMixin, ListView):
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
object
=
self
.
get_object
(
queryset
=
SystemUser
.
objects
.
all
())
return
super
(
SystemUserAssetView
,
self
)
.
get
(
request
,
*
args
,
**
kwargs
)
return
super
()
.
get
(
request
,
*
args
,
**
kwargs
)
def
get_asset_groups
(
self
):
return
self
.
object
.
asset_groups
.
all
()
# Todo: queryset default order by connectivity, need ops support
def
get_queryset
(
self
):
return
list
(
self
.
object
.
get_assets
()
)
return
self
.
object
.
assets
.
all
(
)
def
get_context_data
(
self
,
**
kwargs
):
asset_groups
=
self
.
get_asset_groups
()
assets
=
self
.
get_queryset
()
context
=
{
'app'
:
'assets'
,
'action'
:
'System user asset'
,
'assets_remain'
:
[
asset
for
asset
in
Asset
.
objects
.
all
()
if
asset
not
in
assets
],
'asset_groups'
:
asset_groups
,
'asset_groups_remain'
:
[
asset_group
for
asset_group
in
AssetGroup
.
objects
.
all
()
if
asset_group
not
in
asset_groups
]
'asset_groups'
:
AssetGroup
.
objects
.
all
(),
}
kwargs
.
update
(
context
)
return
super
(
SystemUserAssetView
,
self
)
.
get_context_data
(
**
kwargs
)
...
...
apps/common/celery.py
View file @
0c9e24dc
...
...
@@ -27,8 +27,8 @@ app.conf.update(
'schedule'
:
60
*
60
*
60
*
24
,
'args'
:
(),
},
'test-admin-user-connect
iv
e'
:
{
'task'
:
'assets.tasks.test_admin_user_connect
ive
_period'
,
'test-admin-user-connect
ability_period
e'
:
{
'task'
:
'assets.tasks.test_admin_user_connect
ability
_period'
,
'schedule'
:
60
*
60
*
60
,
'args'
:
(),
},
...
...
apps/common/utils.py
View file @
0c9e24dc
...
...
@@ -15,6 +15,7 @@ from email.utils import formatdate
import
calendar
import
threading
from
six
import
StringIO
import
uuid
import
paramiko
import
sshpubkeys
...
...
@@ -378,4 +379,8 @@ def sum_capacity(cap_list):
return
capacity_convert
(
total
,
expect
=
'auto'
)
def
get_short_uuid_str
():
return
str
(
uuid
.
uuid4
())
.
split
(
'-'
)[
-
1
]
signer
=
Signer
()
apps/jumpserver/settings.py
View file @
0c9e24dc
...
...
@@ -299,6 +299,7 @@ REST_FRAMEWORK = {
'users.authentication.SessionAuthentication'
,
),
'DEFAULT_FILTER_BACKENDS'
:
(
'django_filters.rest_framework.DjangoFilterBackend'
,),
'DATETIME_FORMAT'
:
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
,
}
AUTHENTICATION_BACKENDS
=
[
...
...
apps/ops/ansible/callback.py
View file @
0c9e24dc
...
...
@@ -5,21 +5,21 @@ from ansible.plugins.callback import CallbackBase
class
AdHocResultCallback
(
CallbackBase
):
"""
AdHoc
result Callback
Task
result Callback
"""
def
__init__
(
self
,
display
=
None
):
# result_raw example: {
# "ok": {"hostname":
[{"task_name": {},...]
,..},
# "failed": {"hostname":
["task_name": {}..]
, ..},
# "unreachable: {"hostname":
["task_name": {}, ..]
},
# "skipped": {"hostname":
["task_name": {}, ..]
, ..},
# "ok": {"hostname":
{"task_name": {},...}
,..},
# "failed": {"hostname":
{"task_name": {}..}
, ..},
# "unreachable: {"hostname":
{"task_name": {}, ..}
},
# "skipped": {"hostname":
{"task_name": {}, ..}
, ..},
# }
# results_summary example: {
# "contacted": {"hostname",...},
# "dark": {"hostname":
[{"task_name": "error"},...]
,},
# "dark": {"hostname":
{"task_name": {}, "task_name": {}},...
,},
# }
self
.
results_raw
=
dict
(
ok
=
{},
failed
=
{},
unreachable
=
{},
skipped
=
{})
self
.
results_summary
=
dict
(
contacted
=
set
()
,
dark
=
{})
self
.
results_summary
=
dict
(
contacted
=
[]
,
dark
=
{})
super
()
.
__init__
(
display
)
def
gather_result
(
self
,
t
,
res
):
...
...
@@ -28,23 +28,24 @@ class AdHocResultCallback(CallbackBase):
task_result
=
res
.
_result
if
self
.
results_raw
[
t
]
.
get
(
host
):
self
.
results_raw
[
t
][
host
]
.
append
({
task_name
:
task_result
})
self
.
results_raw
[
t
][
host
]
[
task_name
]
=
task_result
else
:
self
.
results_raw
[
t
][
host
]
=
[{
task_name
:
task_result
}]
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
):
contacted
=
self
.
results_summary
[
"contacted"
]
dark
=
self
.
results_summary
[
"dark"
]
if
t
in
(
"ok"
,
"skipped"
)
and
host
not
in
dark
:
contacted
.
add
(
host
)
if
host
not
in
contacted
:
contacted
.
append
(
host
)
else
:
if
dark
.
get
(
host
):
dark
[
host
]
.
append
({
task_name
:
task_result
})
dark
[
host
]
[
task_name
]
=
task_result
else
:
dark
[
host
]
=
[{
task_name
:
task_result
}]
dark
[
host
]
=
{
task_name
:
task_result
}
if
host
in
contacted
:
contacted
.
remove
(
dark
)
contacted
.
remove
(
host
)
def
v2_runner_on_failed
(
self
,
result
,
ignore_errors
=
False
):
self
.
gather_result
(
"failed"
,
result
)
...
...
apps/ops/ansible/inventory.py
View file @
0c9e24dc
# ~*~ coding: utf-8 ~*~
from
ansible.inventory.group
import
Group
from
ansible.inventory.host
import
Host
from
ansible.vars.manager
import
VariableManager
from
ansible.inventory.manager
import
InventoryManager
from
ansible.parsing.dataloader
import
DataLoader
class
JMSHost
(
Host
):
__all__
=
[
'BaseHost'
,
'BaseInventory'
]
class
BaseHost
(
Host
):
def
__init__
(
self
,
host_data
):
"""
初始化
...
...
@@ -14,6 +18,7 @@ class JMSHost(Host):
"hostname": "",
"ip": "",
"port": "",
# behind is not must be required
"username": "",
"password": "",
"private_key": "",
...
...
@@ -29,7 +34,7 @@ class JMSHost(Host):
self
.
host_data
=
host_data
hostname
=
host_data
.
get
(
'hostname'
)
or
host_data
.
get
(
'ip'
)
port
=
host_data
.
get
(
'port'
)
or
22
super
(
JMSHost
,
self
)
.
__init__
(
hostname
,
port
)
super
()
.
__init__
(
hostname
,
port
)
self
.
__set_required_variables
()
self
.
__set_extra_variables
()
...
...
@@ -37,7 +42,9 @@ class JMSHost(Host):
host_data
=
self
.
host_data
self
.
set_variable
(
'ansible_host'
,
host_data
[
'ip'
])
self
.
set_variable
(
'ansible_port'
,
host_data
[
'port'
])
self
.
set_variable
(
'ansible_user'
,
host_data
[
'username'
])
if
host_data
.
get
(
'username'
):
self
.
set_variable
(
'ansible_user'
,
host_data
[
'username'
])
# 添加密码和秘钥
if
host_data
.
get
(
'password'
):
...
...
@@ -63,30 +70,15 @@ class JMSHost(Host):
return
self
.
name
class
JMS
Inventory
(
InventoryManager
):
class
Base
Inventory
(
InventoryManager
):
"""
提供生成Ansible inventory对象的方法
"""
loader_class
=
DataLoader
variable_manager_class
=
VariableManager
host_manager_class
=
JMS
Host
host_manager_class
=
Base
Host
def
__init__
(
self
,
host_list
=
None
):
if
host_list
is
None
:
host_list
=
[]
self
.
host_list
=
host_list
assert
isinstance
(
host_list
,
list
)
self
.
loader
=
self
.
loader_class
()
self
.
variable_manager
=
self
.
variable_manager_class
()
super
()
.
__init__
(
self
.
loader
)
def
get_groups
(
self
):
return
self
.
_inventory
.
groups
def
get_group
(
self
,
name
):
return
self
.
_inventory
.
groups
.
get
(
name
,
None
)
def
parse_sources
(
self
,
cache
=
False
):
"""
用于生成动态构建Ansible Inventory. super().__init__ 会自动调用
host_list: [{
...
...
@@ -105,9 +97,23 @@ class JMSInventory(InventoryManager):
"vars": {},
},
]
:return: None
:param host_list:
"""
if
host_list
is
None
:
host_list
=
[]
self
.
host_list
=
host_list
assert
isinstance
(
host_list
,
list
)
self
.
loader
=
self
.
loader_class
()
self
.
variable_manager
=
self
.
variable_manager_class
()
super
()
.
__init__
(
self
.
loader
)
def
get_groups
(
self
):
return
self
.
_inventory
.
groups
def
get_group
(
self
,
name
):
return
self
.
_inventory
.
groups
.
get
(
name
,
None
)
def
parse_sources
(
self
,
cache
=
False
):
group_all
=
self
.
get_group
(
'all'
)
ungrouped
=
self
.
get_group
(
'ungrouped'
)
...
...
@@ -119,9 +125,14 @@ class JMSInventory(InventoryManager):
for
group_name
in
groups_data
:
group
=
self
.
get_group
(
group_name
)
if
group
is
None
:
group
=
G
roup
(
group_name
)
self
.
add_group
(
group
)
self
.
add_g
roup
(
group_name
)
group
=
self
.
get_group
(
group_name
)
group
.
add_host
(
host
)
else
:
ungrouped
.
add_host
(
host
)
group_all
.
add_host
(
host
)
def
get_matched_hosts
(
self
,
pattern
):
return
self
.
get_hosts
(
pattern
)
apps/ops/ansible/runner.py
View file @
0c9e24dc
# ~*~ coding: utf-8 ~*~
from
__future__
import
unicode_literals
import
os
from
collections
import
namedtuple
...
...
@@ -11,7 +10,6 @@ from ansible.executor.playbook_executor import PlaybookExecutor
from
ansible.playbook.play
import
Play
import
ansible.constants
as
C
from
.inventory
import
JMSInventory
from
.callback
import
AdHocResultCallback
,
PlaybookResultCallBack
,
\
CommandResultCallback
from
common.utils
import
get_logger
...
...
@@ -71,36 +69,19 @@ class PlayBookRunner:
# Default results callback
results_callback_class
=
PlaybookResultCallBack
inventory_class
=
JMSInventory
loader_class
=
DataLoader
variable_manager_class
=
VariableManager
options
=
get_default_options
()
def
__init__
(
self
,
hosts
=
None
,
options
=
None
):
def
__init__
(
self
,
inventory
=
None
,
options
=
None
):
"""
:param options: Ansible options like ansible.cfg
:param hosts: [
{
"hostname": "",
"ip": "",
"port": "",
"username": "",
"password": "",
"private_key": "",
"become": {
"method": "",
"user": "",
"pass": "",
},
"groups": [],
"vars": {},
},
]
:param inventory: Ansible inventory
"""
if
options
:
self
.
options
=
options
C
.
RETRY_FILES_ENABLED
=
False
self
.
inventory
=
self
.
inventory_class
(
hosts
)
self
.
inventory
=
inventory
self
.
loader
=
self
.
loader_class
()
self
.
results_callback
=
self
.
results_callback_class
()
self
.
playbook_path
=
options
.
playbook_path
...
...
@@ -141,20 +122,19 @@ class AdHocRunner:
ADHoc Runner接口
"""
results_callback_class
=
AdHocResultCallback
inventory_class
=
JMSInventory
loader_class
=
DataLoader
variable_manager_class
=
VariableManager
options
=
get_default_options
()
default_options
=
get_default_options
()
def
__init__
(
self
,
hosts
,
options
=
None
):
def
__init__
(
self
,
inventory
,
options
=
None
):
if
options
:
self
.
options
=
options
self
.
pattern
=
''
self
.
inventory
=
inventory
self
.
loader
=
DataLoader
()
self
.
inventory
=
self
.
inventory_class
(
hosts
)
self
.
variable_manager
=
VariableManager
(
loader
=
self
.
loader
,
inventory
=
self
.
inventory
)
self
.
variable_manager
=
VariableManager
(
loader
=
self
.
loader
,
inventory
=
self
.
inventory
)
@staticmethod
def
check_module_args
(
module_name
,
module_args
=
''
):
...
...
@@ -163,14 +143,22 @@ class AdHocRunner:
raise
AnsibleError
(
err
)
def
check_pattern
(
self
,
pattern
):
if
not
pattern
:
raise
AnsibleError
(
"Pattern `{}` is not valid!"
.
format
(
pattern
))
if
not
self
.
inventory
.
list_hosts
(
"all"
):
raise
AnsibleError
(
"Inventory is empty."
)
if
not
self
.
inventory
.
list_hosts
(
pattern
):
raise
AnsibleError
(
"pattern:
%
s dose not match any hosts."
%
pattern
)
def
clean_tasks
(
self
,
tasks
):
cleaned_tasks
=
[]
for
task
in
tasks
:
self
.
check_module_args
(
task
[
'action'
][
'module'
],
task
[
'action'
]
.
get
(
'args'
))
cleaned_tasks
.
append
(
task
)
return
cleaned_tasks
def
set_option
(
self
,
k
,
v
):
kwargs
=
{
k
:
v
}
self
.
options
=
self
.
options
.
_replace
(
**
kwargs
)
...
...
@@ -182,17 +170,15 @@ class AdHocRunner:
:param play_name: The play name
:return:
"""
self
.
check_pattern
(
pattern
)
results_callback
=
self
.
results_callback_class
()
clean_tasks
=
[]
for
task
in
tasks
:
self
.
check_module_args
(
task
[
'action'
][
'module'
],
task
[
'action'
]
.
get
(
'args'
))
clean_tasks
.
append
(
task
)
cleaned_tasks
=
self
.
clean_tasks
(
tasks
)
play_source
=
dict
(
name
=
play_name
,
hosts
=
pattern
,
gather_facts
=
gather_facts
,
tasks
=
clean_tasks
tasks
=
clean
ed
_tasks
)
play
=
Play
()
.
load
(
...
...
@@ -209,6 +195,9 @@ class AdHocRunner:
stdout_callback
=
results_callback
,
passwords
=
self
.
options
.
passwords
,
)
logger
.
debug
(
"Get inventory matched hosts: {}"
.
format
(
self
.
inventory
.
get_matched_hosts
(
pattern
)
))
try
:
tqm
.
run
(
play
)
...
...
apps/ops/ansible/test_inventory.
bak
→
apps/ops/ansible/test_inventory.
py
View file @
0c9e24dc
...
...
@@ -6,7 +6,7 @@ import unittest
sys
.
path
.
insert
(
0
,
'../..'
)
from ops.ansible.inventory import
JMS
Inventory
from
ops.ansible.inventory
import
Base
Inventory
class
TestJMSInventory
(
unittest
.
TestCase
):
...
...
@@ -41,7 +41,7 @@ class TestJMSInventory(unittest.TestCase):
"vars"
:
{
"love"
:
"yes"
},
}]
self.inventory =
JMS
Inventory(host_list=host_list)
self
.
inventory
=
Base
Inventory
(
host_list
=
host_list
)
def
test_hosts
(
self
):
print
(
"#"
*
10
+
"Hosts"
+
"#"
*
10
)
...
...
apps/ops/ansible/test_runner.
bak
→
apps/ops/ansible/test_runner.
py
View file @
0c9e24dc
...
...
@@ -7,6 +7,7 @@ import sys
sys
.
path
.
insert
(
0
,
"../.."
)
from
ops.ansible.runner
import
AdHocRunner
,
CommandRunner
from
ops.ansible.inventory
import
BaseInventory
class
TestAdHocRunner
(
unittest
.
TestCase
):
...
...
@@ -20,7 +21,8 @@ class TestAdHocRunner(unittest.TestCase):
"password"
:
"redhat"
,
},
]
self.runner = AdHocRunner(hosts=host_data)
inventory
=
BaseInventory
(
host_data
)
self
.
runner
=
AdHocRunner
(
inventory
)
def
test_run
(
self
):
tasks
=
[
...
...
@@ -43,7 +45,8 @@ class TestCommandRunner(unittest.TestCase):
"password"
:
"redhat"
,
},
]
self.runner = CommandRunner(hosts=host_data)
inventory
=
BaseInventory
(
host_data
)
self
.
runner
=
CommandRunner
(
inventory
)
def
test_execute
(
self
):
res
=
self
.
runner
.
execute
(
'ls'
,
'all'
)
...
...
apps/ops/api.py
View file @
0c9e24dc
# ~*~ coding: utf-8 ~*~
from
django.shortcuts
import
get_object_or_404
from
rest_framework
import
viewsets
from
.hands
import
IsSuperUser
from
.models
import
AdHoc
from
.serializers
import
TaskSerializer
from
.models
import
Task
,
AdHoc
,
AdHocRunHistory
from
.serializers
import
TaskSerializer
,
AdHocSerializer
,
AdHocRunHistorySerializer
class
TaskViewSet
(
viewsets
.
ModelViewSet
):
queryset
=
AdHoc
.
objects
.
all
()
queryset
=
Task
.
objects
.
all
()
serializer_class
=
TaskSerializer
permission_classes
=
(
IsSuperUser
,)
class
AdHocViewSet
(
viewsets
.
ModelViewSet
):
queryset
=
AdHoc
.
objects
.
all
()
serializer_class
=
AdHocSerializer
permission_classes
=
(
IsSuperUser
,)
def
get_queryset
(
self
):
task_id
=
self
.
request
.
query_params
.
get
(
'task'
)
if
task_id
:
task
=
get_object_or_404
(
Task
,
id
=
task_id
)
self
.
queryset
=
self
.
queryset
.
filter
(
task
=
task
)
return
self
.
queryset
class
AdHocRunHistorySet
(
viewsets
.
ModelViewSet
):
queryset
=
AdHocRunHistory
.
objects
.
all
()
serializer_class
=
AdHocRunHistorySerializer
permission_classes
=
(
IsSuperUser
,)
def
get_queryset
(
self
):
task_id
=
self
.
request
.
query_params
.
get
(
'task'
)
if
task_id
:
task
=
get_object_or_404
(
Task
,
id
=
task_id
)
adhocs
=
task
.
adhoc
.
all
()
self
.
queryset
=
self
.
queryset
.
filter
(
adhoc__in
=
adhocs
)
return
self
.
queryset
apps/ops/inventory.py
0 → 100644
View file @
0c9e24dc
# -*- coding: utf-8 -*-
#
from
.ansible.inventory
import
BaseInventory
from
assets.utils
import
get_assets_by_hostname_list
,
get_system_user_by_name
__all__
=
[
'JMSInventory'
]
class
JMSInventory
(
BaseInventory
):
"""
JMS Inventory is the manager with jumpserver assets, so you can
write you own manager, construct you inventory
"""
def
__init__
(
self
,
hostname_list
,
run_as_admin
=
False
,
run_as
=
None
,
become_info
=
None
):
self
.
hostname_list
=
hostname_list
self
.
using_admin
=
run_as_admin
self
.
run_as
=
run_as
self
.
become_info
=
become_info
assets
=
self
.
get_jms_assets
()
if
run_as_admin
:
host_list
=
[
asset
.
_to_secret_json
()
for
asset
in
assets
]
else
:
host_list
=
[
asset
.
to_json
()
for
asset
in
assets
]
if
run_as
:
run_user_info
=
self
.
get_run_user_info
()
for
host
in
host_list
:
host
.
update
(
run_user_info
)
if
become_info
:
for
host
in
host_list
:
host
.
update
(
become_info
)
super
()
.
__init__
(
host_list
=
host_list
)
def
get_jms_assets
(
self
):
assets
=
get_assets_by_hostname_list
(
self
.
hostname_list
)
return
assets
def
get_run_user_info
(
self
):
system_user
=
get_system_user_by_name
(
self
.
run_as
)
if
not
system_user
:
return
{}
else
:
return
system_user
.
_to_secret_json
()
apps/ops/models.py
View file @
0c9e24dc
...
...
@@ -6,20 +6,25 @@ import uuid
from
django.db
import
models
from
django.utils.translation
import
ugettext_lazy
as
_
from
common.utils
import
signer
__all__
=
[
"AdHoc"
,
"AdHocRunHistory"
]
__all__
=
[
"
Task"
,
"
AdHoc"
,
"AdHocRunHistory"
]
logger
=
logging
.
getLogger
(
__name__
)
class
AdHoc
(
models
.
Model
):
class
Task
(
models
.
Model
):
"""
This task is different ansible task, Task like 'push system user', 'get asset info' ..
One task can have some versions of adhoc, run a task only run the latest version adhoc
"""
id
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
name
=
models
.
CharField
(
max_length
=
128
,
blank
=
True
,
verbose_name
=
_
(
'Name'
))
is_deleted
=
models
.
BooleanField
(
default
=
False
)
created_by
=
models
.
CharField
(
max_length
=
128
,
blank
=
True
,
default
=
''
)
date_create
=
models
.
DateTimeField
(
auto_now_add
=
True
)
date_create
d
=
models
.
DateTimeField
(
auto_now_add
=
True
)
@property
def
short_id
(
self
):
...
...
@@ -28,24 +33,48 @@ class AdHoc(models.Model):
def
__str__
(
self
):
return
self
.
name
def
get_latest_adhoc
(
self
):
return
self
.
adhoc
.
all
()
.
order_by
(
'date_created'
)
.
last
()
def
get_latest_history
(
self
):
return
self
.
get_latest_adhoc
()
.
get_latest_history
()
def
get_all_run_history
(
self
):
adhocs
=
self
.
adhoc
.
all
()
return
AdHocRunHistory
.
objects
.
filter
(
adhoc__in
=
adhocs
)
def
get_all_run_times
(
self
):
history_all
=
self
.
get_all_run_history
()
total
=
len
(
history_all
)
success
=
len
([
history
for
history
in
history_all
if
history
.
is_success
])
failed
=
len
([
history
for
history
in
history_all
if
not
history
.
is_success
])
return
{
'total'
:
total
,
'success'
:
success
,
'failed'
:
failed
}
class
AdHocData
(
models
.
Model
):
BECOME_METHOD_CHOICES
=
(
(
'sudo'
,
'sudo'
),
(
'su'
,
'su'
),
)
version
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
subject
=
models
.
ForeignKey
(
AdHoc
,
on_delete
=
models
.
CASCADE
)
_tasks
=
models
.
TextField
(
verbose_name
=
_
(
'Tasks'
))
# [{'name': 'task_name', 'action': {'module': '', 'args': ''}, 'other..': ''}, ]
class
Meta
:
db_table
=
'ops_task'
class
AdHoc
(
models
.
Model
):
"""
task: A task reference
_tasks: [{'name': 'task_name', 'action': {'module': '', 'args': ''}, 'other..': ''}, ]
_options: ansible options, more see ops.ansible.runner.Options
_hosts: ["hostname1", "hostname2"], hostname must be unique key of cmdb
run_as_admin: if true, then need get every host admin user run it, because every host may be have different admin user, so we choise host level
run_as: if not run as admin, it run it as a system/common user from cmdb
_become: May be using become [sudo, su] options. {method: "sudo", user: "user", pass: "pass"]
pattern: Even if we set _hosts, We only use that to make inventory, We also can set `patter` to run task on match hosts
"""
id
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
task
=
models
.
ForeignKey
(
Task
,
related_name
=
'adhoc'
,
on_delete
=
models
.
CASCADE
)
_tasks
=
models
.
TextField
(
verbose_name
=
_
(
'Tasks'
))
pattern
=
models
.
CharField
(
max_length
=
64
,
default
=
''
,
verbose_name
=
_
(
'Pattern'
))
_options
=
models
.
CharField
(
max_length
=
1024
,
default
=
''
,
verbose_name
=
_
(
'Options'
))
_hosts
=
models
.
TextField
(
blank
=
True
,
verbose_name
=
_
(
'Hosts'
))
# ['hostname1', 'hostname2']
run_as_admin
=
models
.
BooleanField
(
default
=
False
,
verbose_name
=
_
(
'Run as admin'
))
run_as
=
models
.
CharField
(
max_length
=
128
,
verbose_name
=
_
(
"Run as"
))
become
=
models
.
BooleanField
(
default
=
False
,
verbose_name
=
_
(
"Become"
))
become_method
=
models
.
CharField
(
choices
=
BECOME_METHOD_CHOICES
,
default
=
'sudo'
,
max_length
=
4
)
become_user
=
models
.
CharField
(
default
=
'root'
,
max_length
=
64
)
_become_pass
=
models
.
CharField
(
default
=
''
,
max_length
=
128
)
pattern
=
models
.
CharField
(
max_length
=
64
,
default
=
''
,
verbose_name
=
_
(
'Pattern'
))
created_by
=
models
.
CharField
(
max_length
=
64
,
verbose_name
=
_
(
'Create by'
))
run_as
=
models
.
CharField
(
max_length
=
128
,
default
=
''
,
verbose_name
=
_
(
"Run as"
))
_become
=
models
.
CharField
(
max_length
=
1024
,
default
=
''
,
verbose_name
=
_
(
"Become"
))
created_by
=
models
.
CharField
(
max_length
=
64
,
default
=
''
,
verbose_name
=
_
(
'Create by'
))
date_created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
@property
...
...
@@ -54,7 +83,10 @@ class AdHocData(models.Model):
@tasks.setter
def
tasks
(
self
,
item
):
self
.
_tasks
=
json
.
dumps
(
item
)
if
item
and
isinstance
(
item
,
list
):
self
.
_tasks
=
json
.
dumps
(
item
)
else
:
raise
SyntaxError
(
'Tasks should be a list'
)
@property
def
hosts
(
self
):
...
...
@@ -65,42 +97,83 @@ class AdHocData(models.Model):
self
.
_hosts
=
json
.
dumps
(
item
)
@property
def
become_pass
(
self
):
return
signer
.
unsign
(
self
.
_become_pass
)
def
become
(
self
):
if
self
.
_become
:
return
json
.
loads
(
signer
.
unsign
(
self
.
_become
))
else
:
return
{}
@become.setter
def
become
(
self
,
item
):
"""
:param item: {
method: "sudo",
user: "user",
pass: "pass",
}
:return:
"""
self
.
_become
=
signer
.
sign
(
json
.
dumps
(
item
))
@property
def
options
(
self
):
if
self
.
_options
:
return
json
.
loads
(
self
.
_options
)
else
:
return
{}
@
become_pas
s.setter
def
become_pass
(
self
,
password
):
self
.
_
become_pass
=
signer
.
sign
(
password
)
@
option
s.setter
def
options
(
self
,
item
):
self
.
_
options
=
json
.
dumps
(
item
)
@property
def
short_
version
(
self
):
return
str
(
self
.
version
)
.
split
(
'-'
)[
-
1
]
def
short_
id
(
self
):
return
str
(
self
.
id
)
.
split
(
'-'
)[
-
1
]
def
run
(
self
):
pass
def
get_latest_history
(
self
):
return
self
.
history
.
all
()
.
order_by
(
'date_start'
)
.
last
()
def
__str__
(
self
):
return
"{} of {}"
.
format
(
self
.
subject
.
name
,
self
.
short_version
)
return
"{} of {}"
.
format
(
self
.
task
.
name
,
self
.
short_id
)
class
Meta
:
db_table
=
"ops_adhoc
_data
"
db_table
=
"ops_adhoc"
class
AdHocRunHistory
(
models
.
Model
):
uuid
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
adhoc
=
models
.
ForeignKey
(
AdHocData
,
on_delete
=
models
.
CASCADE
)
"""
AdHoc running history.
"""
id
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
adhoc
=
models
.
ForeignKey
(
AdHoc
,
related_name
=
'history'
,
on_delete
=
models
.
CASCADE
)
date_start
=
models
.
DateTimeField
(
auto_now_add
=
True
,
verbose_name
=
_
(
'Start time'
))
date_finished
=
models
.
DateTimeField
(
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'End time'
))
timedelta
=
models
.
FloatField
(
default
=
0.0
,
verbose_name
=
_
(
'Time'
),
null
=
True
)
is_finished
=
models
.
BooleanField
(
default
=
False
,
verbose_name
=
_
(
'Is finished'
))
is_success
=
models
.
BooleanField
(
default
=
False
,
verbose_name
=
_
(
'Is success'
))
result
=
models
.
TextField
(
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Playbook
raw result'
))
summary
=
models
.
TextField
(
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Playbook
summary'
))
_result
=
models
.
TextField
(
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Adhoc
raw result'
))
_summary
=
models
.
TextField
(
blank
=
True
,
null
=
True
,
verbose_name
=
_
(
'Adhoc result
summary'
))
@property
def
short_id
(
self
):
return
str
(
self
.
id
)
.
split
(
'-'
)[
-
1
]
@property
def
result
(
self
):
return
json
.
loads
(
self
.
_result
)
@result.setter
def
result
(
self
,
item
):
self
.
_result
=
json
.
dumps
(
item
)
@property
def
summary
(
self
):
return
json
.
loads
(
self
.
_summary
)
@summary.setter
def
summary
(
self
,
item
):
self
.
_summary
=
json
.
dumps
(
item
)
def
__str__
(
self
):
return
self
.
short_id
...
...
apps/ops/serializers.py
View file @
0c9e24dc
...
...
@@ -2,12 +2,43 @@
from
__future__
import
unicode_literals
from
rest_framework
import
serializers
from
.models
import
AdHoc
from
.models
import
Task
,
AdHoc
,
AdHocRunHistory
class
TaskSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
AdHoc
model
=
Task
fields
=
'__all__'
class
AdHocSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
AdHoc
exclude
=
(
'_tasks'
,
'_options'
,
'_hosts'
,
'_become'
)
def
get_field_names
(
self
,
declared_fields
,
info
):
fields
=
super
()
.
get_field_names
(
declared_fields
,
info
)
fields
.
extend
([
'tasks'
,
'options'
,
'hosts'
,
'become'
,
'short_id'
])
return
fields
class
AdHocRunHistorySerializer
(
serializers
.
ModelSerializer
):
task
=
serializers
.
SerializerMethodField
()
adhoc_short_id
=
serializers
.
SerializerMethodField
()
class
Meta
:
model
=
AdHocRunHistory
exclude
=
(
'_result'
,
'_summary'
)
@staticmethod
def
get_adhoc_short_id
(
obj
):
return
obj
.
adhoc
.
short_id
@staticmethod
def
get_task
(
obj
):
return
obj
.
adhoc
.
task
.
id
def
get_field_names
(
self
,
declared_fields
,
info
):
fields
=
super
()
.
get_field_names
(
declared_fields
,
info
)
fields
.
extend
([
'summary'
,
'short_id'
])
return
fields
apps/ops/tasks.py
View file @
0c9e24dc
# coding: utf-8
from
__future__
import
absolute_import
,
unicode_literals
from
celery
import
shared_task
from
common.utils
import
get_logger
from
.utils
import
run_AdHoc
from
.utils
import
run_adhoc
logger
=
get_logger
(
__file__
)
def
rerun_task
():
pass
@shared_task
def
rerun_task
(
task_id
):
from
.models
import
Playbook
record
=
Playbook
.
objects
.
get
(
uuid
=
task_id
)
assets
=
record
.
assets_json
task_tuple
=
record
.
module_args
pattern
=
record
.
pattern
task_name
=
record
.
name
return
run_AdHoc
(
task_tuple
,
assets
,
pattern
=
pattern
,
task_name
=
task_name
,
task_id
=
task_id
)
def
run_add_hoc_and_record_async
(
adhoc
,
**
options
):
return
run_adhoc
(
adhoc
,
**
options
)
apps/ops/templates/ops/task_adhoc.html
0 → 100644
View file @
0c9e24dc
{% 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>
<a
href=
"{% url 'ops:task-detail' pk=object.pk %}"
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Task detail' %}
</a>
</li>
<li
class=
"active"
>
<a
href=
"{% url 'ops:task-adhoc' pk=object.pk %}"
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Task versions' %}
</a>
</li>
<li>
<a
href=
"{% url 'ops:task-history' pk=object.pk %}"
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Run history' %}
</a>
</li>
</ul>
</div>
<div
class=
"tab-content"
>
<div
class=
"col-sm-12"
style=
"padding-left: 0"
>
<div
class=
"ibox float-e-margins"
>
<div
class=
"ibox-title"
>
<span
style=
"float: left"
>
{% trans 'Versions of ' %}
<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>
<ul
class=
"dropdown-menu dropdown-user"
>
</ul>
<a
class=
"close-link"
>
<i
class=
"fa fa-times"
></i>
</a>
</div>
</div>
<div
class=
"ibox-content"
>
<table
class=
"table table-hover "
id=
"task-version-list-table"
>
<thead>
<tr>
<th
class=
"text-center"
>
<input
type=
"checkbox"
id=
"check_all"
class=
"ipt_check_all"
>
</th>
<th>
{% trans 'Version' %}
</th>
<th>
{% trans 'Hosts' %}
</th>
<th>
{% trans 'Pattern' %}
</th>
<th>
{% trans 'Run as' %}
</th>
<th>
{% trans 'Become' %}
</th>
<th>
{% trans 'Datetime' %}
</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$
(
document
).
ready
(
function
()
{
var
options
=
{
ele
:
$
(
'#task-version-list-table'
),
buttons
:
[],
order
:
[],
select
:
[],
columnDefs
:
[
{
targets
:
1
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
{
#
var
detail_btn
=
'<a href="'
+
cellData
+
'</a>'
;
#
}
$
(
td
).
html
(
cellData
);
}},
{
targets
:
2
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
var
dataLength
=
cellData
.
length
;
$
(
td
).
html
(
dataLength
);
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
if
(
!
cellData
)
{
$
(
td
).
html
(
"Admin"
)
}
else
{
$
(
td
).
html
(
cellData
)
}
}},
{
targets
:
5
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
if
(
!
cellData
)
{
$
(
td
).
html
(
""
)
}
else
{
$
(
td
).
html
(
cellData
.
user
)
}
}}
],
ajax_url
:
'{% url "api-ops:adhoc-list" %}?task={{ object.pk }}'
,
columns
:
[{
data
:
function
(){
return
""
}},
{
data
:
"short_id"
},
{
data
:
"hosts"
},
{
data
:
"pattern"
},
{
data
:
"run_as"
},
{
data
:
"become"
},
{
data
:
"date_created"
}]
};
jumpserver
.
initDataTable
(
options
);
})
</script>
{% endblock %}
apps/ops/templates/ops/task_detail.html
View file @
0c9e24dc
...
...
@@ -15,8 +15,14 @@
<div
class=
"ibox float-e-margins"
>
<div
class=
"panel-options"
>
<ul
class=
"nav nav-tabs"
>
<li
class=
"active"
>
<a
href=
""
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Task replay detail' %}
</a>
<li
class=
"active"
>
<a
href=
"{% url 'ops:task-detail' pk=object.pk %}"
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Task detail' %}
</a>
</li>
<li>
<a
href=
"{% url 'ops:task-adhoc' pk=object.pk %}"
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Task versions' %}
</a>
</li>
<li>
<a
href=
"{% url 'ops:task-history' pk=object.pk %}"
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Run history' %}
</a>
</li>
</ul>
</div>
...
...
@@ -43,49 +49,47 @@
<table
class=
"table"
>
<tbody>
<tr
class=
"no-borders-tr"
>
<td
width=
"20%"
>
{% trans '
UU
ID' %}:
</td>
<td><b>
{{ object.
uu
id }}
</b></td>
<td
width=
"20%"
>
{% trans 'ID' %}:
</td>
<td><b>
{{ object.id }}
</b></td>
</tr>
<tr>
<td
width=
"20%"
>
{% trans 'Name' %}:
</td>
<td><b>
{{ object.name }}
</b></td>
</tr>
<tr>
<td>
{% trans 'Date start' %}:
</td>
<td><b>
{{ object.date_start }}
</b></td>
<td>
{% trans 'Date created' %}:
</td>
<td><b>
{{ object.date_created }}
</b></td>
</tr>
<tr>
<td>
{% trans 'Total versions' %}
</td>
<td><b>
{{ object.adhoc.all |length }}
</b></td>
</tr>
<tr>
<td>
{% trans 'Date finished' %}:
</td>
<td><b>
{{ object.date_finished }}
</b></td>
<td>
{% trans 'Last version' %}
</td>
<td><b>
{{ object.get_latest_adhoc.short_id }}
</b></td>
</tr>
<tr>
<td>
{% trans 'Latest run' %}:
</td>
<td><b>
{{ object.get_latest_history.date_start }}
</b></td>
</tr>
<tr>
<td>
{% trans 'Time delta' %}:
</td>
<td><b>
{{ object.
timedelta
}} s
</b></td>
<td><b>
{{ object.
get_latest_history.timedelta|floatformat
}} s
</b></td>
</tr>
<tr>
<td>
{% trans 'Is finished' %}:
</td>
<td><b>
{{ object.is_finished|yesno:"Yes,No,Unkown" }}
</b></td>
<td><b>
{{ object.
get_latest_history.
is_finished|yesno:"Yes,No,Unkown" }}
</b></td>
</tr>
<tr>
<td>
{% trans 'Is success ' %}:
</td>
{% if object.is_finished %}
<td><b>
{{ object.is_success|yesno:"Yes,No,Unkown" }}
</b></td>
{% else %}
<td>
<div
class=
"progress progress-striped active"
>
<div
style=
"width: 50%"
aria-valuemax=
"100"
aria-valuemin=
"0"
aria-valuenow=
"75"
role=
"progressbar"
class=
"progress-bar progress-bar-primary"
>
<span
class=
"sr-only"
>
40% Complete (success)
</span>
</div>
</div>
</td>
{% endif %}
<td><b>
{{ object.get_latest_history.is_success|yesno:"Yes,No,Unkown" }}
</b></td>
</tr>
<tr>
<td>
{% trans '
asse
ts' %}:
</td>
<td>
{% trans '
Conen
ts' %}:
</td>
<td>
<b>
{% for
asset in object.total_asset
s %}
{{
asset.hostnam
e }}
<br/>
{% for
task in object.get_latest_adhoc.task
s %}
{{
task.name }} : {{ task.action.modul
e }}
<br/>
{% endfor %}
</b>
</td>
...
...
@@ -94,31 +98,6 @@
</table>
</div>
</div>
<div
class=
"ibox float-e-margins"
>
<div
class=
"ibox-title"
>
<span><b>
Result
</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>
<ul
class=
"dropdown-menu dropdown-user"
>
</ul>
<a
class=
"close-link"
>
<i
class=
"fa fa-times"
></i>
</a>
</div>
</div>
<div
class=
"ibox-content"
>
<pre>
{{ object.result }}
</pre>
</div>
</div>
</div>
</div>
<div
class=
"col-sm-5"
style=
"padding-left: 0;padding-right: 0"
>
<div
class=
"panel panel-danger"
>
...
...
@@ -154,7 +133,7 @@
<div
class=
"panel-body"
>
<table
class=
"table"
>
<tbody>
{% for host in
results.success
%}
{% for host in
object.get_latest_history.summary.contacted
%}
{% if forloop.first %}
<tr
class=
"no-borders-tr"
>
{% else %}
...
...
apps/ops/templates/ops/task_history.html
0 → 100644
View file @
0c9e24dc
{% 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>
<a
href=
"{% url 'ops:task-detail' pk=object.pk %}"
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Task detail' %}
</a>
</li>
<li>
<a
href=
"{% url 'ops:task-adhoc' pk=object.pk %}"
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Task versions' %}
</a>
</li>
<li
class=
"active"
>
<a
href=
"{% url 'ops:task-history' pk=object.pk %}"
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Run history' %}
</a>
</li>
</ul>
</div>
<div
class=
"tab-content"
>
<div
class=
"col-sm-12"
style=
"padding-left: 0"
>
<div
class=
"ibox float-e-margins"
>
<div
class=
"ibox-title"
>
<span
style=
"float: left"
>
{% trans 'Versions of ' %}
<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>
<ul
class=
"dropdown-menu dropdown-user"
>
</ul>
<a
class=
"close-link"
>
<i
class=
"fa fa-times"
></i>
</a>
</div>
</div>
<div
class=
"ibox-content"
>
<table
class=
"table table-hover "
id=
"task-history-list-table"
>
<thead>
<tr>
<th
class=
"text-center"
>
<input
type=
"checkbox"
id=
"check_all"
class=
"ipt_check_all"
>
</th>
<th>
{% trans 'Date start' %}
</th>
<th>
{% trans 'F/S/T' %}
</th>
<th>
{% trans 'Is finished' %}
</th>
<th>
{% trans 'Is success' %}
</th>
<th>
{% trans 'Time' %}
</th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block custom_foot_js %}
<script>
$
(
document
).
ready
(
function
()
{
var
options
=
{
ele
:
$
(
'#task-history-list-table'
),
buttons
:
[],
order
:
[],
select
:
[],
columnDefs
:
[
{
targets
:
1
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
{
#
var
detail_btn
=
'<a href="'
+
cellData
+
'</a>'
;
#
}
$
(
td
).
html
(
cellData
);
}},
{
#
{
targets
:
2
,
createdCell
:
function
(
td
,
cellData
,
rowData
)
{
#
}
{
#
var
dataLength
=
cellData
.
length
;
#
}
{
#
$
(
td
).
html
(
dataLength
);
#
}
{
#
}},
#
}
{
targets
:
3
,
createdCell
:
function
(
td
,
cellData
)
{
if
(
!
cellData
)
{
$
(
td
).
html
(
'<i class="fa fa-times text-danger"></i>'
)
}
else
{
$
(
td
).
html
(
'<i class="fa fa-check text-navy"></i>'
)
}
}},
{
targets
:
4
,
createdCell
:
function
(
td
,
cellData
)
{
if
(
!
cellData
)
{
$
(
td
).
html
(
'<i class="fa fa-times text-danger"></i>'
)
}
else
{
$
(
td
).
html
(
'<i class="fa fa-check text-navy"></i>'
)
}
}},
{
targets
:
5
,
createdCell
:
function
(
td
,
cellData
)
{
if
(
cellData
)
{
$
(
td
).
html
(
cellData
.
toFixed
(
2
)
+
' s'
)
}
else
{
$
(
td
).
html
(
"0"
+
' s'
)
}
}}
],
ajax_url
:
'{% url "api-ops:history-list" %}?task={{ object.pk }}'
,
columns
:
[{
data
:
function
(){
return
""
}},
{
data
:
"date_start"
},
{
data
:
"adhoc_short_id"
},
{
data
:
"adhoc_short_id"
},
{
data
:
"adhoc_short_id"
},
{
data
:
"timedelta"
}]
};
jumpserver
.
initDataTable
(
options
);
})
</script>
{% endblock %}
apps/ops/templates/ops/task_list.html
View file @
0c9e24dc
...
...
@@ -22,7 +22,7 @@
</div>
</div>
<div
class=
"input-group"
>
<input
type=
"text"
class=
"form-control input-sm"
name=
"keyword"
placeholder=
"
Keyword
"
value=
"{{ keyword }}"
>
<input
type=
"text"
class=
"form-control input-sm"
name=
"keyword"
placeholder=
"
Search
"
value=
"{{ keyword }}"
>
</div>
<div
class=
"input-group"
>
<div
class=
"input-group-btn"
>
...
...
@@ -37,10 +37,11 @@
{% block table_head %}
<th
class=
"text-center"
></th>
<th
class=
"text-center"
>
{% trans 'Name' %}
</th>
<th
class=
"text-center"
>
{% trans 'Asset' %}
</th>
<th
class=
"text-center"
>
{% trans 'F/S/T' %}
</th>
<th
class=
"text-center"
>
{% trans 'Versions' %}
</th>
<th
class=
"text-center"
>
{% trans 'Hosts' %}
</th>
<th
class=
"text-center"
>
{% trans 'Success' %}
</th>
<th
class=
"text-center"
>
{% trans 'Finished' %}
</th>
<th
class=
"text-center"
>
{% trans 'Date start' %}
</th>
<th
class=
"text-center"
>
{% trans 'Date' %}
</th>
<th
class=
"text-center"
>
{% trans 'Time' %}
</th>
<th
class=
"text-center"
>
{% trans 'Action' %}
</th>
{% endblock %}
...
...
@@ -49,26 +50,23 @@
{% for object in task_list %}
<tr
class=
"gradeX"
>
<td
class=
"text-center"
><input
type=
"checkbox"
class=
"cbx-term"
>
</td>
<td
class=
"text-center"
><a
href=
"{% url 'ops:task-detail' pk=object.uuid %}"
>
{{ object.name }}
</a></td>
<td
class=
"text-center"
>
{{ object.total_assets|length }}
</td>
<td
class=
"text-center"
><a
href=
"{% url 'ops:task-detail' pk=object.id %}"
>
{{ object.name }}
</a></td>
<td
class=
"text-center"
>
{% if object.is_success %}
<i
class=
"fa fa-check text-navy"
></i>
{% else %}
<i
class=
"fa fa-times text-danger"
></i>
{% endif %}
<span
class=
"text-danger"
>
{{ object.get_all_run_times.failed }}
</span>
/
<span
class=
"text-navy"
>
{{ object.get_all_run_times.success}}
</span>
/{{ object.get_all_run_times.total}}
</td>
<td
class=
"text-center"
>
{{ object.adhoc.all | length}}
</td>
<td
class=
"text-center"
>
{{ object.get_latest_adhoc.hosts | length}}
</td>
<td
class=
"text-center"
>
{% if object.
is_finished
%}
{% if object.
get_latest_history.is_success
%}
<i
class=
"fa fa-check text-navy"
></i>
{% else %}
<i
class=
"fa fa-times text-danger"
></i>
{% endif %}
</td>
<td
class=
"text-center"
>
{{ object.date_start }}
</td>
<td
class=
"text-center"
>
{{ object.
timedelta
}} s
</td>
<td
class=
"text-center"
>
{{ object.
get_latest_history.
date_start }}
</td>
<td
class=
"text-center"
>
{{ object.
get_latest_history.timedelta|floatformat
}} s
</td>
<td
class=
"text-center"
>
<a
href=
"{% url 'ops:task-run' pk=object.
uuid %}"
class=
"btn btn-xs btn-info"
>
{% trans "Run agai
n" %}
</a>
<a
href=
"{% url 'ops:task-run' pk=object.
id %}"
class=
"btn btn-xs btn-info"
>
{% trans "Ru
n" %}
</a>
<a
data-uid=
"{{ object.uuid }}"
class=
"btn btn-xs btn-danger btn-del"
>
{% trans "Delete" %}
</a>
</td>
</tr>
...
...
apps/ops/test_utils.py
View file @
0c9e24dc
...
...
@@ -5,17 +5,16 @@ import sys
import
os
from
django.test
import
TestCase
os
.
environ
.
setdefault
(
"DJANGO_SETTINGS_MODULE"
,
"jumpserver.settings"
)
from
ops.models
import
AdHoc
,
AdHocData
from
ops.utils
import
run_adhoc
from
ops.models
import
Task
,
AdHoc
from
ops.utils
import
run_adhoc_object
class
TestRunAdHoc
(
TestCase
):
def
setUp
(
self
):
adhoc
=
AdHoc
(
name
=
"Test run adhoc"
)
adhoc
=
Task
(
name
=
"Test run adhoc"
)
adhoc
.
save
()
self
.
data
=
AdHoc
Data
(
subject
=
adhoc
,
run_as_admin
=
True
,
pattern
=
'all'
)
self
.
data
=
AdHoc
(
subject
=
adhoc
,
run_as_admin
=
True
,
pattern
=
'all'
)
self
.
data
.
tasks
=
[
{
'name'
:
'run ls'
,
'action'
:
{
'module'
:
'shell'
,
'args'
:
'ls'
}},
{
'name'
:
'echo '
,
'action'
:
{
'module'
:
'shell'
,
'args'
:
'echo 123'
}},
...
...
apps/ops/urls/api_urls.py
View file @
0c9e24dc
...
...
@@ -7,6 +7,8 @@ from .. import api
router
=
DefaultRouter
()
router
.
register
(
r'v1/tasks'
,
api
.
TaskViewSet
,
'task'
)
router
.
register
(
r'v1/adhoc'
,
api
.
AdHocViewSet
,
'adhoc'
)
router
.
register
(
r'v1/history'
,
api
.
AdHocRunHistorySet
,
'history'
)
urlpatterns
=
[]
...
...
apps/ops/urls/view_urls.py
View file @
0c9e24dc
...
...
@@ -11,5 +11,7 @@ urlpatterns = [
# TResource Task url
url
(
r'^task/$'
,
views
.
TaskListView
.
as_view
(),
name
=
'task-list'
),
url
(
r'^task/(?P<pk>[0-9a-zA-Z\-]+)/$'
,
views
.
TaskDetailView
.
as_view
(),
name
=
'task-detail'
),
url
(
r'^task/(?P<pk>[0-9a-zA-Z\-]+)/adhoc/$'
,
views
.
TaskAdhocView
.
as_view
(),
name
=
'task-adhoc'
),
url
(
r'^task/(?P<pk>[0-9a-zA-Z\-]+)/history/$'
,
views
.
TaskHistoryView
.
as_view
(),
name
=
'task-history'
),
url
(
r'^task/(?P<pk>[0-9a-zA-Z\-]+)/run/$'
,
views
.
TaskRunView
.
as_view
(),
name
=
'task-run'
),
]
\ No newline at end of file
apps/ops/utils.py
View file @
0c9e24dc
...
...
@@ -4,20 +4,16 @@ import re
import
time
from
django.utils
import
timezone
from
common.utils
import
get_logger
,
get_object_or_none
from
.ansible
import
AdHocRunner
from
common.utils
import
get_logger
,
get_object_or_none
,
get_short_uuid_str
from
.ansible
import
AdHocRunner
,
CommandResultCallback
from
.inventory
import
JMSInventory
from
.ansible.exceptions
import
AnsibleError
from
.models
import
AdHocRunHistory
from
assets.utils
import
get_assets_by_hostname_list
from
.models
import
AdHocRunHistory
,
Task
,
AdHoc
logger
=
get_logger
(
__file__
)
UUID_PATTERN
=
re
.
compile
(
r'[0-9a-zA-Z\-]{36}'
)
def
run_AdHoc
():
pass
def
is_uuid
(
s
):
if
UUID_PATTERN
.
match
(
s
):
return
True
...
...
@@ -25,97 +21,133 @@ def is_uuid(s):
return
False
def
asset_to_dict
(
asset
):
return
asset
.
to_json
()
def
asset_to_dict_with_credential
(
asset
):
return
asset
.
_to_secret_json
()
def
system_user_to_dict_with_credential
(
system_user
):
return
system_user
.
_to_secret_json
()
def
get_hosts_with_admin
(
hostname_list
):
assets
=
get_assets_by_hostname_list
(
hostname_list
)
return
[
asset
.
_to_secret_json
for
asset
in
assets
]
def
get_hosts
(
hostname_list
):
assets
=
get_assets_by_hostname_list
(
hostname_list
)
return
[
asset
.
to_json
for
asset
in
assets
]
def
get_run_user
(
name
):
from
assets.models
import
SystemUser
system_user
=
get_object_or_none
(
SystemUser
,
name
=
name
)
if
system_user
is
None
:
return
{}
def
record_adhoc
(
func
):
def
_deco
(
adhoc
,
**
options
):
record
=
AdHocRunHistory
(
adhoc
=
adhoc
)
time_start
=
time
.
time
()
try
:
result
=
func
(
adhoc
,
**
options
)
record
.
is_finished
=
True
if
result
.
results_summary
.
get
(
'dark'
):
record
.
is_success
=
False
else
:
record
.
is_success
=
True
record
.
result
=
result
.
results_raw
record
.
summary
=
result
.
results_summary
return
result
finally
:
record
.
date_finished
=
timezone
.
now
()
record
.
timedelta
=
time
.
time
()
-
time_start
record
.
save
()
return
_deco
def
get_adhoc_inventory
(
adhoc
):
if
adhoc
.
become
:
become_info
=
{
'become'
:
{
adhoc
.
become
}
}
else
:
return
system_user
.
_to_secret_json
()
become_info
=
None
inventory
=
JMSInventory
(
adhoc
.
hosts
,
run_as_admin
=
adhoc
.
run_as_admin
,
run_as
=
adhoc
.
run_as
,
become_info
=
become_info
)
return
inventory
def
get_hosts_with_run_user
(
hostname_list
,
run_as
):
hosts_dict
=
get_hosts
(
hostname_list
)
system_user_dct
=
get_run_user
(
run_as
)
for
host
in
hosts_dict
:
host
.
update
(
system_user_dct
)
return
hosts_dict
def
get_inventory
(
hostname_list
,
run_as_admin
=
False
,
run_as
=
None
,
become_info
=
None
):
return
JMSInventory
(
hostname_list
,
run_as_admin
=
run_as_admin
,
run_as
=
run_as
,
become_info
=
become_info
)
def
hosts_add_become
(
hosts
,
adhoc_data
):
if
adhoc_data
.
become
:
become_data
=
{
"become"
:
{
"method"
:
adhoc_data
.
become_method
,
"user"
:
adhoc_data
.
become_user
,
"pass"
:
adhoc_data
.
become_pass
,
}
}
for
host
in
hosts
:
host
.
update
(
become_data
)
return
hosts
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
,
run_as
=
run_as
,
become_info
=
become_info
)
runner
=
AdHocRunner
(
inventory
)
return
runner
def
run_adhoc
(
adhoc_data
,
**
options
):
@record_adhoc
def
run_adhoc_object
(
adhoc
,
**
options
):
"""
:param adhoc
_data: Instance of AdHocData
:param adhoc
: Instance of AdHoc
:param options: ansible support option, like forks ...
:return:
"""
name
=
adhoc_data
.
subject
.
name
hostname_list
=
adhoc_data
.
hosts
if
adhoc_data
.
run_as_admin
:
hosts
=
get_hosts_with_admin
(
hostname_list
)
else
:
hosts
=
get_hosts_with_run_user
(
hostname_list
,
adhoc_data
.
run_as
)
hosts_add_become
(
hosts
,
adhoc_data
)
# admin user 自带become
runner
=
AdHocRunner
(
hosts
)
name
=
adhoc
.
task
.
name
inventory
=
get_adhoc_inventory
(
adhoc
)
runner
=
AdHocRunner
(
inventory
)
for
k
,
v
in
options
:
runner
.
set_option
(
k
,
v
)
record
=
AdHocRunHistory
(
adhoc
=
adhoc_data
)
time_start
=
time
.
time
()
try
:
result
=
runner
.
run
(
adhoc_data
.
tasks
,
adhoc_data
.
pattern
,
name
)
record
.
is_finished
=
True
if
result
.
results_summary
.
get
(
'dark'
):
record
.
is_success
=
False
else
:
record
.
is_success
=
True
record
.
result
=
result
.
results_raw
record
.
summary
=
result
.
results_summary
result
=
runner
.
run
(
adhoc
.
tasks
,
adhoc
.
pattern
,
name
)
return
result
except
AnsibleError
as
e
:
logger
.
error
(
"Failed run adhoc {}, {}"
.
format
(
name
,
e
))
raise
finally
:
record
.
date_finished
=
timezone
.
now
()
record
.
timedelta
=
time
.
time
()
-
time_start
record
.
save
()
def
run_adhoc
(
hostname_list
,
pattern
,
tasks
,
name
=
None
,
run_as_admin
=
False
,
run_as
=
None
,
become_info
=
None
):
if
name
is
None
:
name
=
"Adhoc-task-{}-{}"
.
format
(
get_short_uuid_str
(),
timezone
.
now
()
.
strftime
(
"
%
Y-
%
m-
%
d
%
H:
%
M:
%
S"
),
)
inventory
=
get_inventory
(
hostname_list
,
run_as_admin
=
run_as_admin
,
run_as
=
run_as
,
become_info
=
become_info
)
runner
=
AdHocRunner
(
inventory
)
return
runner
.
run
(
tasks
,
pattern
,
play_name
=
name
)
def
create_and_run_adhoc
(
hostname_list
,
pattern
,
tasks
,
name
=
None
,
run_as_admin
=
False
,
run_as
=
None
,
become_info
=
None
):
if
name
is
None
:
name
=
"Adhoc-task-{}-{}"
.
format
(
get_short_uuid_str
(),
timezone
.
now
()
.
strftime
(
"
%
Y-
%
m-
%
d
%
H:
%
M:
%
S"
),
)
task
=
Task
(
name
=
name
)
task
.
save
()
adhoc
=
AdHoc
(
task
=
task
,
pattern
=
pattern
,
name
=
name
,
run_as_admin
=
run_as_admin
,
run_as
=
run_as
)
adhoc
.
hosts
=
hostname_list
adhoc
.
tasks
=
tasks
adhoc
.
become
=
become_info
adhoc
.
save
()
def
get_task_by_name
(
name
):
task
=
get_object_or_none
(
Task
,
name
=
name
)
return
task
def
create_task
(
name
,
created_by
=
""
):
return
Task
.
objects
.
create
(
name
=
name
,
created_by
=
created_by
)
def
create_adhoc
(
task
,
hosts
,
tasks
,
pattern
=
'all'
,
options
=
None
,
run_as_admin
=
False
,
run_as
=
""
,
become_info
=
None
,
created_by
=
""
):
adhoc
=
AdHoc
(
task
=
task
,
pattern
=
pattern
,
run_as_admin
=
run_as_admin
,
run_as
=
run_as
,
created_by
=
created_by
)
adhoc
.
hosts
=
hosts
adhoc
.
tasks
=
tasks
adhoc
.
options
=
options
adhoc
.
become
=
become_info
adhoc
.
save
()
return
adhoc
apps/ops/views.py
View file @
0c9e24dc
...
...
@@ -9,40 +9,40 @@ from django.views.generic import ListView, DetailView, View
from
django.utils
import
timezone
from
django.shortcuts
import
redirect
,
reverse
from
.models
import
AdHoc
,
AdHocData
,
AdHocRunHistory
from
.models
import
Task
,
AdHoc
,
AdHocRunHistory
from
ops.tasks
import
rerun_task
class
TaskListView
(
ListView
):
paginate_by
=
settings
.
CONFIG
.
DISPLAY_PER_PAGE
model
=
AdHoc
ordering
=
(
'-date_
start
'
,)
model
=
Task
ordering
=
(
'-date_
created
'
,)
context_object_name
=
'task_list'
template_name
=
'ops/task_list.html'
date_format
=
'
%
m/
%
d/
%
Y'
keyword
=
date_from_s
=
date_to_s
=
''
def
get_queryset
(
self
):
date_
now
=
timezone
.
localtime
(
timezone
.
now
()
)
date_
to_default
=
date_now
.
strftime
(
self
.
date_format
)
date_from_default
=
(
date_now
-
timezone
.
timedelta
(
7
))
\
.
strftime
(
self
.
date_format
)
date_
to_default
=
timezone
.
now
(
)
date_
from_default
=
timezone
.
now
()
-
timezone
.
timedelta
(
7
)
date_from_default
_s
=
date_from_default
.
strftime
(
self
.
date_format
)
date_to_default_s
=
date_to_default
.
strftime
(
self
.
date_format
)
self
.
queryset
=
super
(
TaskListView
,
self
)
.
get_queryset
()
self
.
queryset
=
super
()
.
get_queryset
()
self
.
keyword
=
self
.
request
.
GET
.
get
(
'keyword'
,
''
)
self
.
date_from_s
=
self
.
request
.
GET
.
get
(
'date_from'
,
date_from_default
)
self
.
date_to_s
=
self
.
request
.
GET
.
get
(
'date_to'
,
date_to_default
)
self
.
date_from_s
=
self
.
request
.
GET
.
get
(
'date_from'
,
date_from_default
_s
)
self
.
date_to_s
=
self
.
request
.
GET
.
get
(
'date_to'
,
date_to_default
_s
)
if
self
.
date_from_s
:
date_from
=
datetime
.
strptime
(
self
.
date_from_s
,
self
.
date_format
)
date_from
=
date_from
.
replace
(
tzinfo
=
timezone
.
get_current_timezone
())
self
.
queryset
=
self
.
queryset
.
filter
(
date_
start
__gt
=
date_from
)
self
.
queryset
=
self
.
queryset
.
filter
(
date_
created
__gt
=
date_from
)
if
self
.
date_to_s
:
date_to
=
timezone
.
datetime
.
strptime
(
self
.
date_to_s
+
' 23:59:59'
,
'
%
m/
%
d/
%
Y
%
H:
%
M:
%
S'
)
date_to
=
date_to
.
replace
(
tzinfo
=
timezone
.
get_current_timezone
())
self
.
queryset
=
self
.
queryset
.
filter
(
date_
finish
ed__lt
=
date_to
)
self
.
queryset
=
self
.
queryset
.
filter
(
date_
creat
ed__lt
=
date_to
)
if
self
.
keyword
:
self
.
queryset
=
self
.
queryset
.
filter
(
...
...
@@ -63,17 +63,42 @@ class TaskListView(ListView):
class
TaskDetailView
(
DetailView
):
model
=
AdHocRunHistory
model
=
Task
template_name
=
'ops/task_detail.html'
def
get_context_data
(
self
,
**
kwargs
):
context
=
{
'app'
:
'Ops'
,
'action'
:
'Playbook record detail'
,
'results'
:
json
.
loads
(
self
.
object
.
summary
or
'{}'
),
'action'
:
'Task detail'
,
}
kwargs
.
update
(
context
)
return
super
(
TaskDetailView
,
self
)
.
get_context_data
(
**
kwargs
)
return
super
()
.
get_context_data
(
**
kwargs
)
class
TaskAdhocView
(
DetailView
):
model
=
Task
template_name
=
'ops/task_adhoc.html'
def
get_context_data
(
self
,
**
kwargs
):
context
=
{
'app'
:
'Ops'
,
'action'
:
'Task versions'
,
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
class
TaskHistoryView
(
DetailView
):
model
=
Task
template_name
=
'ops/task_history.html'
def
get_context_data
(
self
,
**
kwargs
):
context
=
{
'app'
:
'Ops'
,
'action'
:
'Task run history'
,
}
kwargs
.
update
(
context
)
return
super
()
.
get_context_data
(
**
kwargs
)
class
TaskRunView
(
View
):
...
...
apps/perms/tasks.py
View file @
0c9e24dc
...
...
@@ -3,46 +3,11 @@ from __future__ import absolute_import, unicode_literals
from
celery
import
shared_task
from
common.utils
import
get_logger
,
encrypt_password
from
ops.utils
import
run_AdHoc
logger
=
get_logger
(
__file__
)
@shared_task
(
bind
=
True
)
def
push_users
(
self
,
assets
,
users
):
"""
user: {
name: 'web',
username: 'web',
shell: '/bin/bash',
password: '123123123',
public_key: 'string',
sudo: '/bin/whoami,/sbin/ifconfig'
}
"""
if
isinstance
(
users
,
dict
):
users
=
[
users
]
if
isinstance
(
assets
,
dict
):
assets
=
[
assets
]
task_tuple
=
[]
for
user
in
users
:
# 添加用户, 设置公钥, 设置sudo
task_tuple
.
extend
([
(
'user'
,
'name={} shell={} state=present password={}'
.
format
(
user
[
'username'
],
user
.
get
(
'shell'
,
'/bin/bash'
),
encrypt_password
(
user
.
get
(
'password'
,
None
)))),
(
'authorized_key'
,
"user={} state=present key='{}'"
.
format
(
user
[
'username'
],
user
[
'public_key'
])),
(
'lineinfile'
,
"dest=/etc/sudoers state=present regexp='^{0} ALL=' "
"line='{0} ALL=(ALL) NOPASSWD: {1}' "
"validate='visudo -cf
%
s'"
.
format
(
user
[
'username'
],
user
.
get
(
'sudo'
,
'/sbin/ifconfig'
)
))
])
task_name
=
'Push user {}'
.
format
(
','
.
join
([
user
[
'name'
]
for
user
in
users
]))
task
=
run_AdHoc
(
task_tuple
,
assets
,
pattern
=
'all'
,
task_name
=
task_name
,
task_id
=
self
.
request
.
id
)
return
task
pass
apps/templates/_nav.html
View file @
0c9e24dc
...
...
@@ -52,7 +52,7 @@
<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 '
Playbook
' %}
</a></li>
<li
id=
"task"
><a
href=
"{% url 'ops:task-list' %}"
>
{% trans '
Tasks
' %}
</a></li>
</ul>
</li>
...
...
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