merge: into dev from v4

pull/13088/head^2
Bai 2024-04-29 11:07:43 +08:00
commit cb4402c610
134 changed files with 20417 additions and 7441 deletions

View File

@ -10,17 +10,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
- uses: docker/build-push-action@v3
- name: Build CE Image
uses: docker/build-push-action@v5
with:
context: .
push: false
file: Dockerfile
tags: jumpserver/core-ce:test
file: Dockerfile-ce
platforms: linux/amd64
build-args: |
APT_MIRROR=http://deb.debian.org
PIP_MIRROR=https://pypi.org/simple
@ -28,9 +28,22 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max
- uses: LouisBrunner/checks-action@v1.5.0
if: always()
- name: Prepare EE Image
run: |
sed -i 's@^FROM registry.fit2cloud.com@# FROM registry.fit2cloud.com@g' Dockerfile-ee
sed -i 's@^COPY --from=build-xpack@# COPY --from=build-xpack@g' Dockerfile-ee
- name: Build EE Image
uses: docker/build-push-action@v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: Check Build
conclusion: ${{ job.status }}
context: .
push: false
file: Dockerfile-ee
tags: jumpserver/core-ee:test
platforms: linux/amd64
build-args: |
APT_MIRROR=http://deb.debian.org
PIP_MIRROR=https://pypi.org/simple
PIP_JMS_MIRROR=https://pypi.org/simple
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -1,6 +1,28 @@
FROM python:3.11-slim-bullseye as stage-1
ARG TARGETARCH
ARG DEPENDENCIES=" \
ca-certificates \
wget"
RUN set -ex \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& echo "no" | dpkg-reconfigure dash \
&& apt-get clean all \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt
ARG CHECK_VERSION=v1.0.2
RUN set -ex \
&& wget https://github.com/jumpserver-dev/healthcheck/releases/download/${CHECK_VERSION}/check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz \
&& tar -xf check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz \
&& mv check /usr/local/bin/ \
&& chown root:root /usr/local/bin/check \
&& chmod 755 /usr/local/bin/check \
&& rm -f check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz
ARG VERSION
ENV VERSION=$VERSION
@ -14,7 +36,6 @@ ARG TARGETARCH
ARG BUILD_DEPENDENCIES=" \
g++ \
make \
pkg-config"
ARG DEPENDENCIES=" \
@ -37,11 +58,11 @@ ARG TOOLS=" \
curl \
default-libmysqlclient-dev \
default-mysql-client \
git \
git-lfs \
unzip \
xz-utils \
wget"
libldap2-dev \
libsasl2-dev \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
@ -52,7 +73,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${TOOLS} \
&& echo "no" | dpkg-reconfigure dash
WORKDIR /opt/jumpserver
@ -66,23 +86,22 @@ RUN --mount=type=cache,target=/root/.cache \
&& pip install poetry -i ${PIP_MIRROR} \
&& poetry config virtualenvs.create false \
&& . /opt/py3/bin/activate \
&& poetry install
&& poetry install --only=main
FROM python:3.11-slim-bullseye
ARG TARGETARCH
ENV LANG=zh_CN.UTF-8 \
ENV LANG=en_US.UTF-8 \
PATH=/opt/py3/bin:$PATH
ARG DEPENDENCIES=" \
libjpeg-dev \
libldap2-dev \
libpq-dev \
libx11-dev \
freerdp2-dev \
libxmlsec1-openssl"
ARG TOOLS=" \
ca-certificates \
curl \
default-libmysqlclient-dev \
default-mysql-client \
iputils-ping \
@ -90,11 +109,7 @@ ARG TOOLS=" \
netcat-openbsd \
nmap \
openssh-client \
patch \
sshpass \
telnet \
vim \
wget"
sshpass"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
@ -108,7 +123,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
&& mkdir -p /root/.ssh/ \
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \
&& echo "no" | dpkg-reconfigure dash \
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
&& sed -i "s@# export @export @g" ~/.bashrc \
&& sed -i "s@# alias @alias @g" ~/.bashrc
@ -121,6 +135,7 @@ RUN set -ex \
&& rm -f /opt/receptor.tar.gz
COPY --from=stage-2 /opt/py3 /opt/py3
COPY --from=stage-1 /usr/local/bin /usr/local/bin
COPY --from=stage-1 /opt/jumpserver/release/jumpserver /opt/jumpserver
COPY --from=stage-1 /opt/jumpserver/release/jumpserver/apps/libs/ansible/ansible.cfg /etc/ansible/

View File

@ -1,5 +1,134 @@
ARG VERSION
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
FROM registry.fit2cloud.com/jumpserver/core-ce:${VERSION}
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
FROM python:3.11-slim-bullseye as stage-1
ARG TARGETARCH
ARG DEPENDENCIES=" \
ca-certificates \
wget"
RUN set -ex \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& echo "no" | dpkg-reconfigure dash \
&& apt-get clean all \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt
ARG CHECK_VERSION=v1.0.2
RUN set -ex \
&& wget https://github.com/jumpserver-dev/healthcheck/releases/download/${CHECK_VERSION}/check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz \
&& tar -xf check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz \
&& mv check /usr/local/bin/ \
&& chown root:root /usr/local/bin/check \
&& chmod 755 /usr/local/bin/check \
&& rm -f check-${CHECK_VERSION}-linux-${TARGETARCH}.tar.gz
ARG VERSION
ENV VERSION=$VERSION
WORKDIR /opt/jumpserver
ADD . .
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
RUN echo > /opt/jumpserver/config.yml \
&& cd utils && bash -ixeu build.sh
FROM python:3.11-slim-bullseye as stage-2
ARG TARGETARCH
ARG BUILD_DEPENDENCIES=" \
g++ \
pkg-config"
ARG DEPENDENCIES=" \
default-libmysqlclient-dev \
default-mysql-client \
libldap2-dev \
libsasl2-dev \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core-apt \
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
&& rm -f /etc/apt/apt.conf.d/docker-clean \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& echo "no" | dpkg-reconfigure dash
WORKDIR /opt/jumpserver
ARG PIP_MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple
RUN --mount=type=cache,target=/root/.cache \
--mount=type=bind,source=poetry.lock,target=/opt/jumpserver/poetry.lock \
--mount=type=bind,source=pyproject.toml,target=/opt/jumpserver/pyproject.toml \
set -ex \
&& python3 -m venv /opt/py3 \
&& pip install poetry -i ${PIP_MIRROR} \
&& poetry config virtualenvs.create false \
&& . /opt/py3/bin/activate \
&& poetry install
FROM python:3.11-slim-bullseye
ARG TARGETARCH
ENV LANG=zh_CN.UTF-8 \
PATH=/opt/py3/bin:$PATH
ARG DEPENDENCIES=" \
libldap2-dev \
libx11-dev \
libxmlsec1-openssl"
ARG TOOLS=" \
ca-certificates \
curl \
default-libmysqlclient-dev \
default-mysql-client \
iputils-ping \
locales \
nmap \
openssh-client \
patch \
sshpass \
telnet \
vim \
wget"
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core-apt \
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
&& rm -f /etc/apt/apt.conf.d/docker-clean \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& apt-get update \
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
&& apt-get -y install --no-install-recommends ${TOOLS} \
&& mkdir -p /root/.ssh/ \
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \
&& echo "no" | dpkg-reconfigure dash \
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
&& sed -i "s@# export @export @g" ~/.bashrc \
&& sed -i "s@# alias @alias @g" ~/.bashrc
COPY --from=stage-2 /opt/py3 /opt/py3
COPY --from=stage-1 /usr/local/bin /usr/local/bin
COPY --from=stage-1 /opt/jumpserver/release/jumpserver /opt/jumpserver
WORKDIR /opt/jumpserver
ARG VERSION
ENV VERSION=$VERSION
VOLUME /opt/jumpserver/data
EXPOSE 8080
ENTRYPOINT ["./entrypoint.sh"]

277
README.md
View File

