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
09fbd3a5
Unverified
Commit
09fbd3a5
authored
Oct 16, 2018
by
老广
Committed by
GitHub
Oct 16, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1922 from jumpserver/dev
Dev
parents
0665644f
ebecd005
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
65 additions
and
24 deletions
+65
-24
asset.py
apps/assets/api/asset.py
+1
-1
cmd_filter.py
apps/assets/api/cmd_filter.py
+2
-2
user.py
apps/assets/forms/user.py
+1
-1
asset.py
apps/assets/models/asset.py
+2
-1
cmd_filter.py
apps/assets/models/cmd_filter.py
+2
-2
node.py
apps/assets/models/node.py
+10
-5
user.py
apps/assets/models/user.py
+4
-2
asset_detail.html
apps/assets/templates/assets/asset_detail.html
+4
-0
cmd_filter_list.html
apps/assets/templates/assets/cmd_filter_list.html
+1
-1
asset.py
apps/assets/views/asset.py
+1
-4
forms.py
apps/common/forms.py
+3
-0
common_tags.py
apps/common/templatetags/common_tags.py
+5
-0
django.mo
apps/locale/zh/LC_MESSAGES/django.mo
+0
-0
django.po
apps/locale/zh/LC_MESSAGES/django.po
+0
-0
mixins.py
apps/orgs/mixins.py
+0
-1
api.py
apps/perms/api.py
+1
-1
jumpserver.js
apps/static/js/jumpserver.js
+8
-1
_nav.html
apps/templates/_nav.html
+11
-1
_nav_user.html
apps/templates/_nav_user.html
+7
-1
requirements.txt
requirements/requirements.txt
+2
-0
No files found.
apps/assets/api/asset.py
View file @
09fbd3a5
...
@@ -74,7 +74,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
...
@@ -74,7 +74,7 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet):
.
select_related
(
'admin_user'
)
.
select_related
(
'admin_user'
)
self
.
filter_admin_user_id
()
self
.
filter_admin_user_id
()
self
.
filter_node
()
self
.
filter_node
()
return
self
.
queryset
return
self
.
queryset
.
distinct
()
class
AssetListUpdateApi
(
IDInFilterMixin
,
ListBulkCreateUpdateDestroyAPIView
):
class
AssetListUpdateApi
(
IDInFilterMixin
,
ListBulkCreateUpdateDestroyAPIView
):
...
...
apps/assets/api/cmd_filter.py
View file @
09fbd3a5
...
@@ -26,7 +26,7 @@ class CommandFilterRuleViewSet(BulkModelViewSet):
...
@@ -26,7 +26,7 @@ class CommandFilterRuleViewSet(BulkModelViewSet):
fpk
=
self
.
kwargs
.
get
(
'filter_pk'
)
fpk
=
self
.
kwargs
.
get
(
'filter_pk'
)
if
not
fpk
:
if
not
fpk
:
return
CommandFilterRule
.
objects
.
none
()
return
CommandFilterRule
.
objects
.
none
()
group
=
get_object_or_404
(
CommandFilter
,
pk
=
fpk
)
cmd_filter
=
get_object_or_404
(
CommandFilter
,
pk
=
fpk
)
return
group
.
rules
.
all
()
.
order_by
(
'priority'
)
return
cmd_filter
.
rules
.
all
(
)
apps/assets/forms/user.py
View file @
09fbd3a5
...
@@ -150,7 +150,7 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
...
@@ -150,7 +150,7 @@ class SystemUserForm(OrgModelForm, PasswordAndKeyAuthForm):
'name'
:
'* required'
,
'name'
:
'* required'
,
'username'
:
'* required'
,
'username'
:
'* required'
,
'auto_push'
:
_
(
'Auto push system user to asset'
),
'auto_push'
:
_
(
'Auto push system user to asset'
),
'priority'
:
_
(
'High level will be using login asset as default, '
'priority'
:
_
(
'
1-100,
High level will be using login asset as default, '
'if user was granted more than 2 system user'
),
'if user was granted more than 2 system user'
),
'login_mode'
:
_
(
'If you choose manual login mode, you do not '
'login_mode'
:
_
(
'If you choose manual login mode, you do not '
'need to fill in the username and password.'
)
'need to fill in the username and password.'
)
...
...
apps/assets/models/asset.py
View file @
09fbd3a5
...
@@ -34,7 +34,8 @@ def default_cluster():
...
@@ -34,7 +34,8 @@ def default_cluster():
def
default_node
():
def
default_node
():
try
:
try
:
from
.node
import
Node
from
.node
import
Node
return
Node
.
root
()
root
=
Node
.
root
()
return
root
except
:
except
:
return
None
return
None
...
...
apps/assets/models/cmd_filter.py
View file @
09fbd3a5
...
@@ -44,7 +44,7 @@ class CommandFilterRule(OrgModelMixin):
...
@@ -44,7 +44,7 @@ class CommandFilterRule(OrgModelMixin):
id
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
id
=
models
.
UUIDField
(
default
=
uuid
.
uuid4
,
primary_key
=
True
)
filter
=
models
.
ForeignKey
(
'CommandFilter'
,
on_delete
=
models
.
CASCADE
,
verbose_name
=
_
(
"Filter"
),
related_name
=
'rules'
)
filter
=
models
.
ForeignKey
(
'CommandFilter'
,
on_delete
=
models
.
CASCADE
,
verbose_name
=
_
(
"Filter"
),
related_name
=
'rules'
)
type
=
models
.
CharField
(
max_length
=
16
,
default
=
TYPE_COMMAND
,
choices
=
TYPE_CHOICES
,
verbose_name
=
_
(
"Type"
))
type
=
models
.
CharField
(
max_length
=
16
,
default
=
TYPE_COMMAND
,
choices
=
TYPE_CHOICES
,
verbose_name
=
_
(
"Type"
))
priority
=
models
.
IntegerField
(
default
=
50
,
verbose_name
=
_
(
"Priority"
),
help_text
=
_
(
"1-100, the
low
er will be match first"
),
priority
=
models
.
IntegerField
(
default
=
50
,
verbose_name
=
_
(
"Priority"
),
help_text
=
_
(
"1-100, the
high
er will be match first"
),
validators
=
[
MinValueValidator
(
1
),
MaxValueValidator
(
100
)])
validators
=
[
MinValueValidator
(
1
),
MaxValueValidator
(
100
)])
content
=
models
.
TextField
(
max_length
=
1024
,
verbose_name
=
_
(
"Content"
),
help_text
=
_
(
"One line one command"
))
content
=
models
.
TextField
(
max_length
=
1024
,
verbose_name
=
_
(
"Content"
),
help_text
=
_
(
"One line one command"
))
action
=
models
.
IntegerField
(
default
=
ACTION_DENY
,
choices
=
ACTION_CHOICES
,
verbose_name
=
_
(
"Action"
))
action
=
models
.
IntegerField
(
default
=
ACTION_DENY
,
choices
=
ACTION_CHOICES
,
verbose_name
=
_
(
"Action"
))
...
@@ -54,7 +54,7 @@ class CommandFilterRule(OrgModelMixin):
...
@@ -54,7 +54,7 @@ class CommandFilterRule(OrgModelMixin):
created_by
=
models
.
CharField
(
max_length
=
128
,
blank
=
True
,
default
=
''
,
verbose_name
=
_
(
'Created by'
))
created_by
=
models
.
CharField
(
max_length
=
128
,
blank
=
True
,
default
=
''
,
verbose_name
=
_
(
'Created by'
))
class
Meta
:
class
Meta
:
ordering
=
(
'priority'
,
'action'
)
ordering
=
(
'
-
priority'
,
'action'
)
def
__str__
(
self
):
def
__str__
(
self
):
return
'{}
%
{}'
.
format
(
self
.
type
,
self
.
content
)
return
'{}
%
{}'
.
format
(
self
.
type
,
self
.
content
)
apps/assets/models/node.py
View file @
09fbd3a5
...
@@ -31,6 +31,8 @@ class Node(OrgModelMixin):
...
@@ -31,6 +31,8 @@ class Node(OrgModelMixin):
return
self
.
full_value
return
self
.
full_value
def
__eq__
(
self
,
other
):
def
__eq__
(
self
,
other
):
if
not
other
:
return
False
return
self
.
key
==
other
.
key
return
self
.
key
==
other
.
key
def
__gt__
(
self
,
other
):
def
__gt__
(
self
,
other
):
...
@@ -136,7 +138,7 @@ class Node(OrgModelMixin):
...
@@ -136,7 +138,7 @@ class Node(OrgModelMixin):
args
.
append
(
Q
(
nodes__key__regex
=
pattern
)
|
Q
(
nodes
=
None
))
args
.
append
(
Q
(
nodes__key__regex
=
pattern
)
|
Q
(
nodes
=
None
))
else
:
else
:
kwargs
[
'nodes__key__regex'
]
=
pattern
kwargs
[
'nodes__key__regex'
]
=
pattern
assets
=
Asset
.
objects
.
filter
(
*
args
,
**
kwargs
)
assets
=
Asset
.
objects
.
filter
(
*
args
,
**
kwargs
)
.
distinct
()
return
assets
return
assets
def
get_all_valid_assets
(
self
):
def
get_all_valid_assets
(
self
):
...
@@ -201,13 +203,16 @@ class Node(OrgModelMixin):
...
@@ -201,13 +203,16 @@ class Node(OrgModelMixin):
# 如果使用current_org 在set_current_org时会死循环
# 如果使用current_org 在set_current_org时会死循环
_current_org
=
get_current_org
()
_current_org
=
get_current_org
()
with
transaction
.
atomic
():
with
transaction
.
atomic
():
if
_current_org
.
is_
defaul
t
():
if
_current_org
.
is_
roo
t
():
key
=
'0'
key
=
'0'
elif
_current_org
.
is_default
():
key
=
'1'
else
:
else
:
set_current_org
(
Organization
.
root
())
set_current_org
(
Organization
.
root
())
org_nodes_roots
=
cls
.
objects
.
filter
(
key__regex
=
r'^[0-9]+$'
)
org_nodes_roots
=
cls
.
objects
.
filter
(
key__regex
=
r'^[0-9]+$'
)
org_nodes_roots_keys
=
org_nodes_roots
.
values_list
(
'key'
,
flat
=
True
)
or
[
0
]
org_nodes_roots_keys
=
org_nodes_roots
.
values_list
(
'key'
,
flat
=
True
)
or
[
'1'
]
key
=
str
(
max
([
int
(
k
)
for
k
in
org_nodes_roots_keys
])
+
1
)
key
=
max
([
int
(
k
)
for
k
in
org_nodes_roots_keys
])
key
=
str
(
key
+
1
)
if
key
!=
0
else
'2'
set_current_org
(
_current_org
)
set_current_org
(
_current_org
)
root
=
cls
.
objects
.
create
(
key
=
key
,
value
=
_current_org
.
name
)
root
=
cls
.
objects
.
create
(
key
=
key
,
value
=
_current_org
.
name
)
return
root
return
root
...
@@ -223,7 +228,7 @@ class Node(OrgModelMixin):
...
@@ -223,7 +228,7 @@ class Node(OrgModelMixin):
@classmethod
@classmethod
def
default_node
(
cls
):
def
default_node
(
cls
):
defaults
=
{
'value'
:
'Default'
}
defaults
=
{
'value'
:
'Default'
}
return
cls
.
objects
.
get_or_create
(
defaults
=
defaults
,
key
=
'
0
'
)
return
cls
.
objects
.
get_or_create
(
defaults
=
defaults
,
key
=
'
1
'
)
@classmethod
@classmethod
def
get_tree_name_ref
(
cls
):
def
get_tree_name_ref
(
cls
):
...
...
apps/assets/models/user.py
View file @
09fbd3a5
...
@@ -7,6 +7,7 @@ import logging
...
@@ -7,6 +7,7 @@ import logging
from
django.core.cache
import
cache
from
django.core.cache
import
cache
from
django.db
import
models
from
django.db
import
models
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.core.validators
import
MinValueValidator
,
MaxValueValidator
from
common.utils
import
get_signer
from
common.utils
import
get_signer
from
..const
import
SYSTEM_USER_CONN_CACHE_KEY
from
..const
import
SYSTEM_USER_CONN_CACHE_KEY
...
@@ -111,7 +112,8 @@ class SystemUser(AssetUser):
...
@@ -111,7 +112,8 @@ class SystemUser(AssetUser):
nodes
=
models
.
ManyToManyField
(
'assets.Node'
,
blank
=
True
,
verbose_name
=
_
(
"Nodes"
))
nodes
=
models
.
ManyToManyField
(
'assets.Node'
,
blank
=
True
,
verbose_name
=
_
(
"Nodes"
))
assets
=
models
.
ManyToManyField
(
'assets.Asset'
,
blank
=
True
,
verbose_name
=
_
(
"Assets"
))
assets
=
models
.
ManyToManyField
(
'assets.Asset'
,
blank
=
True
,
verbose_name
=
_
(
"Assets"
))
priority
=
models
.
IntegerField
(
default
=
10
,
verbose_name
=
_
(
"Priority"
))
priority
=
models
.
IntegerField
(
default
=
20
,
verbose_name
=
_
(
"Priority"
),
validators
=
[
MinValueValidator
(
1
),
MaxValueValidator
(
100
)])
protocol
=
models
.
CharField
(
max_length
=
16
,
choices
=
PROTOCOL_CHOICES
,
default
=
'ssh'
,
verbose_name
=
_
(
'Protocol'
))
protocol
=
models
.
CharField
(
max_length
=
16
,
choices
=
PROTOCOL_CHOICES
,
default
=
'ssh'
,
verbose_name
=
_
(
'Protocol'
))
auto_push
=
models
.
BooleanField
(
default
=
True
,
verbose_name
=
_
(
'Auto push'
))
auto_push
=
models
.
BooleanField
(
default
=
True
,
verbose_name
=
_
(
'Auto push'
))
sudo
=
models
.
TextField
(
default
=
'/bin/whoami'
,
verbose_name
=
_
(
'Sudo'
))
sudo
=
models
.
TextField
(
default
=
'/bin/whoami'
,
verbose_name
=
_
(
'Sudo'
))
...
@@ -168,7 +170,7 @@ class SystemUser(AssetUser):
...
@@ -168,7 +170,7 @@ class SystemUser(AssetUser):
from
.cmd_filter
import
CommandFilterRule
from
.cmd_filter
import
CommandFilterRule
rules
=
CommandFilterRule
.
objects
.
filter
(
rules
=
CommandFilterRule
.
objects
.
filter
(
filter__in
=
self
.
cmd_filters
.
all
()
filter__in
=
self
.
cmd_filters
.
all
()
)
.
order_by
(
'priority'
)
.
distinct
()
)
.
distinct
()
return
rules
return
rules
@classmethod
@classmethod
...
...
apps/assets/templates/assets/asset_detail.html
View file @
09fbd3a5
...
@@ -69,6 +69,10 @@
...
@@ -69,6 +69,10 @@
<td>
{% trans 'Port' %}:
</td>
<td>
{% trans 'Port' %}:
</td>
<td><b>
{{ asset.port }}
</b></td>
<td><b>
{{ asset.port }}
</b></td>
</tr>
</tr>
<tr>
<td>
{% trans 'Protocol' %}:
</td>
<td><b>
{{ asset.protocol }}
</b></td>
</tr>
<tr>
<tr>
<td>
{% trans 'Admin user' %}:
</td>
<td>
{% trans 'Admin user' %}:
</td>
<td><b>
{{ asset.admin_user }}
</b></td>
<td><b>
{{ asset.admin_user }}
</b></td>
...
...
apps/assets/templates/assets/cmd_filter_list.html
View file @
09fbd3a5
...
@@ -5,7 +5,7 @@
...
@@ -5,7 +5,7 @@
<div
class=
"alert alert-info help-message"
>
<div
class=
"alert alert-info help-message"
>
{% trans 'System user bound some command filter, each command filter has some rules,'%}
{% trans 'System user bound some command filter, each command filter has some rules,'%}
{% trans 'When user login asset with this system user, then run a command,' %}
{% trans 'When user login asset with this system user, then run a command,' %}
{% trans 'The command will be filter by rules, higher priority
(lower number)
rule run first,' %}
{% trans 'The command will be filter by rules, higher priority rule run first,' %}
{% trans 'When a rule matched, if rule action is allow, then allow command execute,' %}
{% trans 'When a rule matched, if rule action is allow, then allow command execute,' %}
{% trans 'else if action is deny, then command with be deny,' %}
{% trans 'else if action is deny, then command with be deny,' %}
{% trans 'else match next rule, if none matched, allowed' %}
{% trans 'else match next rule, if none matched, allowed' %}
...
...
apps/assets/views/asset.py
View file @
09fbd3a5
...
@@ -45,10 +45,7 @@ class AssetListView(AdminUserRequiredMixin, TemplateView):
...
@@ -45,10 +45,7 @@ class AssetListView(AdminUserRequiredMixin, TemplateView):
template_name
=
'assets/asset_list.html'
template_name
=
'assets/asset_list.html'
def
get_context_data
(
self
,
**
kwargs
):
def
get_context_data
(
self
,
**
kwargs
):
if
current_org
.
is_default
():
Node
.
root
()
Node
.
default_node
()
else
:
Node
.
root
()
context
=
{
context
=
{
'app'
:
_
(
'Assets'
),
'app'
:
_
(
'Assets'
),
'action'
:
_
(
'Asset list'
),
'action'
:
_
(
'Asset list'
),
...
...
apps/common/forms.py
View file @
09fbd3a5
...
@@ -18,6 +18,9 @@ class BaseForm(forms.Form):
...
@@ -18,6 +18,9 @@ class BaseForm(forms.Form):
db_value
=
getattr
(
common_settings
,
name
)
db_value
=
getattr
(
common_settings
,
name
)
django_value
=
getattr
(
settings
,
name
)
if
hasattr
(
settings
,
name
)
else
None
django_value
=
getattr
(
settings
,
name
)
if
hasattr
(
settings
,
name
)
else
None
if
db_value
is
None
and
django_value
is
None
:
continue
if
db_value
is
False
or
db_value
:
if
db_value
is
False
or
db_value
:
if
isinstance
(
db_value
,
dict
):
if
isinstance
(
db_value
,
dict
):
db_value
=
json
.
dumps
(
db_value
)
db_value
=
json
.
dumps
(
db_value
)
...
...
apps/common/templatetags/common_tags.py
View file @
09fbd3a5
...
@@ -106,3 +106,8 @@ def to_dict(data):
...
@@ -106,3 +106,8 @@ def to_dict(data):
def
sort
(
data
):
def
sort
(
data
):
print
(
data
)
print
(
data
)
return
sorted
(
data
)
return
sorted
(
data
)
@register.filter
def
subtract
(
value
,
arg
):
return
value
-
arg
apps/locale/zh/LC_MESSAGES/django.mo
View file @
09fbd3a5
No preview for this file type
apps/locale/zh/LC_MESSAGES/django.po
View file @
09fbd3a5
This diff is collapsed.
Click to expand it.
apps/orgs/mixins.py
View file @
09fbd3a5
...
@@ -4,7 +4,6 @@
...
@@ -4,7 +4,6 @@
from
werkzeug.local
import
Local
from
werkzeug.local
import
Local
from
django.db
import
models
from
django.db
import
models
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.db.models
import
Q
from
django.shortcuts
import
redirect
from
django.shortcuts
import
redirect
from
django.forms
import
ModelForm
from
django.forms
import
ModelForm
from
django.http.response
import
HttpResponseForbidden
from
django.http.response
import
HttpResponseForbidden
...
...
apps/perms/api.py
View file @
09fbd3a5
...
@@ -96,7 +96,7 @@ class UserGrantedNodesApi(ListAPIView):
...
@@ -96,7 +96,7 @@ class UserGrantedNodesApi(ListAPIView):
"""
"""
查询用户授权的所有节点的API, 如果是超级用户或者是 app,切换到root org
查询用户授权的所有节点的API, 如果是超级用户或者是 app,切换到root org
"""
"""
permission_classes
=
(
IsOrgAdmin
,)
permission_classes
=
(
IsOrgAdmin
OrAppUser
,)
serializer_class
=
NodeSerializer
serializer_class
=
NodeSerializer
def
change_org_if_need
(
self
):
def
change_org_if_need
(
self
):
...
...
apps/static/js/jumpserver.js
View file @
09fbd3a5
...
@@ -145,7 +145,14 @@ function activeNav() {
...
@@ -145,7 +145,14 @@ function activeNav() {
var
resource
=
url_array
[
2
];
var
resource
=
url_array
[
2
];
if
(
app
===
''
){
if
(
app
===
''
){
$
(
'#index'
).
addClass
(
'active'
);
$
(
'#index'
).
addClass
(
'active'
);
}
else
{
}
else
if
(
app
===
'xpack'
)
{
var
item
=
url_array
[
3
];
$
(
"#"
+
app
).
addClass
(
'active'
);
$
(
'#'
+
app
+
' #'
+
resource
).
addClass
(
'active'
);
$
(
'#'
+
app
+
' #'
+
resource
+
' #'
+
item
+
' a'
).
css
(
'color'
,
'#ffffff'
);
}
else
{
$
(
"#"
+
app
).
addClass
(
'active'
);
$
(
"#"
+
app
).
addClass
(
'active'
);
$
(
'#'
+
app
+
' #'
+
resource
).
addClass
(
'active'
);
$
(
'#'
+
app
+
' #'
+
resource
).
addClass
(
'active'
);
}
}
...
...
apps/templates/_nav.html
View file @
09fbd3a5
...
@@ -95,7 +95,17 @@
...
@@ -95,7 +95,17 @@
</a>
</a>
<ul
class=
"nav nav-second-level"
>
<ul
class=
"nav nav-second-level"
>
{% for plugin in XPACK_PLUGINS %}
{% for plugin in XPACK_PLUGINS %}
<li
id=
"{{ plugin.name }}"
><a
href=
"{{ plugin.endpoint }}"
>
{% trans plugin.verbose_name %}
</a></li>
{% ifequal plugin.name 'cloud'%}
<li
id=
"{{ plugin.name }}"
>
<a
href=
"#"
><span
class=
"nav-label"
>
{% trans plugin.verbose_name %}
</span><span
class=
"fa arrow"
></span></a>
<ul
class=
"nav nav-third-level"
>
<li
id=
"account"
><a
href=
"{% url 'xpack:cloud:account-list' %}"
>
{% trans 'Account list' %}
</a></li>
<li
id=
"sync-instance-task"
><a
href=
"{% url 'xpack:cloud:sync-instance-task-list' %}"
>
{% trans 'Sync instance' %}
</a></li>
</ul>
</li>
{% else %}
<li
id=
"{{ plugin.name }}"
><a
href=
"{{ plugin.endpoint }}"
>
{% trans plugin.verbose_name %}
</a></li>
{% endifequal %}
{% endfor %}
{% endfor %}
</ul>
</ul>
</li>
</li>
...
...
apps/templates/_nav_user.html
View file @
09fbd3a5
...
@@ -9,8 +9,13 @@
...
@@ -9,8 +9,13 @@
<i
class=
"fa fa-user"
style=
"width: 14px"
></i>
<span
class=
"nav-label"
>
{% trans 'Profile' %}
</span><span
class=
"label label-info pull-right"
></span>
<i
class=
"fa fa-user"
style=
"width: 14px"
></i>
<span
class=
"nav-label"
>
{% trans 'Profile' %}
</span><span
class=
"label label-info pull-right"
></span>
</a>
</a>
</li>
</li>
<li
>
<li>
<a
href=
"{% url 'terminal:web-terminal' %}"
target=
"_blank"
><i
class=
"fa fa-window-maximize"
style=
"width: 14px"
></i>
<a
href=
"{% url 'terminal:web-terminal' %}"
target=
"_blank"
><i
class=
"fa fa-window-maximize"
style=
"width: 14px"
></i>
<span
class=
"nav-label"
>
{% trans 'Web terminal' %}
</span>
<span
class=
"nav-label"
>
{% trans 'Web terminal' %}
</span>
</a>
</a>
</li>
<li>
<a
href=
"{% url 'terminal:web-sftp' %}"
target=
"_blank"
><i
class=
"fa fa-file"
style=
"width: 14px"
></i>
<span
class=
"nav-label"
>
{% trans 'File manager' %}
</span>
</a>
</li>
</li>
\ No newline at end of file
requirements/requirements.txt
View file @
09fbd3a5
...
@@ -72,3 +72,5 @@ vine==1.1.4
...
@@ -72,3 +72,5 @@ vine==1.1.4
drf-yasg==1.9.1
drf-yasg==1.9.1
Werkzeug==0.14.1
Werkzeug==0.14.1
drf-nested-routers==0.90.2
drf-nested-routers==0.90.2
aliyun-python-sdk-core-v3==2.9.1
aliyun-python-sdk-ecs==4.10.1
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