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
7910292e
Commit
7910292e
authored
Nov 29, 2017
by
ibuler
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[Feature] 修改命令上传api
parent
26607bc3
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
281 additions
and
17 deletions
+281
-17
api.py
apps/applications/api.py
+49
-5
__init__.py
apps/applications/backends/__init__.py
+17
-0
__init__.py
apps/applications/backends/command/__init__.py
+0
-0
base.py
apps/applications/backends/command/base.py
+22
-0
db.py
apps/applications/backends/command/db.py
+70
-0
models.py
apps/applications/backends/command/models.py
+22
-0
serializers.py
apps/applications/backends/command/serializers.py
+16
-0
__init__.py
apps/applications/backends/replay/__init__.py
+2
-0
base.py
apps/applications/backends/replay/base.py
+14
-0
db.py
apps/applications/backends/replay/db.py
+31
-0
serializers.py
apps/applications/backends/replay/serializers.py
+20
-0
models.py
apps/applications/models.py
+8
-1
terminal_list.html
apps/applications/templates/applications/terminal_list.html
+3
-3
api_urls.py
apps/applications/urls/api_urls.py
+1
-2
api_urls.py
apps/assets/urls/api_urls.py
+1
-1
api_urls.py
apps/audits/urls/api_urls.py
+1
-1
settings.py
apps/jumpserver/settings.py
+2
-2
django.po
apps/locale/zh/LC_MESSAGES/django.po
+1
-1
task_detail.html
apps/ops/templates/ops/task_detail.html
+1
-1
No files found.
apps/applications/api.py
View file @
7910292e
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
#
import
base64
from
collections
import
OrderedDict
from
collections
import
OrderedDict
import
copy
import
copy
import
logging
import
logging
import
tarfile
import
os
import
os
from
rest_framework
import
viewsets
,
serializers
from
rest_framework
import
viewsets
,
serializers
...
@@ -12,12 +14,13 @@ from django.shortcuts import get_object_or_404
...
@@ -12,12 +14,13 @@ from django.shortcuts import get_object_or_404
from
django.utils
import
timezone
from
django.utils
import
timezone
from
django.conf
import
settings
from
django.conf
import
settings
from
common.utils
import
get_object_or_none
from
.models
import
Terminal
,
TerminalStatus
,
TerminalSession
,
TerminalTask
from
.models
import
Terminal
,
TerminalStatus
,
TerminalSession
,
TerminalTask
from
.serializers
import
TerminalSerializer
,
TerminalStatusSerializer
,
\
from
.serializers
import
TerminalSerializer
,
TerminalStatusSerializer
,
\
TerminalSessionSerializer
,
TerminalTaskSerializer
TerminalSessionSerializer
,
TerminalTaskSerializer
from
.hands
import
IsSuperUserOrAppUser
,
IsAppUser
,
ProxyLog
,
\
from
.hands
import
IsSuperUserOrAppUser
,
IsAppUser
,
ProxyLog
,
\
IsSuperUserOrAppUserOrUserReadonly
IsSuperUserOrAppUserOrUserReadonly
from
common.utils
import
get_object_or_none
from
.backends
import
get_command_store
,
get_replay_store
,
SessionCommandSerializer
logger
=
logging
.
getLogger
(
__file__
)
logger
=
logging
.
getLogger
(
__file__
)
...
@@ -157,11 +160,14 @@ class SessionReplayAPI(APIView):
...
@@ -157,11 +160,14 @@ class SessionReplayAPI(APIView):
session
=
get_object_or_404
(
TerminalSession
,
id
=
session_id
)
session
=
get_object_or_404
(
TerminalSession
,
id
=
session_id
)
record_dir
=
settings
.
CONFIG
.
SESSION_RECORDE_DIR
record_dir
=
settings
.
CONFIG
.
SESSION_RECORDE_DIR
date
=
session
.
date_start
.
strftime
(
"
%
Y-
%
m-
%
d"
)
date
=
session
.
date_start
.
strftime
(
"
%
Y-
%
m-
%
d"
)
record_dir
=
os
.
path
.
join
(
record_dir
,
date
)
record_dir
=
os
.
path
.
join
(
record_dir
,
date
,
str
(
session
.
id
)
)
record_filename
=
os
.
path
.
join
(
record_dir
,
str
(
session
.
id
)
)
record_filename
=
os
.
path
.
join
(
record_dir
,
"replay.tar.gz2"
)
if
not
os
.
path
.
exists
(
record_dir
):
if
not
os
.
path
.
isdir
(
record_dir
):
os
.
makedirs
(
record_dir
)
try
:
os
.
makedirs
(
record_dir
)
except
FileExistsError
:
pass
archive_stream
=
request
.
data
.
get
(
"archive"
)
archive_stream
=
request
.
data
.
get
(
"archive"
)
if
not
archive_stream
:
if
not
archive_stream
:
...
@@ -173,3 +179,41 @@ class SessionReplayAPI(APIView):
...
@@ -173,3 +179,41 @@ class SessionReplayAPI(APIView):
session
.
has_replay
=
True
session
.
has_replay
=
True
session
.
save
()
session
.
save
()
return
Response
({
"session_id"
:
session
.
id
},
status
=
201
)
return
Response
({
"session_id"
:
session
.
id
},
status
=
201
)
class
SessionCommandViewSet
(
viewsets
.
ViewSet
):
"""接受app发送来的command log, 格式如下
{
"user": "admin",
"asset": "localhost",
"system_user": "web",
"session": "xxxxxx",
"input": "whoami",
"output": "d2hvbWFp", # base64.b64encode(s)
"timestamp": 1485238673.0
}
"""
command_store
=
get_command_store
()
serializer_class
=
SessionCommandSerializer
permission_classes
=
(
IsSuperUserOrAppUser
,)
def
get_queryset
(
self
):
self
.
command_store
.
all
()
def
create
(
self
,
request
,
*
args
,
**
kwargs
):
serializer
=
self
.
serializer_class
(
data
=
request
.
data
,
many
=
True
)
if
serializer
.
is_valid
():
ok
=
self
.
command_store
.
bulk_save
(
serializer
.
validated_data
)
if
ok
:
return
Response
(
"ok"
,
status
=
201
)
else
:
return
Response
(
"save error"
,
status
=
500
)
else
:
print
(
serializer
.
errors
)
return
Response
({
"msg"
:
"Not valid: {}"
.
format
(
serializer
.
errors
)},
status
=
401
)
def
list
(
self
,
request
,
*
args
,
**
kwargs
):
queryset
=
list
(
self
.
command_store
.
all
())
serializer
=
self
.
serializer_class
(
queryset
,
many
=
True
)
return
Response
(
serializer
.
data
)
apps/applications/backends/__init__.py
0 → 100644
View file @
7910292e
from
importlib
import
import_module
from
django.conf
import
settings
from
.command.serializers
import
SessionCommandSerializer
def
get_command_store
():
command_engine
=
import_module
(
settings
.
COMMAND_STORE_BACKEND
)
command_store
=
command_engine
.
CommandStore
()
return
command_store
def
get_replay_store
():
replay_engine
=
import_module
(
settings
.
RECORD_STORE_BACKEND
)
replay_store
=
replay_engine
.
RecordStore
()
return
replay_store
apps/applications/backends/command/__init__.py
0 → 100644
View file @
7910292e
apps/applications/backends/command/base.py
0 → 100644
View file @
7910292e
# coding: utf-8
import
abc
class
CommandBase
(
object
):
__metaclass__
=
abc
.
ABCMeta
@abc.abstractmethod
def
save
(
self
,
command
):
pass
@abc.abstractmethod
def
bulk_save
(
self
,
commands
):
pass
@abc.abstractmethod
def
filter
(
self
,
date_from
=
None
,
date_to
=
None
,
user
=
None
,
asset
=
None
,
system_user
=
None
,
command
=
None
,
session
=
None
):
pass
apps/applications/backends/command/db.py
0 → 100644
View file @
7910292e
# ~*~ coding: utf-8 ~*~
import
datetime
from
django.utils
import
timezone
from
.base
import
CommandBase
class
CommandStore
(
CommandBase
):
def
__init__
(
self
):
from
applications.models
import
SessionCommand
self
.
model
=
SessionCommand
def
save
(
self
,
command
):
"""
保存命令到数据库
"""
self
.
model
.
objects
.
create
(
user
=
command
[
"user"
],
asset
=
command
[
"asset"
],
system_user
=
command
[
"system_user"
],
input
=
command
[
"input"
],
output
=
command
[
"output"
],
session
=
command
[
"session"
],
timestamp
=
command
[
"timestamp"
]
)
def
bulk_save
(
self
,
commands
):
"""
批量保存命令到数据库, command的顺序和save中一致
"""
_commands
=
[]
for
c
in
commands
:
_commands
.
append
(
self
.
model
(
user
=
c
[
"user"
],
asset
=
c
[
"asset"
],
system_user
=
c
[
"system_user"
],
input
=
c
[
"input"
],
output
=
c
[
"output"
],
session
=
c
[
"session"
],
timestamp
=
c
[
"timestamp"
]
))
return
self
.
model
.
objects
.
bulk_create
(
_commands
)
def
filter
(
self
,
date_from
=
None
,
date_to
=
None
,
user
=
None
,
asset
=
None
,
system_user
=
None
,
_input
=
None
,
session
=
None
):
filter_kwargs
=
{}
if
date_from
:
filter_kwargs
[
'timestamp__gte'
]
=
int
(
date_from
.
timestamp
())
else
:
week_ago
=
timezone
.
now
()
-
datetime
.
timedelta
(
days
=
7
)
filter_kwargs
[
'timestamp__gte'
]
=
int
(
week_ago
.
timestamp
())
if
date_to
:
filter_kwargs
[
'timestamp__lte'
]
=
int
(
date_to
.
timestamp
())
else
:
filter_kwargs
[
'timestamp__lte'
]
=
int
(
timezone
.
now
()
.
timestamp
())
if
user
:
filter_kwargs
[
'user'
]
=
user
if
asset
:
filter_kwargs
[
'asset'
]
=
asset
if
system_user
:
filter_kwargs
[
'system_user'
]
=
system_user
if
_input
:
filter_kwargs
[
'input__icontains'
]
=
_input
if
session
:
filter_kwargs
[
'session'
]
=
session
queryset
=
self
.
model
.
objects
.
filter
(
**
filter_kwargs
)
return
queryset
def
all
(
self
):
"""返回所有数据"""
return
self
.
model
.
objects
.
iterator
()
apps/applications/backends/command/models.py
0 → 100644
View file @
7910292e
# -*- coding: utf-8 -*-
#
import
uuid
from
django.db
import
models
from
django.utils.translation
import
ugettext_lazy
as
_
class
AbstractSessionCommand
(
models
.
Model
):
id
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
user
=
models
.
CharField
(
max_length
=
64
,
verbose_name
=
_
(
"User"
))
asset
=
models
.
CharField
(
max_length
=
128
,
verbose_name
=
_
(
"Asset"
))
system_user
=
models
.
CharField
(
max_length
=
64
,
verbose_name
=
_
(
"System user"
))
input
=
models
.
CharField
(
max_length
=
128
,
db_index
=
True
,
verbose_name
=
_
(
"Input"
))
output
=
models
.
CharField
(
max_length
=
1024
,
verbose_name
=
_
(
"Output"
))
session
=
models
.
CharField
(
max_length
=
36
,
db_index
=
True
,
verbose_name
=
_
(
"Session"
))
timestamp
=
models
.
IntegerField
(
db_index
=
True
)
class
Meta
:
abstract
=
True
def
__str__
(
self
):
return
self
.
input
apps/applications/backends/command/serializers.py
0 → 100644
View file @
7910292e
# ~*~ coding: utf-8 ~*~
from
rest_framework
import
serializers
class
SessionCommandSerializer
(
serializers
.
Serializer
):
"""使用这个类作为基础Command Log Serializer类, 用来序列化"""
id
=
serializers
.
UUIDField
(
read_only
=
True
)
user
=
serializers
.
CharField
(
max_length
=
64
)
asset
=
serializers
.
CharField
(
max_length
=
128
)
system_user
=
serializers
.
CharField
(
max_length
=
64
)
input
=
serializers
.
CharField
(
max_length
=
128
)
output
=
serializers
.
CharField
(
max_length
=
1024
)
session
=
serializers
.
CharField
(
max_length
=
36
)
timestamp
=
serializers
.
IntegerField
()
apps/applications/backends/replay/__init__.py
0 → 100644
View file @
7910292e
# ~*~ coding: utf-8 ~*~
apps/applications/backends/replay/base.py
0 → 100644
View file @
7910292e
# coding: utf-8
import
abc
class
RecordBase
(
object
):
__metaclass__
=
abc
.
ABCMeta
@abc.abstractmethod
def
save
(
self
,
proxy_log_id
,
output
,
timestamp
):
pass
@abc.abstractmethod
def
filter
(
self
,
date_from_ts
=
None
,
proxy_log_id
=
None
):
pass
apps/applications/backends/replay/db.py
0 → 100644
View file @
7910292e
# ~*~ coding: utf-8 ~*~
from
.base
import
RecordBase
from
audits.models
import
RecordLog
class
RecordStore
(
RecordBase
):
model
=
RecordLog
queryset
=
[]
def
save
(
self
,
proxy_log_id
,
output
,
timestamp
):
return
self
.
model
.
objects
.
create
(
proxy_log_id
=
proxy_log_id
,
output
=
output
,
timestamp
=
timestamp
)
def
filter
(
self
,
date_from_ts
=
None
,
proxy_log_id
=
''
):
filter_kwargs
=
{}
if
date_from_ts
:
filter_kwargs
[
'timestamp__gte'
]
=
date_from_ts
if
proxy_log_id
:
filter_kwargs
[
'proxy_log_id'
]
=
proxy_log_id
if
filter_kwargs
:
self
.
queryset
=
self
.
model
.
objects
.
filter
(
**
filter_kwargs
)
return
self
.
queryset
def
all
(
self
):
"""返回所有数据"""
return
self
.
model
.
objects
.
all
()
apps/applications/backends/replay/serializers.py
0 → 100644
View file @
7910292e
# ~*~ coding: utf-8 ~*~
import
base64
from
rest_framework
import
serializers
from
audits.models
import
RecordLog
from
audits.backends
import
record_store
class
RecordSerializer
(
serializers
.
ModelSerializer
):
"""使用这个类作为基础Command Log Serializer类, 用来序列化"""
class
Meta
:
model
=
RecordLog
fields
=
'__all__'
def
create
(
self
,
validated_data
):
try
:
output
=
validated_data
[
'output'
]
validated_data
[
'output'
]
=
base64
.
b64decode
(
output
)
except
IndexError
:
pass
return
record_store
.
save
(
**
dict
(
validated_data
))
apps/applications/models.py
View file @
7910292e
...
@@ -6,6 +6,7 @@ from django.db import models
...
@@ -6,6 +6,7 @@ from django.db import models
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
users.models
import
User
from
users.models
import
User
from
.backends.command.models
import
AbstractSessionCommand
class
Terminal
(
models
.
Model
):
class
Terminal
(
models
.
Model
):
...
@@ -88,7 +89,7 @@ class TerminalSession(models.Model):
...
@@ -88,7 +89,7 @@ class TerminalSession(models.Model):
is_finished
=
models
.
BooleanField
(
default
=
False
)
is_finished
=
models
.
BooleanField
(
default
=
False
)
has_replay
=
models
.
BooleanField
(
default
=
False
,
verbose_name
=
_
(
"Replay"
))
has_replay
=
models
.
BooleanField
(
default
=
False
,
verbose_name
=
_
(
"Replay"
))
has_command
=
models
.
BooleanField
(
default
=
False
,
verbose_name
=
_
(
"Command"
))
has_command
=
models
.
BooleanField
(
default
=
False
,
verbose_name
=
_
(
"Command"
))
terminal
=
models
.
Integer
Field
(
null
=
True
,
verbose_name
=
_
(
"Terminal"
))
terminal
=
models
.
UUID
Field
(
null
=
True
,
verbose_name
=
_
(
"Terminal"
))
date_start
=
models
.
DateTimeField
(
verbose_name
=
_
(
"Date Start"
))
date_start
=
models
.
DateTimeField
(
verbose_name
=
_
(
"Date Start"
))
date_end
=
models
.
DateTimeField
(
verbose_name
=
_
(
"Date End"
),
null
=
True
)
date_end
=
models
.
DateTimeField
(
verbose_name
=
_
(
"Date End"
),
null
=
True
)
...
@@ -110,3 +111,9 @@ class TerminalTask(models.Model):
...
@@ -110,3 +111,9 @@ class TerminalTask(models.Model):
class
Meta
:
class
Meta
:
db_table
=
"terminal_task"
db_table
=
"terminal_task"
class
SessionCommand
(
AbstractSessionCommand
):
class
Meta
:
db_table
=
"session_command"
apps/applications/templates/applications/terminal_list.html
View file @
7910292e
...
@@ -28,9 +28,9 @@
...
@@ -28,9 +28,9 @@
</th>
</th>
<th
class=
"text-center"
>
{% trans 'Name' %}
</th>
<th
class=
"text-center"
>
{% trans 'Name' %}
</th>
<th
class=
"text-center"
>
{% trans 'Addr' %}
</th>
<th
class=
"text-center"
>
{% trans 'Addr' %}
</th>
<th
class=
"text-center"
>
{% trans 'SSH
P
ort' %}
</th>
<th
class=
"text-center"
>
{% trans 'SSH
p
ort' %}
</th>
<th
class=
"text-center"
>
{% trans 'Http
P
ort' %}
</th>
<th
class=
"text-center"
>
{% trans 'Http
p
ort' %}
</th>
<th
class=
"text-center"
>
{% trans '
Connected
' %}
</th>
<th
class=
"text-center"
>
{% trans '
Sessions
' %}
</th>
<th
class=
"text-center"
>
{% trans 'Active' %}
</th>
<th
class=
"text-center"
>
{% trans 'Active' %}
</th>
<th
class=
"text-center"
>
{% trans 'Alive' %}
</th>
<th
class=
"text-center"
>
{% trans 'Alive' %}
</th>
<th
class=
"text-center"
>
{% trans 'Action' %}
</th>
<th
class=
"text-center"
>
{% trans 'Action' %}
</th>
...
...
apps/applications/urls/api_urls.py
View file @
7910292e
...
@@ -13,10 +13,9 @@ router = routers.DefaultRouter()
...
@@ -13,10 +13,9 @@ router = routers.DefaultRouter()
router
.
register
(
r'v1/terminal/(?P<terminal>[0-9]+)?/?status'
,
api
.
TerminalStatusViewSet
,
'terminal-status'
)
router
.
register
(
r'v1/terminal/(?P<terminal>[0-9]+)?/?status'
,
api
.
TerminalStatusViewSet
,
'terminal-status'
)
router
.
register
(
r'v1/terminal/(?P<terminal>[0-9]+)?/?sessions'
,
api
.
TerminalSessionViewSet
,
'terminal-sessions'
)
router
.
register
(
r'v1/terminal/(?P<terminal>[0-9]+)?/?sessions'
,
api
.
TerminalSessionViewSet
,
'terminal-sessions'
)
router
.
register
(
r'v1/terminal'
,
api
.
TerminalViewSet
,
'terminal'
)
router
.
register
(
r'v1/terminal'
,
api
.
TerminalViewSet
,
'terminal'
)
router
.
register
(
r'v1/command'
,
api
.
SessionCommandViewSet
,
'command'
)
urlpatterns
=
[
urlpatterns
=
[
# url(r'^v1/terminate/connection/$', api.TerminateConnectionView.as_view(),
# name='terminate-connection')
url
(
r'^v1/sessions/(?P<pk>[0-9a-zA-Z\-_]+)/replay/$'
,
api
.
SessionReplayAPI
.
as_view
(),
name
=
'session-replay'
),
url
(
r'^v1/sessions/(?P<pk>[0-9a-zA-Z\-_]+)/replay/$'
,
api
.
SessionReplayAPI
.
as_view
(),
name
=
'session-replay'
),
]
]
...
...
apps/assets/urls/api_urls.py
View file @
7910292e
...
@@ -16,7 +16,7 @@ router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user')
...
@@ -16,7 +16,7 @@ router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user')
urlpatterns
=
[
urlpatterns
=
[
url
(
r'^v1/assets-bulk/$'
,
api
.
AssetListUpdateApi
.
as_view
(),
name
=
'asset-bulk-update'
),
url
(
r'^v1/assets-bulk/$'
,
api
.
AssetListUpdateApi
.
as_view
(),
name
=
'asset-bulk-update'
),
url
(
r'^v1/system-user/(?P<pk>[0-9]+)/auth-info/'
,
api
.
SystemUserAuthInfoApi
.
as_view
(),
url
(
r'^v1/system-user/(?P<pk>[0-9
a-zA-Z\-
]+)/auth-info/'
,
api
.
SystemUserAuthInfoApi
.
as_view
(),
name
=
'system-user-auth-info'
),
name
=
'system-user-auth-info'
),
url
(
r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]+)/groups/$'
,
url
(
r'^v1/assets/(?P<pk>[0-9a-zA-Z\-]+)/groups/$'
,
api
.
AssetUpdateGroupApi
.
as_view
(),
name
=
'asset-update-group'
),
api
.
AssetUpdateGroupApi
.
as_view
(),
name
=
'asset-update-group'
),
...
...
apps/audits/urls/api_urls.py
View file @
7910292e
...
@@ -8,7 +8,7 @@ app_name = 'audits'
...
@@ -8,7 +8,7 @@ app_name = 'audits'
router
=
routers
.
DefaultRouter
()
router
=
routers
.
DefaultRouter
()
router
.
register
(
r'v1/proxy-log'
,
api
.
ProxyLogViewSet
,
'proxy-log'
)
router
.
register
(
r'v1/proxy-log'
,
api
.
ProxyLogViewSet
,
'proxy-log'
)
router
.
register
(
r'v1/command-log'
,
api
.
CommandLogViewSet
,
'command-log'
)
router
.
register
(
r'v1/command-log'
,
api
.
CommandLogViewSet
,
'command-log'
)
router
.
register
(
r'v1/re
cord-log'
,
api
.
RecordLogViewSet
,
'record
-log'
)
router
.
register
(
r'v1/re
play-log'
,
api
.
RecordLogViewSet
,
'replay
-log'
)
urlpatterns
=
[
urlpatterns
=
[
url
(
r'^v1/proxy-log/receive/$'
,
api
.
ProxyLogReceiveView
.
as_view
(),
url
(
r'^v1/proxy-log/receive/$'
,
api
.
ProxyLogReceiveView
.
as_view
(),
...
...
apps/jumpserver/settings.py
View file @
7910292e
...
@@ -363,8 +363,8 @@ CAPTCHA_FOREGROUND_COLOR = '#001100'
...
@@ -363,8 +363,8 @@ CAPTCHA_FOREGROUND_COLOR = '#001100'
CAPTCHA_NOISE_FUNCTIONS
=
(
'captcha.helpers.noise_dots'
,)
CAPTCHA_NOISE_FUNCTIONS
=
(
'captcha.helpers.noise_dots'
,)
CAPTCHA_TEST_MODE
=
CONFIG
.
CAPTCHA_TEST_MODE
CAPTCHA_TEST_MODE
=
CONFIG
.
CAPTCHA_TEST_MODE
COMMAND_STORE_BACKEND
=
'a
udit
s.backends.command.db'
COMMAND_STORE_BACKEND
=
'a
pplication
s.backends.command.db'
RECORD_STORE_BACKEND
=
'a
udits.backends.record
.db'
RECORD_STORE_BACKEND
=
'a
pplications.backends.replay
.db'
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
# Django bootstrap3 setting, more see http://django-bootstrap3.readthedocs.io/en/latest/settings.html
...
...
apps/locale/zh/LC_MESSAGES/django.po
View file @
7910292e
...
@@ -1434,7 +1434,7 @@ msgid "Task summary"
...
@@ -1434,7 +1434,7 @@ msgid "Task summary"
msgstr ""
msgstr ""
#: ops/templates/ops/task_detail.html:19
#: ops/templates/ops/task_detail.html:19
msgid "Task re
cord
detail"
msgid "Task re
play
detail"
msgstr "任务记录详情"
msgstr "任务记录详情"
#: ops/templates/ops/task_detail.html:62
#: ops/templates/ops/task_detail.html:62
...
...
apps/ops/templates/ops/task_detail.html
View file @
7910292e
...
@@ -16,7 +16,7 @@
...
@@ -16,7 +16,7 @@
<div
class=
"panel-options"
>
<div
class=
"panel-options"
>
<ul
class=
"nav nav-tabs"
>
<ul
class=
"nav nav-tabs"
>
<li
class=
"active"
>
<li
class=
"active"
>
<a
href=
""
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Task re
cord
detail' %}
</a>
<a
href=
""
class=
"text-center"
><i
class=
"fa fa-laptop"
></i>
{% trans 'Task re
play
detail' %}
</a>
</li>
</li>
</ul>
</ul>
</div>
</div>
...
...
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