@ -1,125 +1,222 @@
<p align="center">
<div align="center"><a name="readme-top"></a>
<a href="https://jumpserver.org"><img src="https://download.jumpserver.org/images/jumpserver-logo.svg" alt="JumpServer" width="300" /></a>
</p>
<h3 align="center">广受欢迎的开源堡垒机</h3>
### Popular Open-Source Bastion Host
<p align="center">
<a href="https://www.gnu.org/licenses/gpl-3.0.html"><img src="https://img.shields.io/github/license/jumpserver/jumpserver" alt="License: GPLv3"></a>
<a href="https://hub.docker.com/u/jumpserver"><img src="https://img.shields.io/docker/pulls/jumpserver/jms_all.svg" alt="Docker pulls"></a>
<a href="https://github.com/jumpserver/jumpserver/releases/latest"><img src="https://img.shields.io/github/v/release/jumpserver/jumpserver" alt="Latest release"></a>
<a href="https://github.com/jumpserver/jumpserver"><img src="https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square" alt="Stars"></a>
</p>
<!-- SHIELD GROUP-->
[![][license-shield]][license-link]
[![][docker-shield]][docker-link]
[![][github-release-shield]][github-release-link]
[![][github-stars-shield]][github-stars-link]
**English** · [简体中文](./README.zh-CN.md) · [Documents][docs-link] · [Report Bug][github-issues-link] · [Request Feature][github-issues-link]
<p align="center">
9 年时间,倾情投入,用心做好一款开源堡垒机。
</p>
For 9 years, pouring heart and soul into creating a high-quality open-source bastion host. <br/>
------------------------------
JumpServer 是广受欢迎的开源堡垒机,是符合 4A 规范的专业运维安全审计系统。
</div>
JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型的资产,包括:
#
![][image-dashboard]
_[To-do]: Need to design the graphics._
- **SSH**: Linux / Unix / 网络设备 等;
- **Windows**: Web 方式连接 / 原生 RDP 连接;
- **数据库**: MySQL / MariaDB / PostgreSQL / Oracle / SQLServer / ClickHouse 等;
- **NoSQL**: Redis / MongoDB 等;
- **GPT**: ChatGPT 等;
- **云服务**: Kubernetes / VMware vSphere 等;
- **Web 站点**: 各类系统的 Web 管理后台;
- **应用**: 通过 Remote App 连接各类应用。
<details>
<summary><kbd>Table of contents</kbd></summary>
## 产品特色
#### TOC
- **开源**: 零门槛,线上快速获取和安装;
- **无插件**: 仅需浏览器,极致的 Web Terminal 使用体验;
- **分布式**: 支持分布式部署和横向扩展,轻松支持大规模并发访问;
- **多云支持**: 一套系统,同时管理不同云上面的资产;
- **多租户**: 一套系统,多个子公司或部门同时使用;
- **云端存储**: 审计录像云端存储,永不丢失;
- [Getting Started](#getting-started)
- [Introduction](#introduction)
- [Why JumpServer](#why-jumpserver)
- [Installation](#installation)
- [Product Architecture & Components](#product-architecture--components)
- [Features](#features)
- [Contributing](#contributing)
- [Security](#security)
- [License](#license)
####
## UI 展示
<br/>
![UI展示](https://docs.jumpserver.org/zh/v3/img/dashboard.png)
</details>
## 在线体验
## Getting Started
- 环境地址:<https://demo.jumpserver.org/>
Step right into our online demonstration environment, where you can effortlessly experience our product without the need for time-consuming software installations. With just a few clicks, you'll quickly grasp the functionality and features of our product. In the demonstration environment, you can explore the various features of our product to your heart's content and experience our innovative design and exceptional performance.
| :warning: 注意 |
|:-----------------------------|
| 该环境仅作体验目的使用,我们会定时清理、重置数据! |
| 请勿修改体验环境用户的密码! |
| 请勿在环境中添加业务生产环境地址、用户名密码等敏感信息! |
Whether you're new to the experience or a seasoned expert, we invite you to join our Discord community right away! Here, our developers and enthusiastic users come together to offer support and assistance. No matter what challenges you encounter during your usage, we are committed to answering your questions and providing guidance.
## 快速开始
| [![][demo-shield-badge]][demo-link] | No installation or registration necessary! Visit our website to experience it firsthand. |
| :---------------------------------------- | :----------------------------------------------------------------------------------------------------------------- |
| [![][discord-shield-badge]][discord-link] | Join our Discord community! This is where you can connect with developers and other enthusiastic users of JumpServer. |
- [快速入门](https://docs.jumpserver.org/zh/v3/quick_start/)
- [产品文档](https://docs.jumpserver.org)
- [在线学习](https://edu.fit2cloud.com/page/2635362)
- [知识库](https://kb.fit2cloud.com/categories/jumpserver)
> \[!IMPORTANT]
>
> **Star Us**, You will receive all release notifications from GitHub without any delay \~ ⭐️
## 案例研究
![][image-star]
- [腾讯音乐娱乐集团基于JumpServer的安全运维审计解决方案](https://blog.fit2cloud.com/?p=a04cdf0d-6704-4d18-9b40-9180baecd0e2)
- [腾讯海外游戏基于JumpServer构建游戏安全运营能力](https://blog.fit2cloud.com/?p=3704)
- [万华化学通过JumpServer管理全球化分布式IT资产并且实现与云管平台的联动](https://blog.fit2cloud.com/?p=3504)
- [雪花啤酒JumpServer堡垒机使用体会](https://blog.fit2cloud.com/?p=3412)
- [顺丰科技JumpServer 堡垒机护航顺丰科技超大规模资产安全运维](https://blog.fit2cloud.com/?p=1147)
- [沐瞳游戏通过JumpServer管控多项目分布式资产](https://blog.fit2cloud.com/?p=3213)
- [携程JumpServer 堡垒机部署与运营实战](https://blog.fit2cloud.com/?p=851)
- [大智慧JumpServer 堡垒机让“大智慧”的混合 IT 运维更智慧](https://blog.fit2cloud.com/?p=882)
- [小红书JumpServer 堡垒机大规模资产跨版本迁移之路](https://blog.fit2cloud.com/?p=516)
- [中手游JumpServer堡垒机助力中手游提升多云环境下安全运维能力](https://blog.fit2cloud.com/?p=732)
- [中通快递JumpServer主机安全运维实践](https://blog.fit2cloud.com/?p=708)
- [东方明珠JumpServer高效管控异构化、分布式云端资产](https://blog.fit2cloud.com/?p=687)
- [江苏农信JumpServer堡垒机助力行业云安全运维](https://blog.fit2cloud.com/?p=666)
<details>
<summary><kbd>Star History</kbd></summary>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=jumpserver%2Fjumpserver&theme=dark&type=Date">
<img width="100%" src="https://api.star-history.com/svg?repos=jumpserver%2Fjumpserver&type=Date">
</picture>
</details>
## 社区交流
> \[!TIP]
>
> This is a demonstration video that can quickly help you understand the page design and product features of JumpServer.
如果您在使用过程中有任何疑问或对建议,欢迎提交 [GitHub Issue](https://github.com/jumpserver/jumpserver/issues/new/choose)。
<video controls src="https://github.com/jumpserver/jumpserver/assets/32935519/6f984266-24a1-4d1f-9745-4a8e0122f49c" muted="false"></video>
_[To-do]: Need to design the video._
您也可以到我们的 [社区论坛](https://bbs.fit2cloud.com/c/js/5) 当中进行交流沟通。
## Introduction
### 参与贡献
JumpServer is a widely acclaimed open-source bastion host, serving as a professional operational security auditing system compliant with the 4A standards. It helps businesses securely manage and access all types of assets in a more secure manner, enabling pre-authorization, real-time monitoring, and post-audit capabilities.
欢迎提交 PR 参与贡献。 参考 [CONTRIBUTING.md](https://github.com/jumpserver/jumpserver/blob/dev/CONTRIBUTING.md)
JumpServer aims to become the industry's preferred platform, assisting businesses in securely and efficiently managing and accessing all types of assets. By offering a professional operational security auditing system compliant with 4A standards, JumpServer is committed to delivering advanced asset management and access solutions, meeting enterprises' needs for security, reliability, and efficiency.
## 组件项目
JumpServer's vision is to become a leader in the enterprise-level asset management and access control field, providing comprehensive solutions for users to securely and efficiently manage and utilize their assets. Through continuous innovation and enhancement of product features, JumpServer is committed to driving the development of the entire industry and becoming a key supporter and promoter of enterprise digital transformation.
| 项目 | 状态 | 描述 |
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|
| [Lina](https://github.com/jumpserver/lina) | <a href="https://github.com/jumpserver/lina/releases"><img alt="Lina release" src="https://img.shields.io/github/release/jumpserver/lina.svg" /></a> | JumpServer Web UI 项目 |
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal 项目 |
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer 字符协议 Connector 项目 |
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) |
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer RDP 代理 Connector 项目 |
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Windows) |
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Linux) |
| [Magnus](https://github.com/jumpserver/magnus-release) | <a href="https://github.com/jumpserver/magnus-release/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/magnus-release.svg" /> | JumpServer 数据库代理 Connector 项目 |
| [Chen](https://github.com/jumpserver/chen-release) | <a href="https://github.com/jumpserver/chen-release/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen-release.svg" /> | JumpServer Web DB 项目,替代原来的 OmniDB |
| [Kael](https://github.com/jumpserver/kael) | <a href="https://github.com/jumpserver/kael/releases"><img alt="Kael release" src="https://img.shields.io/github/release/jumpserver/kael.svg" /> | JumpServer 连接 GPT 资产的组件项目 |
| [Wisp](https://github.com/jumpserver/wisp) | <a href="https://github.com/jumpserver/wisp/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/wisp.svg" /> | JumpServer 各系统终端组件和 Core API 通信的组件项目 |
| [Clients](https://github.com/jumpserver/clients) | <a href="https://github.com/jumpserver/clients/releases"><img alt="Clients release" src="https://img.shields.io/github/release/jumpserver/clients.svg" /> | JumpServer 客户端 项目 |
| [Installer](https://github.com/jumpserver/installer) | <a href="https://github.com/jumpserver/installer/releases"><img alt="Installer release" src="https://img.shields.io/github/release/jumpserver/installer.svg" /> | JumpServer 安装包 项目 |
![][image-supported-asset-type]
_[To-do]: Need to design the graphics._
## 安全说明
## Why JumpServer
1. **Open Source**: JumpServer is an open-source software, meaning users can freely access, use, and modify its source code to meet individual needs, while also benefiting from community support and collaboration.
2. **Plugin-Free**: JumpServer provides comprehensive functionality without the need for additional plugins or extensions. This simplifies deployment and management processes, reducing potential compatibility and security risks.
3. **Distributed**: JumpServer supports a distributed architecture, allowing easy scaling across multiple nodes for high availability and fault tolerance. This makes it suitable for large-scale deployments and complex network environments.
4. **Multi-Cloud**: JumpServer offers support for various cloud platforms, including AWS, Azure, Google Cloud, etc., enabling users to manage and access assets seamlessly across different cloud environments.
5. **Cloud Storage**: JumpServer supports storing critical data such as audit logs and configuration files in the cloud, ensuring data security and reliability, as well as facilitating cross-region and cross-device access.
6. **Organizational**: JumpServer provides a flexible organizational structure, supporting multi-level organizational hierarchies and permission management. This allows administrators to finely control user access permissions, ensuring asset security and compliance.
JumpServer是一款安全产品请参考 [基本安全建议](https://docs.jumpserver.org/zh/master/install/install_security/)
进行安装部署。如果您发现安全相关问题,请直接联系我们:
## Installation
- 邮箱support@fit2cloud.com
- 电话400-052-0755
JumpServer supports multiple installation methods to cater to diverse user scenarios and preferences:
## License & Copyright
See Docs: https://docs.jumpserver.org/zh/v3/
#### 1. Online
Ideal for users with internet access, this method involves downloading installation scripts or packages directly from the internet. It ensures easy access to the latest updates and dependencies during installation.
Quick installation of JumpServer in just two steps:
1. Prepare a 64-bit Linux host with at least 4 cores and 8 GB of RAM, which has internet access.
2. Execute the following command as the root user for one-click installation of JumpServer.
```sh
curl -sSL https://github.com/jumpserver/jumpserver/releases/latest/download/quick_start.sh | bash
```
#### 2. Offline
Suited for environments without internet connectivity, this method allows users to download all necessary installation files and dependencies beforehand. It ensures seamless installation even in isolated or restricted network environments.
Download offline package: https://community.fit2cloud.com/#/products/jumpserver/downloads
#### 3. Kubernetes (K8s)
JumpServer supports installation on Kubernetes clusters. You can deploy JumpServer as containerized applications on Kubernetes, leveraging the scalability and management features of Kubernetes for running JumpServer.
#### 4. All-in-One
This method provides a simplified installation process where all components of JumpServer are installed on a single server or machine. It's suitable for small-scale deployments or testing purposes where separate component deployment is not required.
#### 5. Enterprise Edition Trial
JumpServer offers a trial version of its enterprise edition, allowing users to test out the enterprise features and functionalities before committing to a full deployment. This trial version typically comes with limited duration or features to provide a glimpse of the capabilities of the enterprise edition.
Each installation method caters to different use cases and deployment scenarios, offering flexibility and options for users based on their requirements and infrastructure setup.
Applying for the Enterprise Edition: https://jumpserver.org/enterprise.html
## Product Architecture & Components
#### 1. Architecture Diagram
Below is the schematic diagram of the JumpServer system architecture, providing a more comprehensive understanding of the product features of JumpServer.
![][image-system-architecture]
_[To-do]: Need to design the graphics._
#### 2. Supporting Components
| Project | Status | Description |
|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
| [Lina](https://github.com/jumpserver/lina) | <a href="https://github.com/jumpserver/lina/releases"><img alt="Lina release" src="https://img.shields.io/github/release/jumpserver/lina.svg" /></a> | JumpServer Web UI |
| [Luna](https://github.com/jumpserver/luna) | <a href="https://github.com/jumpserver/luna/releases"><img alt="Luna release" src="https://img.shields.io/github/release/jumpserver/luna.svg" /></a> | JumpServer Web Terminal |
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer Character Protocol Connector |
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer Graphical Protocol Connector, dependent on [Apache Guacamole](https://guacamole.apache.org/) |
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-private-red" /> | JumpServer RDP Proxy Connector |
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-private-red" /> | JumpServer Remote Application Connector (Windows) |
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-private-red" /> | JumpServer Remote Application Connector (Linux) |
| [Magnus](https://github.com/jumpserver/magnus-release) | <a href="https://github.com/jumpserver/magnus-release/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/magnus-release.svg" /> | JumpServer Database Proxy Connector |
| [Chen](https://github.com/jumpserver/chen-release) | <a href="https://github.com/jumpserver/chen-release/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen-release.svg" /> | JumpServer Web DB |
| [Kael](https://github.com/jumpserver/kael) | <a href="https://github.com/jumpserver/kael/releases"><img alt="Kael release" src="https://img.shields.io/github/release/jumpserver/kael.svg" /> | JumpServer GPT Assets Connector |
| [Wisp](https://github.com/jumpserver/wisp) | <a href="https://github.com/jumpserver/wisp/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/wisp.svg" /> | JumpServer Inter-Project Communication Component with Core API |
| [Clients](https://github.com/jumpserver/clients) | <a href="https://github.com/jumpserver/clients/releases"><img alt="Clients release" src="https://img.shields.io/github/release/jumpserver/clients.svg" /> | JumpServer Client |
| [Installer](https://github.com/jumpserver/installer) | <a href="https://github.com/jumpserver/installer/releases"><img alt="Installer release" src="https://img.shields.io/github/release/jumpserver/installer.svg" /> | JumpServer Installation Tool |
JumpServer consists of multiple key components, which collectively form the functional framework of JumpServer, providing users with comprehensive capabilities for operations management and security control.
## Features
Below are the features supported by JumpServer, covering various aspects of operations management and security control, providing you with a comprehensive solution.
1. User Authentication Supporting Integration with Multiple Single Sign-On Systems (SSO)
2. User Management Based on Role-based Access Control (RBAC)
3. Asset Management of Everything is an Asset
4. Asset Account Management
5. Asset Authorization Management
6. Asset Permission Management Based Access Control Logic (ACL)
7. Remote Application Management for Everything
8. Support for Multiple Asset Connection Methods
9. Comprehensive and Detailed User Behavior Audit System
10. Organization Management with Resource Isolation [![][version-ee-shield-badge]][official-website-en-link]
11. Ticket Management [![][version-ee-shield-badge]][official-website-en-link]
For details, [See Docs][docs-link].
## Contributing
Welcome to submit PR to contribute. Please refer to [CONTRIBUTING.md][contributing-link] for guidelines.
## Security
JumpServer is a secure product. Please refer to the Basic Security Recommendations for installation and deployment. If you encounter any security-related issues, please contact us directly:
- Email: support@fit2cloud.com
- Phone: 400-052-0755
## License
Copyright (c) 2014-2024 飞致云 FIT2CLOUD, All rights reserved.
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
https://www.gnu.org/licenses/gpl-3.0.html
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "
AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an " AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
<!-- JumpServer official link -->
[official-website-en-link]: https://jumpserver.org/
[docs-link]: https://docs.jumpserver.org/
[community-link]: https://community.fit2cloud.com/#/products/jumpserver/downloads
[demo-link]: https://demo.jumpserver.org/
[discord-link]: https://discord.gg/DVz6Hckx
[contributing-link]: https://github.com/jumpserver/jumpserver/blob/dev/CONTRIBUTING.md
<!-- JumpServer Other link-->
[license-link]: https://www.gnu.org/licenses/gpl-3.0.html
[docker-link]: https://hub.docker.com/u/jumpserver
[github-release-shield]: https://img.shields.io/github/v/release/jumpserver/jumpserver
[github-release-link]: https://github.com/jumpserver/jumpserver/releases/latest
[github-stars-shield]: https://img.shields.io/github/stars/jumpserver/jumpserver?color=%231890FF&style=flat-square
[github-stars-link]: https://github.com/jumpserver/jumpserver
[github-issues-link]: https://github.com/jumpserver/jumpserver/issues
<!-- Shield link-->
[docker-shield]: https://img.shields.io/docker/pulls/jumpserver/jms_all.svg
[license-shield]: https://img.shields.io/github/license/jumpserver/jumpserver
[demo-shield-badge]: https://img.shields.io/badge/ONLINE-online?style=plastic&logo=jameson&logoColor=white&label=TRY%20JUMPSERVER&labelColor=black&color=%23148f76
[discord-shield-badge]: https://img.shields.io/badge/JOIN_US_NOW-ONLINE?style=plastic&logo=discord&logoColor=white&label=DISCORD&labelColor=black&color=%23404eed
[version-ee-shield-badge]: https://img.shields.io/badge/Enterprise-black?style=flat-square&logo=vagrant
<!-- Image link -->
[image-jumpserver]: https://download.jumpserver.org/images/jumpserver-logo.svg
[image-dashboard]: https://github.com/jumpserver/jumpserver/assets/32935519/014c2230-82d3-4b53-b907-8149ce44bbd0
[image-star]: https://github.com/jumpserver/jumpserver/assets/32935519/76158e65-783d-4f11-81cd-45556a388e63
[image-supported-asset-type]: https://github.com/jumpserver/jumpserver/assets/32935519/8e769007-5449-4e86-b34b-d04e8e484257
[image-system-architecture]: https://github.com/jumpserver/jumpserver/assets/32935519/8a720b4e-19ed-4e3c-a8aa-325d7581005a

View File

@ -4,6 +4,7 @@ from django.apps import AppConfig
class AccountsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'accounts'
verbose_name = 'App Accounts'
def ready(self):
from . import signal_handlers # noqa

View File

@ -28,9 +28,9 @@ class Migration(migrations.Migration):
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic perform')),
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')),
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')),
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic run')),
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Interval')),
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Crontab')),
('types', models.JSONField(default=list)),
('recipients', models.ManyToManyField(blank=True, related_name='recipient_escape_route_plans',
to=settings.AUTH_USER_MODEL, verbose_name='Recipient')),

View File

@ -24,9 +24,9 @@ class Migration(migrations.Migration):
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('present', models.BooleanField(default=True, verbose_name='Present')),
('date_last_login', models.DateTimeField(null=True, verbose_name='Date last login')),
('date_last_login', models.DateTimeField(null=True, verbose_name='Date login')),
('username', models.CharField(blank=True, db_index=True, max_length=32, verbose_name='Username')),
('address_last_login', models.CharField(default='', max_length=39, verbose_name='Address last login')),
('address_last_login', models.CharField(default='', max_length=39, verbose_name='Address login')),
('asset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='assets.asset', verbose_name='Asset')),
],
options={

View File

@ -18,11 +18,11 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='accountbackupautomation',
name='is_password_divided_by_email',
field=models.BooleanField(default=True, verbose_name='Is Password Divided'),
field=models.BooleanField(default=True, verbose_name='Password divided'),
),
migrations.AlterField(
model_name='accountbackupautomation',
name='is_password_divided_by_obj_storage',
field=models.BooleanField(default=True, verbose_name='Is Password Divided'),
field=models.BooleanField(default=True, verbose_name='Password divided'),
),
]

View File

@ -24,9 +24,9 @@ logger = get_logger(__file__)
class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
types = models.JSONField(default=list)
backup_type = models.CharField(max_length=128, choices=AccountBackupType.choices,
default=AccountBackupType.email.value, verbose_name=_('Backup Type'))
is_password_divided_by_email = models.BooleanField(default=True, verbose_name=_('Is Password Divided'))
is_password_divided_by_obj_storage = models.BooleanField(default=True, verbose_name=_('Is Password Divided'))
default=AccountBackupType.email.value, verbose_name=_('Backup type'))
is_password_divided_by_email = models.BooleanField(default=True, verbose_name=_('Password divided'))
is_password_divided_by_obj_storage = models.BooleanField(default=True, verbose_name=_('Password divided'))
recipients_part_one = models.ManyToManyField(
'users.User', related_name='recipient_part_one_plans', blank=True,
verbose_name=_("Recipient part one")
@ -37,14 +37,15 @@ class AccountBackupAutomation(PeriodTaskModelMixin, JMSOrgBaseModel):
)
obj_recipients_part_one = models.ManyToManyField(
'terminal.ReplayStorage', related_name='obj_recipient_part_one_plans', blank=True,
verbose_name=_("Object Storage Recipient part one")
verbose_name=_("Object storage recipient part one")
)
obj_recipients_part_two = models.ManyToManyField(
'terminal.ReplayStorage', related_name='obj_recipient_part_two_plans', blank=True,
verbose_name=_("Object Storage Recipient part two")
verbose_name=_("Object storage recipient part two")
)
zip_encrypt_password = fields.EncryptCharField(
max_length=4096, blank=True, null=True, verbose_name=_('Zip encrypt password')
)
zip_encrypt_password = fields.EncryptCharField(max_length=4096, blank=True, null=True,
verbose_name=_('Zip Encrypt Password'))
def __str__(self):
return f'{self.name}({self.org_id})'

View File

@ -12,10 +12,10 @@ __all__ = ['GatherAccountsAutomation', 'GatheredAccount']
class GatheredAccount(JMSOrgBaseModel):
present = models.BooleanField(default=True, verbose_name=_("Present"))
date_last_login = models.DateTimeField(null=True, verbose_name=_("Date last login"))
date_last_login = models.DateTimeField(null=True, verbose_name=_("Date login"))
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_("Asset"))
username = models.CharField(max_length=32, blank=True, db_index=True, verbose_name=_('Username'))
address_last_login = models.CharField(max_length=39, default='', verbose_name=_("Address last login"))
address_last_login = models.CharField(max_length=39, default='', verbose_name=_("Address login"))
@property
def address(self):

View File

@ -31,7 +31,9 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
default=False, label=_("Push now"), write_only=True
)
params = serializers.JSONField(
decoder=None, encoder=None, required=False, style={'base_template': 'textarea.html'}
decoder=None, encoder=None, required=False,
style={'base_template': 'textarea.html'},
label=_('Params'),
)
on_invalid = LabeledChoiceField(
choices=AccountInvalidPolicy.choices, default=AccountInvalidPolicy.ERROR,
@ -225,7 +227,7 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
fields = BaseAccountSerializer.Meta.fields + [
'su_from', 'asset', 'version',
'source', 'source_id', 'connectivity',
] + AccountCreateUpdateSerializerMixin.Meta.fields
] + list(set(AccountCreateUpdateSerializerMixin.Meta.fields) - {'params'})
read_only_fields = BaseAccountSerializer.Meta.read_only_fields + [
'connectivity'
]

View File

@ -35,8 +35,7 @@ class AccountBackupSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSer
]
extra_kwargs = {
'name': {'required': True},
'periodic_display': {'label': _('Periodic perform')},
'executed_amount': {'label': _('Executed amount')},
'executed_amount': {'label': _('Executions')},
'recipients': {
'label': _('Recipient'),
'help_text': _('Currently only mail sending is supported')

View File

@ -22,7 +22,7 @@ class AuthValidateMixin(serializers.Serializer):
)
passphrase = serializers.CharField(
allow_blank=True, allow_null=True, required=False, max_length=512,
write_only=True, label=_('Key password')
write_only=True, label=_('Passphrase')
)
@staticmethod

View File

@ -25,7 +25,8 @@ class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSe
class Meta:
read_only_fields = [
'date_created', 'date_updated', 'created_by', 'periodic_display', 'executed_amount'
'date_created', 'date_updated', 'created_by',
'periodic_display', 'executed_amount'
]
fields = read_only_fields + [
'id', 'name', 'is_periodic', 'interval', 'crontab', 'comment',
@ -34,8 +35,7 @@ class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSe
extra_kwargs = {
'name': {'required': True},
'type': {'read_only': True},
'periodic_display': {'label': _('Periodic perform')},
'executed_amount': {'label': _('Executed amount')},
'executed_amount': {'label': _('Executions')},
}
def validate_name(self, name):

View File

@ -7,7 +7,6 @@ from .change_secret import (
class PushAccountAutomationSerializer(ChangeSecretAutomationSerializer):
class Meta(ChangeSecretAutomationSerializer.Meta):
model = PushAccountAutomation
fields = [

View File

@ -4,4 +4,4 @@ from django.utils.translation import gettext_lazy as _
class AclsConfig(AppConfig):
name = 'acls'
verbose_name = _('Acls')
verbose_name = _('App Acls')

View File

@ -6,5 +6,5 @@ class ActionChoices(models.TextChoices):
reject = 'reject', _('Reject')
accept = 'accept', _('Accept')
review = 'review', _('Review')
warning = 'warning', _('Warning')
notice = 'notice', _('Notifications')
warning = 'warning', _('Warn')
notice = 'notice', _('Notify')

View File

@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
class ApplicationsConfig(AppConfig):
name = 'applications'
verbose_name = _('Applications')
verbose_name = _('App Applications')
def ready(self):
super().ready()

View File

@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
class AssetsConfig(AppConfig):
name = 'assets'
verbose_name = _('App assets')
verbose_name = _('App Assets')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

View File

@ -113,8 +113,7 @@ class BaseType(TextChoices):
@classmethod
def get_choices(cls):
if not settings.XPACK_ENABLED:
return [
(tp.value, tp.label)
for tp in cls.get_community_types()
]
return cls.choices
choices = [(tp.value, tp.label) for tp in cls.get_community_types()]
else:
choices = cls.choices
return choices

View File

@ -166,23 +166,26 @@ class AllTypes(ChoicesMixin):
@classmethod
def category_types(cls):
return (
types = [
(Category.HOST, HostTypes),
(Category.DEVICE, DeviceTypes),
(Category.DATABASE, DatabaseTypes),
(Category.CLOUD, CloudTypes),
(Category.WEB, WebTypes),
(Category.GPT, GPTTypes),
(Category.CUSTOM, CustomTypes),
)
]
if settings.XPACK_ENABLED:
types.extend([
(Category.CLOUD, CloudTypes),
(Category.CUSTOM, CustomTypes),
])
return types
@classmethod
def get_types(cls, exclude_custom=False):
choices = []
for name, tp in dict(cls.category_types()).items():
if name == Category.CUSTOM and exclude_custom:
continue
# if name == Category.CUSTOM and exclude_custom:
# continue
choices.extend(tp.get_types())
return choices

View File

@ -123,7 +123,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='asset',
name='nodes',
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Node'),
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
),
migrations.AddField(
model_name='systemuser',

View File

@ -13,11 +13,11 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='gathereduser',
name='date_last_login',
field=models.DateTimeField(null=True, verbose_name='Date last login'),
field=models.DateTimeField(null=True, verbose_name='Date login'),
),
migrations.AddField(
model_name='gathereduser',
name='ip_last_login',
field=models.CharField(default='', max_length=39, verbose_name='IP last login'),
field=models.CharField(default='', max_length=39, verbose_name='IP login'),
),
]

View File

@ -20,9 +20,9 @@ class Migration(migrations.Migration):
fields=[
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic perform')),
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')),
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')),
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic run')),
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Interval')),
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Crontab')),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),

View File

@ -24,9 +24,9 @@ class Migration(migrations.Migration):
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic perform')),
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')),
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')),
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic run')),
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Interval')),
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Crontab')),
('accounts', models.JSONField(default=list, verbose_name='Accounts')),
('type', models.CharField(max_length=16, verbose_name='Type')),
('is_active', models.BooleanField(default=True, verbose_name='Is active')),

View File

@ -161,8 +161,7 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
platform = models.ForeignKey(Platform, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets')
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets',
verbose_name=_("Domain"), on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets',
verbose_name=_("Node"))
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
gathered_info = models.JSONField(verbose_name=_('Gathered info'), default=dict, blank=True) # 资产的一些信息,如 硬件信息
custom_info = models.JSONField(verbose_name=_('Custom info'), default=dict)

View File

@ -5,7 +5,7 @@ import random
from django.db import models
from django.utils.translation import gettext_lazy as _
from common.utils import get_logger
from common.utils import get_logger, lazyproperty
from labels.mixins import LabeledMixin
from orgs.mixins.models import JMSOrgBaseModel
from .gateway import Gateway
@ -29,6 +29,10 @@ class Domain(LabeledMixin, JMSOrgBaseModel):
def select_gateway(self):
return self.random_gateway()
@lazyproperty
def assets_amount(self):
return self.assets.count()
def random_gateway(self):
gateways = [gw for gw in self.active_gateways if gw.is_connective]

View File

@ -125,7 +125,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, ResourceLabelsMixin, Writa
category = LabeledChoiceField(choices=Category.choices, read_only=True, label=_('Category'))
type = LabeledChoiceField(choices=AllTypes.choices(), read_only=True, label=_('Type'))
protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols'), default=())
accounts = AssetAccountSerializer(many=True, required=False, allow_null=True, write_only=True, label=_('Account'))
accounts = AssetAccountSerializer(many=True, required=False, allow_null=True, write_only=True, label=_('Accounts'))
nodes_display = serializers.ListField(read_only=False, required=False, label=_("Node path"))
_accounts = None
@ -140,7 +140,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, ResourceLabelsMixin, Writa
]
read_only_fields = [
'category', 'type', 'connectivity', 'auto_config',
'date_verified', 'created_by', 'date_created',
'date_verified', 'created_by', 'date_created', 'date_updated',
]
fields = fields_small + fields_fk + fields_m2m + read_only_fields
fields_unexport = ['auto_config']
@ -149,7 +149,7 @@ class AssetSerializer(BulkOrgResourceModelSerializer, ResourceLabelsMixin, Writa
'name': {'label': _("Name")},
'address': {'label': _('Address')},
'nodes_display': {'label': _('Node path')},
'nodes': {'allow_empty': True},
'nodes': {'allow_empty': True, 'label': _("Nodes")},
}
def __init__(self, *args, **kwargs):

View File

@ -1,4 +1,4 @@
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from assets.models import Asset, Node, BaseAutomation, AutomationExecution
@ -31,7 +31,6 @@ class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSe
extra_kwargs = {
'name': {'required': True},
'type': {'read_only': True},
'periodic_display': {'label': _('Periodic perform')},
}

View File

@ -18,12 +18,13 @@ class DomainSerializer(ResourceLabelsMixin, BulkOrgResourceModelSerializer):
gateways = ObjectRelatedField(
many=True, required=False, label=_('Gateway'), queryset=Gateway.objects
)
assets_amount = serializers.IntegerField(label=_('Assets amount'), read_only=True)
class Meta:
model = Domain
fields_mini = ['id', 'name']
fields_small = fields_mini + ['comment']
fields_m2m = ['assets', 'gateways']
fields_m2m = ['assets', 'gateways', 'assets_amount']
read_only_fields = ['date_created']
fields = fields_small + fields_m2m + read_only_fields
extra_kwargs = {
@ -54,13 +55,12 @@ class DomainSerializer(ResourceLabelsMixin, BulkOrgResourceModelSerializer):
@classmethod
def setup_eager_loading(cls, queryset):
queryset = queryset \
.annotate(assets_amount=Count('assets')) \
.prefetch_related('labels', 'labels__label')
return queryset
class DomainListSerializer(DomainSerializer):
assets_amount = serializers.IntegerField(label=_('Assets amount'), read_only=True)
class Meta(DomainSerializer.Meta):
fields = list(set(DomainSerializer.Meta.fields + ['assets_amount']) - {'assets'})

View File

@ -30,18 +30,43 @@ class PlatformAutomationSerializer(serializers.ModelSerializer):
]
extra_kwargs = {
# 启用资产探测
"ping_enabled": {"label": _("Ping enabled")},
"ping_enabled": {"label": _("Ping enabled"), "help_text": _("Enable asset detection")},
"ping_method": {"label": _("Ping method")},
"gather_facts_enabled": {"label": _("Gather facts enabled")},
"gather_facts_method": {"label": _("Gather facts method")},
"verify_account_enabled": {"label": _("Verify account enabled")},
"verify_account_method": {"label": _("Verify account method")},
"change_secret_enabled": {"label": _("Change secret enabled")},
"change_secret_method": {"label": _("Change secret method")},
"push_account_enabled": {"label": _("Push account enabled")},
"push_account_method": {"label": _("Push account method")},
"gather_accounts_enabled": {"label": _("Gather accounts enabled")},
"gather_accounts_method": {"label": _("Gather accounts method")},
"gather_facts_enabled": {
"label": _("Gather facts enabled"),
"help_text": _("Enable asset information collection")
},
"gather_facts_method": {
"label": _("Gather facts method"),
},
"verify_account_enabled": {
"label": _("Verify account enabled"),
"help_text": _("Enable account verification")
},
"verify_account_method": {
"label": _("Verify account method"),
},
"change_secret_enabled": {
"label": _("Change secret enabled"),
"help_text": _("Enable account secret auto change")
},
"change_secret_method": {
"label": _("Change secret method"),
},
"push_account_enabled": {
"label": _("Push account enabled"),
"help_text": _("Enable account auto push")
},
"push_account_method": {
"label": _("Push account method"),
},
"gather_accounts_enabled": {
"label": _("Gather accounts enabled"),
"help_text": _("Enable account collection")
},
"gather_accounts_method": {
"label": _("Gather accounts method"),
},
}
@ -158,13 +183,21 @@ class PlatformSerializer(ResourceLabelsMixin, WritableNestedModelSerializer):
'created_by', 'updated_by'
]
fields = fields_small + [
"protocols", "domain_enabled", "su_enabled",
"su_method", "automation", "comment", "custom_fields",
"labels"
"protocols", "domain_enabled", "su_enabled", "su_method",
"automation", "comment", "custom_fields", "labels"
] + read_only_fields
extra_kwargs = {
"su_enabled": {"label": _('Su enabled')},
"domain_enabled": {"label": _('Domain enabled')},
"su_enabled": {
"label": _('Su enabled'),
"help_text": _(
"Login with account when accessing assets, then automatically switch to another, "
"similar to logging in with a regular account and then switching to root"
)
},
"domain_enabled": {
"label": _('Domain enabled'),
"help_text": _("Assets can be connected using a domain gateway")
},
"domain_default": {"label": _('Default Domain')},
}

View File

@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
class AuditsConfig(AppConfig):
name = 'audits'
verbose_name = _('Audits')
verbose_name = _('App Audits')
def ready(self):
from . import signal_handlers # noqa

View File

@ -42,7 +42,7 @@ class Migration(migrations.Migration):
default=True, max_length=2, verbose_name='Status')),
('datetime',
models.DateTimeField(default=django.utils.timezone.now,
verbose_name='Date login')),
verbose_name='Login Date')),
],
options={
'ordering': ['-datetime', 'username'],

View File

@ -24,6 +24,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='userloginlog',
name='datetime',
field=models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='Date login'),
field=models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='Login Date'),
),
]

View File

@ -207,7 +207,7 @@ class UserLoginLog(models.Model):
choices=LoginStatusChoices.choices,
verbose_name=_("Status"),
)
datetime = models.DateTimeField(default=timezone.now, verbose_name=_("Date login"), db_index=True)
datetime = models.DateTimeField(default=timezone.now, verbose_name=_("Login Date"), db_index=True)
backend = models.CharField(
max_length=32, default="", verbose_name=_("Authentication backend")
)

View File

@ -131,7 +131,7 @@ class ActivityUnionLogSerializer(serializers.Serializer):
def get_content(obj):
if not obj['r_detail']:
action = obj['r_action'].replace('_', ' ').capitalize()
ctn = _('User %s %s this resource') % (obj['r_user'], _(action))
ctn = _('%s %s this resource') % (obj['r_user'], _(action).lower())
else:
ctn = i18n_trans(obj['r_detail'])
return ctn

View File

@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _
class AuthenticationConfig(AppConfig):
name = 'authentication'
verbose_name = _('Authentication')
verbose_name = _('App Authentication')
def ready(self):
from . import signal_handlers # noqa

View File

@ -50,7 +50,7 @@ class PrepareRequestMixin:
if idp_metadata_xml.strip():
xml_idp_settings = IdPMetadataParse.parse(idp_metadata_xml)
except Exception as err:
logger.warning('Failed to get IDP metadata XML settings, error: %s', str(err))
logger.warning('Failed to get IDP Metadata XML settings, error: %s', str(err))
url_idp_settings = None
try:
@ -59,7 +59,7 @@ class PrepareRequestMixin:
idp_metadata_url, timeout=20
)
except Exception as err:
logger.warning('Failed to get IDP metadata URL settings, error: %s', str(err))
logger.warning('Failed to get IDP Metadata URL settings, error: %s', str(err))
idp_settings = url_idp_settings or xml_idp_settings

View File

@ -7,7 +7,7 @@
</p>
<p>
<b>{% trans 'Username' %}:</b> {{ username }}<br>
<b>{% trans 'Login time' %}:</b> {{ time }}<br>
<b>{% trans 'Login Date' %}:</b> {{ time }}<br>
<b>{% trans 'Login city' %}:</b> {{ city }}({{ ip }})
</p>

View File

@ -297,8 +297,7 @@
</h2>
<ul class=" nav navbar-top-links navbar-right">
<li class="dropdown">
<a class="dropdown-toggle login-page-language" data-toggle="dropdown" href="#"
target="_blank">
<a class="dropdown-toggle login-page-language" data-toggle="dropdown" href="#" target="_blank">
<i class="fa fa-globe fa-lg" style="margin-right: 2px"></i>
<span>{{ current_lang.title }}<b class="caret"></b></span>
</a>
@ -378,8 +377,8 @@
<div class="more-login-items">
{% for method in auth_methods %}
<a href="{{ method.url }}" class="more-login-item">
<i class="fa">
<img src="{{ method.logo }}" height="15" width="15"></i> {{ method.name }}
<i class="fa"><img src="{{ method.logo }}" height="15" width="15"/> </i>
{{ method.name }}
</a>
{% endfor %}
</div>

View File

@ -203,6 +203,9 @@ class UserLoginView(mixins.AuthMixin, UserLoginContextMixin, FormView):
if self.request.GET.get("admin", 0):
return None
if not settings.XPACK_ENABLED:
return None
auth_types = [m for m in self.get_support_auth_methods() if m.get('auto_redirect')]
if not auth_types:
return None

View File

@ -124,7 +124,7 @@ class SimpleMetadataWithFilters(SimpleMetadata):
if field.field_name == 'id':
field_info['label'] = 'ID'
if field.field_name == 'org_id':
field_info['label'] = _('Organization ID')
field_info['label'] = _('Org ID')
return field_info

View File

@ -356,6 +356,7 @@ class SomeFieldsMixin:
def order_fields(fields):
bool_fields = []
datetime_fields = []
common_fields = []
other_fields = []
for name, field in fields.items():
@ -364,9 +365,11 @@ class SomeFieldsMixin:
bool_fields.append(to_add)
elif isinstance(field, serializers.DateTimeField):
datetime_fields.append(to_add)
elif name in ('comment', 'created_by', 'updated_by'):
common_fields.append(to_add)
else:
other_fields.append(to_add)
_fields = [*other_fields, *bool_fields, *datetime_fields]
_fields = [*other_fields, *bool_fields, *datetime_fields, *common_fields]
fields = OrderedDict()
for name, field in _fields:
fields[name] = field

View File

View File

@ -0,0 +1,67 @@
import asyncio
import os
from tqdm import tqdm
from .const import RED, GREEN, RESET
class BaseTranslateManager:
bulk_size = 15
SEPARATOR = "<SEP>"
LANG_MAPPER = {
# 'ja': 'Japanese',
'en': 'English',
}
def __init__(self, dir_path, oai_trans_instance):
self.oai_trans = oai_trans_instance
self._dir = dir_path
self.dir_name = os.path.basename(self._dir)
if not os.path.exists(self._dir):
os.makedirs(self._dir)
@staticmethod
def split_dict_into_chunks(input_dict, chunk_size=20):
temp = {}
result = []
for i, (k, v) in enumerate(input_dict.items()):
temp[k] = v
if (i + 1) % chunk_size == 0 or i == len(input_dict) - 1:
result.append(temp)
temp = {}
return result
async def create_translate_task(self, data, target_lang):
try:
keys = list(data.keys())
values = list(data.values())
combined_text = self.SEPARATOR.join(values)
translated_text = await self.oai_trans.translate_text(combined_text, target_lang)
translated_texts = translated_text.split(self.SEPARATOR)
return dict(zip(keys, translated_texts))
except Exception as e:
print(f"{RED}Error during translation task: {e}{RED}")
return {}
async def bulk_translate(self, need_trans_dict, target_lang):
split_data = self.split_dict_into_chunks(need_trans_dict, self.bulk_size)
tasks = [self.create_translate_task(batch, target_lang) for batch in split_data]
number_of_tasks = len(tasks)
translated_dict = {}
bar_format = "{l_bar}%s{bar}%s{r_bar}" % (GREEN, RESET)
desc = f"{target_lang} translate"
with tqdm(
total=number_of_tasks, ncols=100,
desc=desc, bar_format=bar_format
) as pbar:
for task in asyncio.as_completed(tasks):
pbar.set_description_str(f"{GREEN}{desc}{RESET}")
result = await task
translated_dict.update(result)
pbar.update(1)
return translated_dict

View File

@ -0,0 +1,12 @@
import os
LOCALE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
RED = "\033[91m"
BLUE = "\033[94m"
CYAN = "\033[96m"
RESET = "\033[0m"
GREEN = "\033[92m"
WHITE = "\033[97m"
YELLOW = "\033[93m"
MAGENTA = "\033[95m"

View File

@ -0,0 +1,48 @@
import os
import polib
from .base import BaseTranslateManager
from .const import RED, GREEN, MAGENTA
class CoreTranslateManager(BaseTranslateManager):
@staticmethod
def get_need_trans_dict(zh_dict, trans_po):
need_trans_dict = {
entry.msgid: zh_dict[entry.msgid]
for entry in trans_po.untranslated_entries() + trans_po.fuzzy_entries()
if entry.msgid in zh_dict
}
return need_trans_dict
@staticmethod
def save_translations_to_po(data, trans_po):
try:
for entry in trans_po.untranslated_entries() + trans_po.fuzzy_entries():
if entry.msgid not in data:
print(f'{MAGENTA}msgid: {entry.msgid} not in data.{MAGENTA}')
continue
entry.flags = []
entry.previous_msgid = None
entry.msgstr = data[entry.msgid]
trans_po.save()
except Exception as e:
print(f'{RED}File save error: {e}{RED}')
async def run(self):
po_file_path = os.path.join(self._dir, 'zh', 'LC_MESSAGES', 'django.po')
po = polib.pofile(po_file_path)
zh_dict = {entry.msgid: entry.msgstr for entry in po.translated_entries()}
for file_prefix, target_lang in self.LANG_MAPPER.items():
po_file_path = os.path.join(self._dir, file_prefix, 'LC_MESSAGES', 'django.po')
trans_po = polib.pofile(po_file_path)
need_trans_dict = self.get_need_trans_dict(zh_dict, trans_po)
print(f'{GREEN}Translate: {self.dir_name} {file_prefix} '
f'django.po need to translate {len(need_trans_dict)}{GREEN}\n')
if not need_trans_dict:
continue
translated_dict = await self.bulk_translate(need_trans_dict, target_lang)
self.save_translations_to_po(translated_dict, trans_po)

View File

@ -0,0 +1,47 @@
import json
import os
from .base import BaseTranslateManager
from .const import RED, GREEN
class OtherTranslateManager(BaseTranslateManager):
@staticmethod
def get_need_trans_dict(zh_dict, other_dict):
diff_keys = set(zh_dict.keys()) - set(other_dict.keys())
need_trans_dict = {k: zh_dict[k] for k in diff_keys if k}
return need_trans_dict
def load_json_as_dict(self, file_prefix='zh'):
file_path = os.path.join(self._dir, f'{file_prefix}.json')
if not os.path.exists(file_path):
return {}
try:
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f'{RED}File: {file_path} load error: {e}{RED}')
return {}
def save_dict_as_json(self, data, file_prefix='ja'):
file_path = os.path.join(self._dir, f'{file_prefix}.json')
try:
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, sort_keys=True, indent=4)
except Exception as e:
print(f'{RED}File: {file_path} save error: {e}{RED}')
async def run(self):
zh_dict = self.load_json_as_dict()
for file_prefix, target_lang in self.LANG_MAPPER.items():
other_dict = self.load_json_as_dict(file_prefix)
need_trans_dict = self.get_need_trans_dict(zh_dict, other_dict)
print(f'{GREEN}Translate: {self.dir_name} {file_prefix} need to translate '
f'{len(need_trans_dict)}{GREEN}\n')
if not need_trans_dict:
continue
translated_dict = await self.bulk_translate(need_trans_dict, target_lang)
other_dict.update(translated_dict)
self.save_dict_as_json(other_dict, file_prefix)

View File

@ -0,0 +1,34 @@
from openai import AsyncOpenAI
class OpenAITranslate:
def __init__(self, key: str | None = None, base_url: str | None = None):
self.client = AsyncOpenAI(api_key=key, base_url=base_url)
async def translate_text(self, text, target_lang="English") -> str | None:
try:
response = await self.client.chat.completions.create(
messages=[
{
"role": "system",
"content": f"Now I ask you to be the translator. "
f"Your goal is to understand the Chinese "
f"I provided you and translate it into {target_lang}. "
f"Please do not use a translation accent when translating, "
f"but translate naturally, smoothly and authentically, "
f"using beautiful and elegant words. way of expression,"
f"If you found word '动作' please translate it to 'Action', because it's short,"
f"If you found word '管理' in menu, you can not translate it, because management is too long in menu"
,
},
{
"role": "user",
"content": text,
},
],
model="gpt-4",
)
except Exception as e:
print("Open AI Error: ", e)
return
return response.choices[0].message.content.strip()

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ceeb2860215e86f3088de6dc00e035d941098718221b9359e2c2cce0b20ee6b1
size 2579

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0fd56734e72b9bed138c98e76424e9bf4b0d95be83e59bec3c75b6a63eac9aff
size 167233

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3453782a925aafc7fe2e05cf07299f8c35dc239f9e09e539d905d10cae15ecea
size 138663

1271
apps/i18n/lina/en.json Normal file

File diff suppressed because it is too large Load Diff

1219
apps/i18n/lina/zh.json Normal file

File diff suppressed because it is too large Load Diff

211
apps/i18n/luna/en.json Normal file
View File

@ -0,0 +1,211 @@
{
"ACL reject login asset": "This login has been rejected due to access control policy restrictions",
"Account info": "Account info",
"Account not found": "Account not found",
"Account: ": "Account: {{value}}",
"Action: ": "Action:",
"Advanced option": "Advanced option",
"All sessions": "All sessions",
"Applet": "Applet",
"Applet connect method": "Applet connect method",
"Are you sure to reconnect it?(RDP not support)": "Are you sure to reconnect it?(RDP not support)",
"Asset disabled": "This asset has been disabled, please contact an administrator",
"Asset not found or You have no permission to access it, please refresh asset tree": "Asset not found or You have no permission to access it, please refresh asset tree",
"Asset tree loading method": "Configure Asset Tree Loading Method",
"Asset: ": "Asset: {{value}}",
"Assignees": "Assignees",
"Automatic login next": "Automatic Login next (right click asset to re-select)",
"Backspace as Ctrl+H": "Backspace as Ctrl+H",
"Batch actions": "Batch actions",
"Batch connect": "Batch connect",
"Belgian French keyboard layout": "Belgian French (Azerty)",
"CLI": "CLI",
"CLI font size": "CLI font size",
"Cancel": "Cancel",
"Charset": "Charset",
"Checkbox": "Checkbox",
"Choose a User": "Choose a User",
"Click to copy": "Click to copy",
"Client": "Client",
"Clone Connect": "Clone Connect",
"Close": "Close",
"Close All Tabs": "Close All Tabs",
"Close Current Tab": "Close Current Tab",
"Close Left Tabs": "Close Left Tabs",
"Close Other Tabs": "Close Other Tabs",
"Close Right Tabs": "Close Right Tabs",
"Close split connect": "Close split connect",
"Collapse": "Collapse",
"Command Line": "Command Line",
"Command line": "Command line",
"Confirm": "Confirm",
"Connect": "Connect",
"Connect method": "Connect method",
"Connect checked": "Connect checked",
"Connect command line": "Connect command line",
"Copied": "Copied",
"Copy link": "Copy link",
"Current online": "Current online",
"Current session": "Current session",
"Database": "Database",
"Database connect info": "Database Connection Information",
"Database disabled": "This type of connection is not supported, please contact an administrator.",
"Database info": "Database info",
"Database token help text": "The database type token that the client connects to will be cached by the component for 5 minutes, which means that the token will not be invalidated immediately after it is used, but five minutes after the client disconnects",
"Databases": "Databases",
"Directly": "Directly",
"Disable auto completion": "Disable auto completion",
"Disconnect": "Disconnect",
"Disfavor": "Disfavor",
"Do not close this page": "Do not close this page",
"Document": "Document",
"Don't prompt again": "Don't prompt again",
"Download": "Download",
"Download the client": "Please download",
"Download the latest client": "Download the latest client",
"Driver redirect": "Driver redirect",
"Expand": "Expand",
"Expand all": "Expand all",
"Expand all asset": "Expand all assets under the current node",
"Expire time": "Expire time",
"Failed to open address": "Failed to Open Address",
"Favorite": "Favorite",
"File Manager": "File Manager",
"Fold": "Collapse",
"Fold all": "Collapse All",
"Force refresh": "Force refresh",
"Found": "Found",
"French keyboard layout": "French (Azerty)",
"Full Screen": "Full Screen",
"Full screen": "Full screen",
"GUI": "GUI",
"General": "General",
"Help": "Help",
"Help or download": "Help → Download",
"Help text": "Help text",
"Hide left manager": "Hide left manager",
"Host": "Host",
"Info": "Info",
"WordSep": " ",
"InstallClientMsg": "JumpServer client not found, Go to download and install?",
"Japanese keyboard layout": "Japanese (Qwerty)",
"Keyboard keys": "Option + Left / Option + Right",
"Keyboard layout": "Keyboard layout",
"Keyboard switch session": "Switch session → Shortcut keys",
"Kubernetes": "Kubernetes",
"Language": "Language",
"Last login": "Last login",
"Launch Program": "Launch Program",
"LeftInfo": "Click the command record to quickly locate the video",
"Load tree async": "Load tree async",
"Loading": "Loading",
"Log out": "Log out",
"Login reminder": "Login reminder",
"Login review approved": "Login review has been approved, connecting assets...",
"LoginExpireMsg": "Login session expired, please re-login",
"Manual accounts": "Manual accounts",
"Module": "Module",
"Multi Screen": "Multi Screen",
"My applications": "My applications",
"My assets": "My assets",
"Name": "Name",
"Native": "Client",
"Need review for login asset": "This login needs manual review. Do you want to continue?",
"Need to use": "Need to use",
"No": "No",
"No account available": "No available accounts",
"No available connect method": "No available connect method",
"No matching found": "No matching found",
"No permission": "No permission",
"No protocol available": "No protocol available",
"Not quick command": "Not quick command",
"Open in new window": "Open in new window",
"Password": "Password",
"Password is token password on the table": "Password is token password on the table",
"Password is your password login to system": "Password is your password login to system",
"Pause": "Pause",
"Pause task has been send": "Pause task has been send",
"Please choose an account": "Please choose an account",
"Please input password": "Please input password",
"Port": "Port",
"Protocol": "Protocol",
"Protocol: ": "Protocol: {{value}}",
"RDP Client": "RDP Client",
"RDP File": "RDP File",
"RDP client options": "RDP client options",
"RDP color quality": "RDP Color Quality",
"RDP resolution": "RDP resolution",
"RDP smart size": "RDP smart size",
"Re-use for a long time after opening": "Re-use for a long time after opening",
"Reconnect": "Reconnect",
"Refresh": "Refresh",
"Remember password": "Remember me",
"Remember select": "Remember selected",
"RemoteApp": "RemoteApp",
"Reselect connection method": "Reselect asset connection method",
"Resume": "Resume",
"Resume task has been send": "Resume task has been send",
"Right click asset": "Right click on the asset",
"Right click node": "Right click node",
"Right mouse quick paste": "Right mouse quick paste",
"Run it by client": "Run it by client",
"SQL Client": "SQL Client",
"Save command": "Save command",
"Save success": "Save success",
"Search": "Search",
"Select account": "Select account",
"Send command": "Send command",
"Send text to all ssh terminals": "Send text to all ssh terminals",
"Set reusable": "Set reusable",
"Setting": "Setting",
"Settings or basic settings": "Settings → Basic Settings",
"Show left manager": "Show left manager",
"Skip": "Skip",
"Skip manual password": "Skip manual password",
"Special accounts": "Special accounts",
"Speed": "Speed",
"Split connect": "Split connect",
"Split connect number": "One session can support up to three split screen connection",
"Split vertically": "Split vertically",
"Start Time: ": "Start time: {{value}}",
"Stop": "Stop",
"Support": "Support",
"Swiss French keyboard layout": "Swiss French (Qwertz)",
"Switch to input command": "Switch to input command",
"Switch to quick command": "Switch to quick command",
"Tab List": "Tabs",
"The connection method is invalid, please refresh the page": "The connection method is invalid, please refresh the page",
"Ticket review approved for login asset": " The login audit has been approved, connect to the asset?",
"Ticket review closed for login asset": "This login review has been closed, and the asset cannot be connected",
"Ticket review pending for login asset": "The login asset has been submitted, waiting for review by the assignee, you can also copy the link and send it to he",
"Ticket review rejected for login asset": "This login review has been rejected, and the asset cannot be connected",
"Tips": "Tips",
"Token expired": "Token has expired",
"Tool download": "Tool download",
"Turkey keyboard layout": "Turkish-Q (Qwerty)",
"Type tree": "Type tree",
"UK English keyboard layout": "UK English (Qwerty)",
"US English keyboard layout": "US English (Qwerty)",
"User": "User",
"User: ": "User: {{value}}",
"Username": "Username",
"Username@Domain": "Username@Domain",
"Users": "",
"Using token": "Using token",
"View": "View",
"VirtualApp": "Virtual App",
"Web Terminal": "Web Terminal",
"Website": "Website",
"With secret accounts": "With secret accounts",
"Yes": "Yes",
"asset": "asset",
"cols": "cols",
"confirm": "confirm",
"connect info": "connect info",
"download": "download",
"rows": "rows",
"start time": "start time",
"success": "success",
"system user": "system user",
"user": "user"
}

208
apps/i18n/luna/zh.json Normal file
View File

@ -0,0 +1,208 @@
{
"ACL reject login asset": "本次登录已拒绝,原因是访问控制策略的限制",
"Account info": "账号信息",
"Account not found": "账号未找到",
"Account: ": "账号: {{value}}",
"Action: ": "操作: ",
"Advanced option": "高级选项",
"All sessions": "全部会话",
"Applet": "远程应用",
"Applet connect method": "远程应用连接方式",
"Are you sure to reconnect it?(RDP not support)": "确定要重新连接吗? (RDP暂不支持)",
"Asset disabled": "该资产已被禁用,请联系管理员",
"Asset not found or You have no permission to access it, please refresh asset tree": "未找到资产或您无权访问它,请刷新资产树",
"Asset tree loading method": "配置资产树加载方式",
"Asset: ": "资产: {{value}}",
"Assignees": "受理人",
"Automatic login next": "下次自动登录 (右击资产连接可以重新选择)",
"Backspace as Ctrl+H": "字符终端Backspace As Ctrl+H",
"Batch actions": "批量操作",
"Batch connect": "批量连接",
"Belgian French keyboard layout": "Belgian French (Azerty)",
"CLI": "命令行",
"CLI font size": "字符终端字体大小",
"Cancel": "取消",
"Charset": "字符集",
"Checkbox": "多选",
"Choose a User": "选择一个用户",
"Click to copy": "点击复制",
"Client": "客户端",
"Clone Connect": "复制窗口",
"Close": "关闭",
"Close All Tabs": "关闭所有",
"Close Current Tab": "关闭当前",
"Close Left Tabs": "关闭左侧",
"Close Other Tabs": "关闭其它",
"Close Right Tabs": "关闭右侧",
"Close split connect": "关闭分屏",
"Command Line": "命令行",
"Command line": "连接命令行",
"Confirm": "确认",
"Connect": "连接",
"Connect method": "连接方式",
"Connect checked": "连接选中",
"Connect command line": "连接命令行",
"Copied": "已复制",
"Copy link": "复制链接",
"Current online": "当前在线",
"Current session": "当前会话",
"Database": "数据库",
"Database connect info": "数据库连接信息",
"Database disabled": "不支持此种链接方式,请联系管理员",
"Database info": "数据库信息",
"Database token help text": "数据库类型 token 会缓存 5 分钟,也就是说 token 使用后,不会立刻失效,而是客户端断开 5 分钟后,这个 token 才会完全失效",
"Databases": "数据库",
"Directly": "用户名指定连接的资产和账号",
"Disable auto completion": "禁用自动补全",
"Disconnect": "断开链接",
"Disfavor": "取消收藏",
"Do not close this page": "不要关闭本页面",
"Document": "文档",
"Don't prompt again": "下次不再提示",
"Download": "下载",
"Download the latest client": "下载最新客户端",
"Driver redirect": "磁盘挂载",
"Expand": "展开",
"Expand all": "展开全部",
"Expand all asset": "展开节点下的所有资产",
"Expire time": "过期时间",
"Failed to open address": "打开地址失败",
"Favorite": "收藏",
"File Manager": "文件管理",
"Fold": "折叠",
"Fold all": "折叠全部",
"Force refresh": "强制刷新",
"Found": "发现",
"French keyboard layout": "French (Azerty)",
"Full Screen": "全屏显示",
"Full screen": "全屏",
"GUI": "图形化",
"General": "基本配置",
"Help": "帮助",
"Help or download": "菜单帮助 → 下载",
"Help text": "说明",
"Hide left manager": "隐藏左边栏",
"Host": "主机",
"Info": "提示",
"InstallClientMsg": "JumpServer 客户端没有安装,现在去下载安装?",
"Japanese keyboard layout": "Japanese (Qwerty)",
"Keyboard keys": "Option + Left / Option + Right",
"Keyboard layout": "键盘布局",
"Keyboard switch session": "切换会话 → 快捷键",
"Kubernetes": "Kubernetes",
"Language": "语言",
"Last login": "上次登录",
"Launch Program": "启动程序",
"LeftInfo": "点击命令记录可快速定位录像",
"Load tree async": "异步加载资产树",
"Loading": "加载中",
"Log out": "退出登录",
"Login reminder": "登录提醒",
"Login review approved": "登录审核已通过, 正在连接资产...",
"LoginExpireMsg": "登录已过期,请重新登录",
"Manual accounts": "手动账号",
"Module": "模块",
"Multi Screen": "多屏显示",
"My applications": "我的应用",
"My assets": "我的资产",
"Name": "名称",
"Native": "客户端",
"Need review for login asset": "本次登录需要进行人工审核,是否继续?",
"Need to use": "需要使用",
"No": "否",
"No account available": "没有可用账号",
"No available connect method": "没有可用的连接方法",
"No matching found": "没有匹配项",
"No permission": "没有权限",
"No protocol available": "没有可用的协议",
"Not quick command": "暂无快捷命令",
"Open in new window": "新窗口打开",
"Password": "密码",
"Password is token password on the table": "密码是表格中的 Token 密码",
"Password is your password login to system": "密码是你登录系统的密码",
"Pause": "暂停",
"Pause task has been send": "暂停任务已发送",
"Please choose an account": "请选择一个用户",
"Please input password": "请输入密码",
"Port": "端口",
"Protocol": "协议",
"Protocol: ": "协议: {{value}}",
"RDP Client": "RDP 客户端",
"RDP File": "RDP 文件",
"RDP client options": "RDP 客户端选项",
"RDP color quality": "RDP 颜色质量",
"RDP resolution": "RDP 分辨率",
"RDP smart size": "RDP 智能大小",
"Re-use for a long time after opening": "开启后该连接信息可长时间多次使用",
"Reconnect": "重新连接",
"Refresh": "刷新",
"Remember password": "记住密码",
"Remember select": "记住选择",
"RemoteApp": "远程应用",
"Reselect connection method": "可重新选择连接方式",
"Resume": "恢复",
"Resume task has been send": "恢复任务已发送",
"Right click asset": "右击资产 → 连接",
"Right click node": "右击节点 → 展开全部",
"Right mouse quick paste": "右键快速粘贴",
"Run it by client": "使用客户端执行",
"SQL Client": "SQL 客户端",
"Save command": "保存命令",
"Save success": "保存成功",
"Search": "搜索",
"Select account": "选择账号",
"Send command": "发送命令",
"Send text to all ssh terminals": "发送文本到所有ssh终端",
"Set reusable": "开启复用",
"Setting": "设置",
"Settings or basic settings": "菜单设置 → 基本设置",
"Show left manager": "显示左边栏",
"Skip": "跳过",
"Skip manual password": "跳过手动密码窗",
"Special accounts": "特殊账号",
"Speed": "速度",
"Split connect": "分屏连接",
"Split connect number": "一个会话最多支持3个分屏连接",
"Split vertically": "垂直分屏",
"Start Time: ": "开始时间: {{value}}",
"Stop": "停止",
"Support": "支持",
"Swiss French keyboard layout": "Swiss French (Qwertz)",
"Switch to input command": "切换到输入命令",
"Switch to quick command": "切换到快捷命令",
"Tab List": "窗口列表",
"The connection method is invalid, please refresh the page": "该连接方式已失效,请刷新页面",
"Ticket review approved for login asset": "本次登录审核已通过,是否连接资产?",
"Ticket review closed for login asset": "本次登录审核已关闭,不能连接资产",
"Ticket review pending for login asset": "登录申请已提交,等待受理人进行复核,你也可以复制链接发给他",
"Ticket review rejected for login asset": "本次登录审核已拒绝,不能连接资产",
"Tips": "提示",
"Token expired": "Token 已过期, 请重新连接",
"Tool download": "工具下载",
"Turkey keyboard layout": "Turkish-Q (Qwerty)",
"Type tree": "类型树",
"UK English keyboard layout": "UK English (Qwerty)",
"US English keyboard layout": "US English (Qwerty)",
"User": "用户",
"User: ": "用户: {{value}}",
"Username": "用户名",
"Username@Domain": "用户名@AD域",
"Users": "用户",
"Using token": "使用 Token",
"View": "视图",
"VirtualApp": "虚拟应用",
"Web Terminal": "Web终端",
"Website": "官网",
"With secret accounts": "托管账号",
"Yes": "是",
"asset": "资产",
"cols": "列数",
"confirm": "确认",
"connect info": "连接信息",
"download": "下载",
"rows": "行数",
"start time": "开始时间",
"success": "成功",
"system user": "系统用户",
"user": "用户"
}

25
apps/i18n/sort_json.py Normal file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env python
#
import json
import os
def sort_json(json_file):
with open(json_file, 'r') as f:
json_data = json.load(f)
with open(json_file, 'w') as f:
json.dump(json_data, f, indent=4, sort_keys=True, ensure_ascii=False)
def walk_dir(dir_path):
for root, dirs, files in os.walk(dir_path):
for file in files:
if file.endswith('.json'):
sort_json(os.path.join(root, file))
if __name__ == '__main__':
base_dir = os.path.dirname(os.path.abspath(__file__))
walk_dir(base_dir)

61
apps/i18n/translate.py Normal file
View File

@ -0,0 +1,61 @@
import asyncio
import os
from _translator.const import LOCALE_DIR, RED
from _translator.core import CoreTranslateManager
from _translator.other import OtherTranslateManager
from _translator.utils import OpenAITranslate
class Translate:
IGNORE_TRANSLATE_DIRS = ('translate',)
def __init__(self, oai_trans_instance):
self.oai_trans = oai_trans_instance
def get_dir_names(self):
dir_names = []
for name in os.listdir(LOCALE_DIR):
_path = os.path.join(LOCALE_DIR, name)
if not os.path.isdir(_path) or name in self.IGNORE_TRANSLATE_DIRS:
continue
dir_names.append(name)
return dir_names
async def core_trans(self, dir_name):
return
_dir = os.path.join(LOCALE_DIR, dir_name)
zh_file = os.path.join(_dir, 'zh', 'LC_MESSAGES', 'django.po')
if not os.path.exists(zh_file):
print(f'{RED}File: {zh_file} not exists.{RED}')
return
await CoreTranslateManager(_dir, self.oai_trans).run()
async def other_trans(self, dir_name):
_dir = os.path.join(LOCALE_DIR, dir_name)
zh_file = os.path.join(_dir, 'zh.json')
if not os.path.exists(zh_file):
print(f'{RED}File: {zh_file} not exists.{RED}\n')
return
await OtherTranslateManager(_dir, self.oai_trans).run()
async def run(self):
dir_names = self.get_dir_names()
if not dir_names:
return
for dir_name in dir_names:
if dir_name.startswith('_'):
continue
if hasattr(self, f'{dir_name}_trans'):
await getattr(self, f'{dir_name}_trans')(dir_name)
else:
await self.other_trans(dir_name)
if __name__ == '__main__':
oai_trans = OpenAITranslate()
manager = Translate(oai_trans)
asyncio.run(manager.run())

View File

@ -297,7 +297,7 @@ USE_TZ = True
# I18N translation
LOCALE_PATHS = [
os.path.join(BASE_DIR, 'locale'),
os.path.join(BASE_DIR, 'i18n', 'core'),
]
# Static files (CSS, JavaScript, Images)

View File

@ -6,6 +6,7 @@ from django.conf import settings
from django.http import HttpResponse
from django.http import HttpResponseRedirect, JsonResponse, Http404
from django.shortcuts import redirect
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View, TemplateView
@ -32,7 +33,8 @@ class I18NView(View):
def get(self, request, lang):
referer_url = request.META.get('HTTP_REFERER', '/')
response = HttpResponseRedirect(referer_url)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang)
expires = timezone.now() + timezone.timedelta(days=365)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang, expires=expires)
return response

View File

@ -5,4 +5,4 @@ from django.utils.translation import gettext_lazy as _
class LabelsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'labels'
verbose_name = _('Labels')
verbose_name = _('App Labels')

View File

@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _
class NotificationsConfig(AppConfig):
name = 'notifications'
verbose_name = _('Notifications')
verbose_name = _('App Notifications')
def ready(self):
from . import signal_handlers # noqa

View File

@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
class OpsConfig(AppConfig):
name = 'ops'
verbose_name = _('App ops')
verbose_name = _('App Ops')
def ready(self):
from orgs.models import Organization

View File

@ -59,7 +59,7 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
('interval', models.IntegerField(blank=True, help_text='Units: seconds', null=True, verbose_name='Interval')),
('crontab', models.CharField(blank=True, help_text='5 * * * *', max_length=128, null=True, verbose_name='Crontab')),
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic perform')),
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic run')),
('callback', models.CharField(blank=True, max_length=128, null=True, verbose_name='Callback')),
('is_deleted', models.BooleanField(default=False)),
('comment', models.TextField(blank=True, verbose_name='Comment')),

View File

@ -13,11 +13,11 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='task',
name='crontab',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform'),
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Crontab'),
),
migrations.AlterField(
model_name='task',
name='interval',
field=models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform'),
field=models.IntegerField(blank=True, default=24, null=True, verbose_name='Interval'),
),
]

View File

@ -60,9 +60,9 @@ class Migration(migrations.Migration):
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic perform')),
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')),
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')),
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic run')),
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Interval')),
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Crontab')),
('name', models.CharField(max_length=128, null=True, verbose_name='Name')),
('instant', models.BooleanField(default=False)),
('args', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Args')),
@ -164,9 +164,9 @@ class Migration(migrations.Migration):
('id', models.UUIDField(db_index=True, default=uuid.uuid4)),
('org_id',
models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic perform')),
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Cycle perform')),
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Regularly perform')),
('is_periodic', models.BooleanField(default=False, verbose_name='Periodic run')),
('interval', models.IntegerField(blank=True, default=24, null=True, verbose_name='Interval')),
('crontab', models.CharField(blank=True, max_length=128, null=True, verbose_name='Crontab')),
('name', models.CharField(max_length=128, null=True, verbose_name='Name')),
('instant', models.BooleanField(default=False)),
('args', models.CharField(blank=True, default='', max_length=1024, null=True, verbose_name='Args')),

View File

@ -20,14 +20,12 @@ class PeriodTaskModelMixin(models.Model):
name = models.CharField(
max_length=128, unique=False, verbose_name=_("Name")
)
is_periodic = models.BooleanField(default=False, verbose_name=_("Periodic perform"))
is_periodic = models.BooleanField(default=False, verbose_name=_("Periodic run"))
interval = models.IntegerField(
default=24, null=True, blank=True,
verbose_name=_("Cycle perform"),
default=24, null=True, blank=True, verbose_name=_("Interval"),
)
crontab = models.CharField(
null=True, blank=True, max_length=128,
verbose_name=_("Regularly perform"),
blank=True, max_length=128, null=True, verbose_name=_("Crontab"),
)
@abc.abstractmethod
@ -85,9 +83,9 @@ class PeriodTaskModelMixin(models.Model):
@property
def periodic_display(self):
if self.is_periodic and self.crontab:
return _('Regularly perform') + " ( {} )".format(self.crontab)
return _('Crontab') + " ( {} )".format(self.crontab)
if self.is_periodic and self.interval:
return _('Cycle perform') + " ( {} h )".format(self.interval)
return _('Interval') + " ( {} h )".format(self.interval)
return '-'
@property
@ -101,14 +99,15 @@ class PeriodTaskModelMixin(models.Model):
class PeriodTaskSerializerMixin(serializers.Serializer):
is_periodic = serializers.BooleanField(default=True, label=_("Periodic perform"))
is_periodic = serializers.BooleanField(default=True, label=_("Periodic run"))
crontab = serializers.CharField(
max_length=128, allow_blank=True,
allow_null=True, required=False, label=_('Regularly perform')
allow_null=True, required=False, label=_('Crontab')
)
interval = serializers.IntegerField(
default=24, allow_null=True, required=False, label=_('Interval')
)
periodic_display = serializers.CharField(read_only=True, label=_('Run period'))
INTERVAL_MAX = 65535
INTERVAL_MIN = 1
@ -135,6 +134,15 @@ class PeriodTaskSerializerMixin(serializers.Serializer):
crontab = self.initial_data.get('crontab')
interval = self.initial_data.get('interval')
if ok and not any([crontab, interval]):
msg = _("Require periodic or regularly perform setting")
msg = _("Require interval or crontab setting")
raise serializers.ValidationError(msg)
return ok
def validate(self, attrs):
attrs = super().validate(attrs)
if not attrs.get('is_periodic'):
attrs['interval'] = None
attrs['crontab'] = ''
if attrs.get('crontab'):
attrs['interval'] = None
return attrs

View File

@ -8,7 +8,7 @@ from common.utils import get_logger
__all__ = ["AdHoc"]
from ops.const import AdHocModules
from ops.const import AdHocModules
from orgs.mixins.models import JMSOrgBaseModel

View File

@ -14,8 +14,8 @@ __all__ = ('ServerPerformanceMessage', 'ServerPerformanceCheckUtil')
class ServerPerformanceMessage(SystemMessage):
category = 'Operations'
category_label = _('App ops')
category = 'Components'
category_label = _('Component')
message_type_label = _('Server performance')
def __init__(self, terms_with_errors):

View File

@ -29,9 +29,11 @@ class CeleryPeriodTaskSerializer(serializers.ModelSerializer):
class CeleryTaskSerializer(serializers.ModelSerializer):
exec_cycle = serializers.CharField(read_only=True)
next_exec_time = serializers.DateTimeField(format="%Y/%m/%d %H:%M:%S", read_only=True)
enabled = serializers.BooleanField(required=False)
exec_cycle = serializers.CharField(read_only=True, label=_('Execution cycle'))
next_exec_time = serializers.DateTimeField(
format="%Y/%m/%d %H:%M:%S", read_only=True, label=_('Next execution time')
)
class Meta:
model = CeleryTask

View File

@ -12,7 +12,7 @@ from orgs.mixins.serializers import BulkOrgResourceModelSerializer
class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
creator = ReadableHiddenField(default=serializers.CurrentUserDefault())
run_after_save = serializers.BooleanField(label=_("Run after save"), default=False, required=False)
run_after_save = serializers.BooleanField(label=_("Execute after saving"), default=False, required=False)
nodes = serializers.ListField(required=False, child=serializers.CharField())
date_last_run = serializers.DateTimeField(label=_('Date last run'), read_only=True)
name = serializers.CharField(label=_('Name'), max_length=128, allow_blank=True, required=False)
@ -48,6 +48,9 @@ class JobSerializer(BulkOrgResourceModelSerializer, PeriodTaskSerializerMixin):
"is_periodic", "interval", "crontab", "nodes",
"run_after_save"
]
extra_kwargs = {
'average_time_cost': {'label': _('Average time cost')},
}
class FileSerializer(serializers.Serializer):

View File

@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _
class OrgsConfig(AppConfig):
name = 'orgs'
verbose_name = _('App organizations')
verbose_name = _('App Organizations')
def ready(self):
from . import signal_handlers # noqa

View File

@ -1,3 +1,4 @@
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.serializers import ModelSerializer
@ -6,15 +7,15 @@ from .utils import get_current_org
class ResourceStatisticsSerializer(serializers.Serializer):
users_amount = serializers.IntegerField(required=False)
groups_amount = serializers.IntegerField(required=False)
users_amount = serializers.IntegerField(required=False, label=_('Users amount'))
groups_amount = serializers.IntegerField(required=False, label=_('User groups amount'))
assets_amount = serializers.IntegerField(required=False)
nodes_amount = serializers.IntegerField(required=False)
domains_amount = serializers.IntegerField(required=False)
gateways_amount = serializers.IntegerField(required=False)
assets_amount = serializers.IntegerField(required=False, label=_('Assets amount'))
nodes_amount = serializers.IntegerField(required=False, label=_('Nodes amount'))
domains_amount = serializers.IntegerField(required=False, label=_('Domains amount'))
gateways_amount = serializers.IntegerField(required=False, label=_('Gateways amount'))
asset_perms_amount = serializers.IntegerField(required=False)
asset_perms_amount = serializers.IntegerField(required=False, label=_('Asset permissions amount'))
class OrgSerializer(ModelSerializer):

View File

@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
class PermsConfig(AppConfig):
name = 'perms'
verbose_name = _('App permissions')
verbose_name = _('App Permissions')
def ready(self):
from . import signal_handlers # noqa

View File

@ -29,16 +29,20 @@ class ActionChoicesField(BitChoicesField):
class AssetPermissionSerializer(ResourceLabelsMixin, BulkOrgResourceModelSerializer):
users = ObjectRelatedField(queryset=User.objects, many=True, required=False, label=_('User'))
users = ObjectRelatedField(queryset=User.objects, many=True, required=False, label=_('Users'))
user_groups = ObjectRelatedField(
queryset=UserGroup.objects, many=True, required=False, label=_('User group')
queryset=UserGroup.objects, many=True, required=False, label=_('Groups')
)
assets = ObjectRelatedField(queryset=Asset.objects, many=True, required=False, label=_('Asset'))
nodes = ObjectRelatedField(queryset=Node.objects, many=True, required=False, label=_('Node'))
assets = ObjectRelatedField(queryset=Asset.objects, many=True, required=False, label=_('Assets'))
nodes = ObjectRelatedField(queryset=Node.objects, many=True, required=False, label=_('Nodes'))
users_amount = serializers.IntegerField(read_only=True, label=_("Users amount"))
user_groups_amount = serializers.IntegerField(read_only=True, label=_("Groups amount"))
assets_amount = serializers.IntegerField(read_only=True, label=_("Assets amount"))
nodes_amount = serializers.IntegerField(read_only=True, label=_("Nodes amount"))
actions = ActionChoicesField(required=False, allow_null=True, label=_("Actions"))
is_valid = serializers.BooleanField(read_only=True, label=_("Is valid"))
is_expired = serializers.BooleanField(read_only=True, label=_("Is expired"))
accounts = serializers.ListField(label=_("Account"), required=False)
accounts = serializers.ListField(label=_("Accounts"), required=False)
protocols = serializers.ListField(label=_("Protocols"), required=False)
template_accounts = AccountTemplate.objects.none()
@ -46,13 +50,14 @@ class AssetPermissionSerializer(ResourceLabelsMixin, BulkOrgResourceModelSeriali
class Meta:
model = AssetPermission
fields_mini = ["id", "name"]
amount_fields = ["users_amount", "user_groups_amount", "assets_amount", "nodes_amount"]
fields_generic = [
"accounts", "protocols", "actions", "created_by", "date_created",
"date_start", "date_expired", "is_active", "is_expired",
"is_valid", "comment", "from_ticket",
]
fields_small = fields_mini + fields_generic
fields_m2m = ["users", "user_groups", "assets", "nodes", "labels"]
fields_m2m = ["users", "user_groups", "assets", "nodes", "labels"] + amount_fields
fields = fields_mini + fields_m2m + fields_generic
read_only_fields = ["created_by", "date_created", "from_ticket"]
extra_kwargs = {
@ -183,11 +188,6 @@ class AssetPermissionSerializer(ResourceLabelsMixin, BulkOrgResourceModelSeriali
class AssetPermissionListSerializer(AssetPermissionSerializer):
users_amount = serializers.IntegerField(read_only=True, label=_("Users amount"))
user_groups_amount = serializers.IntegerField(read_only=True, label=_("User groups amount"))
assets_amount = serializers.IntegerField(read_only=True, label=_("Assets amount"))
nodes_amount = serializers.IntegerField(read_only=True, label=_("Nodes amount"))
class Meta(AssetPermissionSerializer.Meta):
amount_fields = ["users_amount", "user_groups_amount", "assets_amount", "nodes_amount"]
fields = [item for item in (AssetPermissionSerializer.Meta.fields + amount_fields) if

View File

@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _
class RBACConfig(AppConfig):
name = 'rbac'
verbose_name = _('RBAC')
verbose_name = _('App RBAC')
def ready(self):
from . import signal_handlers # noqa

View File

@ -52,7 +52,7 @@ extra_nodes_data = [
{"id": "push_account_node", "name": _("Push account"), "pId": "accounts"},
{"id": "asset_change_plan_node", "name": _("Asset change auth"), "pId": "accounts"},
{"id": "terminal_node", "name": _("Terminal setting"), "pId": "view_setting"},
{'id': "task_center", "name": _("Task Center"), "pId": "view_console"},
{'id': "task_center", "name": _("Job center"), "pId": "view_console"},
{'id': "my_assets", "name": _("My assets"), "pId": "view_workbench"},
{'id': "operation_center", "name": _('App ops'), "pId": "view_workbench"},
{'id': "remote_application", "name": _("Applet"), "pId": "view_setting"},

View File

@ -3,6 +3,7 @@ from .dingtalk import *
from .email import *
from .feishu import *
from .lark import *
from .i18n import *
from .ldap import *
from .public import *
from .security import *

37
apps/settings/api/i18n.py Normal file
View File

@ -0,0 +1,37 @@
import json
import os
from django.conf import settings
from django.utils._os import safe_join
from rest_framework.generics import RetrieveAPIView
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
class ComponentI18nApi(RetrieveAPIView):
base_path = 'locale'
permission_classes = [AllowAny]
def retrieve(self, request, *args, **kwargs):
name = kwargs.get('name')
component_dir = safe_join(settings.APPS_DIR, 'i18n', name)
lang = request.query_params.get('lang')
if os.path.exists(component_dir):
files = os.listdir(component_dir)
else:
files = []
data = {}
for file in files:
if not file.endswith('.json'):
continue
_lang = file.split('.')[0]
with open(safe_join(component_dir, file), 'r') as f:
data[_lang] = json.load(f)
if lang:
data = data.get(lang) or {}
flat = request.query_params.get('flat', '1')
if flat == '0':
data = {lang: data}
return Response(data)

View File

@ -4,7 +4,7 @@ from django.utils.translation import gettext_lazy as _
class SettingsConfig(AppConfig):
name = 'settings'
verbose_name = _('Settings')
verbose_name = _('App Settings')
def ready(self):
from . import signal_handlers # noqa

View File

@ -10,6 +10,7 @@ from django.utils.translation import gettext_lazy as _
from common.db.models import JMSBaseModel
from common.utils import signer, get_logger
from .signals import setting_changed
logger = get_logger(__name__)
@ -84,6 +85,7 @@ class Setting(models.Model):
if not item:
return
item.refresh_setting()
setting_changed.send(sender=cls, name=name, item=item)
def refresh_setting(self):
setattr(settings, self.name, self.cleaned_value)

View File

@ -24,8 +24,8 @@ class AuthSettingSerializer(serializers.Serializer):
AUTH_PASSKEY = serializers.BooleanField(default=False, label=_("Passkey Auth"))
FORGOT_PASSWORD_URL = serializers.CharField(
required=False, allow_blank=True, max_length=1024,
label=_("Forgot password url")
label=_("Forgot Password URL")
)
LOGIN_REDIRECT_MSG_ENABLED = serializers.BooleanField(
required=False, label=_("Enable login redirect msg")
required=False, label=_("Login redirection prompt")
)

View File

@ -9,11 +9,11 @@ __all__ = [
class CASSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('CAS')
AUTH_CAS = serializers.BooleanField(required=False, label=_('Enable CAS Auth'))
CAS_SERVER_URL = serializers.CharField(required=False, max_length=1024, label=_('Server url'))
AUTH_CAS = serializers.BooleanField(required=False, label=_('CAS'))
CAS_SERVER_URL = serializers.CharField(required=False, max_length=1024, label=_('Server'))
CAS_ROOT_PROXIED_AS = serializers.CharField(
required=False, allow_null=True, allow_blank=True,
max_length=1024, label=_('Proxy server url')
max_length=1024, label=_('Proxy Server')
)
CAS_LOGOUT_COMPLETELY = serializers.BooleanField(required=False, label=_('Logout completely'))
CAS_VERSION = serializers.IntegerField(
@ -25,5 +25,8 @@ class CASSettingSerializer(serializers.Serializer):
CAS_APPLY_ATTRIBUTES_TO_USER = serializers.BooleanField(
required=False, label=_('Enable attributes map')
)
CAS_RENAME_ATTRIBUTES = serializers.JSONField(required=False, label=_('Rename attr'))
CAS_CREATE_USER = serializers.BooleanField(required=False, label=_('Create user if not'))
CAS_RENAME_ATTRIBUTES = serializers.JSONField(required=False, label=_('User attribute'))
CAS_CREATE_USER = serializers.BooleanField(
required=False, label=_('Create user'),
help_text=_('Automatically create a new user if not found.')
)

View File

@ -9,7 +9,7 @@ __all__ = ['DingTalkSettingSerializer']
class DingTalkSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('DingTalk')
DINGTALK_AGENTID = serializers.CharField(max_length=256, required=True, label='AgentId')
DINGTALK_APPKEY = serializers.CharField(max_length=256, required=True, label='AppKey')
DINGTALK_APPSECRET = EncryptedField(max_length=256, required=False, label='AppSecret')
AUTH_DINGTALK = serializers.BooleanField(default=False, label=_('Enable DingTalk Auth'))
DINGTALK_AGENTID = serializers.CharField(max_length=256, required=True, label='Agent ID')
DINGTALK_APPKEY = serializers.CharField(max_length=256, required=True, label='App Key')
DINGTALK_APPSECRET = EncryptedField(max_length=256, required=False, label='App Secret')
AUTH_DINGTALK = serializers.BooleanField(default=False, label=_('Dingtalk'))

View File

@ -39,7 +39,7 @@ class LDAPSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('LDAP')
AUTH_LDAP_SERVER_URI = serializers.CharField(
required=True, max_length=1024, label=_('LDAP server'),
required=True, max_length=1024, label=_('Server'),
help_text=_('eg: ldap://localhost:389')
)
AUTH_LDAP_BIND_DN = serializers.CharField(required=False, max_length=1024, label=_('Bind DN'))
@ -55,7 +55,7 @@ class LDAPSettingSerializer(serializers.Serializer):
help_text=_('Choice may be (cn|uid|sAMAccountName)=%(user)s)')
)
AUTH_LDAP_USER_ATTR_MAP = serializers.JSONField(
required=True, label=_('User attr map'),
required=True, label=_('User attribute'),
help_text=_('User attr map present how to map LDAP user attr to '
'jumpserver, username,name,email is jumpserver attr')
)
@ -63,14 +63,14 @@ class LDAPSettingSerializer(serializers.Serializer):
required=False, label=_('Organization'), max_length=36
)
AUTH_LDAP_SYNC_IS_PERIODIC = serializers.BooleanField(
required=False, label=_('Periodic perform')
required=False, label=_('Periodic run')
)
AUTH_LDAP_SYNC_CRONTAB = serializers.CharField(
required=False, max_length=128, allow_null=True, allow_blank=True,
label=_('Regularly perform')
label=_('Crontab')
)
AUTH_LDAP_SYNC_INTERVAL = serializers.IntegerField(
required=False, default=24, allow_null=True, label=_('Cycle perform')
required=False, default=24, allow_null=True, label=_('Interval')
)
AUTH_LDAP_CONNECT_TIMEOUT = serializers.IntegerField(
min_value=1, max_value=300,
@ -90,7 +90,7 @@ class LDAPSettingSerializer(serializers.Serializer):
required=False, label=_('Recipient'), max_length=36
)
AUTH_LDAP = serializers.BooleanField(required=False, label=_('Enable LDAP auth'))
AUTH_LDAP = serializers.BooleanField(required=False, label=_('LDAP'))
@staticmethod
def post_save():

View File

@ -18,7 +18,7 @@ class OAuth2SettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('OAuth2')
AUTH_OAUTH2 = serializers.BooleanField(
default=False, label=_('Enable OAuth2 Auth')
default=False, label=_('OAuth2')
)
AUTH_OAUTH2_LOGO_PATH = SettingImageField(
allow_null=True, required=False, label=_('Logo')
@ -36,24 +36,24 @@ class OAuth2SettingSerializer(serializers.Serializer):
required=True, max_length=1024, label=_('Scope'), allow_blank=True
)
AUTH_OAUTH2_PROVIDER_AUTHORIZATION_ENDPOINT = serializers.CharField(
required=True, max_length=1024, label=_('Provider auth endpoint')
required=True, max_length=1024, label=_('Authorization endpoint')
)
AUTH_OAUTH2_ACCESS_TOKEN_ENDPOINT = serializers.CharField(
required=True, max_length=1024, label=_('Provider token endpoint')
required=True, max_length=1024, label=_('Token endpoint')
)
AUTH_OAUTH2_ACCESS_TOKEN_METHOD = serializers.ChoiceField(
default='GET', label=_('Client authentication method'),
default='GET', label=_('Request method'),
choices=(('GET', 'GET'), ('POST', 'POST-DATA'), ('POST_JSON', 'POST-JSON'))
)
AUTH_OAUTH2_PROVIDER_USERINFO_ENDPOINT = serializers.CharField(
required=True, max_length=1024, label=_('Provider userinfo endpoint')
required=True, max_length=1024, label=_('Userinfo endpoint')
)
AUTH_OAUTH2_PROVIDER_END_SESSION_ENDPOINT = serializers.CharField(
required=False, allow_blank=True, max_length=1024, label=_('Provider end session endpoint')
required=False, allow_blank=True, max_length=1024, label=_('End session endpoint')
)
AUTH_OAUTH2_LOGOUT_COMPLETELY = serializers.BooleanField(required=False, label=_('Logout completely'))
AUTH_OAUTH2_USER_ATTR_MAP = serializers.JSONField(
required=True, label=_('User attr map')
required=True, label=_('User attribute')
)
AUTH_OAUTH2_ALWAYS_UPDATE_USER = serializers.BooleanField(
default=True, label=_('Always update user')

View File

@ -13,7 +13,7 @@ class CommonSettingSerializer(serializers.Serializer):
# OpenID 公有配置参数 (version <= 1.5.8 或 version >= 1.5.8)
BASE_SITE_URL = serializers.CharField(
required=False, allow_null=True, allow_blank=True,
max_length=1024, label=_('Base site url')
max_length=1024, label=_('Base site URL')
)
AUTH_OPENID_CLIENT_ID = serializers.CharField(
required=False, max_length=1024, label=_('Client Id')
@ -27,14 +27,14 @@ class CommonSettingSerializer(serializers.Serializer):
('client_secret_basic', 'Client Secret Basic'),
('client_secret_post', 'Client Secret Post')
),
label=_('Client authentication method')
label=_('Request method')
)
AUTH_OPENID_SHARE_SESSION = serializers.BooleanField(required=False, label=_('Share session'))
AUTH_OPENID_IGNORE_SSL_VERIFICATION = serializers.BooleanField(
required=False, label=_('Ignore ssl verification')
required=False, label=_('Ignore SSL verification')
)
AUTH_OPENID_USER_ATTR_MAP = serializers.JSONField(
required=True, label=_('User attr map'),
required=True, label=_('User attribute'),
help_text=_('User attr map present how to map OpenID user attr to '
'jumpserver, username,name,email is jumpserver attr')
)
@ -51,7 +51,7 @@ class KeycloakSettingSerializer(CommonSettingSerializer):
label=_("Use Keycloak"), required=False, default=False
)
AUTH_OPENID_SERVER_URL = serializers.CharField(
required=False, max_length=1024, label=_('Server url')
required=False, max_length=1024, label=_('Server')
)
AUTH_OPENID_REALM_NAME = serializers.CharField(
required=False, max_length=1024, allow_null=True, label=_('Realm name')
@ -60,37 +60,37 @@ class KeycloakSettingSerializer(CommonSettingSerializer):
class OIDCSettingSerializer(KeycloakSettingSerializer):
# OpenID 新配置参数 (version >= 1.5.9)
AUTH_OPENID = serializers.BooleanField(required=False, label=_('Enable OPENID Auth'))
AUTH_OPENID = serializers.BooleanField(required=False, label=_('OIDC'))
AUTH_OPENID_PROVIDER_ENDPOINT = serializers.CharField(
required=False, max_length=1024, label=_('Provider endpoint')
)
AUTH_OPENID_PROVIDER_AUTHORIZATION_ENDPOINT = serializers.CharField(
required=False, max_length=1024, label=_('Provider auth endpoint')
required=False, max_length=1024, label=_('Authorization endpoint')
)
AUTH_OPENID_PROVIDER_TOKEN_ENDPOINT = serializers.CharField(
required=False, max_length=1024, label=_('Provider token endpoint')
required=False, max_length=1024, label=_('Token endpoint')
)
AUTH_OPENID_PROVIDER_JWKS_ENDPOINT = serializers.CharField(
required=False, max_length=1024, label=_('Provider jwks endpoint')
required=False, max_length=1024, label=_('JWKS endpoint')
)
AUTH_OPENID_PROVIDER_USERINFO_ENDPOINT = serializers.CharField(
required=False, max_length=1024, label=_('Provider userinfo endpoint')
required=False, max_length=1024, label=_('Userinfo endpoint')
)
AUTH_OPENID_PROVIDER_END_SESSION_ENDPOINT = serializers.CharField(
required=False, max_length=1024, label=_('Provider end session endpoint')
required=False, max_length=1024, label=_('End session endpoint')
)
AUTH_OPENID_PROVIDER_SIGNATURE_ALG = serializers.CharField(
required=False, max_length=1024, label=_('Provider sign alg')
required=False, max_length=1024, label=_('Signature algorithm')
)
AUTH_OPENID_PROVIDER_SIGNATURE_KEY = serializers.CharField(
required=False, max_length=1024, allow_null=True, label=_('Provider sign key')
required=False, max_length=1024, allow_null=True, label=_('Signing key')
)
AUTH_OPENID_SCOPES = serializers.CharField(required=False, max_length=1024, label=_('Scopes'))
AUTH_OPENID_ID_TOKEN_MAX_AGE = serializers.IntegerField(
required=False, label=_('Id token max age (s)')
required=False, label=_('ID Token max age (s)')
)
AUTH_OPENID_ID_TOKEN_INCLUDE_CLAIMS = serializers.BooleanField(
required=False, label=_('Id token include claims')
required=False, label=_('ID Token include claims')
)
AUTH_OPENID_USE_STATE = serializers.BooleanField(required=False, label=_('Use state'))
AUTH_OPENID_USE_NONCE = serializers.BooleanField(required=False, label=_('Use nonce'))

View File

@ -8,15 +8,15 @@ class PasskeySettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('Passkey')
AUTH_PASSKEY = serializers.BooleanField(
default=False, label=_('Enable passkey Auth'),
default=False, label=_('Passkey'),
help_text=_('Only SSL domain can use passkey auth')
)
FIDO_SERVER_ID = serializers.CharField(
max_length=255, label=_('FIDO server ID'), required=False, allow_blank=True,
max_length=255, label=_('FIDO Server ID'), required=False, allow_blank=True,
help_text=_(
'The hostname can using passkey auth, If not set, '
'will use request host and the request host in DOMAINS, '
'If multiple domains, use comma to separate'
)
)
FIDO_SERVER_NAME = serializers.CharField(max_length=255, label=_('FIDO server name'))
FIDO_SERVER_NAME = serializers.CharField(max_length=255, label=_('FIDO Server name'))

View File

@ -12,7 +12,7 @@ __all__ = ['RadiusSettingSerializer']
class RadiusSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('Radius')
AUTH_RADIUS = serializers.BooleanField(required=False, label=_('Enable Radius Auth'))
AUTH_RADIUS = serializers.BooleanField(required=False, label=_('Radius'))
RADIUS_SERVER = serializers.CharField(required=False, allow_blank=True, max_length=1024, label=_('Host'))
RADIUS_PORT = serializers.IntegerField(required=False, label=_('Port'))
RADIUS_SECRET = EncryptedField(

View File

@ -10,13 +10,13 @@ class SAML2SettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('SAML2')
AUTH_SAML2 = serializers.BooleanField(
default=False, required=False, label=_('Enable SAML2 Auth')
default=False, required=False, label=_('SAML2')
)
SAML2_IDP_METADATA_URL = serializers.URLField(
allow_blank=True, required=False, label=_('IDP metadata URL')
allow_blank=True, required=False, label=_('IDP Metadata URL')
)
SAML2_IDP_METADATA_XML = serializers.CharField(
allow_blank=True, required=False, label=_('IDP metadata XML')
allow_blank=True, required=False, label=_('IDP Metadata XML')
)
SAML2_SP_ADVANCED_SETTINGS = serializers.JSONField(
required=False, label=_('SP advanced settings')
@ -29,6 +29,6 @@ class SAML2SettingSerializer(serializers.Serializer):
allow_blank=True, required=False,
write_only=True, label=_('SP cert')
)
SAML2_RENAME_ATTRIBUTES = serializers.JSONField(required=False, label=_('Rename attr'))
SAML2_RENAME_ATTRIBUTES = serializers.JSONField(required=False, label=_('User attribute'))
SAML2_LOGOUT_COMPLETELY = serializers.BooleanField(required=False, label=_('Logout completely'))
AUTH_SAML2_ALWAYS_UPDATE_USER = serializers.BooleanField(required=False, label=_('Always update user'))

View File

@ -9,7 +9,7 @@ __all__ = ['SlackSettingSerializer']
class SlackSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('Slack')
AUTH_SLACK = serializers.BooleanField(default=False, label=_('Enable Slack Auth'))
AUTH_SLACK = serializers.BooleanField(default=False, label=_('Slack'))
SLACK_CLIENT_ID = serializers.CharField(max_length=256, required=True, label='Client ID')
SLACK_CLIENT_SECRET = EncryptedField(max_length=256, required=False, label='Client Secret')
SLACK_BOT_TOKEN = EncryptedField(max_length=256, required=False, label='Client bot Token')

View File

@ -14,7 +14,7 @@ __all__ = [
class SMSSettingSerializer(serializers.Serializer):
SMS_ENABLED = serializers.BooleanField(default=False, label=_('Enable SMS'))
SMS_ENABLED = serializers.BooleanField(default=False, label=_('SMS'))
SMS_BACKEND = serializers.ChoiceField(
choices=BACKENDS.choices, default=BACKENDS.ALIBABA, label=_('SMS provider / Protocol')
)
@ -70,10 +70,10 @@ class HuaweiSMSSettingSerializer(BaseSMSSettingSerializer):
class CMPP2SMSSettingSerializer(BaseSMSSettingSerializer):
CMPP2_HOST = serializers.CharField(max_length=256, required=True, label=_('Host'))
CMPP2_PORT = serializers.IntegerField(default=7890, label=_('Port'))
CMPP2_SP_ID = serializers.CharField(max_length=128, required=True, label=_('Enterprise code(SP id)'))
CMPP2_SP_SECRET = EncryptedField(max_length=256, required=False, label=_('Shared secret(Shared secret)'))
CMPP2_SRC_ID = serializers.CharField(max_length=256, required=False, label=_('Original number(Src id)'))
CMPP2_SERVICE_ID = serializers.CharField(max_length=256, required=True, label=_('Business type(Service id)'))
CMPP2_SP_ID = serializers.CharField(max_length=128, required=True, label=_('Enterprise code'))
CMPP2_SP_SECRET = EncryptedField(max_length=256, required=False, label=_('Shared secret'))
CMPP2_SRC_ID = serializers.CharField(max_length=256, required=False, label=_('Original number'))
CMPP2_SERVICE_ID = serializers.CharField(max_length=256, required=True, label=_('Business type'))
CMPP2_VERIFY_SIGN_NAME = serializers.CharField(max_length=256, required=True, label=_('Signature'))
CMPP2_VERIFY_TEMPLATE_CODE = serializers.CharField(
max_length=69, required=True, label=_('Template'),

View File

@ -9,7 +9,7 @@ __all__ = ['WeComSettingSerializer']
class WeComSettingSerializer(serializers.Serializer):
PREFIX_TITLE = _('WeCom')
WECOM_CORPID = serializers.CharField(max_length=256, required=True, label='corpid')
WECOM_AGENTID = serializers.CharField(max_length=256, required=True, label='agentid')
WECOM_SECRET = EncryptedField(max_length=256, required=False, label='secret')
AUTH_WECOM = serializers.BooleanField(default=False, label=_('Enable WeCom Auth'))
WECOM_CORPID = serializers.CharField(max_length=256, required=True, label='Corporation ID')
WECOM_AGENTID = serializers.CharField(max_length=256, required=True, label='App Agent ID')
WECOM_SECRET = EncryptedField(max_length=256, required=False, label='App Secret')
AUTH_WECOM = serializers.BooleanField(default=False, label=_('WeCom'))

Some files were not shown because too many files have changed in this diff Show More