chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
.github
|
||||
logs
|
||||
temp
|
||||
database
|
||||
doc
|
||||
http
|
||||
image
|
||||
.pytest_cache
|
||||
@@ -0,0 +1,15 @@
|
||||
# database
|
||||
DB_CONNECT_URL=mysql://root:123456@127.0.0.1:3306/data
|
||||
|
||||
# app mode: production / development
|
||||
APP_MODE=production
|
||||
|
||||
# allow commands eg:
|
||||
ALLOW_COMMANDS=/opt/nginx/sbin/nginx -s reload
|
||||
|
||||
# allow user register
|
||||
ENABLED_REGISTER=true
|
||||
|
||||
# shodan
|
||||
SHODAN_API_KEY=
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# 构建 VitePress 站点并将其部署到 GitHub Pages 的示例工作流程
|
||||
#
|
||||
name: Deploy VitePress site to Pages
|
||||
|
||||
on:
|
||||
# 在针对 `main` 分支的推送上运行。如果你
|
||||
# 使用 `master` 分支作为默认分支,请将其更改为 `master`
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
# 允许你从 Actions 选项卡手动运行此工作流程
|
||||
workflow_dispatch:
|
||||
|
||||
# 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# 只允许同时进行一次部署,跳过正在运行和最新队列之间的运行队列
|
||||
# 但是,不要取消正在进行的运行,因为我们希望允许这些生产部署完成
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# 构建工作
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # 如果未启用 lastUpdated,则不需要
|
||||
- uses: pnpm/action-setup@v3 # 如果使用 pnpm,请取消注释
|
||||
with:
|
||||
version: 8
|
||||
# - uses: oven-sh/setup-bun@v1 # 如果使用 Bun,请取消注释
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: pnpm # 或 pnpm / yarn
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile # 或 pnpm install / yarn install / bun install
|
||||
- name: Build with VitePress
|
||||
run: pnpm docs:build # 或 pnpm docs:build / yarn docs:build / bun run docs:build
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs/.vitepress/dist
|
||||
|
||||
# 部署工作
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# https://github.com/actions/starter-workflows/blob/main/ci/python-publish.yml
|
||||
name: Doc Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branch: ['master']
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: '3.8'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r doc/requirements.txt
|
||||
|
||||
- name: Build and publish
|
||||
run: |
|
||||
cd ./doc && make html && cd ../
|
||||
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@4.1.1
|
||||
with:
|
||||
branch: doc # The branch the action should deploy to.
|
||||
folder: doc/build/html # The folder the action should deploy.
|
||||
@@ -0,0 +1,71 @@
|
||||
# 参考
|
||||
# Build and push Docker images
|
||||
# https://github.com/marketplace/actions/build-and-push-docker-images
|
||||
# Docker Metadata action
|
||||
# https://github.com/marketplace/actions/docker-metadata-action
|
||||
# Multi-platform image with GitHub Actions
|
||||
# https://docs.docker.com/build/ci/github-actions/multi-platform/
|
||||
|
||||
name: Docker Publish
|
||||
|
||||
#on:
|
||||
# release:
|
||||
# types: [published]
|
||||
# push:
|
||||
# branch: ['master']
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: mouday/domain-admin
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: 'mouday/domain-admin-web'
|
||||
ref: 'dist'
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
path: 'domain_admin/public'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: 'mouday/domain-admin-mini'
|
||||
ref: 'dist'
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
path: 'domain_admin/public/m'
|
||||
|
||||
# remove .git
|
||||
- name: Remove .git Dir
|
||||
run: rm -rf '.git' 'domain_admin/public/.git' 'domain_admin/public/m/.git'
|
||||
|
||||
- name: Build Docker Image
|
||||
uses: docker/build-push-action@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -0,0 +1,86 @@
|
||||
# https://github.com/actions/starter-workflows/blob/main/ci/python-publish.yml
|
||||
# https://docs.pypi.org/trusted-publishers/adding-a-publisher/
|
||||
name: Upload Python Package
|
||||
|
||||
#on:
|
||||
# release:
|
||||
# types: [published]
|
||||
|
||||
#on:
|
||||
# push:
|
||||
# branch: ['master']
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/domain-admin
|
||||
|
||||
permissions:
|
||||
# IMPORTANT: this permission is mandatory for trusted publishing
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install setuptools wheel twine
|
||||
|
||||
# 拉取前端最新的dist代码
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: 'mouday/domain-admin-web'
|
||||
ref: 'dist'
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
path: 'domain_admin/public'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: 'mouday/domain-admin-mini'
|
||||
ref: 'dist'
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
path: 'domain_admin/public/m'
|
||||
|
||||
# remove .git
|
||||
- name: Remove .git Dir
|
||||
run: rm -rf '.git' 'domain_admin/public/.git' 'domain_admin/public/m/.git'
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
python setup.py sdist bdist_wheel --python-tag py2.py3
|
||||
twine check dist/*
|
||||
|
||||
# - name: Build and publish
|
||||
# if: startsWith(github.ref, 'refs/tags/')
|
||||
#
|
||||
# env:
|
||||
# TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
|
||||
# TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
|
||||
# run: |
|
||||
# python setup.py sdist bdist_wheel --python-tag py2.py3
|
||||
# twine check dist/*
|
||||
# twine upload dist/*
|
||||
# Publishing with a Trusted Publisher
|
||||
# https://docs.pypi.org/trusted-publishers/using-a-publisher/
|
||||
# https://github.com/pypa/gh-action-pypi-publish/
|
||||
- name: Publish package distributions to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
@@ -0,0 +1,104 @@
|
||||
#https://github.com/yyx990803/release-tag
|
||||
|
||||
name: Create Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
#on:
|
||||
# push:
|
||||
# branch: ['master']
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Master
|
||||
uses: actions/checkout@master
|
||||
|
||||
# 拉取web前端dist的代码
|
||||
- name: Checkout Web Dist
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: 'mouday/domain-admin-web'
|
||||
ref: 'dist'
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
path: 'domain_admin/public'
|
||||
|
||||
- name: Checkout Mini Dist
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: 'mouday/domain-admin-mini'
|
||||
ref: 'dist'
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
path: 'domain_admin/public/m'
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install setuptools wheel twine
|
||||
|
||||
- name: Build Dist
|
||||
run: |
|
||||
python setup.py sdist bdist_wheel --python-tag py2.py3
|
||||
|
||||
# 拉取前端最新的master代码
|
||||
- name: Checkout Web Master
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: 'mouday/domain-admin-web'
|
||||
ref: 'master'
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
path: 'domain_admin_web'
|
||||
|
||||
# https://github.com/a7ul/tar-action
|
||||
# https://docs.github.com/zh/actions/learn-github-actions/contexts#github-context
|
||||
- name: Compress action step Web
|
||||
uses: a7ul/tar-action@v1.1.0
|
||||
id: compress-web
|
||||
with:
|
||||
command: c
|
||||
cwd: ./
|
||||
files: |
|
||||
./domain_admin_web
|
||||
outPath: ./dist/domain-admin-web-${{github.ref_name}}.tar.gz
|
||||
|
||||
# 拉取H5前端最新的master代码
|
||||
- name: Checkout Mini Master
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: 'mouday/domain-admin-mini'
|
||||
ref: 'master'
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
path: 'domain_admin_mini'
|
||||
|
||||
# remove .git
|
||||
- name: Remove .git Dir
|
||||
run: rm -rf '.git' 'domain_admin/public/.git' 'domain_admin/public/m/.git'
|
||||
|
||||
- name: Compress action step Mini
|
||||
uses: a7ul/tar-action@v1.1.0
|
||||
id: compress-mini
|
||||
with:
|
||||
command: c
|
||||
cwd: ./
|
||||
files: |
|
||||
./domain_admin_mini
|
||||
outPath: ./dist/domain-admin-mini-${{github.ref_name}}.tar.gz
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: ./dist/*
|
||||
body: Please refer to [CHANGELOG.md](https://domain-admin.readthedocs.io/zh_CN/latest/manual/changelog.html) for details.
|
||||
# note you'll typically need to create a personal access token
|
||||
# with permissions to create releases in the other repo
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Upstream Sync
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync_with_upstream:
|
||||
name: Sync with Upstream
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.repository.fork }}
|
||||
|
||||
steps:
|
||||
- name: Checkout target repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Sync Upstream
|
||||
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
|
||||
with:
|
||||
target_repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
upstream_sync_repo: dromara/domain-admin
|
||||
upstream_sync_branch: master
|
||||
target_sync_branch: master
|
||||
test_mode: false
|
||||
|
||||
- name: Check for Failure
|
||||
if: failure()
|
||||
run: |
|
||||
echo "[Error] Due to a change in the workflow file of the upstream repository, GitHub has automatically suspended the scheduled automatic update. You need to manually sync your fork."
|
||||
exit 1
|
||||
@@ -0,0 +1,57 @@
|
||||
# 测试
|
||||
# doc: https://docs.github.com/zh/actions/automating-builds-and-tests/building-and-testing-python
|
||||
# https://github.com/actions/python-versions
|
||||
# https://github.com/actions/python-versions/blob/main/versions-manifest.json
|
||||
|
||||
name: Run tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
# Run in all these versions of Python
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
||||
# 排除版本
|
||||
exclude:
|
||||
- os: ubuntu-latest
|
||||
python-version: "3.5"
|
||||
- os: macos-latest
|
||||
python-version: "3.5"
|
||||
- os: macos-latest
|
||||
python-version: "3.7"
|
||||
|
||||
steps:
|
||||
# Checkout the latest code from the repo
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
# Setup which version of Python to use
|
||||
- name: Set Up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
# Display the Python version being used
|
||||
- name: Display Python version
|
||||
run: python -c "import sys; print(sys.version)"
|
||||
# Install pytest (you can use some other testing utility)
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements/development.txt
|
||||
# Install the package using the setup.py
|
||||
- name: Install package
|
||||
run: python setup.py install
|
||||
# Run the tests. I'm using pytest and the file is in the tests directory.
|
||||
- name: Run tests
|
||||
# run: pytest -c pytest.ini tests/api/test_index.py
|
||||
run: python -m unittest tests/test_hello.py
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
.idea
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.sqlite
|
||||
*.db
|
||||
build/
|
||||
domain_admin/public/
|
||||
dist/
|
||||
*.egg-info/
|
||||
*.log
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
.env
|
||||
config.yml
|
||||
logs/
|
||||
.deta
|
||||
/temp
|
||||
database
|
||||
.pytest_cache
|
||||
venv/
|
||||
node_modules/
|
||||
docs/.vitepress/dist/
|
||||
docs/.vitepress/cache/
|
||||
deploy-docs.sh
|
||||
@@ -0,0 +1,35 @@
|
||||
# Read the Docs configuration file for Sphinx projects
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the OS, Python version and other tools you might need
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.7"
|
||||
# You can also specify other tool versions:
|
||||
# nodejs: "20"
|
||||
# rust: "1.70"
|
||||
# golang: "1.20"
|
||||
|
||||
# Build documentation in the "docs/" directory with Sphinx
|
||||
sphinx:
|
||||
configuration: doc/source/conf.py
|
||||
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
|
||||
# builder: "dirhtml"
|
||||
# Fail on all warnings to avoid broken references
|
||||
# fail_on_warning: true
|
||||
|
||||
# Optionally build your docs in additional formats such as PDF and ePub
|
||||
# formats:
|
||||
# - pdf
|
||||
# - epub
|
||||
|
||||
# Optional but recommended, declare the Python requirements required
|
||||
# to build your documentation
|
||||
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
|
||||
python:
|
||||
install:
|
||||
- requirements: doc/requirements.txt
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
language: python
|
||||
|
||||
python:
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
|
||||
# command to install dependencies
|
||||
install:
|
||||
- pip install -r requirements/development.txt
|
||||
# command to run tests
|
||||
script:
|
||||
- pytest
|
||||
+821
@@ -0,0 +1,821 @@
|
||||
- v1.6.78(2026-06-07)
|
||||
- 修复多级域名问题
|
||||
|
||||
- v1.6.77(2026-05-24)
|
||||
- fix close #220
|
||||
- fix: 修复证书过期误报并优化不可达场景回退刷新 (#219)
|
||||
|
||||
- v1.6.76(2026-04-04)
|
||||
- fix: 根域名写入为空的问题
|
||||
- Merge branch 'master' of github.com:dromara/domain-admin
|
||||
- fix uk
|
||||
- feat: 新增单点登录功能 (#217)
|
||||
|
||||
- v1.6.75(2026-03-15)
|
||||
- fix getDomainInfoById not permission for admin
|
||||
|
||||
- v1.6.74(2025-12-22)
|
||||
- 增加升级脚本 for #211
|
||||
- feat: implement dns-host association and auto-renew result tracking (#211)
|
||||
- Update CMD to bind to IPv6 address (#210)
|
||||
|
||||
- v1.6.73(2025-10-19)
|
||||
- fix
|
||||
|
||||
- v1.6.72(2025-10-19)
|
||||
- 支持.lt的注册信息查询
|
||||
|
||||
- v1.6.71(2025-08-17)
|
||||
- add log params
|
||||
- add log
|
||||
- fix
|
||||
- fix
|
||||
- fix
|
||||
|
||||
- v1.6.70(2025-07-04)
|
||||
- refactor(issue_certificate): 重构证书续签的验证逻辑,避免重复创建订单 (#193)
|
||||
|
||||
- v1.6.69(2025-07-02)
|
||||
- feat(证书验证): 添加验证文件部署和DNS记录部署的可选参数 (#192)
|
||||
- Fix acme_v2_api.py (#188)
|
||||
|
||||
- v1.6.68(2025-06-04)
|
||||
- 移除多余代码
|
||||
|
||||
- v1.6.67(2025-06-04)
|
||||
- 新增 网址微信拦截状态检查
|
||||
- fix doc
|
||||
- check_wechat (#185)
|
||||
|
||||
- v1.6.66(2025-04-02)
|
||||
- Support FTP cert(port 21) (#176)
|
||||
|
||||
- v1.6.65(2025-03-13)
|
||||
- 支持纯ipv6域名做SSL证书到期检测 (#171)
|
||||
- fix
|
||||
|
||||
- v1.6.64(2025-02-07)
|
||||
- fix: DNS域名验证以及一键部署成功后,还是无法自动续期 #134 (#167)
|
||||
|
||||
- v1.6.63(2024-12-24)
|
||||
- 新增繁体中文
|
||||
|
||||
- v1.6.62(2024-12-20)
|
||||
- 修复.tw后缀域名无法查询 fix #159
|
||||
|
||||
- v1.6.61(2024-11-23)
|
||||
- Cert Saver, ordered by expire_time (#153)
|
||||
|
||||
- v1.6.60(2024-11-17)
|
||||
- 优化:主机列表/DNS列表接口密码返回为空 fix:#149
|
||||
|
||||
- v1.6.59(2024-11-05)
|
||||
- doc: dingtalk webhook (#146)
|
||||
- 修复普通用户网站监测日志查看权限
|
||||
|
||||
- v1.6.58(2024-11-02)
|
||||
- fix 升级失败
|
||||
- fix
|
||||
|
||||
- v1.6.57(2024-11-02)
|
||||
- 新增:网站监测支持超时时间到毫秒级
|
||||
- 修复证书文件导入失败的问题
|
||||
|
||||
- v1.6.55(2024-11-01)
|
||||
- 修复多ip证书获取问题
|
||||
|
||||
- v1.6.54(2024-10-31)
|
||||
- 修复服务部署后访问地址是域名加子路径时,导出会报错
|
||||
|
||||
- v1.6.53(2024-10-27)
|
||||
- 修复修改域名时提示数据不存在的问题 #141
|
||||
- 修复普通用户分组关联无法搜索的问题
|
||||
|
||||
- v1.6.52(2024-10-20)
|
||||
- 通过api部署托管证书
|
||||
|
||||
- v1.6.51(2024-09-27)
|
||||
- fix:www的域名查询不到证书过期时间信息
|
||||
- 优化显示
|
||||
- 增加允许注册判断
|
||||
|
||||
- v1.6.50(2024-09-21)
|
||||
- 修改文档
|
||||
- 支持用户注册
|
||||
|
||||
- v1.6.49(2024-09-08)
|
||||
- move project to dromara
|
||||
|
||||
- v1.6.48(2024-08-28)
|
||||
- bugfix:修复管理员提示无权限问题
|
||||
|
||||
- v1.6.47(2024-08-25)
|
||||
- 修复zerossl无法申请的问题
|
||||
- fix #126
|
||||
- 版本升级
|
||||
- 修改权限策略
|
||||
- add zerossl
|
||||
- 优化权限控制
|
||||
|
||||
- v1.6.46(2024-07-28)
|
||||
- 优化:SSH秘钥兼容常见RSA,DSS,ECDSA,Ed5519算法 (#124)
|
||||
- 完善文档
|
||||
|
||||
- v1.6.45(2024-07-26)
|
||||
- 修复1.6.32 升级失败的问题
|
||||
|
||||
- v1.6.44(2024-07-25)
|
||||
- 新增证书部署到阿里云CDN和DCND
|
||||
|
||||
- v1.6.43(2024-07-25)
|
||||
- 优化参数
|
||||
- 新增 证书部署到阿里云oss
|
||||
- 完善文档
|
||||
|
||||
- v1.6.42(2024-07-22)
|
||||
- fix:not found directory_url
|
||||
|
||||
- v1.6.41(2024-07-22)
|
||||
- fix: type must be an integer
|
||||
|
||||
- v1.6.40(2024-07-10)
|
||||
- 修复域名icp查询超时问题
|
||||
- 增加证书到期邮件提醒备注字段
|
||||
- 修复 webhook部署不能自动续期问题
|
||||
|
||||
- v1.6.39(2024-07-05)
|
||||
- 修改证书导出命名格式 fix#113
|
||||
|
||||
- v1.6.38(2024-07-05)
|
||||
- 修复通配符域名多级子域名判定错误
|
||||
|
||||
- v1.6.37(2024-07-03)
|
||||
- 修复低版本升级到v1.6.35失败的问题
|
||||
- 更新文档
|
||||
|
||||
- v1.6.36(2024-06-28)
|
||||
- 支持dns账号:腾讯云
|
||||
|
||||
- v1.6.35(2024-06-27)
|
||||
- 新增多种签发机构
|
||||
- 修复DNS添加TXT报错提示问题
|
||||
|
||||
- v1.6.34(2024-06-25)
|
||||
- 新增 通过账号添加dns的TXT记录
|
||||
- 支持 dns验证和api部署的自动续期
|
||||
|
||||
- v1.6.33(2024-06-23)
|
||||
- 支持DNS账号部署
|
||||
- 新增DNS账户管理
|
||||
|
||||
- v1.6.32(2024-06-07)
|
||||
- 增加电报通知
|
||||
- 完善文档
|
||||
|
||||
- v1.6.31(2024-05-07)
|
||||
- 新增 网站监控异常恢复通知事件
|
||||
|
||||
- v1.6.30(2024-04-23)
|
||||
- 域名列表新增导入子域证书按钮
|
||||
|
||||
- v1.6.29(2024-04-22)
|
||||
- 修复 证书申请文件验证失败问题
|
||||
- remove git
|
||||
|
||||
- v1.6.28(2024-04-18)
|
||||
- 修复 sqlite数据库域名监控无法导入的问题
|
||||
|
||||
- v1.6.27(2024-04-16)
|
||||
- 尝试修复运行超时数据库链接断开问题
|
||||
|
||||
- v1.6.26(2024-04-11)
|
||||
- 优化 修改秘钥字段长度
|
||||
- 修复 企业微信通知配置提示isJson is not defined fix:#98
|
||||
|
||||
- v1.6.25(2024-04-08)
|
||||
- 修复:证书一键部署失败的问题
|
||||
- 修复:证书申请后自动监控过滤通配符的域名
|
||||
|
||||
- v1.6.24(2024-04-08)
|
||||
- 修复 域名监控/域名列表排序问题
|
||||
|
||||
- v1.6.23(2024-04-03)
|
||||
- 修复 域名监控/域名列表 自动更新字段排序无效问题
|
||||
- 修复 工具箱/证书托管天数排序无效的问题
|
||||
|
||||
- v1.6.22(2024-04-02)
|
||||
- 修复 自定义指令为空时的处理问题
|
||||
|
||||
- v1.6.21(2024-04-01)
|
||||
- 增加 自动更新whois文件的机制 fix:#95
|
||||
- 更新whois list
|
||||
- 新增 支持用户自定义配置的命令
|
||||
- 完善文档
|
||||
|
||||
- v1.6.20(2024-03-31)
|
||||
- 新增 托管证书一键部署
|
||||
- 完善文档
|
||||
|
||||
- v1.6.19(2024-03-27)
|
||||
- 增加证书检测重试次数,避免错误检测
|
||||
|
||||
- v1.6.18(2024-03-26)
|
||||
- 修复 手动编辑域名或证书到期时间,天数筛选不匹配的问题
|
||||
|
||||
- v1.6.17(2024-03-22)
|
||||
- 新增:托管证书到期提醒 #69
|
||||
- 新增 远程api部署ssl证书
|
||||
|
||||
- v1.6.16(2024-03-19)
|
||||
- 优化:证书托管弹框点击遮罩不关闭
|
||||
|
||||
- v1.6.15(2024-03-05)
|
||||
- 退登自动跳转到登录界面
|
||||
- 完善文档
|
||||
- 修复日志不滚动的问题
|
||||
- 优化证书dns验证txt记录key显示,避免误导使用者
|
||||
|
||||
- v1.6.14(2024-02-27)
|
||||
- 邮件配置时支持邮件发送测试
|
||||
- 退登自动跳转到登录界面
|
||||
|
||||
- v1.6.13(2024-02-26)
|
||||
- bugfix: 如果用户删除分组后,分组数据不存在会出现null问题
|
||||
|
||||
- v1.6.12(2024-02-25)
|
||||
- 支持证书托管
|
||||
- 新增网站监控列表批量删除
|
||||
|
||||
- v1.6.11(2024-02-24)
|
||||
- 优化网站监控异常提示信息
|
||||
- 网站监控增加错误重试次数
|
||||
- 网站监控支持导入导出
|
||||
- 优化耗时显示
|
||||
|
||||
- v1.6.10(2024-02-21)
|
||||
- fix:修复分组关联域名弹框分页数量无法更改的问题
|
||||
- fix:修复触发分组新增分组后无法自动勾选问题
|
||||
- 增加证书列表批量操作
|
||||
- fix:修复手动设置启动网站监测状态后不运行的问题
|
||||
|
||||
- v1.6.9(2024-02-20)
|
||||
- 支持自定义主机ip地址不清空
|
||||
|
||||
- v1.6.8(2024-02-19)
|
||||
- 完善大屏统计数据
|
||||
- 优化兼容性
|
||||
- 增加网站探活请求头
|
||||
|
||||
- v1.6.7(2024-02-06)
|
||||
- 移除预安装依赖:psycopg2
|
||||
|
||||
- v1.6.6(2024-02-03)
|
||||
- 新增域名修改时搜索已有标签
|
||||
- 优化代码备注
|
||||
|
||||
- v1.6.5(2024-02-02)
|
||||
- 修复到期邮件发送失败的bug
|
||||
|
||||
- v1.6.4(2024-02-01)
|
||||
- fix:修复icp缓存不过期问题
|
||||
- 支持域名icp信息的导入
|
||||
- fix:修复timeout类型转换错误的bug
|
||||
- 更新文档
|
||||
|
||||
- v1.6.3(2024-01-31)
|
||||
- 修复监控列表删除bug
|
||||
- 添加标签表
|
||||
- 增加icp查询缓存
|
||||
- 文件导入域名自动更新子域证书列表
|
||||
|
||||
- v1.6.2(2024-01-29)
|
||||
- 增加首页控制台数据接口
|
||||
- 修复时间显示错误问题
|
||||
- 更新备注
|
||||
- 替换备案查询接口
|
||||
- 优化时间统计
|
||||
- 完善问文档
|
||||
|
||||
- v1.6.1(2024-01-28)
|
||||
- 添加网站监控
|
||||
|
||||
- v1.5.39(2024-01-27)
|
||||
- fix
|
||||
|
||||
- v1.5.38(2024-01-24)
|
||||
- fix
|
||||
|
||||
- v1.5.37(2024-01-23)
|
||||
- 移除icp查询的第三方接口
|
||||
- fix:修复批量导入域名端口为0的情况
|
||||
- 完善文档
|
||||
|
||||
- v1.5.36(2023-11-30)
|
||||
- 新增excel格式的导入导出
|
||||
|
||||
- v1.5.35(2023-11-29)
|
||||
- fix:TypeError: 'NoneType' object is not iterable
|
||||
- 完善文档
|
||||
|
||||
- v1.5.34(2023-11-29)
|
||||
- 支持通知指定特定分组
|
||||
- 新增主机列表移除功能
|
||||
- docs: 增加docker-compose启动 (#73)
|
||||
|
||||
- v1.5.33(2023-11-28)
|
||||
- bugfix: 重载docker版nginx (#72)
|
||||
- 完善文档
|
||||
|
||||
- v1.5.32(2023-11-28)
|
||||
- 优化数据库迁移升级配置
|
||||
- 证书部署支持非默认的SSH连接端口
|
||||
- 完善文档
|
||||
|
||||
- v1.5.31(2023-11-22)
|
||||
- 完善注释
|
||||
- fix: 新增docker和红帽系reload (#70)
|
||||
|
||||
- v1.5.30(2023-11-22)
|
||||
- 修复命令无法取到的问题
|
||||
|
||||
- v1.5.29(2023-11-22)
|
||||
- 证书自动部署功能新增docker 下的重启命令
|
||||
- 完善文档
|
||||
|
||||
- v1.5.28(2023-11-21)
|
||||
- prometheus metrics接口新增全部域名字段
|
||||
- 完善文档
|
||||
|
||||
- v1.5.27(2023-11-21)
|
||||
- 新增prometheus接口/metrics域名数据
|
||||
|
||||
- v1.5.25(2023-10-23)
|
||||
- 修复 证书申请列表无法搜索的问题
|
||||
- 增加开发环境debug日志
|
||||
- 完善文档
|
||||
|
||||
- v1.5.24(2023-09-17)
|
||||
- 移除flask json默认序列化
|
||||
- 移除动态主机选项
|
||||
- 新增SSL加密方式:STARTTLS
|
||||
- 完善文档
|
||||
|
||||
- v1.5.23(2023-09-12)
|
||||
- 新增:证书查询工具字段:证书品牌和证书类型
|
||||
- 优化:自动识别动态主机
|
||||
- 完善文档
|
||||
|
||||
- v1.5.22(2023-09-07)
|
||||
- 优化:域名导入,支持标签导入
|
||||
- 优化:证书部署出于安全考虑,仅能使用预设的命令
|
||||
- 优化:命令白名单
|
||||
- 优化 flask 响应处理
|
||||
- 完善文档
|
||||
|
||||
- v1.5.21(2023-08-30)
|
||||
- fix #54 修复 .kr域名的信息查询
|
||||
- fix #52 证书列表同域名不同端口无法添加
|
||||
- fix #53 register 接口从白名单移除
|
||||
- 优化 证书申请域名验证通过后自动加入到证书监控列表
|
||||
- 完善文档
|
||||
|
||||
- v1.5.20(2023-08-25)
|
||||
- 优化:企业微信、飞书、钉钉通知支持模板变量
|
||||
- 优化前端UI显示,将ssl证书申请移动到左侧菜单栏
|
||||
- 完善文档
|
||||
|
||||
- v1.5.19(2023-08-24)
|
||||
- 优化:增加admin使用系统给定的默认密码用户提示
|
||||
- 完善文档
|
||||
|
||||
- v1.5.18(2023-08-23)
|
||||
- 优化 增加webhook模板参数
|
||||
- 完善文档
|
||||
|
||||
- v1.5.17(2023-08-22)
|
||||
- fix: 定时任务OperationalError: (2006, ‘MySQL server has gone away’)
|
||||
- 更新最新版本截图
|
||||
- 完善文档
|
||||
|
||||
- v1.5.16(2023-08-21)
|
||||
- 优化:细化通知触发类型日志
|
||||
- 优化:前端时间显示
|
||||
- 优化:证书申请服务器地址自动匹配问题
|
||||
- 优化:邮件通知宽度调整
|
||||
- 完善文档
|
||||
|
||||
- v1.5.15(2023-08-16)
|
||||
- 修复 cron表达式星期参数的bug
|
||||
- 完善文档
|
||||
|
||||
- v1.5.14(2023-08-15)
|
||||
- 优化 证书、域名数据导出支持条件和排序
|
||||
- 完善文档
|
||||
|
||||
- v1.5.13(2023-08-04)
|
||||
- 新增 部分的英文界面支持
|
||||
- 新增 主机私钥文件认证方式
|
||||
- 新增 手动续期SSL证书
|
||||
- 完善文档
|
||||
|
||||
- v1.5.12(2023-08-02)
|
||||
- 优化 捕获后台任务异常
|
||||
- 优化 重新申请证书执行流程
|
||||
- 优化 日志执行状态显示
|
||||
- 优化 通知测试使用用户真实数据
|
||||
- 优化 记录通知日志
|
||||
- 修复 .im域名信息查询
|
||||
- 完善 文档
|
||||
|
||||
- v1.5.11(2023-08-01)
|
||||
- 新增 证书申请后查看证书文件的功能
|
||||
- 优化 完善文档
|
||||
|
||||
- v1.5.10(2023-07-31)
|
||||
- 完善 接口文档
|
||||
- 修复 证书申请后浏览器显示不受信任问题
|
||||
|
||||
- v1.5.9(2023-07-30)
|
||||
- 增加SSL证书DNS验证,远程部署,自动续期
|
||||
- 修复 查询SSL证书信息剩余时间错误的问题
|
||||
- 优化 完善文档
|
||||
|
||||
- v1.5.8(2023-07-24)
|
||||
- 优化 用户输入的cron表达式有多余的空格
|
||||
- 优化 SSL证书申请添加必要的提示
|
||||
|
||||
- v1.5.7(2023-07-24)
|
||||
- 优化 完善部分文档
|
||||
- 修复 邮件收件人不显示的问题
|
||||
- 修复 执行时间计算错误的bug
|
||||
|
||||
- v1.5.6(2023-07-23)
|
||||
- 新增 基于Webroot的Let’s Encrypt的SSL证书申请
|
||||
- 优化 邮件通知模板
|
||||
- 优化 发布前端代码文件
|
||||
|
||||
- v1.5.5(2023-07-22)
|
||||
- 优化 接入新icp接口
|
||||
- 新增 数据管理支持负责人字段修改
|
||||
- 新增 添加角色字段
|
||||
- 优化 完善项目文档
|
||||
|
||||
- v1.5.4(2023-07-22)
|
||||
- 新增 域名列表自定义显示列
|
||||
- 新增 更新icp备案字段功能
|
||||
- 新增 工具箱icp备案查询功能
|
||||
- 优化 完善项目文档
|
||||
|
||||
- v1.5.3(2023-07-20)
|
||||
- 优化 记录异步任务日志
|
||||
- 修复 Py2导入证书失败问题
|
||||
- 新增 重置用户密码
|
||||
- 新增 批量补全域名icp信息
|
||||
- 新增 异步任务日志记录
|
||||
- 修复 自动发布代码滞后的问题
|
||||
|
||||
- v1.5.2(2023-07-20)
|
||||
- 修复 Py2下导出异常
|
||||
- 新增 域名支持更新标签字段
|
||||
- 新增 导出文件添加标签和备案信息字段
|
||||
- 修复 Py2下代码抛出的异常返回不正确
|
||||
- 新增 域名添加和更新接口支持标签和备案信息
|
||||
- 新增 数据表升级v1.5.1=>v1.5.2
|
||||
- 优化 增加需求记录
|
||||
|
||||
- v1.5.1(2023-07-16)
|
||||
- 修复 域名列表点击证书数量跳转错误
|
||||
|
||||
- v1.5.0(2023-07-16)
|
||||
- 新增 清空操作日志
|
||||
- 修复 Python2证书查询失败问题
|
||||
- 新增 数据管理功能
|
||||
- 优化 文档新增k8s自动获取到ingress的域名到domain-admin
|
||||
- 优化 添加py2兼容性备注
|
||||
|
||||
- v1.4.36(2023-07-14)
|
||||
- 优化 自动写入CHANGELOG
|
||||
|
||||
- v1.4.35(2023-07-13)
|
||||
- 修复 子账号无法使用子域名查询和whois的的查询功能bug
|
||||
- 优化 smtp的配置密码加密处理
|
||||
- 新增 工具箱 `证书信息查询`
|
||||
|
||||
- v1.4.34(2023-07-11)
|
||||
- 修复 前端页面`极客` 模式下左侧导航栏样式异常的bug [issues#42](https://github.com/mouday/domain-admin/issues/42)
|
||||
- 优化 prometheus 接口 `/metrics` 新增返回参数:root_domain、group_name
|
||||
|
||||
- v1.4.33(2023-07-11)
|
||||
- 修复 多实例同时启动版本号写入失败bug
|
||||
- 优化 移除部分无用代码
|
||||
|
||||
- v1.4.32(2023-07-10)
|
||||
- 修复 部分代码在Python2版本下兼容性问题
|
||||
- 新增 工具箱目录下新页面:域名子域名查询
|
||||
- 新增 添加域名时可选项:子域证书,可以自动添加该域名下子域名到证书列表 [issues#41](https://github.com/mouday/domain-admin/issues/41)
|
||||
|
||||
- v1.4.31(2023-07-09)
|
||||
- 修复 只读用户无法使用搜索功能bug
|
||||
- 优化 手动编辑更新证书信息接口时间过长的问题
|
||||
- 优化 录入域名的时候可以手动设置`自动更新`字段
|
||||
|
||||
- v1.4.30(2023-07-09)
|
||||
- 修复 域名列表查看详情后列表显示无权限编辑的bug
|
||||
|
||||
- v1.4.29(2023-07-09)
|
||||
- 新增 支持Python版本:Python >= 2.7 或者 Python >= 3.4
|
||||
|
||||
- v1.4.28(2023-07-08)
|
||||
- 新增 域名后缀的信息查询: ws, cm, by [issues#39](https://github.com/mouday/domain-admin/issues/39)
|
||||
|
||||
- v1.4.27(2023-07-08)
|
||||
- 新增 移动端H5页面
|
||||
|
||||
- v1.4.26(2023-07-07)
|
||||
- 修复 久远的旧版本升级到新版本出错的问题
|
||||
|
||||
- v1.4.25(2023-07-07)
|
||||
- 修复 定时任务提示missed ,没执行的问题
|
||||
- 修复 前端页面`极客`模式下左侧导航栏,切换的时候会闪一次方块UI异常的问题
|
||||
- 新增 前端顶部栏增加`工具箱`入口
|
||||
|
||||
- v1.4.24(2023-07-06)
|
||||
- 优化 证书列表编辑框支持修改证书时间
|
||||
- 新增 分组权限控制,每个用户可以属于多个组,组权限分为 write和read(write可以添加编辑删除,read只允许查看)
|
||||
|
||||
- v1.4.23(2023-07-05)
|
||||
- 修复 用户通知方式只能添加一个的bug
|
||||
|
||||
- v1.4.22(2023-07-05)
|
||||
- 修复 证书编辑接口`参数缺失'id'` 的bug
|
||||
|
||||
- v1.4.21(2023-07-04)
|
||||
- 新增 证书表、域名表、分组表、通知表操作日志
|
||||
- 新增 前端新增`操作日志` 列表
|
||||
- 优化 前端`分组列表` 新增ID字段
|
||||
|
||||
- v1.4.20(2023-07-03)
|
||||
- 修复 `.sg`后缀域名过期时间获取不到的问题
|
||||
- 优化 前端界面`主办单位名称` 字段显示不全问题
|
||||
|
||||
- v1.4.19(2023-07-01)
|
||||
- 修复 过期时间为`null` 未触发通知提醒的问题
|
||||
- 优化 前端路由无权限却进入页面的问题
|
||||
- 优化 拆分前端系统邮箱配置和cron定时配置
|
||||
- 优化 前端提交cron表达式验证和显示执行计划
|
||||
|
||||
- v1.4.18(2023-06-30)
|
||||
- 修复 邮件发送失败问题
|
||||
|
||||
- v1.4.17(2023-06-30)
|
||||
- 优化 支持发送匿名邮件
|
||||
- 新增 域名详情页添加ICP备案查询
|
||||
|
||||
- v1.4.16(2023-06-30)
|
||||
- 修复 `.co` 域名服务商`registrar_url` 字段缺少`http://` 的问题
|
||||
|
||||
- v1.4.15(2023-06-30)
|
||||
- 修复 `.co` 域名服务商`registrar_url` 字段缺少`http://` 的问题
|
||||
|
||||
- v1.4.14(2023-06-30)
|
||||
- 新增 域名`注册商` 字段,感谢 [@mjwtc0722](https://github.com/mjwtc0722) 贡献的部分代码
|
||||
|
||||
- v1.4.13(2023-06-30)
|
||||
- 修复 添加prometheus-client依赖
|
||||
|
||||
- v1.4.12(2023-06-30)
|
||||
- 新增 支持`prometheus` 的`/metrics`数据获取接口
|
||||
|
||||
- v1.4.11(2023-06-29)
|
||||
- 新增 分组名称批量删除
|
||||
- 新增 证书列表和域名列表支持`分组`字段导入
|
||||
|
||||
- v1.4.10(2023-06-29)
|
||||
- 优化 增加`.tw`后缀域名的支持
|
||||
|
||||
- v1.4.9(2023-06-26)
|
||||
- 优化 增加域名信息更新条件,到期时间 `<30` 才更新,减少无效更新
|
||||
- 优化 移除未使用的接口和代码
|
||||
|
||||
- v1.4.8(2023-06-25)
|
||||
- 优化 证书和域名列表的分组筛选器
|
||||
- 优化 证书列表增加`域名天数` 列
|
||||
- 修复 删除主机地址后,证书数据未更新的问题
|
||||
- 修复 docker缺失mysql驱动的问题
|
||||
|
||||
- v1.4.7(2023-06-23)
|
||||
- 优化 获取证书支持:IP证书、多域名证书、通配符证书
|
||||
- 优化 移除未使用的域名信息缓存表`cache_domain_info`
|
||||
|
||||
- v1.4.6(2023-06-22)
|
||||
- 新增 通知方式增加备注字段
|
||||
- 新增 通知方式新增钉钉、飞书渠道
|
||||
|
||||
- v1.4.5(2023-06-22)
|
||||
- 修复 证书列表和域名列表`监测` 列排序无效问题
|
||||
- 优化 通知方式列表支持排序
|
||||
|
||||
- v1.4.4(2023-06-22)
|
||||
- 新增 批量删除主机地址
|
||||
- 优化 支持多个通知渠道
|
||||
|
||||
- v1.4.3(2023-06-20)
|
||||
- 修复 邮件通知测试报错 send_domain_list_email() missing 1 required positional argument: 'rows'
|
||||
|
||||
- v1.4.2(2023-06-20)
|
||||
- 修复 删除证书报错 type object 'DomainModel' has no attribute 'domain_id'
|
||||
|
||||
- v1.4.1(2023-06-20)
|
||||
- 修复 前端部分显示问题
|
||||
- 修复 字段domain_expire_monitor缺失问题
|
||||
|
||||
- v1.4.0(2023-06-20)
|
||||
- 优化 移除未使用到的历史字段
|
||||
- 修复 前端局部更新异常的问题
|
||||
|
||||
- v1.4.0-alpha(2023-06-14)
|
||||
- 新增 域名监控和SSL证书监控分离
|
||||
- 新增 证书监控支持动态IP
|
||||
- 修复 Bug: 'AddressModel' object has no attribute 'address_id'
|
||||
|
||||
- v1.3.6(2023-06-13)
|
||||
- 修复 ipv6查询失败问题,暂时隐藏ipv6
|
||||
- 新增 支持自定义配置数据库链接方式
|
||||
|
||||
- v1.3.5(2023-06-13)
|
||||
- 修复 添加域名时查询证书失败问题
|
||||
|
||||
- v1.3.4(2023-06-09)
|
||||
- 新增 前端显示版本号
|
||||
|
||||
- v1.3.3(2023-06-07)
|
||||
- 修复 添加非443端口超时问题
|
||||
- 添加前端UI多风格界面
|
||||
- 优化部分问题
|
||||
|
||||
- v1.3.2(2023-06-04)
|
||||
- 修复 升级失败问题,修改字段port默认值为443
|
||||
|
||||
- v1.3.1(2023-06-04)
|
||||
- 新增 支持一个域名解析到多主机ip地址的SSL证书查询
|
||||
- 优化 优化前端界面显示
|
||||
|
||||
- v1.2.23(2023-06-01)
|
||||
- 新增 实验室tab,增加查询whois原始信息功能
|
||||
|
||||
- v1.2.22(2023-05-30)
|
||||
- 新增 中文域名的支持
|
||||
|
||||
- v1.2.21(2023-05-30)
|
||||
- 修复:`ModuleNotFoundError: No module named 'pytz_deprecation_shim'`
|
||||
- 移除依赖`pytz`
|
||||
- 固定依赖版本号
|
||||
|
||||
- v1.2.18(2023-05-29)
|
||||
- 新增:增加对.sg后缀的域名到期时间支持,感谢[@poctopus](https://github.com/poctopus)贡献代码
|
||||
|
||||
- v1.2.17(2023-05-17)
|
||||
- 新增:增加对.jp后缀的域名到期时间支持
|
||||
|
||||
- v1.2.16(2023-04-29)
|
||||
- 新增:增加筛选功能: 筛选域名状态、证书状态、网站状态
|
||||
- 新增:增加批量删除域名的功能
|
||||
|
||||
- v1.2.15(2023-04-28)
|
||||
- 优化:更新whois-servers.txt,感谢[@poctopus](https://github.com/poctopus)贡献代码
|
||||
|
||||
- v1.2.14(2023-04-28)
|
||||
- 修复:邮件正文字段空白的问题
|
||||
|
||||
- v1.2.13(2023-04-26)
|
||||
- 新增:支持内网用户自己设置域名过期时间、证书过期时间
|
||||
|
||||
- v1.2.12(2023-04-22)
|
||||
- 优化:修改域名信息和证书信息更新缓存机制,避免频繁更新导致请求失败
|
||||
- 修复:没有设置邮件导致webhook失效的问题
|
||||
|
||||
- v1.2.11(2023-04-20)
|
||||
- 更新whois-servers.txt,感谢[@poctopus](https://github.com/poctopus)贡献代码
|
||||
|
||||
- v1.2.10 (2023-04-16)
|
||||
- 新增域名后缀的支持:.hk
|
||||
|
||||
- v1.2.9 (2023-04-16)
|
||||
- 新增域名后缀的支持:.tv .live .info .top .xyz .biz .net等等
|
||||
|
||||
- v1.2.8 (2023-04-08)
|
||||
- 优化:移除前端打包后的静态文件
|
||||
|
||||
- v1.2.4(2023-04-07)
|
||||
- 新增:分组批量关联域名
|
||||
- 优化:域名导出新增分组字段
|
||||
- 优化:域名导入支持txt(仅域名字段)、csv(域名、备注)
|
||||
- 修复:域名编辑分组的时候,看不到第10个以后的分组名字。
|
||||
- 优化:域名分组列表ID改为序号
|
||||
|
||||
- v1.2.3(2023-04-05)
|
||||
- 优化:支持docker多平台
|
||||
|
||||
- v1.2.2(2023-04-04)
|
||||
- 修复:前端域名列表只有一条数据,分页下拉和编辑“分组”下拉列表打不开
|
||||
|
||||
- v1.2.1(2023-04-04)
|
||||
- 修复:前端域名列表删除操作bug
|
||||
|
||||
- v1.2.0(2023-04-04)
|
||||
- 新增:支持域名分组
|
||||
- 优化:异步操作的前端状态显示
|
||||
- 优化:优化前端域名列表字段显示
|
||||
|
||||
- v1.1.10(2023-04-03)
|
||||
- 修复:部分域名剩余天数检测失败的问题
|
||||
- 优化:查询域名之前进行域名优化处理,减少查询请求
|
||||
- 优化:增加相同域名查询缓存,减少查询请求
|
||||
|
||||
- v1.1.9(2023-03-31)
|
||||
- 新增:webhook通知方式,支持jinja2语法
|
||||
- 发布GitHub Pages
|
||||
|
||||
- v1.1.8(2023-03-28)
|
||||
- 修改自动发布action的触发方式
|
||||
|
||||
- v1.1.7(2023-03-28)
|
||||
- 添加自动发布action
|
||||
|
||||
- 1.1.6
|
||||
- bugfix: 用户删除token过期天数变量后登录报错
|
||||
|
||||
- 1.1.5
|
||||
- 移除whois依赖,使用socket方式替代
|
||||
- 前端域名列表增加字段排序
|
||||
|
||||
- 1.1.4
|
||||
- 修复Linux二级域名报错问题
|
||||
|
||||
- 1.1.3
|
||||
- 修复二级域名查询失败的问题
|
||||
- 优化域名排序显示
|
||||
|
||||
- 1.1.2
|
||||
- 暂时使用whois模块实现域名过期时间查询
|
||||
|
||||
- 1.1.1
|
||||
- 修复前端备注不显示的问题
|
||||
- 移除whois模块,因为它依赖whois/whois.exe
|
||||
|
||||
- 1.1.0
|
||||
- 优化日志输出文件大小,减少磁盘空间占用
|
||||
- 新增域名到期时间监测,感谢群友 @Roy 提出的建议
|
||||
- 移除前端不必要的信息显示
|
||||
|
||||
- 1.0.6
|
||||
- 新增域名备注
|
||||
|
||||
- 1.0.5
|
||||
- 新增通知方式:企业微信
|
||||
|
||||
- 1.0.4
|
||||
- 修复 `1.0.0=> 1.0.3` 自动更新异常
|
||||
|
||||
- 1.0.3
|
||||
- 新增单个域名的检测开关,可控制单个域名的证书监测
|
||||
|
||||
- 1.0.2
|
||||
- 添加SMTP STARTTLS 支持 587端口,感谢[@kudosiscon](https://github.com/kudosiscon)贡献的代码
|
||||
|
||||
- 1.0.1
|
||||
- 修改批量更新方式为异步更新,避免接口超时
|
||||
|
||||
- 1.0.0
|
||||
- 修复前端批量导入域名按钮只能导入一次的问题
|
||||
- 增加options请求缓存时间,减少请求
|
||||
- 增加网站连接状态默认值为:未知(黄色)
|
||||
- 修改批量导入为异步导入执行,导入测试文件大小11.8M
|
||||
- 修复导入域名解析错误
|
||||
|
||||
- 0.0.18
|
||||
- 修复部分公司邮件校验失败的问题
|
||||
- 修复添加异常域名后系统奔溃的问题
|
||||
|
||||
- v0.017
|
||||
- 通过openssl支持自签名证书
|
||||
|
||||
- v0.0.16
|
||||
- 支持自签名证书,感谢[@star7th](https://github.com/mouday/domain-admin/issues/7#issuecomment-1304415797) 提出的建议
|
||||
|
||||
- v0.0.14 v0.0.15
|
||||
- 修复bug: peewee.OperationalError: no such table: tb_version,感谢[@star7th](https://github.com/mouday/domain-admin/issues/7#issuecomment-1300634496) 提出的反馈
|
||||
|
||||
- v0.0.13
|
||||
- 支持非443端口,感谢[@star7th](https://github.com/mouday/domain-admin/issues/7) 提出的建议
|
||||
- 修复前端网络错误没有错误弹窗提示的问题
|
||||
|
||||
- v0.0.12
|
||||
- 新增webhook通知方式,感谢[@star7th](https://github.com/mouday/domain-admin/issues/3) 提出的建议
|
||||
|
||||
- v0.0.11
|
||||
- 优化前端页面显示,增加页面加载进度条
|
||||
- 修复有效期天数显示不对的问题
|
||||
|
||||
- v0.0.10
|
||||
- 更新域名证书获取方式为socket,替换curl,移除curl依赖,兼容windows,感谢[@cbr252522489](https://github.com/mouday/domain-admin/issues/1) 提出的反馈
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
FROM python:3.7.15-alpine3.16
|
||||
|
||||
ADD . /app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||
&& apk add --no-cache --virtual .build-deps gcc musl-dev libffi-dev openssl-dev bash \
|
||||
&& apk add --no-cache libffi openssl \
|
||||
&& pip install --no-cache-dir --upgrade pip setuptools wheel gunicorn \
|
||||
&& pip install --no-cache-dir -r requirements/production.txt \
|
||||
&& apk del .build-deps
|
||||
|
||||
CMD gunicorn --bind '[::]:8000' --timeout 120 'domain_admin.main:app'
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018-present, Mouday
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
graft domain_admin
|
||||
include README.md
|
||||
include requirements/production.txt
|
||||
prune tests
|
||||
prune doc
|
||||
prune image
|
||||
global-exclude .DS_Store
|
||||
global-exclude *.pyc
|
||||
global-exclude *.log
|
||||
global-exclude *.db
|
||||
global-exclude *.db-shm
|
||||
global-exclude *.db-wal
|
||||
global-exclude *.sqlite
|
||||
global-exclude .env
|
||||
global-exclude .gitignore
|
||||
global-exclude .dockerignore
|
||||
global-exclude .travis.yml
|
||||
global-exclude Dockerfile
|
||||
global-exclude Makefile
|
||||
@@ -0,0 +1,69 @@
|
||||
# 伪目标
|
||||
.PHONY: dev pro build clean upload publish
|
||||
|
||||
# 运行开发环境
|
||||
dev:
|
||||
source venv/bin/activate && gunicorn --bind '0.0.0.0:5001' --timeout 120 --reload 'domain_admin.main:app'
|
||||
|
||||
# 运行生产环境
|
||||
pro:
|
||||
source venv/bin/activate && gunicorn --bind '0.0.0.0:5001' --timeout 120 'domain_admin.main:app'
|
||||
|
||||
# 发布 make release
|
||||
.PHONY: release
|
||||
release:
|
||||
python ./version-cli/auto_release.py
|
||||
|
||||
# 打包
|
||||
build:
|
||||
python setup.py sdist bdist_wheel --python-tag py2.py3
|
||||
|
||||
# 制作 docker 镜像
|
||||
.PHONY: docker-build
|
||||
docker-build:
|
||||
docker build -t mouday/domain-admin:latest -f Dockerfile .
|
||||
|
||||
# 构建并运行 docker 镜像
|
||||
.PHONY: docker-run
|
||||
docker-run:
|
||||
docker run -p 8000:8000 mouday/domain-admin:latest
|
||||
|
||||
# 构建并运行 docker 镜像
|
||||
.PHONY: docker-build-run
|
||||
docker-build-run:
|
||||
make docker-build
|
||||
make docker-run
|
||||
|
||||
# 清空打包产物
|
||||
clean:
|
||||
rm -rf temp logs .pytest_cache
|
||||
rm -rf dist build *.egg-info
|
||||
|
||||
# 上传打包产物到 pypi
|
||||
upload:
|
||||
twine check dist/*
|
||||
twine upload dist/*
|
||||
|
||||
# 发布 make publish
|
||||
publish:
|
||||
make clean
|
||||
make build
|
||||
make upload
|
||||
make clean
|
||||
|
||||
# 运行所有测试
|
||||
.PHONY: test
|
||||
test:
|
||||
pytest -c pytest.ini tests/api/test_index.py
|
||||
|
||||
# 安装开发环境依赖
|
||||
# make install-require
|
||||
.PHONY: install-require
|
||||
install-require:
|
||||
pip install -r requirements/development.txt
|
||||
|
||||
# 快速提交
|
||||
# make fix
|
||||
.PHONY: fix
|
||||
fix:
|
||||
git add . && git commit -m 'fix' && git push
|
||||
@@ -0,0 +1,55 @@
|
||||
# OpenID Connect (OIDC) 单点登录配置指南
|
||||
|
||||
本项目已集成 OpenID Connect (OIDC) 单点登录功能,支持 pocket-id 等 OIDC 提供商。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 使用 authlib 库实现标准 OIDC 协议
|
||||
- 支持自动创建用户账号
|
||||
- 不修改现有数据模型
|
||||
- 与现有 JWT 认证系统无缝集成
|
||||
|
||||
## 配置方式
|
||||
|
||||
### 1. 环境变量配置
|
||||
|
||||
在 `.env` 文件中添加以下配置:
|
||||
|
||||
```bash
|
||||
# 启用 OIDC 单点登录
|
||||
OIDC_ENABLED=true
|
||||
|
||||
# OIDC 客户端 ID(必填)
|
||||
OIDC_CLIENT_ID=your_client_id
|
||||
|
||||
# OIDC 客户端密钥(必填)
|
||||
OIDC_CLIENT_SECRET=your_client_secret
|
||||
|
||||
# OIDC 提供商的 Issuer URL(必填,不包含 .well-known/openid-configuration)
|
||||
OIDC_ISSUER_URL=https://your-oidc-provider.com
|
||||
|
||||
# OIDC 请求的权限范围(可选,默认为 "openid profile email")
|
||||
OIDC_SCOPES=openid profile email
|
||||
|
||||
# 自动创建用户的默认角色(可选,默认为 1)
|
||||
# 1 = 普通用户(RoleEnum.USER)
|
||||
# 10 = 管理员(RoleEnum.ADMIN)
|
||||
OIDC_AUTO_CREATE_USER_ROLE=1
|
||||
|
||||
# 自动创建用户的默认状态(可选,默认为 false)
|
||||
# true = 启用账号
|
||||
# false = 禁用账号(需要管理员手动启用)
|
||||
OIDC_AUTO_CREATE_USER_STATUS=false
|
||||
```
|
||||
|
||||
**重要说明**:
|
||||
- 回调地址由系统自动生成(`/api/oidc/callback`),无需手动配置
|
||||
- 在 OIDC 提供商中配置回调地址时,使用完整 URL,例如:`https://xxx.com/api/oidc/callback`
|
||||
- 生产环境建议使用 HTTPS 协议
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 本实现尽可能不修改现有数据模型,OIDC 用户使用现有的 `UserModel`
|
||||
2. OIDC 用户的 `password` 字段为空字符串,不影响系统功能
|
||||
3. 用户可以同时使用 OIDC 登录和传统登录(如果管理员为 OIDC 用户设置了密码)
|
||||
4. 系统会自动处理用户创建和更新,无需手动干预
|
||||
@@ -0,0 +1,153 @@
|
||||
# Domain Admin
|
||||
|
||||
[](https://pypi.org/project/domain-admin)
|
||||
[](https://pypi.org/project/domain-admin)
|
||||
[](https://pypi.org/project/domain-admin)
|
||||
[](https://hub.docker.com/r/mouday/domain-admin)
|
||||
[](https://hub.docker.com/r/mouday/domain-admin)
|
||||
[](https://app.travis-ci.com/mouday/domain-admin)
|
||||
[](https://github.com/mouday/domain-admin/blob/master/LICENSE)
|
||||
[](https://domain-admin.readthedocs.io/zh_CN/latest/?badge=latest)
|
||||
[](https://github.com/dromara/domain-admin/releases)
|
||||
[](https://github.com/dromara/domain-admin)
|
||||
[](https://www.bt.cn/u/MaBJJC)
|
||||
|
||||
|
||||

|
||||
|
||||
> 该项目已加入 [Dromara开源社区](https://github.com/dromara/domain-admin)
|
||||
|
||||
基于Python + Vue3.js 技术栈实现的域名和SSL证书监测平台
|
||||
|
||||
用于解决,不同业务域名SSL证书,申请自不同的平台,到期后不能及时收到通知,导致线上访问异常,被老板责骂的问题
|
||||
|
||||
Domain Admin是一个轻量级监控方案,占用系统资源较少。同时,Domain Admin也可以作为一个Flask 和 Vue.js前后端分离的项目模板
|
||||
|
||||
- 项目优势
|
||||
- 集中管理: 提供一个统一的平台来管理多个域名,极大地提高了管理效率。
|
||||
- 自动提醒: 支持域名到期提醒,帮助用户避免因域名过期导致的服务中断。
|
||||
- 开源灵活: 作为开源项目,用户可以根据自身需求进行定制和扩展。
|
||||
- 社区支持: 拥有活跃的社区,可以获得持续的更新和问题支持。
|
||||
- 用户友好: 界面简洁直观,容易上手。
|
||||
|
||||
- 功能描述
|
||||
- 核心功能:`域名`、`SSL证书` 和 `托管证书文件` 的过期监控,到期提醒
|
||||
- 支持证书:单域名证书、多域名证书、泛域名(通配符)证书、IP证书、自签名证书
|
||||
- 证书部署:单一主机部署、多主机部署、动态主机部署
|
||||
- 通知渠道:支持邮件、Webhook、企业微信、钉钉、飞书等通知方式
|
||||
- 支持平台:macOS、Linux、Windows
|
||||
- 辅助功能:`Let’s Encrypt` SSL证书免费申请和SSL证书自动续期
|
||||
- 多语言:支持中文、英文
|
||||
|
||||
- 项目地址:[后端代码(github)](https://github.com/dromara/domain-admin)、[后端代码(国内镜像)](https://gitee.com/dromara/domain-admin)
|
||||
|
||||
- 发布渠道:[PyPI](https://pypi.org/project/domain-admin)、[Docker](https://hub.docker.com/r/mouday/domain-admin)、[Releases](https://github.com/mouday/domain-admin/releases)、[1Panel](https://apps.fit2cloud.com/1panel/domain-admin)、[宝塔](https://www.bt.cn/u/MaBJJC)
|
||||
|
||||
- 使用文档:[readthedocs](https://domain-admin.readthedocs.io/zh_CN/latest/?badge=latest)
|
||||
|
||||
- 接口文档:[github](https://mouday.github.io/domain-admin/)、[gitee](https://mouday.gitee.io/domain-admin/)
|
||||
|
||||
## 安装
|
||||
|
||||
请参考安装文档:[https://domain-admin.readthedocs.io/zh_CN/latest/manual/install.html](https://domain-admin.readthedocs.io/zh_CN/latest/manual/install.html)
|
||||
|
||||
如果不想安装,可以直接使用我们部署好的线上应用,需要体验的用户可以加入`QQ群` 或 `微信群`,提供邮箱即可
|
||||
|
||||
- 预览版(纯静态、无实际功能,账号密码随意):https://mouday.github.io/domain-admin-web/
|
||||
- 体验版(由大家伙捐献的服务器,邮箱注册即可体验):https://demo.domain-admin.cn/ (服务器已到期)
|
||||
|
||||
建议自行部署,这样比较安全
|
||||
|
||||
> 服务器和域名由群友赞助提供
|
||||
|
||||
本项目支持的安装方式
|
||||
|
||||
| 安装方式 | 链接 |
|
||||
|--------|----------------------------------|
|
||||
| 宝塔 | [链接](https://www.bt.cn/u/MaBJJC) |
|
||||
| Docker | [链接](https://domain-admin.readthedocs.io/zh-cn/latest/manual/install.html#docker) |
|
||||
| 源码 | [链接](https://domain-admin.readthedocs.io/zh-cn/latest/manual/install.html#id4) |
|
||||
| 1Panel | [链接](https://domain-admin.readthedocs.io/zh-cn/latest/manual/install.html#panel) |
|
||||
| k8s | [链接](https://domain-admin.readthedocs.io/zh-cn/latest/manual/install.html#k8s) |
|
||||
| pip | [链接](https://domain-admin.readthedocs.io/zh-cn/latest/manual/install.html#pip) |
|
||||
|
||||
|
||||
|
||||
## 项目截图
|
||||
|
||||
账号密码随意(例如:admin/123456),预览模式仅提供模拟数据,无法操作修改
|
||||
|
||||
1、网页版:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
- 预览地址:[https://mouday.github.io/domain-admin-web/](https://mouday.github.io/domain-admin-web/)
|
||||
|
||||
本项目采用的是前后端分离模式,前端代码在另外一个仓库。
|
||||
|
||||
前端项目地址(请自行解码):aHR0cHM6Ly9naXRodWIuY29tL21vdWRheS9kb21haW4tYWRtaW4td2Vi
|
||||
|
||||
或者关注微信公众号:
|
||||
|
||||
<img src="https://gitee.com/mouday/domain-admin/raw/master/image/coding-big-tree.jpg" width="300">
|
||||
|
||||
回复:`domain-admin-web`,获取完整的前端代码
|
||||
|
||||
2、移动端版:
|
||||
|
||||
<img src="https://gitee.com/mouday/domain-admin/raw/master/image/screencapture-mini.png" width="220">
|
||||
|
||||
- 移动端预览地址(请使用移动端窗口体验):[https://mouday.github.io/domain-admin-mini/](https://mouday.github.io/domain-admin-mini/)
|
||||
|
||||
移动端项目地址(请自行解码):aHR0cHM6Ly9naXRodWIuY29tL21vdWRheS9kb21haW4tYWRtaW4tbWluaQ==
|
||||
|
||||
为了更多地人参与到项目中来,现已开放前端代码,加入QQ群即可获取前端项目地址
|
||||
|
||||
## 问题反馈交流
|
||||
|
||||
由于访问github的网络不稳定,如果需要及时获得反馈,请通过以下方式联系
|
||||
|
||||
QQ群号:
|
||||
- (一群已满) 731742868
|
||||
- (三群) 452761242
|
||||
|
||||
邀请码:domain-admin
|
||||
|
||||
<img src="https://gitee.com/mouday/domain-admin/raw/master/image/qq-group.jpeg" width="300">
|
||||
|
||||
微信交流群
|
||||
|
||||
<img src="https://gitee.com/mouday/domain-admin/raw/master/image/coding-big-tree.jpg" width="300">
|
||||
|
||||
回复:`domain-admin`,和众多使用者一起交流学习使用经验,反馈使用问题,获得更及时的解答和修复
|
||||
|
||||
提交PR的小伙伴,可以进去`核心开发者交流群`,和更多志同道合的朋友交流
|
||||
|
||||
## 更新日志
|
||||
|
||||
[CHANGELOG.md](https://domain-admin.readthedocs.io/zh_CN/latest/manual/changelog.html)
|
||||
|
||||
## 使用者
|
||||
|
||||
虚位以待,可以将使用者公司或个人的名字放到这里
|
||||
|
||||
赞助者可以进入`赞助商群`,优先处理你的问题
|
||||
|
||||
| 时间 | 赞助者 | 金额 |
|
||||
|------------| - | - |
|
||||
| 2023-11-21 | [@1275788667](https://github.com/1275788667) | ¥50
|
||||
| - | [@hhdebb](https://github.com/hhdebb) | 若干
|
||||
| 2024-04-23 | [@1275788667](https://github.com/1275788667) | ¥50
|
||||
| - | `星河 ๑. ` | 若干
|
||||
| 2024-07-10 | [@1275788667](https://github.com/1275788667) | ¥50
|
||||
| 2024-10-31 | ldytech | ¥200
|
||||
| 2024-11-01 | [@xiaobiaozhao](https://github.com/xiaobiaozhao) | ¥50
|
||||
|
||||
|
||||
<img src="https://gitee.com/mouday/domain-admin/raw/master/image/alipay.jpg" width="300">
|
||||
|
||||
[](https://starchart.cc/mouday/domain-admin)
|
||||
|
||||
<a href="https://hellogithub.com/repository/2b44fb56aca14df7a6279b0997f7325c" target="_blank"><img src="https://api.hellogithub.com/v1/widgets/recommend.svg?rid=2b44fb56aca14df7a6279b0997f7325c&claim_uid=Hb7yUF2AEhnmSwJ" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`dromara/domain-admin`
|
||||
- 原始仓库:https://github.com/dromara/domain-admin
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
@@ -0,0 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@File : dev.py
|
||||
@Date : 2023-06-14
|
||||
|
||||
启动方式
|
||||
$ flask run
|
||||
"""
|
||||
from domain_admin.main import app
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=8000)
|
||||
@@ -0,0 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@File : dev.py
|
||||
@Date : 2023-06-14
|
||||
"""
|
||||
from domain_admin.main import app
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, port=5001)
|
||||
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
@@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
@@ -0,0 +1,8 @@
|
||||
sphinx
|
||||
sphinx_rtd_theme
|
||||
sphinx-autobuild
|
||||
recommonmark
|
||||
sphinx-markdown-tables
|
||||
myst-parser
|
||||
linkify-it-py
|
||||
m2r2
|
||||
@@ -0,0 +1,62 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# For the full list of built-in configuration values, see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = 'Domain Admin'
|
||||
copyright = '2023, Peng Shiyu'
|
||||
author = 'Peng Shiyu'
|
||||
release = 'latest'
|
||||
version = 'latest'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.napoleon',
|
||||
'sphinx.ext.doctest',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.mathjax',
|
||||
'sphinx.ext.napoleon',
|
||||
'sphinx.ext.githubpages',
|
||||
'm2r2',
|
||||
# 'myst_parser',
|
||||
'sphinx_markdown_tables',
|
||||
]
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = []
|
||||
|
||||
language = 'zh_CN'
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
# html_theme = 'alabaster'
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_static_path = ['_static']
|
||||
|
||||
source_suffix = ['.rst', '.md']
|
||||
|
||||
myst_enable_extensions = [
|
||||
"amsmath",
|
||||
"attrs_inline",
|
||||
"colon_fence",
|
||||
"deflist",
|
||||
"dollarmath",
|
||||
"fieldlist",
|
||||
"html_admonition",
|
||||
"html_image",
|
||||
"linkify",
|
||||
"replacements",
|
||||
"smartquotes",
|
||||
"strikethrough",
|
||||
"substitution",
|
||||
"tasklist",
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
.. mdinclude:: ../../README.md
|
||||
|
||||
目录
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
manual/index.md
|
||||
manual/install.md
|
||||
manual/notify.md
|
||||
manual/ssl-cert.md
|
||||
manual/question.md
|
||||
manual/plugins.md
|
||||
manual/plan.md
|
||||
manual/development.md
|
||||
manual/changelog.md
|
||||
manual/article.md
|
||||
@@ -0,0 +1,46 @@
|
||||
# 相关文章
|
||||
|
||||
## 媒体报道
|
||||
|
||||
- 2022-10-14 Coding Big Tree [Domain Admin:监测域名SSL证书,到期邮件提醒](https://mp.weixin.qq.com/s/batUjnNscGqYS5x5Vbn_Vw)
|
||||
- 2022-10-21 阮一峰的网络日志 [科技爱好者周刊(第227期):脸书的公司入职教育](https://mp.weixin.qq.com/s/8dkgEtH5r5GgdwaT-DAysw)
|
||||
- 2023-04-14 阮一峰的网络日志 [科技爱好者周刊#250:新技术的最大风险](https://mp.weixin.qq.com/s/IS4XveCr2w1poEY2NvK29w)
|
||||
- 2023-04-18 HACK学习呀 [干货 | Github安全搬运工 2023年第一期](https://mp.weixin.qq.com/s/gnWTz6GhAI0A2XhFXVI_YQ)
|
||||
- 2023-05-29 HelloGitHub [《HelloGitHub》第 86 期](https://mp.weixin.qq.com/s/c2iJXLyMA8YtteyNC5sbkQ)
|
||||
- 2023-05-30 PHP学习网 [【开源推荐】基于 Python + Vue3.js 技术栈实现的域名和SSL证书监测平台](https://mp.weixin.qq.com/s/slZgFXR3M4xVfpYkHnS67w)
|
||||
- 2023-06-05 HelloGitHub [暑期实习开始啦「GitHub 热点速览」](https://mp.weixin.qq.com/s/Ocw6FBgSLVop2bMKU9hzmg)
|
||||
- 2023-06-10 Python猫 [Python潮流周刊#6:Python 3.12 有我贡献的代码!](https://mp.weixin.qq.com/s/g-vVxgY_JpMmXTC3EntNeg)
|
||||
- 2023-06-19 开源前线 [YYDS!GitHub 这些项目真香!](https://mp.weixin.qq.com/s/05hGJqDs8ImF6mXJ-Hff-w)
|
||||
- 2023-06-19 GitHub黑板报 [重磅推荐:一款开源的域名 SSL 证书监测平台](https://mp.weixin.qq.com/s/qr5j_bJNRshN7xxwn4a7tw)
|
||||
- 2023-06-27 云海天教程 [开源的域名 SSL 证书监测平台](https://mp.weixin.qq.com/s/UJkQtoXXAwPAu1lg0YO2mw)
|
||||
- 2023-06-29 奇妙的Linux世界 [又一款好用的开源域名 SSL 证书监测平台](https://mp.weixin.qq.com/s/IXD6oaHTfMBkoXs7lnR1zg)
|
||||
- 2023-07-06 TJ君 [重磅推荐:一款开源的域名 SSL 证书监测平台](https://mp.weixin.qq.com/s/zBQccvuwCAzapcHhVFjZXg)
|
||||
- 2023-07-07 框架师 [GitHub 热榜日报:发现更多有趣的项目!](https://mp.weixin.qq.com/s/ywbubMMyrekV4sKv9NzsrQ)
|
||||
- 2023-07-14 良许Linux [又一款好用的开源域名 SSL 证书监测平台](https://mp.weixin.qq.com/s/drFLJCqz3CEF-DPfa5jnSQ)
|
||||
- 2023-07-21 IT仔的笔记本 [一套属于自己的域名、证书管理系统,只需要1分钟就能上手,(收藏起来,会用到的)](https://mp.weixin.qq.com/s/rYpWBgZgizqDWOaPq2g1_g)
|
||||
- 2023-07-27 DevOps运维民工 [推荐一款开源的域名和SSL证书监测平台](https://mp.weixin.qq.com/s/LlaD7KUogwdfQMs_v7QgPg)
|
||||
- 2023-07-29 云原生运维技术 [运维百宝箱-https证书的过期巡检监控](https://mp.weixin.qq.com/s/iL5AHjw1gZ5_vnWsI8OfNA)
|
||||
- 2023-08-20 帝国辉耀1 [domain-admin域名监控的源码搭建](https://blog.csdn.net/u013901725/article/details/132394530)
|
||||
- 2023-08-21 帝国辉耀1 [domain-admin域名监控的使用(二)](https://blog.csdn.net/u013901725/article/details/132403565)
|
||||
- 2023-08-21 PHP学习网 [【开源推荐】基于Python + Vue3.js 技术栈实现的域名和SSL证书监测平台](https://mp.weixin.qq.com/s/7qvYTALe5uIlkD3jEgQ1Kw)
|
||||
- 2023-08-30 运维开发故事 [基于Python + Vue3.js 技术栈实现的域名和SSL证书监测平台](https://mp.weixin.qq.com/s/cOOJN6eUSqP0wmt8-3sOJw)
|
||||
- 2023-09-11 浩道Linux [【来自读者投稿】这款开源的域名和SSL证书监测平台真香!](https://mp.weixin.qq.com/s/z1MOe8ptp76F0PEHaxVsjQ)
|
||||
- 2023-09-15 Python技术 [Python:获取域名ssl证书信息和到期时间!](https://mp.weixin.qq.com/s/E9uha-1UDTahNx1lEKvfXQ)
|
||||
- 2023-09-16 Python客栈 [Python:获取域名ssl证书信息和到期时间!](https://mp.weixin.qq.com/s/Mz2WSuEinOT1qQfpSiz7LA)
|
||||
- 2023-11-03 测试指北 [Domain Admin—基于Python + Vue3.js 技术栈实现的域名和SSL证书监测平台](https://mp.weixin.qq.com/s/kvUNi25ge3YLKUYcsR-CGA)
|
||||
- 2023-12-28 跬步之巅 [开源初探 - domain-admin](https://mp.weixin.qq.com/s/dnFitCv_n9os20TLYLbITw)
|
||||
- 2024-01-31 IT仔的笔记本 [网站管理员的福利 | 一个开源的域名和SSL证书监测平台](https://mp.weixin.qq.com/s/AzaiyhUpBQHBCMdhEkYmSQ)
|
||||
- 2024-02-23 漫跑的小兔 [用Docker部署Domain Admin](https://mp.weixin.qq.com/s/sie9zDP86Ez93FqvJlnU6Q)
|
||||
- 2024-03-16 ITBLOG [Domain Admin:域名和SSL证书监测系统](https://mp.weixin.qq.com/s/yZ01dMUYcvN2Ms1CsnExhg)
|
||||
- 2024-03-19 ITBLOG [pip安装Domain-Admin](https://mp.weixin.qq.com/s/cxklXTktXBRzlDxnN5T5tQ)
|
||||
- 2024-03-23 开源技术小栈 [开源域名SSL证书监测平台 Domain Admin](https://mp.weixin.qq.com/s/DY65JI1jLj42YM1E6-LUGw)
|
||||
- 2024-04-09 辣码甄源 [Domain Admin:方便快捷的图形化域名和SSL证书监测平台](https://mp.weixin.qq.com/s/29MGqNbSX5VLBi8Im5GQDQ)
|
||||
- 2024-05-10 刘哥聊技术 [开源|一款基于Python + Vue3.js 技术栈实现的域名和SSL证书监测管理平台](https://mp.weixin.qq.com/s/BOQLpvGjXvvNgIoeAGJBRg)
|
||||
- 2024-05-12 云原生生态圈 [这个域名证书监控平台又吊打Uptimekuma了](https://mp.weixin.qq.com/s/6C1zQJHMo-a4sy6DLJY4Xw)
|
||||
- 2024-10-28 IT运维技术圈 [推荐一个非常轻便的ssl监控工具](https://mp.weixin.qq.com/s/cq4id4OfoyTQxhXvO-bA_w)
|
||||
|
||||
|
||||
## 参考文章
|
||||
|
||||
- [Python:获取域名ssl证书信息和到期时间](https://pengshiyu.blog.csdn.net/article/details/115861795)
|
||||
- [一文搞定:whois数据库查询域名信息(WHOIS)](https://pengshiyu.blog.csdn.net/article/details/129691736)
|
||||
@@ -0,0 +1,3 @@
|
||||
# 升级日志
|
||||
|
||||
.. mdinclude:: ../../../CHANGELOG.md
|
||||
@@ -0,0 +1,62 @@
|
||||
# 二次开发
|
||||
|
||||
## 技术选型
|
||||
|
||||
前端选型(网页版)
|
||||
|
||||
- Node.js
|
||||
- Vite.js
|
||||
- Vue3.js
|
||||
- Vue Router
|
||||
- Pinia
|
||||
- Element Plus
|
||||
- Tailwind CSS
|
||||
|
||||
前端选型(桌面版)
|
||||
|
||||
- node.js v16.15.1
|
||||
- vue3.js
|
||||
- quasar + electron
|
||||
|
||||
后端选型
|
||||
|
||||
- Python3.7.0
|
||||
- [Flask](https://flask.palletsprojects.com/en/2.2.x/)
|
||||
- [jinja2](https://jinja.palletsprojects.com/en/3.1.x/)
|
||||
- [peewee(sqlite)](http://docs.peewee-orm.com/en/latest/index.html#)
|
||||
- [apscheduler](https://apscheduler.readthedocs.io/en/3.x/)
|
||||
- [supervisord](http://supervisord.org/index.html) 部署推荐
|
||||
- [gunicorn](https://docs.gunicorn.org/) mac/linux 推荐
|
||||
- [waitress](https://github.com/Pylons/waitress) windows 推荐
|
||||
|
||||
|
||||
## 接口文档
|
||||
|
||||
[https://mouday.github.io/domain-admin/](https://mouday.github.io/domain-admin/)
|
||||
|
||||
代码推送
|
||||
|
||||
```bash
|
||||
# github
|
||||
git push -u origin master
|
||||
|
||||
# gitee
|
||||
git push -u gitee master
|
||||
```
|
||||
|
||||
## 测试证书
|
||||
|
||||
证书测试 [https://badssl.com/](https://badssl.com/)
|
||||
|
||||
获取证书列表
|
||||
|
||||
```js
|
||||
JSON.stringify([...document.querySelectorAll('a')].map(a=>a.href))
|
||||
```
|
||||
|
||||
批量域名列表 (746314个)
|
||||
|
||||
- [alexa-top-1m.csv.zip](http://s3.amazonaws.com/alexa-static/top-1m.csv.zip)
|
||||
- [docs/top-1m.csv](tests/top-1m.txt)
|
||||
|
||||

|
||||
@@ -0,0 +1,85 @@
|
||||
# 用户手册
|
||||
|
||||
## 项目简介
|
||||
|
||||
功能:
|
||||
|
||||
- 权限
|
||||
- 用户登录
|
||||
- 用户退出
|
||||
- 修改密码
|
||||
|
||||
- 域名管理
|
||||
- 域名添加
|
||||
- 域名删除
|
||||
- 域名搜索
|
||||
- 域名导入、导出功能
|
||||
- 域名信息
|
||||
|
||||
- 证书监控
|
||||
- 定时监控
|
||||
- 到期邮件提醒
|
||||
- 微信提醒
|
||||
- 手动/自动更新证书信息
|
||||
|
||||
- 用户管理
|
||||
- 添加用户
|
||||
- 删除用户
|
||||
- 禁用/启用用户
|
||||
|
||||
- 监控日志
|
||||
|
||||
- 管理界面
|
||||
- api接口(用于二次开发)
|
||||
- web浏览器
|
||||
- 桌面
|
||||
- ~~移动端(app+小程序)~~
|
||||
|
||||
## 使用说明
|
||||
|
||||
批量导入域名
|
||||
|
||||
### 1、快速导入
|
||||
|
||||
以`.txt`结尾的文件,视为快速导入文件
|
||||
|
||||
| 支持字段 | 必填 |
|
||||
| - | - |
|
||||
| 域名 | 是 |
|
||||
| 端口 | 否,默认:443 |
|
||||
|
||||
示例:domain.txt
|
||||
|
||||
```bash
|
||||
www.baidu.com
|
||||
www.zhihu.com:443
|
||||
```
|
||||
|
||||
默认端口是443,如果是非443端口可以用`:`冒号分隔域名和端口号
|
||||
|
||||
### 2、完整导入
|
||||
|
||||
以`.csv`结尾的文件,视为完整导入文件
|
||||
|
||||
> 注意:用普通文件编辑器编辑即可,不要用excel编辑,可能会出现乱码问题
|
||||
|
||||
| 支持字段 | 必填 |
|
||||
| - | - |
|
||||
| 域名 | 是 |
|
||||
| 端口 | 否,默认:443 |
|
||||
| 分组 | 否 |
|
||||
| 备注 | 否 |
|
||||
|
||||
示例:domain.csv
|
||||
|
||||
```bash
|
||||
域名,端口,分组,备注
|
||||
www.zhihu.com,,问答,知乎
|
||||
www.baidu.com,443,,
|
||||
```
|
||||
|
||||
备注:逗号不能省略,字段顺序可以改变,多余的字段会被忽略
|
||||
|
||||
首次导入数据后,后台会更新证书信息,此时不要频繁操作,否则会出现锁表错误。
|
||||
|
||||
稍等一会,刷新页面,就能看到完整的证书信息了。
|
||||
@@ -0,0 +1,475 @@
|
||||
# 安装方式
|
||||
|
||||
管理员默认的账号和密码
|
||||
|
||||
- 账号:admin
|
||||
- 密码:123456
|
||||
|
||||
> `强烈建议`:登录系统后修改默认密码
|
||||
|
||||
Domain Admin是一个Flask应用,启动部署方式和Flask应用是一样的
|
||||
|
||||
> `特别注意`:
|
||||
> - 【测试环境】可以使用默认的sqlite数据库;
|
||||
> - 【生产环境】推荐使用MySQL数据库,避免锁表,配置方式:[可选配置](#id5)
|
||||
|
||||
## 方式一:宝塔面板安装
|
||||
|
||||
[](https://www.bt.cn/u/MaBJJC)
|
||||
|
||||
宝塔Linux面板是提升运维效率的服务器管理软件,支持一键LAMP/LNMP/集群/监控/网站/FTP/数据库/JAVA等100多项服务器管理功能。
|
||||
|
||||
有30个人的专业团队研发及维护,经过200多个版本的迭代,功能全,少出错且足够安全,已获得全球百万用户认可安装。
|
||||
|
||||
如果还未安装宝塔,请先前往[下载](https://www.bt.cn/u/MaBJJC)注册安装
|
||||
|
||||
面板版本v9.2.0: 宝塔面板 --> Docker --> 应用商店 --> Domain Admin --> 安装
|
||||
|
||||
[【教程贴】Docker应用-Domain Admin 安装帮助](https://www.bt.cn/bbs/thread-139621-1-1.html)
|
||||
|
||||
<a title="宝塔服务器面板,一键全能部署及管理" href="https://www.bt.cn/u/MaBJJC"><img src="https://mouday.github.io/img/2024/10/24/fxkd3qe.jpg" width="200"></a>
|
||||
|
||||

|
||||
|
||||
## 方式二:docker启动
|
||||
|
||||
感谢[@miss85246](https://github.com/miss85246) 提供Docker支持
|
||||
|
||||
```bash
|
||||
# 本地文件夹和容器文件夹映射
|
||||
$ docker run \
|
||||
-v $(pwd)/database:/app/database \
|
||||
-v $(pwd)/logs:/app/logs \
|
||||
-p 8000:8000 \
|
||||
--name domain-admin \
|
||||
mouday/domain-admin:latest
|
||||
```
|
||||
|
||||
- database:sqlite数据库和重要数据的目录
|
||||
- logs:日志目录,用于排查问题
|
||||
|
||||
启动命令增加参数:`-d`,可以后台运行
|
||||
|
||||
```bash
|
||||
# 本地文件夹和容器文件夹映射
|
||||
$ docker run \
|
||||
-d \
|
||||
-v $(pwd)/database:/app/database \
|
||||
-v $(pwd)/logs:/app/logs \
|
||||
-p 8000:8000 \
|
||||
--name domain-admin \
|
||||
mouday/domain-admin:latest
|
||||
```
|
||||
|
||||
也可以用dokcer-compose来一键启动
|
||||
|
||||
可以使用`docker-compose up -d`来启动
|
||||
|
||||
当版本更新时,可以用`docker-compose pull`来拉取镜像,然后用`docker-compose up -d`来重启新镜像
|
||||
|
||||
```yaml
|
||||
version: '3.3'
|
||||
services:
|
||||
domain-admin:
|
||||
volumes:
|
||||
- './database:/app/database'
|
||||
- './logs:/app/logs'
|
||||
ports:
|
||||
- '8000:8000'
|
||||
container_name: domain-admin
|
||||
image: mouday/domain-admin:latest
|
||||
```
|
||||
|
||||
可用的docker镜像站
|
||||
|
||||
```bash
|
||||
$ sudo mkdir -p /etc/docker
|
||||
|
||||
$ sudo tee /etc/docker/daemon.json <<EOF
|
||||
{
|
||||
"registry-mirrors": [
|
||||
"https://dockerpull.com",
|
||||
"https://dockerproxy.cn",
|
||||
"https://docker.loli.voto",
|
||||
"https://docker.chenby.cn",
|
||||
"https://docker-cf.registry.cyou",
|
||||
"https://docker.m.daocloud.io",
|
||||
"https://docker.ketches.cn",
|
||||
"https://docker.1panel.dev",
|
||||
"https://docker.1panel.live",
|
||||
"https://docker.rainbond.cc",
|
||||
"https://hub.rat.dev",
|
||||
"https://docker.aityp.com/",
|
||||
"https://docker.catiz.eu.org/"
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
$ sudo systemctl daemon-reload
|
||||
$ sudo systemctl restart docker
|
||||
```
|
||||
|
||||
## 方式三:下载源码安装
|
||||
|
||||
下载地址:[https://github.com/mouday/domain-admin/releases](https://github.com/mouday/domain-admin/releases)
|
||||
|
||||
本方式仅适用于二次开发,推荐采用 pip安装 或者 docker启动 的方式体验
|
||||
|
||||
例如:以`v1.5.8`为例
|
||||
|
||||
每次发布都会包含以下代码包
|
||||
|
||||
```
|
||||
domain-admin-1.5.8.tar.gz # 完整源码包,包含打包后的H5端、web端代码
|
||||
domain_admin-1.5.8-py2.py3-none-any.whl # pip包,可直接安装
|
||||
domain-admin-mini-v1.5.8.tar.gz # H5端源码
|
||||
domain-admin-web-v1.5.8.tar.gz # web端源码
|
||||
Source code(zip) # Python源码 windows适用
|
||||
Source code(tar.gz) # Python源码 linux/mac适用
|
||||
```
|
||||
|
||||
此处以linux/macos为例,windows用户可直接点击下载,自行使用加压软件解压
|
||||
|
||||
1、下载发布包
|
||||
|
||||
```bash
|
||||
# 下载 domain-admin-1.5.8.tar.gz
|
||||
wget https://github.com/mouday/domain-admin/releases/download/v1.5.8/domain-admin-1.5.8.tar.gz
|
||||
|
||||
# 如果下载速度过慢,可以使用加速方案
|
||||
wget https://ghproxy.com/https://github.com/mouday/domain-admin/releases/download/v1.5.8/domain-admin-1.5.8.tar.gz
|
||||
```
|
||||
|
||||
2、解压进入
|
||||
|
||||
```bash
|
||||
tar -zxvf domain-admin-1.5.8.tar.gz
|
||||
|
||||
cd domain-admin-1.5.8
|
||||
```
|
||||
|
||||
3、创建虚拟环境
|
||||
|
||||
```bash
|
||||
# 创建名为 venv 的虚拟环境,windows用户参考方式一
|
||||
$ python3 -m venv venv && source venv/bin/activate
|
||||
|
||||
# 安装依赖
|
||||
pip3 install .
|
||||
```
|
||||
|
||||
4、新建启动文件 `app.py`
|
||||
|
||||
```python
|
||||
from domain_admin.main import app
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=8000)
|
||||
```
|
||||
|
||||
此时的目录结构如下
|
||||
|
||||
```bash
|
||||
$ tree -L 1
|
||||
.
|
||||
├── LICENSE
|
||||
├── MANIFEST.in
|
||||
├── PKG-INFO
|
||||
├── README.md
|
||||
├── app.py # 新建的启动文件
|
||||
├── build
|
||||
├── database
|
||||
├── domain_admin
|
||||
├── domain_admin.egg-info
|
||||
├── logs
|
||||
├── requirements
|
||||
├── setup.cfg
|
||||
├── setup.py
|
||||
├── temp
|
||||
└── venv
|
||||
```
|
||||
|
||||
5、启动运行
|
||||
|
||||
```bash
|
||||
# 启动运行
|
||||
$ python app.py
|
||||
|
||||
* Serving Flask app 'domain_admin.main'
|
||||
* Debug mode: off
|
||||
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
|
||||
* Running on http://127.0.0.1:8000
|
||||
Press CTRL+C to quit
|
||||
```
|
||||
|
||||
- warning提示可以忽略,推荐参考方式一,使用`gunicorn` 或者 `waitress-serve` 启动
|
||||
|
||||
访问地址:[http://127.0.0.1:8000](http://127.0.0.1:8000)
|
||||
|
||||
## 方式四:1Panel面板安装
|
||||
|
||||
1Panel是一个Linux 服务器运维管理面板,Domain-Admin已发布到它的应用商店,可以直接安装
|
||||
|
||||
安装地址:
|
||||
|
||||
[https://apps.fit2cloud.com/1panel/domain-admin](https://apps.fit2cloud.com/1panel/domain-admin)
|
||||
|
||||
|
||||
## 方式五:k8s部署
|
||||
|
||||
配置文件示例
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/instance: domain-admin-latest
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
app.kubernetes.io/name: domain-admin
|
||||
app.kubernetes.io/version: 1.16.0
|
||||
argocd.argoproj.io/instance: domain-admin-latest
|
||||
helm.sh/chart: domain-admin-0.1.0
|
||||
name: domain-admin-latest
|
||||
namespace: domain-admin-production
|
||||
spec:
|
||||
progressDeadlineSeconds: 600
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 10
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/instance: domain-admin-latest
|
||||
app.kubernetes.io/name: domain-admin
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 25%
|
||||
maxUnavailable: 25%
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app.kubernetes.io/instance: domain-admin-latest
|
||||
app.kubernetes.io/name: domain-admin
|
||||
spec:
|
||||
containers:
|
||||
- name: domain-admin
|
||||
image: mouday/domain-admin:latest
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: DB_CONNECT_URL
|
||||
value: "sqlite:///database/database.db"
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
scheme: HTTP
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 1
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
name: http
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
scheme: HTTP
|
||||
periodSeconds: 10
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 1
|
||||
```
|
||||
|
||||
|
||||
## 方式六:pip安装
|
||||
|
||||
运行环境:
|
||||
|
||||
- Python >= 2.7 或者 Python >= 3.4
|
||||
- sqlite、openssl
|
||||
|
||||
python的安装,可以参考文章:[Linux环境下安装Python3](https://pengshiyu.blog.csdn.net/article/details/85304557)
|
||||
|
||||
可以使用 `pyenv` + `venv` 管理多个Python版本和隔离虚拟环境
|
||||
|
||||
确保已经安装Python解释器
|
||||
|
||||
```bash
|
||||
$ python3 --version
|
||||
Python 3.7.0
|
||||
```
|
||||
|
||||
linux / macos 安装
|
||||
|
||||
```bash
|
||||
# 创建名为 venv 的虚拟环境并激活
|
||||
$ python3 -m venv venv && source venv/bin/activate
|
||||
|
||||
# 安装 domain-admin
|
||||
$ pip install gunicorn domain-admin
|
||||
|
||||
# 启动运行
|
||||
$ gunicorn --bind '127.0.0.1:8000' 'domain_admin.main:app'
|
||||
```
|
||||
|
||||
windows 安装
|
||||
|
||||
```bash
|
||||
# 创建名为 venv 的虚拟环境
|
||||
> py -3 -m venv venv
|
||||
|
||||
# 激活虚拟环境
|
||||
> venv\Scripts\activate
|
||||
|
||||
# 安装 domain-admin
|
||||
> pip install waitress domain-admin
|
||||
|
||||
# 启动运行
|
||||
> waitress-serve --listen=127.0.0.1:8000 domain_admin.main:app
|
||||
```
|
||||
|
||||
访问地址:http://127.0.0.1:8000
|
||||
|
||||
升级到最新版本
|
||||
|
||||
```bash
|
||||
$ pip3 install -U domain-admin -i https://pypi.org/simple
|
||||
```
|
||||
|
||||
Python2.7如果安装依赖报错,可以使用依赖文件 [production-2.7.txt](https://github.com/mouday/domain-admin/tree/master/requirements/production-2.7.txt)
|
||||
|
||||
先安装好依赖文件的依赖,再安装domain-admin即可
|
||||
|
||||
## 其他部署方式
|
||||
|
||||
可以参考[https://flask.palletsprojects.com/en/2.3.x/deploying/](https://flask.palletsprojects.com/en/2.3.x/deploying/)
|
||||
|
||||
## 平滑升级
|
||||
|
||||
Domain Admin所有版本都支持平滑升级
|
||||
|
||||
安装最新版,重启即可
|
||||
|
||||
> 注意:如果是docker安装的,注意`database` 目录是不是手动挂载的,不要删除
|
||||
|
||||
## 可选配置
|
||||
|
||||
通过配置`.env` 文件或者直接设置系统环境变量
|
||||
|
||||
1、修改数据库存储方式
|
||||
|
||||
```bash
|
||||
# sqlite 默认 (少量用户推荐)
|
||||
DB_CONNECT_URL=sqlite:///database/database.db
|
||||
|
||||
# mysql (团队使用推荐)
|
||||
DB_CONNECT_URL=mysql://root:123456@127.0.0.1:3306/data_domain
|
||||
|
||||
# postgresql,需自己安装psycopg2依赖:pip install psycopg2
|
||||
DB_CONNECT_URL=postgresql://root:123456@localhost:5432/data_domain
|
||||
```
|
||||
|
||||
说明:以上配置仅为示例,根据自己实际情况修改
|
||||
|
||||
如果使用mysql数据库,建议指定默认字符编码为`utf8mb4`
|
||||
|
||||
```sql
|
||||
create database data_domain default character set=utf8mb4;
|
||||
```
|
||||
|
||||
|
||||
## 配置参数
|
||||
|
||||
可以通过运行目录下添加 `.env` 文件配置项目的可选参数
|
||||
|
||||
```bash
|
||||
# 数据库链接参数
|
||||
# 支持数据库:sqlite(默认)、mysql、postgresql
|
||||
DB_CONNECT_URL=mysql://root:123456@127.0.0.1:3306/data
|
||||
|
||||
# 运行模式
|
||||
# 可选:production(默认) / development(可以打印更多的调试日志)
|
||||
APP_MODE=production
|
||||
|
||||
# 允许远程执行的命令白名单,多个英文分号(;)分隔
|
||||
ALLOW_COMMANDS=/opt/nginx/sbin/nginx -s reload;nginx -s reload
|
||||
|
||||
# 允许用户注册,目前仅支持邮箱验证码登录
|
||||
ENABLED_REGISTER=true
|
||||
```
|
||||
|
||||
还可以通过环境变量来暴露
|
||||
|
||||
```bash
|
||||
export APP_MODE=production
|
||||
```
|
||||
|
||||
在 `Dockerfile` 中使用 `ENV` 指令设置环境变量
|
||||
|
||||
```bash
|
||||
# 设置单个环境变量
|
||||
ENV APP_MODE production
|
||||
|
||||
# 设置多个环境变量
|
||||
ENV APP_MODE=production ALLOW_COMMANDS='nginx -s reload'
|
||||
```
|
||||
|
||||
使用 `docker run` 命令的 `-e` 或 `--env` 选项在运行容器时设置环境变量
|
||||
|
||||
```bash
|
||||
# 设置单个环境变量
|
||||
docker run -e APP_MODE=production my_image
|
||||
|
||||
# 设置多个环境变量
|
||||
docker run -e APP_MODE=production -e ALLOW_COMMANDS='nginx -s reload' my_image
|
||||
```
|
||||
|
||||
在`docker-compose.yml`文件中使用`environment`关键字
|
||||
|
||||
```yaml
|
||||
version: '3.3'
|
||||
services:
|
||||
domain-admin:
|
||||
volumes:
|
||||
- './database:/app/database'
|
||||
- './logs:/app/logs'
|
||||
ports:
|
||||
- '8000:8000'
|
||||
environment:
|
||||
- APP_MODE=production
|
||||
container_name: domain-admin
|
||||
image: mouday/domain-admin:latest
|
||||
```
|
||||
|
||||
在`docker-compose.yml`文件从宿主机获取环境变量,可以使用`env_file`关键字指向一个文件
|
||||
|
||||
```yaml
|
||||
version: '3.3'
|
||||
services:
|
||||
domain-admin:
|
||||
volumes:
|
||||
- './database:/app/database'
|
||||
- './logs:/app/logs'
|
||||
ports:
|
||||
- '8000:8000'
|
||||
env_file:
|
||||
- .env
|
||||
container_name: domain-admin
|
||||
image: mouday/domain-admin:latest
|
||||
```
|
||||
|
||||
可以通过 `printenv` 命令查看设置的环境变量
|
||||
|
||||
## 更新脚本
|
||||
|
||||
可以使用类似的shell脚本,快速更新到新的版本
|
||||
|
||||
```shell
|
||||
# update.sh
|
||||
git pull && \
|
||||
supervisorctl restart domain-admin && \
|
||||
echo '部署成功'
|
||||
```
|
||||
@@ -0,0 +1,507 @@
|
||||
# 通知配置
|
||||
|
||||
> 备注:如果点击`测试` 无法接收到消息,可尝试给`剩余天数` 设置一个比较大的值大
|
||||
|
||||
## 1、邮件
|
||||
|
||||
第一步:需要在 `系统管理/系统设置/邮箱设置` 中设置好系统发件邮件
|
||||
|
||||
> 如果需要对域名进行到期监控和邮件提醒,必须设置发件邮件
|
||||
|
||||
注意:
|
||||
1. 如果使用的是163邮箱,密码处填写的是`授权码`
|
||||
|
||||

|
||||
|
||||
第二步:填写收件人列表
|
||||
|
||||

|
||||
|
||||
需要填写json格式的数据,例如:
|
||||
|
||||
```json
|
||||
[
|
||||
"tom@qq.com",
|
||||
"jack@qq.com"
|
||||
]
|
||||
```
|
||||
|
||||
## 2、WebHook
|
||||
|
||||
### 2.1、webhook发送微信消息
|
||||
|
||||
可以使用的微信推送平台有很多,可以参考
|
||||
|
||||
[微信推送消息通知接口汇总](https://pengshiyu.blog.csdn.net/article/details/124135877)
|
||||
|
||||
### 2.2、webhook发送钉钉消息
|
||||
|
||||
文档:[https://open.dingtalk.com/document/orgapp/custom-bot-send-message-type](https://open.dingtalk.com/document/orgapp/custom-bot-send-message-type)
|
||||
|
||||
webhook地址
|
||||
|
||||
```
|
||||
https://oapi.dingtalk.com/robot/send?access_token=<access_token>
|
||||
```
|
||||
|
||||
设置请求头
|
||||
|
||||
```json
|
||||
{
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
```
|
||||
|
||||
示例:SSL证书到期(文本格式)
|
||||
|
||||
```json
|
||||
{
|
||||
"msgtype": "text",
|
||||
"text": {
|
||||
"content": "以下证书临期,请检查:\n {% for row in list %}{{row.domain}} {{row.start_date or '-' }} ~ {{row.expire_date or '-' }} (剩余{{row.expire_days}}天)\n{% endfor %}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
示例:SSL证书到期(markdown格式)
|
||||
|
||||
```json
|
||||
{
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title": "SSL证书到期提醒",
|
||||
"text": "### SSL证书到期提醒 🔔\n\n| 域名 | 到期 |\n| ---- | ---- |\n{% for row in list %}| {{row.domain}} | {{row.expire_date or '未知'}}({{row.expire_days}}天) |\n{% endfor %}\n\n> 请及时更新即将到期的SSL证书。"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
示例:网站监控异常(markdown格式)
|
||||
|
||||
```json
|
||||
{
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title": "网站监控异常提醒",
|
||||
"text": "{{monitor_row.title}} 监测异常,请检查:\n
|
||||
请求URL:<font color=\"red\">{{monitor_row.http_url}}</font>
|
||||
重试次数:<font color=\"red\">{{monitor_row.allow_error_count}}</font>
|
||||
状态:{% if monitor_row.status==2 %}<font color=\"red\">失败</font>{% elif monitor_row.status==1 %}<font color=\"green\">成功</font>{% else %}<font color=\"comment\">未知</font>{% endif %}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
可以参考 [@PanZongQing](https://github.com/PanZongQing) 分享的钉钉webhook配置:
|
||||
|
||||
[对接钉钉群内自定义webhook机器人发送告警注意事项](https://github.com/dromara/domain-admin/issues/47)
|
||||
|
||||
|
||||
### 2.3、webhook发送Resend邮件
|
||||
|
||||
Resend 是一个为开发者提供的email 接口
|
||||
|
||||
工作原理:通过api接口使用HTTP协议发送邮件到Resend服务器,再通过Resend服务器使用SMTP协议发送邮件到目标邮箱
|
||||
|
||||
步骤:
|
||||
|
||||
1、注册账号:[https://resend.com/](https://resend.com/)
|
||||
|
||||
2、获取API Key
|
||||
|
||||
3、配置webhook
|
||||
|
||||
- 请求方法: POST
|
||||
|
||||
- 请求地址: https://api.resend.com/emails
|
||||
|
||||
- 请求头
|
||||
```json
|
||||
{
|
||||
"Authorization": "Bearer <API Key>",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
```
|
||||
|
||||
- 请求体
|
||||
|
||||
```json
|
||||
{
|
||||
"from": "onboarding@resend.dev",
|
||||
"to": "123456@qq.com",
|
||||
"subject": "Hello World",
|
||||
"html": "<p><strong>证书到期提醒</strong></p>"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4、webhook配置showdoc
|
||||
|
||||
showdoc是一个从服务器推送消息到手机的工具,可以通过api接口推送到微信服务号消息
|
||||
|
||||
注册地址:[https://push.showdoc.com.cn/](https://push.showdoc.com.cn/)
|
||||
|
||||
- 请求方法: POST
|
||||
|
||||
- 请求地址:
|
||||
|
||||
```bash
|
||||
https://push.showdoc.com.cn/server/api/push/<API Key>
|
||||
```
|
||||
|
||||
- 请求头
|
||||
|
||||
```json
|
||||
{
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
```
|
||||
|
||||
- 请求体
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "域名到期提醒",
|
||||
"content": "{% for row in list %}{{row.domain}} {{row.start_date or '-' }} - {{row.expire_date or '-' }} ({{row.expire_days}}){% endfor %}"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5、webhook发送飞书消息
|
||||
|
||||
模板示例由微信群友@kaka 提供
|
||||
|
||||
```json
|
||||
{
|
||||
"msg_type": "interactive",
|
||||
"card": {
|
||||
"config": {
|
||||
"wide_screen_mode": true,
|
||||
"enable_forward": true
|
||||
},
|
||||
"elements": [{
|
||||
"tag": "div",
|
||||
"text": {
|
||||
"tag": "plain_text",
|
||||
"content": "",
|
||||
"lines": 1
|
||||
}
|
||||
,
|
||||
"fields": [
|
||||
|
||||
{
|
||||
|
||||
"text": {
|
||||
"tag": "lark_md",
|
||||
"content": "**域名证书 **: <ul> {%- for row in list %} {%- if row.expire_days > 0 %} <li> {{row.domain}} {{"当前域名或证书 申请时间: "}} {{row.start_date or '-' }} - {{"到期时间: "}} {{row.expire_date or '-' }} {{"剩余: "}}( {% if row.expire_days > 0 %} {{row.expire_days}} {% endif %} ) {{"天; "}} {% endif %} {% endfor %} </li> </ul> "
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"actions": [{
|
||||
"tag": "button",
|
||||
|
||||
"text": {
|
||||
"content": "域名或证书过期提醒 :玫瑰:",
|
||||
"tag": "lark_md"
|
||||
},
|
||||
"url": "https://dc.console.aliyun.com/",
|
||||
"type": "primary",
|
||||
"value": {
|
||||
"chosen": "approve"
|
||||
}
|
||||
}],
|
||||
"tag": "action"
|
||||
}],
|
||||
"header": {
|
||||
"title": {
|
||||
"content": "域名或证书过期 告警",
|
||||
"tag": "plain_text"
|
||||
},
|
||||
"template": "red"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.6、webhook发送企业微信消息
|
||||
|
||||
文档:[https://developer.work.weixin.qq.com/document/path/99110](https://developer.work.weixin.qq.com/document/path/99110)
|
||||
|
||||
webhook地址
|
||||
|
||||
```
|
||||
https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa
|
||||
```
|
||||
|
||||
消息体
|
||||
```json
|
||||
{
|
||||
"msgtype": "text",
|
||||
"text": {
|
||||
"content": "广州今日天气:29度,大部分多云,降雨概率:60%",
|
||||
"mentioned_list":["wangqing","@all"],
|
||||
"mentioned_mobile_list":["13800001111","@all"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
示例:监控证书到期(文本格式)
|
||||
|
||||
```json
|
||||
{
|
||||
"msgtype": "text",
|
||||
"text": {
|
||||
"content": "SSL证书到期提醒:{% for row in list %}{{row.domain}} {{row.start_date or '-' }} - {{row.expire_date or '-' }} ({{row.expire_days}}){% endfor %}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
示例:监控证书到期(markdown格式)
|
||||
|
||||
```json
|
||||
{
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title": "SSL证书到期提醒",
|
||||
"content": "### SSL证书到期提醒 🔔\n\n| 域名 | 生效日期 | 到期日期 | 剩余天数 |\n| ---- | -------- | -------- | :--------: |\n{% for row in list %}| {{row.domain}} | {{row.start_date or '未知'}} | {{row.expire_date or '未知'}} | {{row.expire_days}}天 |\n{% endfor %}\n\n> 请及时更新即将到期的SSL证书,以确保网站安全性。"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
示例:网站监控异常
|
||||
|
||||
```json
|
||||
{
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"content": "{{monitor_row.title}}监测提醒,请相关同事注意。\n
|
||||
>请求URL:<font color=\"red\">{{monitor_row.http_url}}</font>
|
||||
>重试次数:<font color=\"red\">{{monitor_row.allow_error_count}}</font>
|
||||
>状态:{% if monitor_row.status==2 %}<font color=\"red\">失败</font>{% elif monitor_row.status==1 %}<font color=\"green\">成功</font>{% else %}<font color=\"comment\">未知</font>{% endif %}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 3、企业微信
|
||||
|
||||
```json
|
||||
{
|
||||
"touser": "UserName",
|
||||
"msgtype": "text",
|
||||
"agentid": 1000001,
|
||||
"text": {
|
||||
"content": "你的域名证书即将到期\n点击查看<a href=\"http://www.demo.com/\">Domain Admin</a>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
自定义通知模板示例
|
||||
|
||||
```json
|
||||
{
|
||||
"touser": "UserName",
|
||||
"msgtype": "text",
|
||||
"agentid": 1000001,
|
||||
"text": {
|
||||
"content": "SSL证书到期提醒:\n{% for row in list %}{{row.domain}} {{row.group_name or '-'}} ({{row.expire_days}}){% endfor %}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
参考:[https://developer.work.weixin.qq.com/document/path/90236](https://developer.work.weixin.qq.com/document/path/90236)
|
||||
|
||||
## 4、钉钉
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_id": "<agent_id>",
|
||||
"userid_list": "<userid_list>",
|
||||
"msg": {
|
||||
"msgtype": "text",
|
||||
"text": {
|
||||
"content": "域名或证书过期提醒"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
参考文档:[https://open.dingtalk.com/document/orgapp/asynchronous-sending-of-enterprise-session-messages](https://open.dingtalk.com/document/orgapp/asynchronous-sending-of-enterprise-session-messages)
|
||||
|
||||
## 5、飞书
|
||||
|
||||
```json
|
||||
{
|
||||
"receive_id": "<receive_id>",
|
||||
"msg_type": "text",
|
||||
"content": "{\"text\":\"域名或证书过期提醒\"}"
|
||||
}
|
||||
```
|
||||
|
||||
参考文档:[https://open.feishu.cn/document/server-docs/im-v1/message/create](https://open.feishu.cn/document/server-docs/im-v1/message/create)
|
||||
|
||||
|
||||
## 6、模板和参数
|
||||
|
||||
采用`jinja2` 模板引擎
|
||||
|
||||
WebHook、企业微信、飞书、钉钉均支持自定义通知模板
|
||||
|
||||
### 6.1、通用示例
|
||||
|
||||
传入模板的参数示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"list":[
|
||||
{
|
||||
"domain": "www.demo.com",
|
||||
"start_date": "2023-06-01",
|
||||
"expire_date": "2023-06-21",
|
||||
"expire_days": 20
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
参数说明
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| -| - | - |
|
||||
| domain | string | 域名/证书域名
|
||||
| start_date | string | 生效时间
|
||||
| expire_date | string | 过期时间
|
||||
| expire_days | int | 剩余天数
|
||||
|
||||
> 备注:list仅包含满足通知条件的数据
|
||||
|
||||
示例
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "域名到期提醒",
|
||||
"content": "{% for row in list %}{{row.domain}} {{row.start_date or '-' }} - {{row.expire_date or '-' }} ({{row.expire_days}}){% endfor %}"
|
||||
}
|
||||
```
|
||||
|
||||
渲染结果
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "域名到期提醒",
|
||||
"content": "www.demo.com 2023-06-01 - 2023-06-21 (20)"
|
||||
}
|
||||
```
|
||||
|
||||
不同的事件参数稍有不同,会有其独特的参数
|
||||
|
||||
### 6.2、域名到期
|
||||
|
||||
```json
|
||||
{
|
||||
"list": [
|
||||
{
|
||||
"id": 9,
|
||||
"user_id": 1,
|
||||
|
||||
"domain": "www.baidu.com",
|
||||
|
||||
"group_id": 9,
|
||||
"group_name": "百度系",
|
||||
|
||||
"comment": "备注",
|
||||
|
||||
"start_time": "2010-08-28 04:11:35",
|
||||
"expire_time": "2023-08-28 04:11:35",
|
||||
"start_date": "2010-08-28",
|
||||
"expire_date": "2023-08-28",
|
||||
"expire_days": 5,
|
||||
|
||||
"domain_registrar": "厦门易名科技股份有限公司",
|
||||
"domain_registrar_url": "https://www.ename.net/",
|
||||
"icp_company": "北京百度网讯科技有限公司",
|
||||
"icp_licence": "京ICP证030173号-1",
|
||||
|
||||
"tags": [
|
||||
"企业服务",
|
||||
"汽车服务"
|
||||
],
|
||||
"tags_str": "企业服务、汽车服务",
|
||||
|
||||
"is_auto_update": true,
|
||||
"is_expire_monitor": true,
|
||||
|
||||
"create_time": "2023-07-20 22:59:20",
|
||||
"update_time": "2023-08-22 16:01:13",
|
||||
"update_time_label": "13分钟前"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3、SSL证书到期
|
||||
|
||||
```json
|
||||
{
|
||||
"list": [
|
||||
{
|
||||
"id": 3,
|
||||
"user_id": 1,
|
||||
|
||||
"domain": "www.baidu.com",
|
||||
"root_domain": "baidu.com",
|
||||
"port": 443,
|
||||
|
||||
"group_id": 4,
|
||||
"group_name": "百度系",
|
||||
|
||||
"comment": "备注字段",
|
||||
|
||||
"start_time": "2023-07-06 09:51:06",
|
||||
"expire_time": "2024-08-06 09:51:05",
|
||||
"start_date": "2023-07-06",
|
||||
"expire_date": "2024-08-06",
|
||||
"expire_days": 349,
|
||||
|
||||
"is_auto_update": true,
|
||||
"is_expire_monitor": true,
|
||||
"is_dynamic_host": false,
|
||||
|
||||
"create_time": "2023-08-22 16:28:19",
|
||||
"update_time": "2023-08-22 16:28:19",
|
||||
"update_time_label": "刚刚"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 6.4、监控异常
|
||||
|
||||
异常通知和异常恢复通知的字段一致
|
||||
|
||||
```json
|
||||
{
|
||||
"monitor_row": {
|
||||
"title": "名称",
|
||||
"http_url": "请求URL",
|
||||
"allow_error_count": 3,
|
||||
"status": 2
|
||||
},
|
||||
"error": "报错信息"
|
||||
}
|
||||
```
|
||||
|
||||
参数说明
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| -| - | - |
|
||||
| error | string | 报错信息
|
||||
| monitor_row.title | string | 名称
|
||||
| monitor_row.http_url | string | 请求URL
|
||||
| monitor_row.allow_error_count | int | 重试次数
|
||||
| monitor_row.status | int | 状态:0未知,1成功,2失败
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# 开发计划
|
||||
|
||||
- `已完成` 支持企业微信通知
|
||||
- `已完成` 支持域名分组
|
||||
- 增加理员权限,权限分级:root 管理员 普通用户
|
||||
- `已完成` 解决批量导入超时问题,支持1000条数据导入
|
||||
- `已完成` 支持域名备注
|
||||
- `已完成` 支持域名到期数据
|
||||
- `已完成` webhook支持变量
|
||||
- `已完成` 异步操作的前端状态显示
|
||||
- 暗黑模式
|
||||
- `已完成` 支持内网用户自己设置过期时间
|
||||
- `已完成` 增加筛选功能: 筛选域名状态、证书状态、网站状态
|
||||
- `已完成` 增加批量删除域名的功能
|
||||
- 支持Telegram 告警
|
||||
- 监控网站恢复后发送恢复的报警邮件
|
||||
|
||||
- v1.5.x(开发中)
|
||||
- 新增网站监控 [issues#17](https://github.com/mouday/domain-admin/issues/17)
|
||||
- `已完成` SSL证书申请
|
||||
- `已完成` SSL证书部署
|
||||
- admin拥有所有权限
|
||||
- `已完成` 用户密码重置
|
||||
- `已完成` 域名导出功能新增字段:主办单位名称、ICP备案
|
||||
- `已完成` 域名列表新增字段:标签
|
||||
- `已完成` 域名列表管理端支持修改:创建人
|
||||
- `已完成` 域名列表支持:标签搜索
|
||||
- `已完成` 支持dns验证
|
||||
- `已完成` 验证文件一键部署
|
||||
- `已完成` 证书一键部署
|
||||
- `已完成` 证书自动续期
|
||||
- `已完成` 证书自动部署
|
||||
- `已完成` SSL证书手动续期
|
||||
- 首次导入SSL证书列表,显示待处理状态
|
||||
- `已完成` 动态主机 开关问题
|
||||
- 自定义列
|
||||
- `已完成` 备案主体列表
|
||||
- `已完成` 自动更新证书的地方带一个端口,有的服务器默认用的不是22
|
||||
- 数据导入导出支持excel
|
||||
- `已完成` 通知配置增加分组参数
|
||||
- `已完成` 主机host管理列表,支持移除
|
||||
- 通过webhook重启
|
||||
|
||||
一些又有的链接
|
||||
|
||||
- [小熊猫去水印](https://qushuiyin.guijianpan.com)
|
||||
- [ICP备案查询:ICP_Query](https://github.com/HG-ha/ICP_Query)
|
||||
- [ICP备案查询:ICP-Checker](https://github.com/wongzeon/ICP-Checker)
|
||||
|
||||
- [某查询网站点选逆向分析](https://mp.weixin.qq.com/s/1Dw7ylfRmBjV_khOF4nuGQ)
|
||||
- [文字点选验证码-破解之路](https://mp.weixin.qq.com/s/Lo9GveZUpnn_NTF8jL2ORg)
|
||||
@@ -0,0 +1,7 @@
|
||||
# 扩展插件
|
||||
|
||||
## domain-admin-cronjob
|
||||
|
||||
import domain from kubenetes ingress
|
||||
|
||||
[https://github.com/mjwtc0722/domain-admin-cronjob](https://github.com/mjwtc0722/domain-admin-cronjob)
|
||||
@@ -0,0 +1,342 @@
|
||||
# 常见问题
|
||||
|
||||
## 1、暂不支持多进程方式启动
|
||||
|
||||
使用 master + 多worker 方式启动应用,会启动多个定时任务Scheduler,导致多次执行任务
|
||||
|
||||
如果小规模使用,启动一个进程即可
|
||||
|
||||
如果是需要支持并发访问,可自行改进应用
|
||||
|
||||
将定时器独立出来,单独一个进程控制,行成 scheduler + Flask(master + 多worker)
|
||||
|
||||
## 2、为什么外网访问不到?
|
||||
|
||||
```bash
|
||||
# 启动运行
|
||||
$ gunicorn 'domain_admin.main:app'
|
||||
|
||||
# 支持外网可访问,云服务器(阿里云或腾讯云)需要设置安全组
|
||||
# 默认内网访问 --bind 127.0.0.1:8000
|
||||
$ gunicorn --bind '0.0.0.0:8000' domain_admin.main:app'
|
||||
```
|
||||
|
||||
更多设置,可参考[gunicorn](https://docs.gunicorn.org/en/stable/index.html)
|
||||
|
||||
## 3、Windows平台启动报错,找不到模块 `fcntl`
|
||||
|
||||
gunicorn不支持Windows,可以使用[waitress](https://github.com/Pylons/waitress) 替换,感谢[@cbr252522489](https://github.com/cbr252522489)提供的解决方案
|
||||
|
||||
```bash
|
||||
$ pip install waitress
|
||||
|
||||
$ waitress-serve --listen=127.0.0.1:8000 domain_admin.main:app
|
||||
```
|
||||
|
||||
参考:[https://stackoverflow.com/questions/45228395/error-no-module-named-fcntl](https://stackoverflow.com/questions/45228395/error-no-module-named-fcntl)
|
||||
|
||||
## 4、添加域名数据后系统异常
|
||||
|
||||
可按如下步骤删除异常数据
|
||||
|
||||
docker 启动方式
|
||||
|
||||
```bash
|
||||
# 查看容器的运行信息
|
||||
$ docker ps
|
||||
|
||||
# 进入容器
|
||||
$ docker exec -it <容器id> /bin/sh
|
||||
|
||||
# 安装依赖
|
||||
$ apk add sqlite
|
||||
|
||||
# 进入sqlite3
|
||||
$ sqlite3
|
||||
|
||||
sqlite> .open /app/database/database.db
|
||||
|
||||
sqlite> .tables
|
||||
log_scheduler tb_group tb_system tb_version
|
||||
tb_domain tb_notify tb_user
|
||||
|
||||
# 查看数据
|
||||
sqlite> select * from tb_domain;
|
||||
|
||||
# 删除数据
|
||||
sqlite> DELETE FROM tb_domain WHERE id = 1;
|
||||
|
||||
# 退出
|
||||
sqlite> .quit
|
||||
```
|
||||
|
||||
## 5、邮件发送失败
|
||||
|
||||
可尝试更换端口:25、465、587
|
||||
|
||||
## 7、监控域名非443的端口
|
||||
|
||||
域名格式
|
||||
|
||||
```
|
||||
域名:端口
|
||||
|
||||
eg:
|
||||
|
||||
www.baidu.com:8080
|
||||
```
|
||||
|
||||
## 8、修改数据库链接
|
||||
|
||||
系统默认使用sqlite数据库作为后端存储,仅支持单线程操作
|
||||
|
||||
如果操作频繁会出现`database locked` 提示,就是数据库锁表了,可以更换后端存储为mysql,或者其他数据库
|
||||
|
||||
通过配置`.env` 文件或者直接设置系统环境变量
|
||||
|
||||
```bash
|
||||
# sqlite 默认
|
||||
DB_CONNECT_URL=sqlite:///database/database.db
|
||||
|
||||
# mysql
|
||||
# 需要安装模块 pymysql
|
||||
# pip install pymysql
|
||||
DB_CONNECT_URL=mysql://root:123456@127.0.0.1:3306/data_domain
|
||||
|
||||
# 来自群友 @〖斗魂〗繁←星 的分享
|
||||
# 如果 mysql 开启了 ssl,mysql 连接字符串应该写成
|
||||
DB_CONNECT_URL=mysql://root:123456@127.0.0.1:3306/data_domain?ssl_verify_cert=true
|
||||
|
||||
# postgresql
|
||||
DB_CONNECT_URL=postgresql://root:123456@localhost:5432/data_domain
|
||||
```
|
||||
|
||||
说明:以上配置示例,需要提前创建名为`data_domain` 的数据库,也可以自定义其他名称的数据库
|
||||
|
||||
更多mysql的设置可参考:[https://pymysql.readthedocs.io/en/latest/modules/connections.html](https://pymysql.readthedocs.io/en/latest/modules/connections.html)
|
||||
|
||||
## 10、支持`prometheus` 的`/metrics`接口
|
||||
|
||||
请求地址:http://127.0.0.1:8000/metrics
|
||||
|
||||
示例
|
||||
|
||||
```
|
||||
POST http://127.0.0.1:8000/metrics
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
1、第一步、需要在 `系统设置/API KEY` 获取授权key
|
||||
|
||||
2、第二步、配置 prometheus.yml
|
||||
|
||||
```yaml
|
||||
scrape_configs:
|
||||
bearer_token: 'f60c03bfff8bb42dcf6821542e5fd11e'
|
||||
```
|
||||
|
||||
如果是夜莺-Nightingale [参考文档](https://flashcat.cloud/docs/content/flashcat-monitor/categraf/plugin/prometheus/)
|
||||
|
||||
```yaml
|
||||
[[instances.domain]]
|
||||
urls = [
|
||||
"http://127.0.0.1:8000/metrics"
|
||||
]
|
||||
|
||||
url_label_key = "instance"
|
||||
url_label_value = "{{.Host}}"
|
||||
headers = ["Authorization", "Bearer f60c03bfff8bb42dcf6821542e5fd11e"]
|
||||
```
|
||||
|
||||
该配置由[@1275788667](https://github.com/1275788667) 提供
|
||||
|
||||
返回数据示例:
|
||||
|
||||
```text
|
||||
# HELP domain_admin this is a domain admin data
|
||||
# TYPE domain_admin gauge
|
||||
domain_admin{domain="pgmanage.qnvip.com"} 0.0
|
||||
domain_admin{domain="fanyi.baidu.com"} 37.0
|
||||
domain_admin{domain="www.tmall.com"} 37.0
|
||||
domain_admin{domain="www.baidu.com"} 37.0
|
||||
domain_admin{domain="www.taobao.com"} 37.0
|
||||
```
|
||||
|
||||
@since v1.5.27 升级为
|
||||
|
||||
```
|
||||
# HELP domain_admin this is a domain admin data
|
||||
# TYPE domain_admin gauge 证书列表数据
|
||||
domain_admin{domain="www.baidu.com",group_name="百度系",root_domain="baidu.com"} 258.0
|
||||
domain_admin{domain="www.163.com",group_name="",root_domain="163.com"} 153.0
|
||||
# HELP domain_info this is a domain info data
|
||||
# TYPE domain_info gauge 域名列表数据
|
||||
domain_info{domain="163.com",group_name="百度系"} 1392.0
|
||||
domain_info{domain="qq.com",group_name=""} 3535.0
|
||||
```
|
||||
|
||||
## 11、部分域名无法查询到信息
|
||||
|
||||
已知不支持的域名后缀:`.lc`、`.ml`、`.ai`、`.my`、`.ch`、`.edu.cn`、`.name`
|
||||
|
||||
## 12、获取ingress的域名
|
||||
|
||||
从k8s里面自动获取ingress的域名,然后添加到domain-admin里面
|
||||
|
||||
安装依赖
|
||||
|
||||
```bash
|
||||
pip install tldextract requests kubernetes
|
||||
```
|
||||
|
||||
参考代码 由群友 `@旺仔牛奶` 贡献
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
import re
|
||||
from kubernetes import config, client
|
||||
import tldextract
|
||||
import logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
|
||||
def get_token(host, username, password):
|
||||
url = '{}/api/login'.format(host)
|
||||
data = {
|
||||
'username': username,
|
||||
'password': password
|
||||
}
|
||||
try:
|
||||
response = requests.post(url=url, headers=headers, data=json.dumps(data))
|
||||
result = json.loads(response.text)
|
||||
if result['code'] == 0:
|
||||
token = result['data']['token']
|
||||
return token
|
||||
else:
|
||||
logging.error(response.text)
|
||||
return False
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
|
||||
|
||||
def add_domain(host, token, domain):
|
||||
url = '{}/api/addDomainInfo'.format(host)
|
||||
data = {
|
||||
'domain': domain
|
||||
}
|
||||
headers['X-Token'] = token
|
||||
try:
|
||||
response = requests.post(url=url, headers=headers, data=json.dumps(data))
|
||||
result = json.loads(response.text)
|
||||
if result['code'] == 0:
|
||||
return True
|
||||
else:
|
||||
logging.error(response.text)
|
||||
return False
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
|
||||
|
||||
def main():
|
||||
config.load_incluster_config()
|
||||
kube_api = client.ExtensionsV1beta1Api()
|
||||
ingresses = kube_api.list_ingress_for_all_namespaces()
|
||||
_set = set()
|
||||
for item in ingresses.items:
|
||||
if not re.search(os.getenv('NAMESPACE_MATCH'), item.metadata.namespace):
|
||||
continue
|
||||
if re.search(os.getenv('NAMESPACE_NOT_MATCH'), item.metadata.namespace):
|
||||
continue
|
||||
for rule in item.spec.rules:
|
||||
domain = tldextract.extract(rule.host).registered_domain
|
||||
_set.add(domain)
|
||||
token = get_token(host=os.getenv('DOMAIN_ADMIN_HOST'),
|
||||
username=os.getenv('DOMAIN_ADMIN_USERNAME'),
|
||||
password=os.getenv('DOMAIN_ADMIN_PASSWORD'))
|
||||
for item in _set:
|
||||
add_domain(host=os.getenv('DOMAIN_ADMIN_HOST'), token=token, domain=item)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
## 13、证书监控原理
|
||||
|
||||
第一步:`证书数据更新`:通过实时访问网站,取回网站部署的证书,解析证书签发时间和过期时间,更新到数据库
|
||||
|
||||
第二步:`到期通知`:通过配置好的通知触发条件,查询数据库,如果查询到记录则发送通知,没有查询到数据就忽略
|
||||
|
||||
> 如果网站不可访问,可以将 `自动更新` 关闭,不影响到期通知
|
||||
|
||||
## 14、域名监控原理
|
||||
|
||||
第一步:`域名数据更新`:通过访问域名服务器,取回域名信息,解析出域名注册时间和过期时间,更新到数据库
|
||||
|
||||
第二步:`到期通知`:通过配置好的通知触发条件,查询数据库,如果查询到记录则发送通知,没有查询到数据就忽略
|
||||
|
||||
> 如果域名查询不到信息,可以将 `自动更新` 关闭,不影响到期通知
|
||||
> 考虑到域名注册后到期时间不会改变,所以增加了到期前`30` 天才更新域名信息的条件,减少程序运行时间;
|
||||
> 如果实时查询域名信息,请求过快会被域名信息服务器会拦截,从而查询不到域名信息,触发误报的问题
|
||||
>
|
||||
|
||||
## 15、提示缺少依赖
|
||||
|
||||
可参考网友写的总结文章:
|
||||
- [domain-admin域名监控的源码搭建](https://blog.csdn.net/u013901725/article/details/132394530)
|
||||
|
||||
## 16、主机地址显示错误
|
||||
|
||||
由于系统不会主动清空已存在的主机地址,对于CND等证书,可以打开【动态主机】,每次更新数据都会先清空主机IP列表,重新获取数据,避免误报的问题。
|
||||
|
||||
|
||||
## 17、忘记admin密码怎么办
|
||||
|
||||
把数据库表`tb_user`密码字段`password`替换为这个:
|
||||
|
||||
```bash
|
||||
$2b$12$c/tJvOYaWxzis4CXSyGN9ua4B7wzor8j9WrGsgV/2pdJnsrAMJxiK
|
||||
```
|
||||
|
||||
可以重置为:123456
|
||||
|
||||
示例
|
||||
|
||||
```sql
|
||||
# sqlite3
|
||||
sqlite> ATTACH DATABASE '/opt/domain-admin/database/database.db' AS mydatabase;
|
||||
sqlite> UPDATE tb_user SET password = "$2b$12$c/tJvOYaWxzis4CXSyGN9ua4B7wzor8j9WrGsgV/2pdJnsrAMJxiK" ;
|
||||
```
|
||||
|
||||
## 18、database is locked
|
||||
|
||||
数据库锁表了,由于默认使用sqlite数据库,不支持多线程操作
|
||||
|
||||
如果遇到此提示,可以等待一会再进行操作
|
||||
|
||||
还可以直接换成mysql数据库,参考[可选配置](https://domain-admin.readthedocs.io/zh_CN/latest/manual/install.html#id5)
|
||||
|
||||
## 19、Python2.7 依赖问题
|
||||
|
||||
可以手动安装适合Python2.7 的依赖版本,也可以使用整理好的依赖文件
|
||||
|
||||
[production-2.7.txt](https://github.com/mouday/domain-admin/blob/master/requirements/production-2.7.txt)
|
||||
|
||||
## 20、grafana 的展示模版
|
||||
|
||||
可参考群友 友志 提供的模板
|
||||
|
||||
https://grafana.com/grafana/dashboards/14605-domain-exporter-for-prometheus/
|
||||
|
||||
https://grafana.com/grafana/dashboards/13924-9116-domain/
|
||||
@@ -0,0 +1,210 @@
|
||||
# SSL证书申请
|
||||
|
||||
申请步骤与阿里云申请证书一致
|
||||
|
||||
考虑到大部分用户采用的是nginx,其他用户可自行按照如下思路研究
|
||||
|
||||
首次使用申请ssl证书,推荐使用`手动验证`,流程跑通熟练之后,再使用`一键部署`功能
|
||||
|
||||
申请步骤:
|
||||
|
||||
## 1、填写域名
|
||||
|
||||
可以是一个域名,也可以是多个域名,每行一个
|
||||
|
||||
例如:
|
||||
|
||||
```bash
|
||||
baidu.com
|
||||
www.baidu.com
|
||||
```
|
||||
|
||||
## 2、验证域名
|
||||
|
||||
2.1、手动文件验证
|
||||
|
||||
- 下载`验证文件`,到服务器
|
||||
- 浏览器打开`验证URL`,如果没有报错,有任何响应结果,就是部署正确
|
||||
- 点击`验证` 按钮,进行域名验证;如果验证失败,请自行检查部署是否正确
|
||||
|
||||
> 说明:如果是多域名,需要将所有的待验证文件都放到服务器
|
||||
|
||||
2.2、自动部署验证文件
|
||||
|
||||
- 填写`服务器账号和密码`
|
||||
- 填写`服务器目录`,如果目录不存在,需要自行创建
|
||||
- 点击`一键部署`,验证文件就会自动拷贝到指定的服务器目录
|
||||
- 点击`验证` 按钮,进行域名验证;如果验证失败,请自行检查部署是否正确
|
||||
|
||||
|
||||
## 3、下载证书
|
||||
|
||||
3.1、手动部署
|
||||
|
||||
- 点击zip 下载SSL证书,自行解压
|
||||
- 上传到服务器对应目录下
|
||||
|
||||
3.2、自动部署证书文件
|
||||
|
||||
- 同样配置好这几个参数:`服务器地址`、`私钥部署路径`、`公钥部署路径`、`重启命令`
|
||||
- 点击`一键部署`,证书文件就会自动拷贝到指定的服务器目录,并重启nginx服务
|
||||
|
||||
## 4、远程部署
|
||||
|
||||
通过调用远程api接口,将证书数据发送到远程接口,可自定义实现的部署逻辑
|
||||
|
||||
申请证书api推送格式
|
||||
|
||||
```json
|
||||
{
|
||||
"domains": [
|
||||
"www.baidu.com",
|
||||
"zhidao.baidu.com"
|
||||
],
|
||||
"ssl_certificate":"-----BEGIN CERTIFICATE-----\nMIIGdTCCBN2gAwI\n-----END CERTIFICATE-----",
|
||||
"ssl_certificate_key":"-----BEGIN PRIVATE KEY-----\nMIIEvH+bpTwI=\n-----END PRIVATE KEY-----",
|
||||
"start_time": "2023-01-04 14:33:39",
|
||||
"expire_time": "2023-04-04 14:33:39"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
托管证书api推送格式
|
||||
|
||||
```json
|
||||
{
|
||||
"domain": "www.baidu.com",
|
||||
"ssl_certificate":"-----BEGIN CERTIFICATE-----\nMIIGdTCCBN2gAwI\n-----END CERTIFICATE-----",
|
||||
"ssl_certificate_key":"-----BEGIN PRIVATE KEY-----\nMIIEvH+bpTwI=\n-----END PRIVATE KEY-----",
|
||||
"start_time": "2023-01-04 14:33:39",
|
||||
"expire_time": "2023-04-04 14:33:39"
|
||||
}
|
||||
```
|
||||
|
||||
参数说明
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| -|--------------------------| - |
|
||||
| domains | `array<string>` / string | 域名列表
|
||||
| ssl_certificate | string | 证书公钥
|
||||
| ssl_certificate_key | string | 证书私钥
|
||||
| start_time | string | 证书生效时间
|
||||
| expire_time | string | 证书过期时间
|
||||
|
||||
|
||||
远程服务器实现示例
|
||||
|
||||
```python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@File : fake_server.py
|
||||
@Date : 2024-03-22
|
||||
"""
|
||||
from flask import Flask, request, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route(rule='/issueCertificate', methods=['POST'])
|
||||
def issue_certificate():
|
||||
print(request.json)
|
||||
print(request.headers)
|
||||
# 此处可自行实现部署逻辑
|
||||
return jsonify({'result': 'ok'})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=8082)
|
||||
```
|
||||
|
||||
完整示例,由群友 `@星河 ๑.`提供
|
||||
|
||||
```python
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
from flask import Flask, request, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
def delayed_restart_nginx():
|
||||
"""在延迟5秒后重启nginx"""
|
||||
time.sleep(5)
|
||||
try:
|
||||
subprocess.run(['docker', 'restart', 'nginx'])
|
||||
print('重启nginx完成。')
|
||||
except subprocess.CalledProcessError as e:
|
||||
print('重启nginx失败: ', e)
|
||||
|
||||
@app.route('/deploy-cert', methods=['POST'])
|
||||
def deploy_cert():
|
||||
token = request.headers.get('token')
|
||||
if token != 'token':
|
||||
return jsonify({'error': 'Invalid token'}), 401
|
||||
|
||||
# 获取请求参数
|
||||
data = request.get_json()
|
||||
# 获取域名列表
|
||||
domains = data.get('domains', [])
|
||||
# 获取证书文件
|
||||
ssl_certificate = data.get('ssl_certificate', '')
|
||||
# 获取证书密钥
|
||||
ssl_certificate_key = data.get('ssl_certificate_key', '')
|
||||
|
||||
# 检测是否有值,避免无效请求
|
||||
if not domains or not ssl_certificate or not ssl_certificate_key:
|
||||
return jsonify({'error': 'Missing required fields'}), 400
|
||||
|
||||
# 定义基础路径,因为本人的 nginx 是 docker 运行的,做了磁盘映射
|
||||
# nginx 的配置文件都在此目录,加上域名作为不同域名的证书目录
|
||||
# 如:/server/dockerApps/nginx/nginx/certs/simple.com/fullchain.pem
|
||||
# 如:/server/dockerApps/nginx/nginx/certs/simple.com/cert.key
|
||||
base_path = '/server/dockerApps/nginx/nginx/certs'
|
||||
|
||||
# 遍历域名列表
|
||||
for domain in domains:
|
||||
# 将泛域名中的 *. 去掉
|
||||
if domain.startswith('*.'):
|
||||
domain = domain.replace('*.', '', 1)
|
||||
# 创建目录
|
||||
domain_dir = os.path.join(base_path, domain)
|
||||
if os.path.exists(domain_dir):
|
||||
shutil.rmtree(domain_dir)
|
||||
os.makedirs(domain_dir)
|
||||
|
||||
# 生成证书文件
|
||||
cert_path = os.path.join(domain_dir, 'fullchain.pem')
|
||||
key_path = os.path.join(domain_dir, 'cert.key')
|
||||
|
||||
with open(cert_path, 'w') as cert_file:
|
||||
cert_file.write(ssl_certificate)
|
||||
|
||||
with open(key_path, 'w') as key_file:
|
||||
key_file.write(ssl_certificate_key)
|
||||
|
||||
print(domain, " ==> 证书创建成功")
|
||||
|
||||
# 执行重启nginx的线程
|
||||
threading.Thread(target=delayed_restart_nginx).start()
|
||||
|
||||
# 返回前端结果
|
||||
return jsonify({'result': 'ok'}), 200
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=8001)
|
||||
```
|
||||
|
||||
基于golang实现的部署方案,由网友提供
|
||||
|
||||
- [https://github.com/Gourds/domain-admin-ssl-deploy](https://github.com/Gourds/domain-admin-ssl-deploy) [issues#121](https://github.com/mouday/domain-admin/issues/121)
|
||||
|
||||
## 说明
|
||||
|
||||
- 如果是全程采用`一键部署`方式操作,域名到期前30天会自动续期
|
||||
- 申请到的证书,不会到期提醒,如需到期提醒,请将部署该证书的域名添加到`证书监控`列表
|
||||
- 如果总是验证不通过,可能是验证服务器有问题,可以使用其他证书申请手段,临时处理证书申请问题
|
||||
- 阿里云:每一个自然年内,提供20个90天个人测试证书(免费版) [点击跳转](https://help.aliyun.com/zh/ssl-certificate/user-guide/purchase-an-individual-test-certificate?from=domain-admin.cn)
|
||||
- 腾讯云:申请20张免费证书 [点击跳转](https://cloud.tencent.com/document/product/400/6814?from=domain-admin.cn)
|
||||
- acme.sh 自动申请证书也可以很好的配合domain-admin的到期提醒
|
||||
@@ -0,0 +1,156 @@
|
||||
import {defineConfig} from "vitepress";
|
||||
import AutoSidebar from "vite-plugin-vitepress-auto-sidebar";
|
||||
|
||||
// https://vitepress.dev/zh/reference/site-config
|
||||
// ref: https://gitee.com/zhontai/zhontai-admin-doc/blob/master/docs/.vitepress/config.ts
|
||||
export default defineConfig({
|
||||
lang: "zh-CN",
|
||||
title: "Domain Admin-SSL证书监测平台、到期提醒",
|
||||
base: "/domain-admin/",
|
||||
ignoreDeadLinks: true,
|
||||
lastUpdated: true,
|
||||
markdown: {
|
||||
image: {
|
||||
// 默认禁用图片懒加载
|
||||
lazyLoading: true,
|
||||
},
|
||||
},
|
||||
head: [
|
||||
["link", {rel: "icon", href: "https://demo.domain-admin.cn/favicon.ico"}],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
name: 'keywords',
|
||||
content: 'SSL,SSL证书监测,Let’s Encrypt,免费申请SSL证书,自动SSL证书续签,SSL证书到期提醒',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
name: 'description',
|
||||
content: 'Domain Admin是域名和SSL证书监测平台,基于Let’s Encrypt实现免费申请SSL证书,自动SSL证书续签,SSL证书到期提醒,主要的竞品有:certd、okhttp、acme.sh、cetrbot',
|
||||
},
|
||||
],
|
||||
// 百度统计
|
||||
[
|
||||
"script",
|
||||
{},
|
||||
`
|
||||
var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?1323b8c197b13150a6592146d5dbc928";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
`,
|
||||
],
|
||||
],
|
||||
sitemap: {
|
||||
hostname: "https://mouday.github.io/domain-admin",
|
||||
},
|
||||
locales: {
|
||||
"/": {
|
||||
lang: "zh-CN", // 设置为中文
|
||||
},
|
||||
},
|
||||
themeConfig: {
|
||||
search: {
|
||||
provider: "local",
|
||||
options: {
|
||||
translations: {
|
||||
button: {
|
||||
buttonText: "搜索文档",
|
||||
buttonAriaLabel: "搜索文档",
|
||||
},
|
||||
modal: {
|
||||
noResultsText: "无法找到相关结果",
|
||||
resetButtonTitle: "清除查询条件",
|
||||
displayDetails: "显示详细列表",
|
||||
footer: {
|
||||
navigateText: "切换",
|
||||
selectText: "选择",
|
||||
closeText: "关闭",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{text: "接口文档", link: "/api"},
|
||||
{text: "使用文档", link: "https://domain-admin.readthedocs.io/"},
|
||||
],
|
||||
|
||||
socialLinks: [
|
||||
{icon: "github", link: "https://github.com/dromara/domain-admin"},
|
||||
{icon: {
|
||||
svg: "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" t=\"1695538305440\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" p-id=\"1444\" style=\"width:1.25rem;height:1.25rem;vertical-align:middle;\"><path d=\"M512 1024C229.222 1024 0 794.778 0 512S229.222 0 512 0s512 229.222 512 512-229.222 512-512 512z m259.149-568.883h-290.74a25.293 25.293 0 0 0-25.292 25.293l-0.026 63.206c0 13.952 11.315 25.293 25.267 25.293h177.024c13.978 0 25.293 11.315 25.293 25.267v12.646a75.853 75.853 0 0 1-75.853 75.853h-240.23a25.293 25.293 0 0 1-25.267-25.293V417.203a75.853 75.853 0 0 1 75.827-75.853h353.946a25.293 25.293 0 0 0 25.267-25.292l0.077-63.207a25.293 25.293 0 0 0-25.268-25.293H417.152a189.62 189.62 0 0 0-189.62 189.645V771.15c0 13.977 11.316 25.293 25.294 25.293h372.94a170.65 170.65 0 0 0 170.65-170.65V480.384a25.293 25.293 0 0 0-25.293-25.267z\" fill=\"#C71D23\" p-id=\"1445\"></path></svg>",
|
||||
},
|
||||
link: "https://gitee.com/dromara/domain-admin"},
|
||||
],
|
||||
|
||||
outlineTitle: "导航目录",
|
||||
darkModeSwitchLabel: "外观",
|
||||
sidebarMenuLabel: "菜单",
|
||||
returnToTopLabel: "返回顶部",
|
||||
lastUpdatedText: "上次更新",
|
||||
outline: {
|
||||
/**
|
||||
* outline 中要显示的标题级别。
|
||||
* 单个数字表示只显示该级别的标题。
|
||||
* 如果传递的是一个元组,第一个数字是最小级别,第二个数字是最大级别。
|
||||
* `'deep'` 与 `[2, 6]` 相同,将显示从 `<h2>` 到 `<h6>` 的所有标题。
|
||||
*
|
||||
* @default 2
|
||||
*/
|
||||
level: "deep",
|
||||
/**
|
||||
* 显示在 outline 上的标题。
|
||||
*
|
||||
* @default 'On this page'
|
||||
*/
|
||||
label: "大纲",
|
||||
},
|
||||
|
||||
sidebar: [],
|
||||
footer: {
|
||||
message: '<a href="https://beian.miit.gov.cn/" target="_blank">京ICP备2024081455号-2</a> | <a href="https://beian.mps.gov.cn/#/query/webSearch?code=11011302007323" rel="noreferrer" target="_blank">京公网安备11011302007323</a>',
|
||||
copyright: 'Copyright © 2024'
|
||||
}
|
||||
},
|
||||
vite: {
|
||||
plugins: [
|
||||
// https://github.com/QC2168/vite-plugin-vitepress-auto-sidebar
|
||||
AutoSidebar({
|
||||
ignoreList: ["/public/", "assets", "img", "demo"],
|
||||
titleFromFile: true,
|
||||
// 侧边栏排序
|
||||
beforeCreateSideBarItems: (data: string[]): string[] => {
|
||||
// console.log(data);
|
||||
// 通过正则提取文件名中的数字
|
||||
function getOrder(item: string): number {
|
||||
if (item == "index.md") {
|
||||
return 0;
|
||||
}
|
||||
let res = item.match(/(?<order>\d+)/);
|
||||
if (res && res.groups) {
|
||||
return parseInt(res.groups.order);
|
||||
} else {
|
||||
return 999;
|
||||
}
|
||||
}
|
||||
|
||||
data.sort((a, b) => {
|
||||
return getOrder(a) - getOrder(b);
|
||||
});
|
||||
return data.filter((n) => n != ".DS_Store");
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
docFooter: {
|
||||
prev: "上一页",
|
||||
next: "下一页",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
- [使用说明](/)
|
||||
- [接口文档](/api.md)
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# 二次开发接口
|
||||
|
||||
## 使用说明
|
||||
|
||||
请求方式统一为`POST json`
|
||||
|
||||
```
|
||||
POST /api/<url>
|
||||
Content-Type: application/json
|
||||
X-TOKEN: <token>
|
||||
|
||||
{}
|
||||
```
|
||||
|
||||
## 注册登录
|
||||
|
||||
| 接口 | 说明 | 文档 |
|
||||
| - | - | -|
|
||||
| /api/register | 用户注册 | [register.md](../auth/register.md) |
|
||||
| /api/login | 用户登录 | [login.md](../auth/login.md) |
|
||||
|
||||
## 域名管理
|
||||
|
||||
| 接口 | 说明 | 文档 |
|
||||
| - | - | -|
|
||||
| /api/addDomain | 添加域名 | [addDomain.md](../domain/addDomain.md) |
|
||||
| /api/getDomainList | 获取域名列表 | [getDomainList.md](../domain/getDomainList.md) |
|
||||
| /api/getDomainById | 获取域名 | [getDomainById.md](../domain/getDomainById.md) |
|
||||
| /api/updateDomainById | 更新数据 | [updateDomainById.md](../domain/updateDomainById.md) |
|
||||
| /api/deleteDomainById | 删除域名 | [deleteDomainById.md](../domain/deleteDomainById.md) |
|
||||
| /api/updateDomainCertInfoById | 更新域名证书信息 | [updateDomainCertInfoById.md](../domain/updateDomainCertInfoById.md) |
|
||||
| /api/updateAllDomainCertInfo | 更新所有域名证书信息 | [updateAllDomainCertInfo.md](../domain/updateAllDomainCertInfo.md) |
|
||||
| /api/updateAllDomainCertInfoOfUser | 更新当前用户的所有域名信息 | [updateAllDomainCertInfoOfUser.md](../domain/updateAllDomainCertInfoOfUser.md) |
|
||||
| /api/sendDomainInfoListEmail | 发送域名证书信息到邮箱 | [sendDomainInfoListEmail.md](../domain/sendDomainInfoListEmail.md) |
|
||||
| /api/checkDomainCert | 检查域名证书到期信息 | [checkDomainCert.md](../domain/checkDomainCert.md) |
|
||||
| /api/importDomainFromFile | 从文件导入域名 | [importDomainFromFile.md](../domain/importDomainFromFile.md) |
|
||||
| /api/getAllDomainListOfUser | 获取用户的所有域名数据 | [getAllDomainListOfUser.md](../domain/getAllDomainListOfUser.md) |
|
||||
|
||||
## 分组管理
|
||||
|
||||
| 接口 | 说明 | 文档 |
|
||||
| - | - | -|
|
||||
| /api/addGroup | 添加分组 | [addGroup.md](../group/addGroup.md) |
|
||||
| /api/getGroupList | 获取分组列表 | [getGroupList.md](../group/getGroupList.md) |
|
||||
| /api/getGroupById | 获取分组数据 | [getGroupById.md](../group/getGroupById.md) |
|
||||
| /api/updateGroupById | 更新数据 | [updateGroupById.md](../group/updateGroupById.md) |
|
||||
| /api/deleteGroupById | 删除分组 | [deleteGroupById.md](../group/deleteGroupById.md) |
|
||||
|
||||
|
||||
## 用户信息管理
|
||||
|
||||
| 接口 | 说明 | 文档 |
|
||||
| - | - | -|
|
||||
| /api/getUserInfo | 获取当前用户信息 | [getUserInfo.md](../user/getUserInfo.md) |
|
||||
| /api/updateUserInfo | 更新当前用户信息 | [updateUserInfo.md](../user/updateUserInfo.md) |
|
||||
| /api/updateUserPassword | 更新用户密码 | [updateUserPassword.md](../user/updateUserPassword.md) |
|
||||
|
||||
## 调度日志
|
||||
|
||||
| 接口 | 说明 | 文档 |
|
||||
| - | - | -|
|
||||
| /api/getLogSchedulerList | 获取调度日志列表 | [getLogSchedulerList.md](../log_scheduler/getLogSchedulerList.md) |
|
||||
|
||||
## 系统管理
|
||||
|
||||
需要管理员权限
|
||||
|
||||
| 接口 | 说明 | 文档 |
|
||||
| - | - | -|
|
||||
| /api/getAllSystemConfig | 获取所有配置项 | [getAllSystemConfig.md](../system_config/getAllSystemConfig.md) |
|
||||
| /api/updateSystemConfig | 更新单个配置 | [updateSystemConfig.md](../system_config/updateSystemConfig.md) |
|
||||
| /api/getSystemVersion | 获取当前应用版本号 | [getSystemVersion.md](../system_config/getSystemVersion.md) |
|
||||
|
||||
## 用户管理
|
||||
|
||||
管理员权限
|
||||
|
||||
| 接口 | 说明 | 文档 |
|
||||
| - | - | -|
|
||||
| /api/getUserList | 获取用户列表 | [getUserList.md](../user/getUserList.md) |
|
||||
| /api/addUser | 添加用户 | [addUser.md](../user/addUser.md) |
|
||||
| /api/updateUserStatus | 更新账号可用状态 | [updateUserStatus.md](../user/updateUserStatus.md) |
|
||||
| /api/deleteUser | 删除用户账号 | [deleteUser.md](../user/deleteUser.md) |
|
||||
|
||||
## 域名信息
|
||||
|
||||
| 接口 | 说明 | 文档 |
|
||||
| - | - | -|
|
||||
| /api/getCertInformation | 获取域名证书信息 | [getCertInformation.md](../cert/getCertInformation.md) |
|
||||
|
||||
## 通知配置管理
|
||||
|
||||
`v0.0.12`
|
||||
|
||||
| 接口 | 说明 | 文档 |
|
||||
| - | - | -|
|
||||
| /api/getNotifyOfUser | 获取用户通知配置 | [getNotifyOfUser.md](../notify/getNotifyOfUser.md) |
|
||||
| /api/updateNotifyOfUser | 更新用户通知配置 | [updateNotifyOfUser.md](../notify/updateNotifyOfUser.md) |
|
||||
| /api/testWebhookNotifyOfUser | 测试webhook调用 | [testWebhookNotifyOfUser.md](../notify/testWebhookNotifyOfUser.md) |
|
||||
@@ -0,0 +1,45 @@
|
||||
# 用户登录
|
||||
|
||||
1、请求地址:/api/login
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| username | string | 是 | 用户名(账号)
|
||||
| password | string | 是 | 密码
|
||||
|
||||
|
||||
4、返回参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| -| - | - |
|
||||
| token | string | 登录凭据
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "tom",
|
||||
"password": "123456"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoyLCJleHAiOjE2NjUwNDQ5ODB9.4YnSKathN753sUp4a1njGsFUTanU-xEDoZflHprLYB0"
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# 用户注册
|
||||
|
||||
1、请求地址:/api/register
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| username | string | 是 | 用户名(账号)
|
||||
| password | string | 是 | 密码
|
||||
| password_repeat | string | 是 | 重复密码
|
||||
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/register
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "tom",
|
||||
"password": "123456",
|
||||
"password_repeat": "123456"
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# 获取域名证书信息
|
||||
|
||||
1、请求地址:/api/getCertInformation
|
||||
|
||||
2、请求方式:GET / POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
|domain | string | 是 | 查询的域名,eg: www.baidu.com
|
||||
|
||||
4、返回参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|-|-|-|
|
||||
| domain | string | 域名 |
|
||||
| start_date | string | 颁发时间(东八区时间)|
|
||||
| expire_date | string | 截止时间(东八区时间) |
|
||||
| issuer | object | 颁发者 |
|
||||
| issuer.CN |string | 公用名 (CN) |
|
||||
| issuer.O |string | 组织 (O) |
|
||||
| issuer.OU |string | 组织单位 (OU) |
|
||||
| subject | object | 颁发对象 |
|
||||
| subject.CN | string | 公用名 (CN) |
|
||||
| subject.O | string | 组织 (O) |
|
||||
| subject.OU | string | 组织单位 (OU) |
|
||||
|
||||
|
||||
|
||||
5、请求示例
|
||||
|
||||
GET
|
||||
```
|
||||
GET {{baseUrl}}/api/getCertInformation?domain=www.baidu.com
|
||||
X-Token: <token>
|
||||
```
|
||||
|
||||
POST
|
||||
```
|
||||
POST {{baseUrl}}/api/getCertInformation
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"domain": "www.baidu.com"
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"domain": "www.baidu.com",
|
||||
"expire_date": "2023-08-06 13:16:01",
|
||||
"issuer": {
|
||||
"C": "BE",
|
||||
"CN": "GlobalSign RSA OV SSL CA 2018",
|
||||
"O": "GlobalSign nv-sa"
|
||||
},
|
||||
"start_date": "2022-07-05 13:16:02",
|
||||
"subject": {
|
||||
"C": "CN",
|
||||
"CN": "baidu.com",
|
||||
"L": "beijing",
|
||||
"O": "Beijing Baidu Netcom Science Technology Co., Ltd",
|
||||
"OU": "service operation department",
|
||||
"ST": "beijing"
|
||||
}
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# 添加域名
|
||||
|
||||
1、请求地址:/api/addDomain
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| domain | string | 是 | 查询的域名,eg: www.baidu.com
|
||||
| alias | string | 否 | 域名别名
|
||||
| group_id | int | 否 | 域名分组id
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/addDomain
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"domain": "www.baidu.com",
|
||||
"alias": "百度",
|
||||
"group_id": 1,
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# 检查域名证书到期信息
|
||||
|
||||
1、请求地址:/api/checkDomainCert
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
无
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
GET
|
||||
```
|
||||
GET {{baseUrl}}/api/checkDomainCert
|
||||
X-Token: <token>
|
||||
```
|
||||
|
||||
POST
|
||||
```
|
||||
POST {{baseUrl}}/api/checkDomainCert
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# 删除域名
|
||||
|
||||
1、请求地址:/api/deleteDomainById
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
|id | int | 是 | 域名id
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/deleteDomainById
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# 获取用户的所有域名数据
|
||||
|
||||
1、请求地址:/api/getAllDomainListOfUser
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
无
|
||||
|
||||
4、返回参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| -| - | - |
|
||||
| domain | string | 查询的域名
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/getAllDomainListOfUser
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"domain": "www.qq.com"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# 获取域名
|
||||
|
||||
1、请求地址:/api/getDomainById
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
|id | int | 是 | 域名id
|
||||
|
||||
4、返回参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| -| - | - |
|
||||
| id | int | 域名id
|
||||
| domain | string | 查询的域名
|
||||
| ip | string | 域名ip地址
|
||||
| alias | string | 域名别名
|
||||
| group_id | int | 域名分组id
|
||||
| group | object/null | 域名分组对象,同 [getGroupById.md](/group/getGroupById.md)
|
||||
| start_time | datetime | 证书颁发时间
|
||||
| expire_time | datetime | 证书过期时间
|
||||
| check_time | datetime | 证书检查时间
|
||||
| connect_status | bool | 域名连接状态
|
||||
| notify_status | bool | 域名到期后是否通知
|
||||
| total_days | int | 域名有效期总天数
|
||||
| expire_days | int | 域名过期剩余天数
|
||||
| real_time_expire_days | int | 域名过期剩余天数(实时计算)
|
||||
| detail | object | 域名信息,同 [getCertInformation.md](/cert/getCertInformation.md)
|
||||
| create_time | datetime | 域名添加时间
|
||||
| update_time | datetime | 域名更新时间
|
||||
|
||||
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/getDomainById
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
未获取证书信息
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"alias": "百度",
|
||||
"check_time": "2022-09-24 21:19:34",
|
||||
"connect_status": true,
|
||||
"create_time": "2022-09-24 21:13:19",
|
||||
"detail": {
|
||||
"domain": "www.baidu.com",
|
||||
"expire_date": "2023-08-06 13:16:01",
|
||||
"issuer": {
|
||||
"C": "BE",
|
||||
"CN": "GlobalSign RSA OV SSL CA 2018",
|
||||
"O": "GlobalSign nv-sa"
|
||||
},
|
||||
"start_date": "2022-07-05 13:16:02",
|
||||
"subject": {
|
||||
"C": "CN",
|
||||
"CN": "baidu.com",
|
||||
"L": "beijing",
|
||||
"O": "Beijing Baidu Netcom Science Technology Co., Ltd",
|
||||
"OU": "service operation department",
|
||||
"ST": "beijing"
|
||||
}
|
||||
},
|
||||
"domain": "www.baidu.com",
|
||||
"expire_days": 315,
|
||||
"real_time_expire_days": 314,
|
||||
"expire_time": "2023-08-06 13:16:01",
|
||||
"group": {
|
||||
"create_time": "2022-09-24 21:52:42",
|
||||
"id": 1,
|
||||
"name": "项目1",
|
||||
"update_time": "2022-09-24 21:52:42"
|
||||
},
|
||||
"group_id": 1,
|
||||
"id": 1,
|
||||
"start_time": "2022-07-05 13:16:02",
|
||||
"total_days": 396,
|
||||
"update_time": "2022-09-24 21:13:19",
|
||||
"ip": "110.242.68.3",
|
||||
"notify_status": true
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,62 @@
|
||||
# 获取域名列表
|
||||
|
||||
1、请求地址:/api/getDomainList
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
|page | int | 否 | 页码,默认1
|
||||
|size | int | 否 | 每页数量,默认10
|
||||
|group_id | int | 否 | 分组id
|
||||
|
||||
4、返回参数
|
||||
|
||||
参见:[getDomainById.md](/domain/getDomainById.md)
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/getDomainList
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"page": 1,
|
||||
"size": 1
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"alias": "百度",
|
||||
"check_time": "2022-09-24 22:31:55",
|
||||
"connect_status": true,
|
||||
"create_time": "2022-09-24 22:29:02",
|
||||
"domain": "www.baidu.com",
|
||||
"expire_days": 315,
|
||||
"real_time_expire_days": 314,
|
||||
"expire_time": "2023-08-06 13:16:01",
|
||||
"group_id": 2,
|
||||
"id": 3,
|
||||
"start_time": "2022-07-05 13:16:02",
|
||||
"total_days": 396,
|
||||
"update_time": "2022-09-24 22:29:02",
|
||||
"ip": "110.242.68.3",
|
||||
"notify_status": true
|
||||
}
|
||||
],
|
||||
"total": 3
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# 从文件导入域名
|
||||
|
||||
1、请求地址:/api/importDomainFromFile
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| file | file | 是 | file对象
|
||||
|
||||
|
||||
4、返回参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| -| - | - |
|
||||
| count | int | 导入成功的数量
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/updateDomainCertInfoById
|
||||
Content-Type: multipart/form-data
|
||||
X-Token: <token>
|
||||
|
||||
file: (二进制)
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"count": 10
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
> 说明:导入的文件域名按照每行一个排列
|
||||
|
||||
例如
|
||||
|
||||
domain.txt
|
||||
|
||||
```
|
||||
www.baidu.com
|
||||
www.taobao.com
|
||||
```
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# 发送域名证书信息到邮箱
|
||||
|
||||
1、请求地址:/api/sendDomainInfoListEmail
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| to_addresses | `array[string]` | 是 | 邮箱列表
|
||||
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/sendDomainInfoListEmail
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"to_addresses": ["xxx@qq.com"]
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# 更新所有域名证书信息
|
||||
|
||||
1、请求地址:/api/updateAllDomainCertInfo
|
||||
|
||||
2、请求方式:GET/POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
无
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
GET
|
||||
```
|
||||
GET {{baseUrl}}/api/updateAllDomainCertInfo
|
||||
X-Token: <token>
|
||||
```
|
||||
|
||||
POST
|
||||
```
|
||||
POST {{baseUrl}}/api/updateAllDomainCertInfo
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# 更新当前用户的所有域名信息
|
||||
|
||||
1、请求地址:/api/updateAllDomainCertInfoOfUser
|
||||
|
||||
2、请求方式:GET/POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
无
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
GET
|
||||
```
|
||||
GET {{baseUrl}}/api/updateAllDomainCertInfoOfUser
|
||||
X-Token: <token>
|
||||
```
|
||||
|
||||
POST
|
||||
```
|
||||
POST {{baseUrl}}/api/updateAllDomainCertInfoOfUser
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# 更新数据
|
||||
|
||||
1、请求地址:/api/updateDomainById
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| id | int | 是 | 域名id
|
||||
| domain | string | 是 | 查询的域名,eg: www.baidu.com
|
||||
| alias | string | 是 | 域名别名
|
||||
| group_id | int | 是 | 域名分组id
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/updateDomainById
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"domain": "www.baidu.com",
|
||||
"alias": "百度",
|
||||
"group_id": 1
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# 更新域名证书信息
|
||||
|
||||
1、请求地址:/api/updateDomainCertInfoById
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| id | int | 是 | 域名id
|
||||
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/updateDomainCertInfoById
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
# 域名检查接口文档
|
||||
|
||||
## 功能描述
|
||||
|
||||
提供域名检查功能,可以检测指定域名是否被封禁。该接口通过调用腾讯安全接口来实现域名状态的检查。
|
||||
|
||||
## API 接口
|
||||
|
||||
### 请求信息
|
||||
|
||||
- 接口路径:`/api/v1/domain/check`
|
||||
- 请求方法:`GET`
|
||||
- 请求参数:
|
||||
|
||||
| 参数名 | 类型 | 是否必须 | 描述 |
|
||||
|--------|--------|----------|----------|
|
||||
| url | string | 是 | 待检查的URL |
|
||||
|
||||
### 响应信息
|
||||
|
||||
响应格式:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "结果信息"
|
||||
}
|
||||
```
|
||||
|
||||
响应状态码说明:
|
||||
|
||||
| 状态码 | 说明 |
|
||||
|--------|------|
|
||||
| 200 | 域名正常 |
|
||||
| 202 | 域名被封或参数错误 |
|
||||
| 500 | 系统错误 |
|
||||
|
||||
### 示例
|
||||
|
||||
1. 请求示例:
|
||||
```
|
||||
GET /api/v1/domain/check?url=http://example.com
|
||||
```
|
||||
|
||||
2. 响应示例:
|
||||
|
||||
域名正常:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "normal"
|
||||
}
|
||||
```
|
||||
|
||||
域名被封:
|
||||
```json
|
||||
{
|
||||
"code": 202,
|
||||
"msg": "域名被封"
|
||||
}
|
||||
```
|
||||
|
||||
参数错误:
|
||||
```json
|
||||
{
|
||||
"code": 202,
|
||||
"msg": "请传入URL参数"
|
||||
}
|
||||
```
|
||||
|
||||
系统错误:
|
||||
```json
|
||||
{
|
||||
"code": 500,
|
||||
"msg": "检测失败: Connection error"
|
||||
}
|
||||
```
|
||||
|
||||
## 实现细节
|
||||
|
||||
### 代码位置
|
||||
|
||||
- API实现:`domain_admin/api/domain_check.py`
|
||||
- 测试用例:`tests/test_domain_check.py`
|
||||
|
||||
### 主要依赖
|
||||
|
||||
- Flask:Web框架
|
||||
- requests:HTTP客户端
|
||||
- domain_admin.log:日志模块
|
||||
- domain_admin.utils.response:统一响应处理
|
||||
|
||||
### 错误处理
|
||||
|
||||
1. 参数验证
|
||||
- 检查URL参数是否存在
|
||||
- 返回202状态码和提示信息
|
||||
|
||||
2. 网络请求异常
|
||||
- 捕获requests.exceptions.RequestException
|
||||
- 记录错误日志
|
||||
- 返回500状态码和错误信息
|
||||
|
||||
3. 响应解析异常
|
||||
- 捕获json.JSONDecodeError
|
||||
- 记录错误日志
|
||||
- 返回500状态码和解析失败提示
|
||||
|
||||
## 测试用例
|
||||
|
||||
测试用例覆盖以下场景:
|
||||
|
||||
1. 测试未提供URL参数的情况
|
||||
```python
|
||||
def test_check_domain_no_url(self):
|
||||
response = self.client.get('/api/v1/domain/check')
|
||||
# 验证返回202状态码和相应提示
|
||||
```
|
||||
|
||||
2. 测试域名被封的情况
|
||||
```python
|
||||
@patch('requests.get')
|
||||
def test_check_domain_blocked(self, mock_get):
|
||||
# 模拟域名被封的响应
|
||||
# 验证返回202状态码和域名被封提示
|
||||
```
|
||||
|
||||
3. 测试域名正常的情况
|
||||
```python
|
||||
@patch('requests.get')
|
||||
def test_check_domain_normal(self, mock_get):
|
||||
# 模拟域名正常的响应
|
||||
# 验证返回200状态码和正常状态
|
||||
```
|
||||
|
||||
4. 测试请求异常的情况
|
||||
```python
|
||||
@patch('requests.get')
|
||||
def test_check_domain_request_error(self, mock_get):
|
||||
# 模拟网络请求异常
|
||||
# 验证返回500状态码和错误信息
|
||||
```
|
||||
|
||||
5. 测试JSON解析错误的情况
|
||||
```python
|
||||
@patch('requests.get')
|
||||
def test_check_domain_json_error(self, mock_get):
|
||||
# 模拟JSON解析错误
|
||||
# 验证返回500状态码和解析失败提示
|
||||
```
|
||||
|
||||
### 运行测试
|
||||
|
||||
执行以下命令运行测试:
|
||||
```bash
|
||||
python -m pytest tests/test_domain_check.py -v
|
||||
```
|
||||
|
||||
## 日志记录
|
||||
|
||||
接口在关键操作点都添加了日志记录:
|
||||
|
||||
1. 开始检查域名时记录信息日志
|
||||
2. 域名被封时记录警告日志
|
||||
3. 域名正常时记录信息日志
|
||||
4. 发生异常时记录错误日志
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 接口依赖腾讯安全检查服务,需确保网络可访问
|
||||
2. 建议在调用接口时设置合适的超时时间
|
||||
3. 对于高频调用,建议考虑添加缓存机制
|
||||
4. 需要注意处理特殊字符的URL编码
|
||||
@@ -0,0 +1,9 @@
|
||||
# NotifyTypeEnum
|
||||
|
||||
通知方式枚举值
|
||||
|
||||
| 枚举值 | 变量 | 含义 |
|
||||
| - | - | - |
|
||||
| 0 | Unknown | 未知 |
|
||||
| 1 | Email | 邮件 |
|
||||
| 2 | WebHook | webHook |
|
||||
@@ -0,0 +1,38 @@
|
||||
# 添加分组
|
||||
|
||||
1、请求地址:/api/addGroup
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| name | string | 是 | 分组名称
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/addGroup
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"name": "项目1"
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# 删除分组
|
||||
|
||||
1、请求地址:/api/deleteGroupById
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
|id | int | 是 | 数据id
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/deleteGroupById
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# 获取分组数据
|
||||
|
||||
1、请求地址:/api/getGroupById
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
|id | int | 是 | 数据id
|
||||
|
||||
4、返回参数
|
||||
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| -| - | - |
|
||||
| id | int | id
|
||||
| name | string | 名称
|
||||
| create_time | datetime | 域名添加时间
|
||||
| update_time | datetime | 域名更新时间
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/getGroupById
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"create_time": "2022-09-24 16:21:59",
|
||||
"id": 1,
|
||||
"name": "项目1",
|
||||
"update_time": "2022-09-24 16:21:59"
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# 获取分组列表
|
||||
|
||||
1、请求地址:/api/getGroupList
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
无
|
||||
|
||||
会返回全部分组
|
||||
|
||||
4、返回参数
|
||||
|
||||
略
|
||||
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/getGroupList
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"create_time": "2022-09-24 16:21:59",
|
||||
"id": 1,
|
||||
"name": "项目1",
|
||||
"update_time": "2022-09-24 16:21:59"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# 更新数据
|
||||
|
||||
1、请求地址:/api/updateGroupById
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| id | int | 是 | 域名id
|
||||
| name | string | 是 | 分组名称
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/updateGroupById
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"name": "项目2"
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
# https://vitepress.dev/reference/default-theme-home-page
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: "Domain Admin"
|
||||
text: "SSL证书监测平台"
|
||||
tagline: 申请证书,自动续签,到期提醒,支持独立部署
|
||||
image:
|
||||
src: https://mouday.github.io/img/2024/06/20/xvg0dfg.png
|
||||
alt: Domain Admin
|
||||
|
||||
actions:
|
||||
|
||||
- theme: brand
|
||||
text: 在线体验
|
||||
link: https://demo.domain-admin.cn/
|
||||
|
||||
- theme: alt
|
||||
text: 快速开始
|
||||
link: https://domain-admin.readthedocs.io/zh-cn/latest/manual/install.html
|
||||
|
||||
- theme: alt
|
||||
text: Github
|
||||
link: https://github.com/dromara/domain-admin
|
||||
|
||||
|
||||
features:
|
||||
- title: 申请证书
|
||||
details: 基于Let’s Encrypt实现免费SSL证书申请
|
||||
- title: 自动续签
|
||||
details: 自动续签SSL证书,证书永不过期
|
||||
- title: 到期提醒
|
||||
details: 到期前7天提醒,保障网站安全
|
||||
---
|
||||
|
||||
微信交流群
|
||||
|
||||
<img src="https://mouday.github.io/img/2024/06/20/h1buset.png" width="300">
|
||||
|
||||
回复:`domain-admin`,和众多使用者一起交流学习使用经验,反馈使用问题,获得更及时的解答和修复
|
||||
|
||||
## 友情链接
|
||||
|
||||
<a href="http://captcha.tianai.cloud/?from=domain-admin.cn" target="_blank">tianai-captcha</a>
|
||||
|
||||
<a href="https://sa-token.cc/?from=domain-admin.cn" target="_blank">Sa-Token</a>
|
||||
|
||||
<a href="https://x-file-storage.xuyanwu.cn/?from=domain-admin.cn" target="_blank">X Spring File Storage</a>
|
||||
|
||||
<a href="https://springdoc.cn/?from=domain-admin.cn" target="_blank">spring 中文网</a>
|
||||
@@ -0,0 +1,61 @@
|
||||
# 获取调度日志列表
|
||||
|
||||
1、请求地址:/api/getLogSchedulerList
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
|page | int | 否 | 页码,默认1
|
||||
|size | int | 否 | 每页数量,默认10
|
||||
|
||||
4、返回参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| -| - | - |
|
||||
| id | int | 数据id
|
||||
| status | bool | 状态
|
||||
| error_message | string | 错误信息
|
||||
| total_time | int | 执行时长
|
||||
| total_time_label | string | 执行时长显示值
|
||||
| create_time | datetime | 添加时间
|
||||
| update_time | datetime | 更新时间
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/getLogSchedulerList
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"page": 1,
|
||||
"size": 1
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"create_time": "2022-10-09 18:10:00",
|
||||
"error_message": "",
|
||||
"id": 423,
|
||||
"status": true,
|
||||
"total_time": 11,
|
||||
"total_time_label": "11s",
|
||||
"update_time": "2022-10-09 18:10:11"
|
||||
}
|
||||
],
|
||||
"total": 423
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
|
||||
```
|
||||
@@ -0,0 +1,81 @@
|
||||
# 获取用户通知配置
|
||||
|
||||
1、请求地址:/api/getNotifyOfUser
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
|type_id | int | 是 | 通知类型 [NotifyTypeEnum](/enums/NotifyTypeEnum.md)
|
||||
|
||||
4、返回参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| -| - | - |
|
||||
| id | int | 主键id
|
||||
| user_id | int | 用户id
|
||||
| type_id | int | 通知类型id [NotifyTypeEnum](/enums/NotifyTypeEnum.md)
|
||||
| value | object | 通知配置参数,见返回示例
|
||||
| create_time | datetime | 域名添加时间
|
||||
| update_time | datetime | 域名更新时间
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/getNotifyOfUser
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"type_id": 1
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
邮件配置
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"create_time": "2022-10-14 18:15:21",
|
||||
"id": 1,
|
||||
"type_id": 1,
|
||||
"update_time": "2022-10-14 18:15:21",
|
||||
"user_id": 1,
|
||||
"value": {
|
||||
"email_list": [
|
||||
"123456@qq.com"
|
||||
]
|
||||
}
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
Webhook配置
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"create_time": "2022-10-30 21:38:01",
|
||||
"id": 2,
|
||||
"type_id": 2,
|
||||
"update_time": "2022-10-30 21:38:01",
|
||||
"user_id": 1,
|
||||
"value": {
|
||||
"body": "{\n \"title\": \"SSL证书到期\",\n \"content\": \"查看:http://127.0.0.1:5173/\"\n}",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"method": "POST",
|
||||
"url": "https://push.showdoc.com.cn/server/api/push/a8f4fe6f8a5ac089193ac05472bf3c972003778561"
|
||||
}
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
# 测试webhook调用
|
||||
|
||||
1、请求地址:/api/testWebhookNotifyOfUser
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
无
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
POST
|
||||
```
|
||||
POST {{baseUrl}}/api/testWebhookNotifyOfUser
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# 更新用户通知配置
|
||||
|
||||
1、请求地址:/api/updateNotifyOfUser
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| type_id | int | 是 | 通知类型id [NotifyTypeEnum](/enums/NotifyTypeEnum.md)
|
||||
| value | object | 是 | 通知配置参数,见示例
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
邮件配置
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/updateNotifyOfUser
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"type_id": 1,
|
||||
"value": {
|
||||
"email_list": ["123@qq.com"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Webhook配置
|
||||
|
||||
```json
|
||||
POST {{baseUrl}}/api/updateNotifyOfUser
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"type_id":2,
|
||||
"value": {
|
||||
"method":"POST",
|
||||
"url":"https://server.com/api/push/",
|
||||
"headers":{
|
||||
"Content-Type":"application/json"
|
||||
},
|
||||
"body":"{\n \"title\": \"SSL证书到期\",\n \"content\": \"查看:http://127.0.0.1:5173/\"\n}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
webhook方式提交的body参数支持[jinja2模板语法](http://doc.yonyoucloud.com/doc/jinja2-docs-cn/index.html)
|
||||
|
||||
例如
|
||||
```json
|
||||
{
|
||||
"title": "推送的消息标题",
|
||||
"content": "{% for item in domain_list %} {{item.domain}} - {{item.expire_days}} \n {% endfor %}"
|
||||
}
|
||||
```
|
||||
|
||||
模板变量同 [获取域名列表](/domain/getDomainList.md)
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,57 @@
|
||||
# 获取所有配置项
|
||||
|
||||
1、请求地址:/api/getAllSystemConfig
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
无
|
||||
|
||||
4、返回参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| -| - | - |
|
||||
| id | int | 数据id
|
||||
| key | string | 键
|
||||
| value | string | 值
|
||||
| label | string | 显示
|
||||
| placeholder | string | 输入提示
|
||||
| create_time | datetime | 添加时间
|
||||
| update_time | datetime | 更新时间
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/getAllSystemConfig
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"is_show_value": true,
|
||||
"key": "mail_host",
|
||||
"value": "smtp.163.com",
|
||||
"label": "发件邮箱服务器地址",
|
||||
"placeholder": "发件邮箱服务器地址",
|
||||
"create_time": "2022-10-03 23:07:21",
|
||||
"update_time": "2022-10-03 23:07:21"
|
||||
}
|
||||
],
|
||||
"total": 9
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
# 获取当前应用版本号
|
||||
|
||||
1、请求地址:/api/getSystemVersion
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
无
|
||||
|
||||
4、返回参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| -| - | - |
|
||||
| version | string | 版本号
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/getSystemVersion
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"version": "0.0.8"
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
# 更新单个配置
|
||||
|
||||
1、请求地址:/api/updateSystemConfig
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| key | string | 键
|
||||
| value | string | 值
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/updateSystemConfig
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"key": "mail_username",
|
||||
"value": "123@qq.com"
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,41 @@
|
||||
# 添加用户
|
||||
|
||||
1、请求地址:/api/addUser
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| username | string | 用户名
|
||||
| password | string | 登录密码
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/addUser
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"username": "mouday",
|
||||
"password": "ooxx"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# 删除用户账号
|
||||
|
||||
1、请求地址:/api/deleteUser
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| user_id | int | 是 | 用户id
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/deleteUser
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"user_id": 1
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# 获取当前用户信息
|
||||
|
||||
1、请求地址:/api/getUserInfo
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
无
|
||||
|
||||
4、返回参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| -| - | - |
|
||||
| id | int | 用户id
|
||||
| username | string | 用户名
|
||||
| avatar_url | string | 头像url
|
||||
| before_expire_days | int | 过期前多少天提醒
|
||||
| email_list | string | 邮件列表
|
||||
| create_time | datetime | 添加时间
|
||||
| update_time | datetime | 更新时间
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/getUserInfo
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"avatar_url": "",
|
||||
"before_expire_days": 3,
|
||||
"email_list": [
|
||||
"1940607002@qq.com"
|
||||
],
|
||||
"status": true,
|
||||
"create_time": "2022-10-03 23:07:21",
|
||||
"update_time": "2022-10-08 16:54:08"
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,66 @@
|
||||
# 获取用户列表
|
||||
|
||||
1、请求地址:/api/getUserList
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
|page | int | 否 | 页码,默认1
|
||||
|size | int | 否 | 每页数量,默认10
|
||||
|keyword | string | 否 | 搜索关键词
|
||||
|
||||
4、返回参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
| -| - | - |
|
||||
| id | int | 用户id
|
||||
| username | string | 用户名
|
||||
| avatar_url | string | 头像url
|
||||
| before_expire_days | int | 过期前多少天提醒
|
||||
| email_list | string | 邮件列表
|
||||
| create_time | datetime | 添加时间
|
||||
| update_time | datetime | 更新时间
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/getUserList
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"page": 1,
|
||||
"size": 10,
|
||||
"keyword": ""
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"status": true,
|
||||
"avatar_url": "https://www.image.com/image.png",
|
||||
"before_expire_days": 1,
|
||||
"email_list": [
|
||||
"123@qq.com"
|
||||
],
|
||||
"create_time": "2022-10-03 23:07:21",
|
||||
"update_time": "2022-10-11 17:52:56"
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
},
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# 更新当前用户信息
|
||||
|
||||
1、请求地址:/api/updateUserInfo
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| avatar_url | string | 是 | 头像url
|
||||
| before_expire_days | int | 是 | 过期前多少天提醒
|
||||
| email_list | string | 是 | 邮件列表
|
||||
|
||||
> 备注:该接口仅更新当前登录用户的信息,token中解析用户id
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/updateUserInfo
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"avatar_url": "https://www.image.com/image.png",
|
||||
"email_list": ["123@qq.com"],
|
||||
"before_expire_days": 1
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# 更新用户密码
|
||||
|
||||
1、请求地址:/api/updateUserPassword
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| password | string | 是 | 旧密码
|
||||
| new_password | string | 是 | 新密码
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/updateUserPassword
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"password": "123456",
|
||||
"new_password": "234567"
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# 更新账号可用状态
|
||||
|
||||
1、请求地址:/api/updateUserStatus
|
||||
|
||||
2、请求方式:POST
|
||||
|
||||
3、请求参数
|
||||
|
||||
| 参数 | 类型 | 必须 | 说明 |
|
||||
| -| - | - | - |
|
||||
| user_id | int | 是 | 用户id
|
||||
| status | bool | 是 | 状态
|
||||
|
||||
4、返回参数
|
||||
|
||||
无
|
||||
|
||||
5、请求示例
|
||||
|
||||
```
|
||||
POST {{baseUrl}}/api/updateUserStatus
|
||||
Content-Type: application/json
|
||||
X-Token: <token>
|
||||
|
||||
{
|
||||
"user_id": 1,
|
||||
"status": false
|
||||
}
|
||||
```
|
||||
|
||||
6、返回示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": null,
|
||||
"msg": "success"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -0,0 +1,267 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
address_api.py
|
||||
"""
|
||||
|
||||
from __future__ import print_function, unicode_literals, absolute_import, division
|
||||
from flask import request, g
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.model.address_model import AddressModel
|
||||
from domain_admin.model.domain_model import DomainModel
|
||||
from domain_admin.service import domain_service, auth_service
|
||||
from domain_admin.utils.flask_ext.app_exception import AppException, DataNotFoundAppException, ForbiddenAppException
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_address_list_by_domain_id():
|
||||
"""
|
||||
通过域名获取关联的主机地址列表
|
||||
:return:
|
||||
@since v1.3.1
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_id = request.json['domain_id']
|
||||
page = request.json.get('page', 1)
|
||||
size = request.json.get('size', 10)
|
||||
|
||||
# check
|
||||
domain_row = DomainModel.select().where(
|
||||
DomainModel.id == domain_id,
|
||||
DomainModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not domain_row:
|
||||
raise ForbiddenAppException()
|
||||
|
||||
query = AddressModel.select().where(
|
||||
AddressModel.domain_id == domain_id,
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
lst = []
|
||||
|
||||
if total > 0:
|
||||
rows = query.order_by(
|
||||
AddressModel.ssl_expire_days,
|
||||
AddressModel.id,
|
||||
).paginate(page, size)
|
||||
|
||||
lst = list(map(lambda m: model_to_dict(
|
||||
model=m,
|
||||
extra_attrs=[
|
||||
'ssl_start_date',
|
||||
'ssl_expire_date',
|
||||
'real_time_ssl_expire_days',
|
||||
'update_time_label',
|
||||
]
|
||||
), rows))
|
||||
|
||||
return {
|
||||
"list": lst,
|
||||
"total": total
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def add_address():
|
||||
"""
|
||||
添加主机地址
|
||||
:return:
|
||||
@since v1.3.1
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_id = request.json['domain_id']
|
||||
host = request.json['host']
|
||||
ssl_start_time = request.json.get('ssl_start_time')
|
||||
ssl_expire_time = request.json.get('ssl_expire_time')
|
||||
comment = request.json.get('comment') or ''
|
||||
# ssl_auto_update = request.json.get('ssl_auto_update', True)
|
||||
# ssl_expire_monitor = request.json.get('ssl_expire_monitor', True)
|
||||
|
||||
# check
|
||||
domain_row = DomainModel.select().where(
|
||||
DomainModel.id == domain_id,
|
||||
DomainModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not domain_row:
|
||||
raise ForbiddenAppException()
|
||||
|
||||
data = {
|
||||
'domain_id': domain_id,
|
||||
'host': host,
|
||||
'source': 1,
|
||||
'comment': comment,
|
||||
}
|
||||
|
||||
if not domain_row.auto_update:
|
||||
data['ssl_start_time'] = ssl_start_time
|
||||
data['ssl_expire_time'] = ssl_expire_time
|
||||
|
||||
address_row = AddressModel.create(**data)
|
||||
|
||||
if domain_row.auto_update:
|
||||
domain_service.update_address_row_info_with_sync_domain_row(address_row.id)
|
||||
else:
|
||||
domain_service.sync_address_info_to_domain_info(domain_row)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def delete_address_by_id():
|
||||
"""
|
||||
删除主机地址
|
||||
:return:
|
||||
@since v1.3.1
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
address_id = request.json['address_id']
|
||||
|
||||
# update to cert
|
||||
address_row = AddressModel.get_by_id(address_id)
|
||||
|
||||
# check
|
||||
domain_row = DomainModel.select().where(
|
||||
DomainModel.id == address_row.domain_id,
|
||||
DomainModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not domain_row:
|
||||
raise ForbiddenAppException()
|
||||
|
||||
AddressModel.delete().where(
|
||||
AddressModel.id == address_id
|
||||
).execute()
|
||||
|
||||
domain_service.sync_address_info_to_domain_info(domain_row)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def delete_address_by_ids():
|
||||
"""
|
||||
批量删除主机地址
|
||||
:return:
|
||||
@since v1.4.4
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
address_ids = request.json['address_ids']
|
||||
|
||||
# update to cert
|
||||
address_rows = AddressModel.select(
|
||||
AddressModel.domain_id
|
||||
).where(
|
||||
AddressModel.id.in_(address_ids)
|
||||
)
|
||||
|
||||
AddressModel.delete().where(
|
||||
AddressModel.id.in_(address_ids)
|
||||
).execute()
|
||||
|
||||
domain_ids = [row.domain_id for row in address_rows]
|
||||
|
||||
domain_rows = DomainModel.select().where(
|
||||
DomainModel.id.in_(domain_ids),
|
||||
DomainModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
for domain_row in domain_rows:
|
||||
domain_service.sync_address_info_to_domain_info(domain_row)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_address_by_id():
|
||||
"""
|
||||
获取主机地址
|
||||
:return:
|
||||
@since v1.3.1
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
address_id = request.json['address_id']
|
||||
|
||||
return AddressModel.get_by_id(address_id)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_address_by_id():
|
||||
"""
|
||||
更新主机地址
|
||||
:return:
|
||||
@since v1.3.1
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
address_id = request.json['address_id']
|
||||
host = request.json['host']
|
||||
ssl_start_time = request.json.get('ssl_start_time')
|
||||
ssl_expire_time = request.json.get('ssl_expire_time')
|
||||
comment = request.json.get('comment') or ''
|
||||
# ssl_auto_update = request.json.get('ssl_auto_update', True)
|
||||
# ssl_expire_monitor = request.json.get('ssl_expire_monitor', True)
|
||||
|
||||
address_row = AddressModel.get_by_id(address_id)
|
||||
domain_row = DomainModel.get_by_id(address_row.domain_id)
|
||||
|
||||
data = {
|
||||
'host': host,
|
||||
'comment': comment,
|
||||
}
|
||||
|
||||
if not domain_row.auto_update:
|
||||
data['ssl_start_time'] = ssl_start_time
|
||||
data['ssl_expire_time'] = ssl_expire_time
|
||||
|
||||
AddressModel.update(data).where(
|
||||
AddressModel.id == address_id
|
||||
).execute()
|
||||
|
||||
if domain_row.auto_update:
|
||||
domain_service.update_address_row_info_with_sync_domain_row(address_id)
|
||||
else:
|
||||
domain_service.sync_address_info_to_domain_info(domain_row)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_address_list_info_by_domain_id():
|
||||
"""
|
||||
更新主机地址信息
|
||||
:return:
|
||||
@since v1.3.1
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_id = request.json['domain_id']
|
||||
|
||||
domain_row = DomainModel.get_by_id(domain_id)
|
||||
|
||||
err = domain_service.update_domain_row(domain_row)
|
||||
|
||||
if err:
|
||||
raise AppException(err)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_address_row_info_by_id():
|
||||
"""
|
||||
更新主机地址信息
|
||||
:return:
|
||||
@since v1.3.1
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
address_id = request.json['address_id']
|
||||
|
||||
domain_service.update_address_row_info_with_sync_domain_row(address_id)
|
||||
@@ -0,0 +1,44 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, unicode_literals, absolute_import, division
|
||||
from flask import request
|
||||
|
||||
from domain_admin import config
|
||||
from domain_admin.service import auth_service
|
||||
from domain_admin.utils.flask_ext.app_exception import AppException
|
||||
|
||||
|
||||
def login():
|
||||
"""
|
||||
用户登录
|
||||
"""
|
||||
username = request.json['username']
|
||||
password = request.json['password']
|
||||
|
||||
token = auth_service.login(username, password)
|
||||
|
||||
return {'token': token}
|
||||
|
||||
|
||||
def login_by_email():
|
||||
"""
|
||||
邮箱登录
|
||||
"""
|
||||
|
||||
email = request.json['email']
|
||||
code = request.json['code']
|
||||
|
||||
token = auth_service.login_by_email(email, code)
|
||||
|
||||
return {'token': token}
|
||||
|
||||
|
||||
def send_code():
|
||||
"""
|
||||
发送验证码
|
||||
"""
|
||||
email = request.json['email']
|
||||
|
||||
if not config.ENABLED_REGISTER:
|
||||
raise AppException("请联系管理员开放注册")
|
||||
|
||||
auth_service.send_verify_code_async(email)
|
||||
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, unicode_literals, absolute_import, division
|
||||
|
||||
from flask import request, Response
|
||||
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.service import auth_service, cert_service
|
||||
from domain_admin.utils import domain_util
|
||||
from domain_admin.utils.cert_util import cert_openssl_v2, cert_common
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_cert_information():
|
||||
"""
|
||||
获取域名证书信息
|
||||
:return:
|
||||
"""
|
||||
if request.method == 'GET':
|
||||
domain = request.args['domain']
|
||||
else:
|
||||
domain = request.json['domain']
|
||||
|
||||
# 解析域名
|
||||
return cert_service.get_cert_information(domain=domain)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def parse_public_cert():
|
||||
certificate = request.json['certificate']
|
||||
parsed_cert = cert_common.parse_public_cert(certificate)
|
||||
return parsed_cert.to_dict() if parsed_cert else parsed_cert
|
||||
@@ -0,0 +1,316 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@File : certificate_api.py
|
||||
@Date : 2024-02-25
|
||||
"""
|
||||
import json
|
||||
|
||||
from flask import g, request
|
||||
from peewee import SQL
|
||||
|
||||
from domain_admin.enums.deploy_status_enum import DeployStatusEnum
|
||||
from domain_admin.enums.object_enum import ObjectEnum
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.model.certificate_model import CertificateModel
|
||||
from domain_admin.model.deploy_webhook_model import DeployWebhookModel
|
||||
from domain_admin.service import certificate_service, auth_service
|
||||
from domain_admin.utils.flask_ext.app_exception import AppException, DataNotFoundAppException, ForbiddenAppException
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_certificate_list():
|
||||
"""
|
||||
获取列表
|
||||
:return:
|
||||
@since v1.6.12
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
page = request.json.get('page', 1)
|
||||
size = request.json.get('size', 10)
|
||||
keyword = request.json.get('keyword')
|
||||
order_prop = request.json.get('order_prop') or 'expire_time'
|
||||
order_type = request.json.get('order_type') or 'asc'
|
||||
|
||||
if order_prop not in ['create_time','expire_time']:
|
||||
raise AppException('params error: order_prop')
|
||||
|
||||
if order_type not in ['desc', 'asc']:
|
||||
raise AppException('params error: order_type')
|
||||
|
||||
query = CertificateModel.select().where(
|
||||
CertificateModel.user_id == current_user_id,
|
||||
)
|
||||
|
||||
if keyword:
|
||||
query = query.where(CertificateModel.domain.contains(keyword))
|
||||
|
||||
total = query.count()
|
||||
lst = []
|
||||
|
||||
if total > 0:
|
||||
ordering = [
|
||||
SQL(f"`{order_prop}` {order_type}"),
|
||||
CertificateModel.id.desc()
|
||||
]
|
||||
|
||||
rows = query.order_by(*ordering).paginate(page, size)
|
||||
|
||||
lst = [row.to_dict() for row in rows]
|
||||
|
||||
# 查询部署数量
|
||||
certificate_service.load_cert_deploy_count(lst)
|
||||
|
||||
# 查询api部署状态
|
||||
certificate_service.load_api_deploy_status(lst)
|
||||
|
||||
return {
|
||||
"list": lst,
|
||||
"total": total
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def add_certificate():
|
||||
"""
|
||||
添加
|
||||
:return:
|
||||
@since v1.6.12
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain = request.json['domain']
|
||||
ssl_certificate = request.json['ssl_certificate']
|
||||
ssl_certificate_key = request.json['ssl_certificate_key']
|
||||
start_time = request.json.get('start_time')
|
||||
expire_time = request.json.get('expire_time')
|
||||
comment = request.json.get('comment') or ''
|
||||
|
||||
data = {
|
||||
'user_id': current_user_id,
|
||||
'domain': domain,
|
||||
'start_time': start_time,
|
||||
'expire_time': expire_time,
|
||||
'ssl_certificate': ssl_certificate,
|
||||
'ssl_certificate_key': ssl_certificate_key,
|
||||
'comment': comment,
|
||||
}
|
||||
|
||||
CertificateModel.create(**data)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_certificate_by_id():
|
||||
"""
|
||||
更新主机地址
|
||||
:return:
|
||||
@since v1.6.12
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
certificate_id = request.json['certificate_id']
|
||||
|
||||
domain = request.json['domain']
|
||||
ssl_certificate = request.json['ssl_certificate']
|
||||
ssl_certificate_key = request.json['ssl_certificate_key']
|
||||
start_time = request.json.get('start_time')
|
||||
expire_time = request.json.get('expire_time')
|
||||
comment = request.json.get('comment') or ''
|
||||
|
||||
# check data
|
||||
certificate_row = CertificateModel.select().where(
|
||||
CertificateModel.id == certificate_id,
|
||||
CertificateModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
data = {
|
||||
'domain': domain,
|
||||
'start_time': start_time,
|
||||
'expire_time': expire_time,
|
||||
'ssl_certificate': ssl_certificate,
|
||||
'ssl_certificate_key': ssl_certificate_key,
|
||||
'comment': comment,
|
||||
}
|
||||
|
||||
CertificateModel.update(data).where(
|
||||
CertificateModel.id == certificate_id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def delete_certificate_by_id():
|
||||
"""
|
||||
删除
|
||||
:return:
|
||||
@since v1.6.12
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
certificate_id = request.json['certificate_id']
|
||||
|
||||
# check data
|
||||
certificate_row = CertificateModel.select().where(
|
||||
CertificateModel.id == certificate_id,
|
||||
CertificateModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
# delete
|
||||
CertificateModel.delete().where(
|
||||
CertificateModel.id == certificate_row.id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def delete_certificate_by_ids():
|
||||
"""
|
||||
批量删除
|
||||
:return:
|
||||
@since v1.6.12
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
certificate_ids = request.json['certificate_ids']
|
||||
|
||||
CertificateModel.delete().where(
|
||||
CertificateModel.id.in_(certificate_ids),
|
||||
CertificateModel.user_id == current_user_id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_certificate_by_id():
|
||||
"""
|
||||
获取
|
||||
:return:
|
||||
@since v1.6.12
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
certificate_id = request.json['certificate_id']
|
||||
|
||||
# check data
|
||||
certificate_row = CertificateModel.select().where(
|
||||
CertificateModel.id == certificate_id,
|
||||
CertificateModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
# 查询部署数量
|
||||
certificate_dict = certificate_row.to_dict()
|
||||
certificate_service.load_cert_deploy_count([certificate_dict])
|
||||
|
||||
# 查询api部署状态
|
||||
certificate_service.load_api_deploy_status([certificate_dict])
|
||||
|
||||
return certificate_dict
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def deploy_certificate_by_webhook():
|
||||
"""
|
||||
通过webhook部署托管证书
|
||||
@since v1.6.52
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
certificate_id = request.json['certificate_id']
|
||||
url = request.json['url']
|
||||
headers = request.json.get('headers') or {}
|
||||
|
||||
# check data
|
||||
certificate_row = certificate_service.get_certificate_row(
|
||||
certificate_id=certificate_id,
|
||||
user_id=current_user_id
|
||||
)
|
||||
|
||||
# update config
|
||||
deploy_webhook_row = DeployWebhookModel.select().where(
|
||||
DeployWebhookModel.user_id == current_user_id,
|
||||
DeployWebhookModel.object_id == certificate_row.id,
|
||||
DeployWebhookModel.object_type == ObjectEnum.Certificate
|
||||
).first()
|
||||
|
||||
if not deploy_webhook_row:
|
||||
deploy_webhook_row = DeployWebhookModel.create(
|
||||
user_id=current_user_id,
|
||||
object_id=certificate_row.id,
|
||||
object_type=ObjectEnum.Certificate,
|
||||
url=url,
|
||||
header_raw=json.dumps(headers),
|
||||
)
|
||||
else:
|
||||
DeployWebhookModel.update(
|
||||
url=url,
|
||||
header_raw=json.dumps(headers),
|
||||
).where(
|
||||
DeployWebhookModel.id == deploy_webhook_row.id
|
||||
).execute()
|
||||
|
||||
# refresh
|
||||
deploy_webhook_row = DeployWebhookModel.get_by_id(deploy_webhook_row.id)
|
||||
|
||||
# deploy
|
||||
res = certificate_service.deploy_certificate_by_webhook(
|
||||
certificate_row=certificate_row,
|
||||
deploy_webhook_row=deploy_webhook_row,
|
||||
)
|
||||
|
||||
# report result
|
||||
if res.ok:
|
||||
DeployWebhookModel.update(
|
||||
status=DeployStatusEnum.SUCCESS
|
||||
).where(
|
||||
DeployWebhookModel.id == deploy_webhook_row.id
|
||||
).execute()
|
||||
|
||||
return {
|
||||
'result': res.text
|
||||
}
|
||||
else:
|
||||
DeployWebhookModel.update(
|
||||
status=DeployStatusEnum.ERROR
|
||||
).where(
|
||||
DeployWebhookModel.id == deploy_webhook_row.id
|
||||
).execute()
|
||||
|
||||
raise res.raise_for_status()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_deploy_webhook():
|
||||
"""
|
||||
获取配置部署托管证书
|
||||
@since v1.6.52
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
certificate_id = request.json['certificate_id']
|
||||
|
||||
# check data
|
||||
certificate_row = certificate_service.get_certificate_row(
|
||||
certificate_id=certificate_id,
|
||||
user_id=current_user_id
|
||||
)
|
||||
|
||||
deploy_webhook_row = DeployWebhookModel.select().where(
|
||||
DeployWebhookModel.user_id == current_user_id,
|
||||
DeployWebhookModel.object_id == certificate_row.id,
|
||||
DeployWebhookModel.object_type == ObjectEnum.Certificate
|
||||
).first()
|
||||
|
||||
if deploy_webhook_row:
|
||||
return deploy_webhook_row.to_dict()
|
||||
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
@File : check_wechat_api.py
|
||||
@Date : 2025-06-04
|
||||
"""
|
||||
from flask import request
|
||||
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.service import auth_service
|
||||
from domain_admin.utils import wechat_util
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def validate_url_for_wechat():
|
||||
url = request.json['url']
|
||||
return wechat_util.validate_url_for_wechat(url)
|
||||
@@ -0,0 +1,221 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@File : deploy_cert_api.py
|
||||
@Date : 2024-03-31
|
||||
"""
|
||||
|
||||
from flask import g, request
|
||||
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.model.deploy_cert_model import DeployCertModel
|
||||
from domain_admin.model.host_model import HostModel
|
||||
from domain_admin.service import deploy_cert_service, auth_service
|
||||
from domain_admin.utils.flask_ext.app_exception import DataNotFoundAppException
|
||||
from domain_admin.utils.open_api import aliyun_oss_api
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_deploy_list_by_cert_id():
|
||||
"""
|
||||
获取列表
|
||||
:return:
|
||||
@since v1.6.20
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
cert_id = request.json['cert_id']
|
||||
page = request.json.get('page', 1)
|
||||
size = request.json.get('size', 10)
|
||||
|
||||
query = DeployCertModel.select().where(
|
||||
DeployCertModel.cert_id == cert_id,
|
||||
DeployCertModel.user_id == current_user_id,
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
lst = []
|
||||
|
||||
if total > 0:
|
||||
rows = query.order_by(
|
||||
DeployCertModel.id,
|
||||
).paginate(page, size)
|
||||
|
||||
lst = [row.to_dict() for row in rows]
|
||||
|
||||
# deploy_host
|
||||
deploy_cert_service.load_cert_deploy_host(lst)
|
||||
|
||||
return {
|
||||
"list": lst,
|
||||
"total": total
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def add_deploy_cert():
|
||||
"""
|
||||
添加详情
|
||||
:return:
|
||||
@since v1.6.20
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
cert_id = request.json['cert_id']
|
||||
deploy_host_id = request.json['deploy_host_id']
|
||||
deploy_key_file = request.json['deploy_key_file']
|
||||
deploy_fullchain_file = request.json['deploy_fullchain_file']
|
||||
deploy_reloadcmd = request.json['deploy_reloadcmd']
|
||||
|
||||
data = {
|
||||
'user_id': current_user_id,
|
||||
'cert_id': cert_id,
|
||||
'deploy_host_id': deploy_host_id,
|
||||
'deploy_key_file': deploy_key_file,
|
||||
'deploy_fullchain_file': deploy_fullchain_file,
|
||||
'deploy_reloadcmd': deploy_reloadcmd,
|
||||
}
|
||||
|
||||
DeployCertModel.create(**data)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_deploy_cert_by_id():
|
||||
"""
|
||||
更新详情
|
||||
:return:
|
||||
@since v1.6.20
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
deploy_cert_id = request.json['deploy_cert_id']
|
||||
deploy_host_id = request.json['deploy_host_id']
|
||||
deploy_key_file = request.json['deploy_key_file']
|
||||
deploy_fullchain_file = request.json['deploy_fullchain_file']
|
||||
deploy_reloadcmd = request.json['deploy_reloadcmd']
|
||||
|
||||
# check data
|
||||
deploy_cert_row = DeployCertModel.select().where(
|
||||
DeployCertModel.id == deploy_cert_id,
|
||||
DeployCertModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not deploy_cert_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
data = {
|
||||
'deploy_host_id': deploy_host_id,
|
||||
'deploy_key_file': deploy_key_file,
|
||||
'deploy_fullchain_file': deploy_fullchain_file,
|
||||
'deploy_reloadcmd': deploy_reloadcmd,
|
||||
}
|
||||
|
||||
DeployCertModel.update(data).where(
|
||||
DeployCertModel.id == deploy_cert_row.id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def delete_by_deploy_cert_id():
|
||||
"""
|
||||
删除详情
|
||||
:return:
|
||||
@since v1.6.20
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
deploy_cert_id = request.json['deploy_cert_id']
|
||||
|
||||
# check data
|
||||
deploy_cert_row = DeployCertModel.select().where(
|
||||
DeployCertModel.id == deploy_cert_id,
|
||||
DeployCertModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not deploy_cert_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
DeployCertModel.delete_by_id(deploy_cert_id)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def delete_by_deploy_cert_ids():
|
||||
"""
|
||||
批量删除详情
|
||||
:return:
|
||||
@since v1.6.20
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
deploy_cert_ids = request.json['deploy_cert_ids']
|
||||
|
||||
DeployCertModel.delete().where(
|
||||
DeployCertModel.id.in_(deploy_cert_ids),
|
||||
DeployCertModel.user_id == current_user_id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_deploy_cert_by_id():
|
||||
"""
|
||||
获取详情
|
||||
:return:
|
||||
@since v1.6.20
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
deploy_cert_id = request.json['deploy_cert_id']
|
||||
|
||||
# check data
|
||||
deploy_cert_row = DeployCertModel.select().where(
|
||||
DeployCertModel.id == deploy_cert_id,
|
||||
DeployCertModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not deploy_cert_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
deploy_cert_dict = deploy_cert_row.to_dict()
|
||||
|
||||
deploy_cert_dict['deploy_host'] = HostModel.get_by_id(deploy_cert_dict['deploy_host_id'])
|
||||
|
||||
return deploy_cert_dict
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def handle_deploy_cert():
|
||||
"""
|
||||
部署证书
|
||||
@since v1.6.20
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
deploy_cert_id = request.json['deploy_cert_id']
|
||||
|
||||
# check data
|
||||
deploy_cert_row = DeployCertModel.select().where(
|
||||
DeployCertModel.id == deploy_cert_id,
|
||||
DeployCertModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not deploy_cert_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
err = deploy_cert_service.handle_deploy_cert(deploy_cert_row)
|
||||
if err:
|
||||
raise err
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_aliyun_endpoint_options():
|
||||
"""
|
||||
阿里云endpoint
|
||||
:return:
|
||||
"""
|
||||
return aliyun_oss_api.ENDPOINT_OPTIONS
|
||||
@@ -0,0 +1,153 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@File : dns_api.py
|
||||
@Date : 2023-07-29
|
||||
"""
|
||||
from flask import request, g
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from domain_admin.enums.dns_type_enum import DnsTypeEnum
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.model.dns_model import DnsModel
|
||||
from domain_admin.service import auth_service
|
||||
from domain_admin.utils.flask_ext.app_exception import AppException, DataNotFoundAppException
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def add_dns():
|
||||
"""
|
||||
添加Dns
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
dns_type_id = request.json.get('dns_type_id') or DnsTypeEnum.ALIYUN
|
||||
name = request.json['name']
|
||||
access_key = request.json['access_key']
|
||||
secret_key = request.json['secret_key']
|
||||
|
||||
row = DnsModel.create(
|
||||
user_id=current_user_id,
|
||||
dns_type_id=dns_type_id,
|
||||
name=name,
|
||||
access_key=access_key,
|
||||
secret_key=secret_key,
|
||||
)
|
||||
|
||||
return row
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_dns_by_id():
|
||||
"""
|
||||
更新Dns
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
dns_type_id = request.json.get('dns_type_id') or DnsTypeEnum.ALIYUN
|
||||
dns_id = request.json['dns_id']
|
||||
name = request.json['name']
|
||||
access_key = request.json['access_key']
|
||||
secret_key = request.json['secret_key']
|
||||
|
||||
# data check
|
||||
dns_row = DnsModel.select().where(
|
||||
DnsModel.id == dns_id,
|
||||
DnsModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not dns_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
DnsModel.update(
|
||||
dns_type_id=dns_type_id,
|
||||
name=name,
|
||||
access_key=access_key,
|
||||
secret_key=secret_key,
|
||||
).where(
|
||||
DnsModel.id == dns_row.id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_dns_by_id():
|
||||
"""
|
||||
获取Dns
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
dns_id = request.json['dns_id']
|
||||
|
||||
dns_row = DnsModel.select().where(
|
||||
DnsModel.id == dns_id,
|
||||
DnsModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not dns_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
dns_row.secret_key = ''
|
||||
|
||||
return dns_row
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def delete_dns_by_id():
|
||||
"""
|
||||
移除Dns
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
dns_id = request.json['dns_id']
|
||||
|
||||
dns_row = DnsModel.select().where(
|
||||
DnsModel.id == dns_id,
|
||||
DnsModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not dns_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
return DnsModel.delete_by_id(dns_row.id)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_dns_list():
|
||||
"""
|
||||
Dns列表
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
page = request.json.get('page', 1)
|
||||
size = request.json.get('size', 10)
|
||||
keyword = request.json.get('keyword')
|
||||
dns_type_id = request.json.get('dns_type_id')
|
||||
|
||||
query = DnsModel.select().where(
|
||||
DnsModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
if keyword:
|
||||
query = query.where(DnsModel.name.contains(keyword))
|
||||
if dns_type_id:
|
||||
query = query.where(DnsModel.dns_type_id == dns_type_id)
|
||||
|
||||
total = query.count()
|
||||
|
||||
rows = query.order_by(
|
||||
DnsModel.create_time.desc(),
|
||||
DnsModel.id.desc()
|
||||
)
|
||||
|
||||
lst = list(map(lambda m: model_to_dict(model=m), rows))
|
||||
|
||||
for row in lst:
|
||||
row['secret_key'] = ''
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': total,
|
||||
}
|
||||
@@ -0,0 +1,738 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
由于历史原因,domain指代 SSL证书的域名
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals, absolute_import, division
|
||||
|
||||
from operator import itemgetter
|
||||
|
||||
from flask import request, g
|
||||
from playhouse.shortcuts import model_to_dict, fn
|
||||
|
||||
from domain_admin.enums.operation_enum import OperationEnum
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.enums.ssl_type_enum import SSLTypeEnum
|
||||
from domain_admin.model.address_model import AddressModel
|
||||
from domain_admin.model.domain_info_model import DomainInfoModel
|
||||
from domain_admin.model.domain_model import DomainModel
|
||||
from domain_admin.model.group_model import GroupModel
|
||||
from domain_admin.model.group_user_model import GroupUserModel
|
||||
from domain_admin.service import async_task_service, domain_info_service, group_service, operation_service, auth_service
|
||||
from domain_admin.service import domain_service
|
||||
from domain_admin.service import file_service
|
||||
from domain_admin.utils import datetime_util, domain_util
|
||||
from domain_admin.utils.cert_util import cert_consts
|
||||
from domain_admin.utils.flask_ext.app_exception import AppException, DataNotFoundAppException, ForbiddenAppException
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=DomainModel,
|
||||
operation_type_id=OperationEnum.CREATE
|
||||
)
|
||||
def add_domain():
|
||||
"""
|
||||
添加域名
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain = request.json['domain']
|
||||
|
||||
alias = request.json.get('alias') or ''
|
||||
group_id = request.json.get('group_id') or 0
|
||||
# is_dynamic_host = request.json.get('is_dynamic_host', False)
|
||||
start_time = request.json.get('start_time')
|
||||
expire_time = request.json.get('expire_time')
|
||||
auto_update = request.json.get('auto_update', True)
|
||||
port = request.json.get('port') or cert_consts.SSL_DEFAULT_PORT
|
||||
ssl_type = request.json.get('ssl_type', SSLTypeEnum.SSL_TLS)
|
||||
|
||||
data = {
|
||||
# 基本信息
|
||||
'user_id': current_user_id,
|
||||
'domain': domain.strip(),
|
||||
'port': int(port), # fix: TypeError: an integer is required (got type str)
|
||||
'root_domain': domain_util.get_root_domain(domain),
|
||||
'alias': alias,
|
||||
'group_id': group_id,
|
||||
# 'is_dynamic_host': is_dynamic_host,
|
||||
'start_time': start_time,
|
||||
'expire_time': expire_time,
|
||||
'auto_update': auto_update,
|
||||
'ssl_type': ssl_type,
|
||||
}
|
||||
|
||||
row = DomainModel.create(**data)
|
||||
|
||||
if auto_update:
|
||||
domain_service.update_domain_row(row)
|
||||
|
||||
# 顺带添加到域名监测列表
|
||||
if not domain_util.is_ipv4(domain):
|
||||
|
||||
first_domain_info_row = DomainInfoModel.select(
|
||||
DomainInfoModel.id
|
||||
).where(
|
||||
DomainInfoModel.domain == data['root_domain'],
|
||||
DomainInfoModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not first_domain_info_row:
|
||||
domain_info_service.add_domain_info(
|
||||
domain=domain_util.get_root_domain(domain),
|
||||
comment=alias,
|
||||
group_id=group_id,
|
||||
user_id=current_user_id,
|
||||
)
|
||||
|
||||
return {'id': row.id}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=DomainModel,
|
||||
operation_type_id=OperationEnum.UPDATE
|
||||
)
|
||||
def update_domain_by_id():
|
||||
"""
|
||||
更新数据
|
||||
id domain alias group_id notify_status
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
data = request.json
|
||||
domain_id = request.json['id']
|
||||
|
||||
# domain_service.check_permission_and_get_row(domain_id, current_user_id)
|
||||
|
||||
data['update_time'] = datetime_util.get_datetime()
|
||||
data['group_id'] = data.get('group_id') or 0
|
||||
|
||||
# data check
|
||||
before_domain_row = DomainModel.select().where(
|
||||
DomainModel.id == domain_id
|
||||
).first()
|
||||
|
||||
if not before_domain_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
# edit permission check
|
||||
has_permission = domain_service.has_edit_permission(
|
||||
domain_row=before_domain_row,
|
||||
user_id=current_user_id
|
||||
)
|
||||
|
||||
if not has_permission:
|
||||
raise ForbiddenAppException()
|
||||
|
||||
DomainModel.update(data).where(
|
||||
DomainModel.id == domain_id
|
||||
).execute()
|
||||
|
||||
after_domain_row = DomainModel.get_by_id(domain_id)
|
||||
|
||||
# 域名、端口、SSL加密方式没改变,就不更新
|
||||
if before_domain_row.domain == after_domain_row.domain \
|
||||
and before_domain_row.port == after_domain_row.port \
|
||||
and before_domain_row.ssl_type == after_domain_row.ssl_type:
|
||||
pass
|
||||
else:
|
||||
if after_domain_row.auto_update:
|
||||
domain_service.update_domain_row(after_domain_row)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_domain_expire_monitor_by_id():
|
||||
"""
|
||||
更新监控状态
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_id = request.json.get('domain_id')
|
||||
|
||||
# data check
|
||||
domain_row = DomainModel.select().where(
|
||||
DomainModel.id == domain_id
|
||||
).first()
|
||||
|
||||
if not domain_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
# edit permission check
|
||||
has_permission = domain_service.has_edit_permission(
|
||||
domain_row=domain_row,
|
||||
user_id=current_user_id
|
||||
)
|
||||
|
||||
if not has_permission:
|
||||
raise ForbiddenAppException()
|
||||
|
||||
data = {
|
||||
"is_monitor": request.json.get('is_monitor', True)
|
||||
}
|
||||
|
||||
DomainModel.update(
|
||||
data
|
||||
).where(
|
||||
DomainModel.id == domain_row.id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=DomainModel,
|
||||
operation_type_id=OperationEnum.UPDATE,
|
||||
primary_key='domain_id'
|
||||
)
|
||||
def update_domain_field_by_id():
|
||||
"""
|
||||
更新单个数据
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_id = request.json['domain_id']
|
||||
field = request.json.get('field')
|
||||
value = request.json.get('value')
|
||||
|
||||
if field not in ['auto_update']:
|
||||
raise AppException("not allow field")
|
||||
|
||||
data = {
|
||||
field: value,
|
||||
}
|
||||
|
||||
# data check
|
||||
domain_row = DomainModel.select().where(
|
||||
DomainModel.id == domain_id
|
||||
).first()
|
||||
|
||||
if not domain_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
# edit permission check
|
||||
has_permission = domain_service.has_edit_permission(
|
||||
domain_row=domain_row,
|
||||
user_id=current_user_id
|
||||
)
|
||||
|
||||
if not has_permission:
|
||||
raise ForbiddenAppException()
|
||||
|
||||
DomainModel.update(data).where(
|
||||
DomainModel.id == domain_row.id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_domain_field_by_ids():
|
||||
"""
|
||||
批量更新单个字段值
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_ids = request.json['domain_ids']
|
||||
field = request.json.get('field')
|
||||
value = request.json.get('value')
|
||||
|
||||
if field not in ['auto_update']:
|
||||
raise AppException("not allow field")
|
||||
|
||||
data = {
|
||||
field: value,
|
||||
}
|
||||
|
||||
DomainModel.update(data).where(
|
||||
DomainModel.id.in_(domain_ids),
|
||||
DomainModel.user_id == current_user_id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=DomainModel,
|
||||
operation_type_id=OperationEnum.DELETE,
|
||||
primary_key='id'
|
||||
)
|
||||
def delete_domain_by_id():
|
||||
"""
|
||||
删除
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_id = request.json['id']
|
||||
|
||||
# domain_service.check_permission_and_get_row(domain_id, current_user_id)
|
||||
|
||||
# data check
|
||||
domain_row = DomainModel.select().where(
|
||||
DomainModel.id == domain_id
|
||||
).first()
|
||||
|
||||
if not domain_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
# edit permission check
|
||||
has_permission = domain_service.has_edit_permission(
|
||||
domain_row=domain_row,
|
||||
user_id=current_user_id
|
||||
)
|
||||
|
||||
if not has_permission:
|
||||
raise ForbiddenAppException()
|
||||
|
||||
DomainModel.delete_by_id(domain_row.id)
|
||||
|
||||
# 同时移除主机信息
|
||||
AddressModel.delete().where(
|
||||
AddressModel.domain_id == domain_id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=DomainModel,
|
||||
operation_type_id=OperationEnum.BATCH_DELETE,
|
||||
primary_key='ids'
|
||||
)
|
||||
def delete_domain_by_ids():
|
||||
"""
|
||||
批量删除
|
||||
@since v1.2.16
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_ids = request.json['ids']
|
||||
|
||||
DomainModel.delete().where(
|
||||
DomainModel.id.in_(domain_ids),
|
||||
DomainModel.user_id == current_user_id
|
||||
).execute()
|
||||
|
||||
# 同时移除主机信息
|
||||
AddressModel.delete().where(
|
||||
AddressModel.domain_id.in_(domain_ids)
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_domain_by_id():
|
||||
"""
|
||||
获取
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_id = request.json.get('domain_id') or request.json['id']
|
||||
|
||||
# row = domain_service.check_permission_and_get_row(domain_id, current_user_id)
|
||||
# row = DomainModel.get_by_id(domain_id)
|
||||
|
||||
# data check
|
||||
domain_row = DomainModel.select().where(
|
||||
DomainModel.id == domain_id
|
||||
).first()
|
||||
|
||||
if not domain_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
# edit permission check
|
||||
has_permission = domain_service.has_edit_permission(
|
||||
domain_row=domain_row,
|
||||
user_id=current_user_id
|
||||
)
|
||||
|
||||
if not has_permission:
|
||||
raise ForbiddenAppException()
|
||||
|
||||
row = model_to_dict(
|
||||
model=domain_row,
|
||||
extra_attrs=[
|
||||
'real_time_expire_days',
|
||||
'domain_url',
|
||||
'update_time_label',
|
||||
'expire_status',
|
||||
]
|
||||
)
|
||||
|
||||
# 主机数量
|
||||
address_count = AddressModel.select().where(
|
||||
AddressModel.domain_id == domain_id
|
||||
).count()
|
||||
|
||||
row['address_count'] = address_count
|
||||
row['group_name'] = group_service.get_group_name_by_id(row['group_id'])
|
||||
|
||||
if row['user_id'] == current_user_id:
|
||||
has_edit_permission = True
|
||||
else:
|
||||
first_row = GroupUserModel.select().where(
|
||||
GroupUserModel.group_id == row['group_id'],
|
||||
GroupUserModel.user_id == current_user_id,
|
||||
GroupUserModel.has_edit_permission == True
|
||||
).first()
|
||||
|
||||
if first_row:
|
||||
has_edit_permission = True
|
||||
else:
|
||||
has_edit_permission = False
|
||||
|
||||
row['has_edit_permission'] = has_edit_permission
|
||||
|
||||
return row
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_all_domain_cert_info():
|
||||
"""
|
||||
更新所有域名证书信息
|
||||
:return:
|
||||
"""
|
||||
|
||||
domain_service.update_all_domain_cert_info()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_all_domain_cert_info_of_user():
|
||||
"""
|
||||
更新当前用户的所有域名信息
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_service.update_all_domain_cert_info_of_user(user_id=current_user_id)
|
||||
# async_task_service.submit_task(fn=domain_service.update_all_domain_cert_info_of_user, user_id=current_user_id)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_domain_row_info_by_id():
|
||||
"""
|
||||
更新域名关联的证书信息
|
||||
:return:
|
||||
@since v1.3.1
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
# @since v1.2.24 支持参数 domain_id
|
||||
domain_id = request.json.get('domain_id') or request.json['id']
|
||||
|
||||
# row = domain_service.check_permission_and_get_row(domain_id, current_user_id)
|
||||
# row = DomainModel.get_by_id(domain_id)
|
||||
|
||||
# data check
|
||||
domain_row = DomainModel.select().where(
|
||||
DomainModel.id == domain_id
|
||||
).first()
|
||||
|
||||
if not domain_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
# edit permission check
|
||||
has_permission = domain_service.has_read_permission(
|
||||
domain_row=domain_row,
|
||||
user_id=current_user_id
|
||||
)
|
||||
|
||||
if not has_permission:
|
||||
raise ForbiddenAppException()
|
||||
|
||||
domain_service.update_domain_row(domain_row=domain_row)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_all_domain_list_of_user():
|
||||
"""
|
||||
获取用户的所有域名数据
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
# temp_filename = domain_service.export_domain_to_file(current_user_id)
|
||||
|
||||
rows = DomainModel.select().where(
|
||||
DomainModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
lst = [{'domain': row.domain} for row in rows]
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': len(lst)
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def import_domain_from_file():
|
||||
"""
|
||||
从文件导入域名
|
||||
支持 txt 和 csv格式
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
update_file = request.files.get('file')
|
||||
|
||||
filename = file_service.save_temp_file(update_file)
|
||||
|
||||
# 导入数据
|
||||
domain_service.add_domain_from_file(filename, current_user_id)
|
||||
|
||||
# 异步导入
|
||||
# async_task_service.submit_task(fn=domain_service.add_domain_from_file, filename=filename, user_id=current_user_id)
|
||||
|
||||
# 异步查询
|
||||
domain_service.update_all_domain_cert_info_of_user(user_id=current_user_id)
|
||||
# async_task_service.submit_task(fn=domain_service.update_all_domain_cert_info_of_user, user_id=current_user_id)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def export_domain_file():
|
||||
"""
|
||||
导出域名文件
|
||||
csv格式
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
keyword = request.json.get('keyword')
|
||||
group_id = request.json.get('group_id')
|
||||
group_ids = request.json.get('group_ids')
|
||||
expire_days = request.json.get('expire_days')
|
||||
role = request.json.get('role')
|
||||
ext = request.json.get('ext', 'csv')
|
||||
|
||||
order_prop = request.json.get('order_prop') or 'expire_days'
|
||||
order_type = request.json.get('order_type') or 'ascending'
|
||||
|
||||
params = {
|
||||
'keyword': keyword,
|
||||
'group_id': group_id,
|
||||
'group_ids': group_ids,
|
||||
'expire_days': expire_days,
|
||||
'role': role,
|
||||
'user_id': current_user_id,
|
||||
}
|
||||
|
||||
query = domain_service.get_domain_list_query(**params)
|
||||
ordering = domain_service.get_domain_ordering(order_prop=order_prop, order_type=order_type)
|
||||
|
||||
rows = query.order_by(*ordering)
|
||||
|
||||
# 分组名
|
||||
lst = list(map(lambda m: model_to_dict(
|
||||
model=m,
|
||||
extra_attrs=[
|
||||
'start_date',
|
||||
'expire_date',
|
||||
'expire_days',
|
||||
'create_time_label',
|
||||
'real_time_expire_days',
|
||||
'real_time_ssl_total_days',
|
||||
'real_time_ssl_expire_days',
|
||||
'domain_url',
|
||||
'update_time_label',
|
||||
'expire_status',
|
||||
]
|
||||
), rows))
|
||||
|
||||
group_service.load_group_name(lst)
|
||||
|
||||
filename = domain_service.export_domain_to_file(rows=lst, ext=ext)
|
||||
|
||||
return {
|
||||
'name': filename,
|
||||
'url': file_service.resolve_temp_url(filename)
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def domain_relation_group():
|
||||
"""
|
||||
分组关联域名
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
# temp_filename = domain_service.export_domain_to_file(current_user_id)
|
||||
domain_ids = request.json['domain_ids']
|
||||
group_id = request.json['group_id']
|
||||
|
||||
DomainModel.update(
|
||||
group_id=group_id
|
||||
).where(
|
||||
DomainModel.id.in_(domain_ids),
|
||||
DomainModel.user_id == current_user_id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_domain_list():
|
||||
"""
|
||||
获取域名列表
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
page = request.json.get('page', 1)
|
||||
size = request.json.get('size', 10)
|
||||
|
||||
keyword = request.json.get('keyword')
|
||||
group_id = request.json.get('group_id')
|
||||
group_ids = request.json.get('group_ids')
|
||||
expire_days = request.json.get('expire_days')
|
||||
role = request.json.get('role')
|
||||
|
||||
order_prop = request.json.get('order_prop') or 'expire_days'
|
||||
order_type = request.json.get('order_type') or 'ascending'
|
||||
|
||||
# 组权限
|
||||
group_user_permission_map = {}
|
||||
|
||||
if role == RoleEnum.ADMIN:
|
||||
pass
|
||||
|
||||
else:
|
||||
# 所在分组
|
||||
group_user_rows = GroupUserModel.select().where(
|
||||
GroupUserModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
group_user_list = list(group_user_rows)
|
||||
|
||||
# 组员权限
|
||||
group_user_permission_map = {row.group_id: row.has_edit_permission for row in group_user_list}
|
||||
|
||||
params = {
|
||||
'keyword': keyword,
|
||||
'group_id': group_id,
|
||||
'group_ids': group_ids,
|
||||
'expire_days': expire_days,
|
||||
'role': role,
|
||||
'user_id': current_user_id,
|
||||
}
|
||||
|
||||
query = domain_service.get_domain_list_query(**params)
|
||||
ordering = domain_service.get_domain_ordering(order_prop=order_prop, order_type=order_type)
|
||||
|
||||
total = query.count()
|
||||
lst = []
|
||||
|
||||
if total > 0:
|
||||
lst = query.order_by(*ordering).paginate(page, size)
|
||||
|
||||
lst = list(map(lambda m: model_to_dict(
|
||||
model=m,
|
||||
extra_attrs=[
|
||||
'expire_days',
|
||||
'create_time_label',
|
||||
'real_time_expire_days',
|
||||
'real_time_ssl_total_days',
|
||||
'real_time_ssl_expire_days',
|
||||
'domain_url',
|
||||
'update_time_label',
|
||||
'expire_status',
|
||||
]
|
||||
), lst))
|
||||
|
||||
# 加载主机数量
|
||||
domain_service.load_address_count(lst)
|
||||
|
||||
# 加载域名过期时间
|
||||
domain_service.load_domain_expire_days(lst)
|
||||
|
||||
# 分组名
|
||||
group_service.load_group_name(lst)
|
||||
|
||||
for row in lst:
|
||||
if role == RoleEnum.ADMIN:
|
||||
has_edit_permission = True
|
||||
|
||||
elif row['user_id'] == current_user_id:
|
||||
has_edit_permission = True
|
||||
else:
|
||||
has_edit_permission = group_user_permission_map.get(row['group_id'], False)
|
||||
|
||||
row['has_edit_permission'] = has_edit_permission
|
||||
|
||||
# lst = model_util.list_with_relation_one(lst, 'group', GroupModel)
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': total
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_domain_group_filter():
|
||||
"""
|
||||
获取证书分组筛选器
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
# 分组列表数据
|
||||
query = GroupModel.select().where(
|
||||
GroupModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
# 所在分组
|
||||
group_user_rows = GroupUserModel.select().where(
|
||||
GroupUserModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
group_user_list = list(group_user_rows)
|
||||
user_group_ids = [row.group_id for row in group_user_list]
|
||||
|
||||
if user_group_ids:
|
||||
query = query.orwhere(GroupModel.id.in_(user_group_ids))
|
||||
|
||||
total = query.count()
|
||||
lst = []
|
||||
if total > 0:
|
||||
lst = [model_to_dict(row) for row in query]
|
||||
group_ids = [row['id'] for row in lst]
|
||||
|
||||
# 证书分组统计
|
||||
cert_groups = DomainModel.select(
|
||||
DomainModel.group_id,
|
||||
fn.COUNT(DomainModel.id).alias('count')
|
||||
).where(
|
||||
DomainModel.group_id.in_(group_ids)
|
||||
).group_by(DomainModel.group_id)
|
||||
|
||||
cert_groups_map = {
|
||||
str(row.group_id): row.count
|
||||
for row in cert_groups
|
||||
}
|
||||
|
||||
for row in lst:
|
||||
row['cert_count'] = cert_groups_map.get(str(row['id']), 0)
|
||||
|
||||
# leader
|
||||
if row['user_id'] == current_user_id:
|
||||
row['is_leader'] = True
|
||||
else:
|
||||
row['is_leader'] = False
|
||||
|
||||
# if cert_groups_map.get('0'):
|
||||
# lst.append({
|
||||
# 'cert_count': cert_groups_map.get('0'),
|
||||
# 'id': 0,
|
||||
# 'name': '未分组',
|
||||
# })
|
||||
|
||||
lst.sort(key=itemgetter('cert_count'), reverse=True)
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': len(lst),
|
||||
}
|
||||
@@ -0,0 +1,678 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
domain_info_api.py
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals, absolute_import, division
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from operator import itemgetter
|
||||
|
||||
from flask import request, g
|
||||
from peewee import fn
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from domain_admin.enums.operation_enum import OperationEnum
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.model.domain_info_model import DomainInfoModel
|
||||
from domain_admin.model.domain_model import DomainModel
|
||||
from domain_admin.model.group_model import GroupModel
|
||||
from domain_admin.model.group_user_model import GroupUserModel
|
||||
from domain_admin.service import domain_info_service, async_task_service, file_service, group_service, \
|
||||
operation_service, group_user_service, domain_service, common_service, domain_icp_service, tag_service, auth_service
|
||||
from domain_admin.utils import domain_util, time_util, icp_util
|
||||
from domain_admin.utils.flask_ext.app_exception import AppException, DataNotFoundAppException
|
||||
from domain_admin.utils.open_api import crtsh_api
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=DomainInfoModel,
|
||||
operation_type_id=OperationEnum.CREATE,
|
||||
primary_key='domain_info_id'
|
||||
)
|
||||
def add_domain_info():
|
||||
"""
|
||||
添加域名
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain = request.json['domain']
|
||||
root_domain = domain_util.get_root_domain(domain)
|
||||
if not root_domain:
|
||||
raise AppException("不能解析根域名:{}".format(domain))
|
||||
else:
|
||||
domain = root_domain
|
||||
|
||||
domain_start_time = request.json.get('domain_start_time')
|
||||
domain_expire_time = request.json.get('domain_expire_time')
|
||||
is_auto_update = request.json.get('is_auto_update', True)
|
||||
is_auto_subdomain = request.json.get('is_auto_subdomain', False)
|
||||
comment = request.json.get('comment', '')
|
||||
tags = request.json.get('tags')
|
||||
icp_company = request.json.get('icp_company', '')
|
||||
icp_licence = request.json.get('icp_licence', '')
|
||||
group_id = request.json.get('group_id') or 0
|
||||
user_id = request.json.get('user_id')
|
||||
|
||||
row = domain_info_service.add_domain_info(
|
||||
domain=domain,
|
||||
domain_start_time=domain_start_time,
|
||||
domain_expire_time=domain_expire_time,
|
||||
comment=comment,
|
||||
tags=tags,
|
||||
group_id=group_id,
|
||||
user_id=user_id or current_user_id,
|
||||
icp_company=icp_company,
|
||||
icp_licence=icp_licence,
|
||||
is_auto_update=is_auto_update
|
||||
)
|
||||
|
||||
# 异步提交
|
||||
if is_auto_subdomain:
|
||||
domain_service.auto_import_from_domain_async(
|
||||
root_domain=domain,
|
||||
group_id=group_id,
|
||||
user_id=current_user_id
|
||||
)
|
||||
# async_task_service.submit_task(
|
||||
# fn=domain_service.auto_import_from_domain,
|
||||
# root_domain=domain,
|
||||
# group_id=group_id,
|
||||
# user_id=current_user_id
|
||||
# )
|
||||
|
||||
tag_service.add_tags(tags)
|
||||
|
||||
return {'domain_info_id': row.id}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=DomainInfoModel,
|
||||
operation_type_id=OperationEnum.UPDATE,
|
||||
primary_key='domain_info_id'
|
||||
)
|
||||
def update_domain_info_by_id():
|
||||
"""
|
||||
更新数据
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_info_id = request.json['domain_info_id']
|
||||
|
||||
domain = domain_util.get_root_domain(request.json['domain'])
|
||||
domain_start_time = request.json.get('domain_start_time')
|
||||
domain_expire_time = request.json.get('domain_expire_time')
|
||||
is_auto_update = request.json.get('is_auto_update', True)
|
||||
is_auto_subdomain = request.json.get('is_auto_subdomain', False)
|
||||
comment = request.json.get('comment', '')
|
||||
group_id = request.json.get('group_id') or 0
|
||||
tags = request.json.get('tags')
|
||||
icp_company = request.json.get('icp_company', '')
|
||||
icp_licence = request.json.get('icp_licence', '')
|
||||
user_id = request.json.get('user_id')
|
||||
|
||||
# check data
|
||||
domain_info_row = DomainInfoModel.select().where(
|
||||
DomainInfoModel.id == domain_info_id,
|
||||
DomainInfoModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not domain_info_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
# is_auto_update = request.json.get('is_auto_update', True)
|
||||
# is_expire_monitor = request.json.get('is_expire_monitor', True)
|
||||
|
||||
data = {
|
||||
'domain': domain,
|
||||
'comment': comment,
|
||||
'group_id': group_id,
|
||||
'tags_raw': json.dumps(tags, ensure_ascii=False),
|
||||
'icp_company': icp_company,
|
||||
'icp_licence': icp_licence,
|
||||
'is_auto_update': is_auto_update
|
||||
}
|
||||
|
||||
# 不自动更新,才可以提交开始时间和结束时间
|
||||
if is_auto_update is False:
|
||||
data['domain_start_time'] = domain_start_time
|
||||
data['domain_expire_time'] = domain_expire_time
|
||||
|
||||
if domain_expire_time:
|
||||
data['domain_expire_days'] = time_util.get_diff_days(datetime.now(),
|
||||
time_util.parse_time(domain_expire_time))
|
||||
else:
|
||||
data['domain_expire_days'] = 0
|
||||
|
||||
if user_id:
|
||||
data['user_id'] = user_id
|
||||
|
||||
DomainInfoModel.update(data).where(
|
||||
DomainInfoModel.id == domain_info_id
|
||||
).execute()
|
||||
|
||||
if domain_info_row.domain != domain and is_auto_update:
|
||||
# 需要自动更新
|
||||
domain_info_service.update_domain_info_row(domain_info_row)
|
||||
|
||||
if is_auto_subdomain:
|
||||
domain_service.auto_import_from_domain_async(
|
||||
root_domain=domain,
|
||||
group_id=group_id,
|
||||
user_id=current_user_id
|
||||
)
|
||||
#
|
||||
# async_task_service.submit_task(
|
||||
# fn=domain_service.auto_import_from_domain,
|
||||
# root_domain=domain,
|
||||
# group_id=group_id,
|
||||
# user_id=current_user_id
|
||||
# )
|
||||
|
||||
tag_service.add_tags(tags)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=DomainInfoModel,
|
||||
operation_type_id=OperationEnum.UPDATE,
|
||||
primary_key='domain_info_id'
|
||||
)
|
||||
def update_domain_info_field_by_id():
|
||||
"""
|
||||
更新单个数据
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_info_id = request.json['domain_info_id']
|
||||
field = request.json.get('field')
|
||||
value = request.json.get('value')
|
||||
|
||||
if field not in ['is_auto_update', 'is_expire_monitor']:
|
||||
raise AppException("not allow field")
|
||||
|
||||
data = {
|
||||
field: value,
|
||||
}
|
||||
|
||||
# check data
|
||||
domain_info_row = DomainInfoModel.select().where(
|
||||
DomainInfoModel.id == domain_info_id,
|
||||
DomainInfoModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not domain_info_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
DomainInfoModel.update(data).where(
|
||||
DomainInfoModel.id == domain_info_row.id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=DomainInfoModel,
|
||||
operation_type_id=OperationEnum.DELETE,
|
||||
primary_key='domain_info_id'
|
||||
)
|
||||
def delete_domain_info_by_id():
|
||||
"""
|
||||
删除
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_info_id = request.json['domain_info_id']
|
||||
|
||||
# check data
|
||||
domain_info_row = DomainInfoModel.select().where(
|
||||
DomainInfoModel.id == domain_info_id,
|
||||
DomainInfoModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not domain_info_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
DomainInfoModel.delete_by_id(domain_info_row.id)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=DomainInfoModel,
|
||||
operation_type_id=OperationEnum.BATCH_DELETE,
|
||||
primary_key='domain_info_ids'
|
||||
)
|
||||
def delete_domain_info_by_ids():
|
||||
"""
|
||||
批量删除
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_info_ids = request.json['domain_info_ids']
|
||||
|
||||
DomainInfoModel.delete().where(
|
||||
DomainInfoModel.id.in_(domain_info_ids),
|
||||
DomainInfoModel.user_id == current_user_id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_domain_info_by_id():
|
||||
"""
|
||||
获取
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_info_id = request.json['domain_info_id']
|
||||
|
||||
# check data
|
||||
if g.current_user_row.role == RoleEnum.ADMIN:
|
||||
domain_info_row = DomainInfoModel.select().where(
|
||||
DomainInfoModel.id == domain_info_id
|
||||
).first()
|
||||
else:
|
||||
domain_info_row = DomainInfoModel.select().where(
|
||||
DomainInfoModel.id == domain_info_id,
|
||||
DomainInfoModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not domain_info_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
domain_row = model_to_dict(
|
||||
model=domain_info_row,
|
||||
extra_attrs=[
|
||||
'real_domain_expire_days',
|
||||
'update_time_label',
|
||||
'tags',
|
||||
],
|
||||
exclude=['tags_raw'],
|
||||
)
|
||||
|
||||
# 主机数量
|
||||
ssl_count = DomainModel.select().where(
|
||||
DomainModel.root_domain == domain_row['domain'],
|
||||
DomainModel.user_id == current_user_id
|
||||
).count()
|
||||
|
||||
domain_row['ssl_count'] = ssl_count
|
||||
domain_row['group_name'] = group_service.get_group_name_by_id(domain_row['group_id'])
|
||||
|
||||
# 编辑权限
|
||||
group_user_permission_map = group_user_service.get_group_user_permission_map(current_user_id)
|
||||
|
||||
if domain_row['user_id'] == current_user_id:
|
||||
has_edit_permission = True
|
||||
else:
|
||||
has_edit_permission = group_user_permission_map.get(domain_row['group_id'], False)
|
||||
|
||||
domain_row['has_edit_permission'] = has_edit_permission
|
||||
|
||||
common_service.load_user_name([domain_row])
|
||||
|
||||
return domain_row
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_domain_info_row_by_id():
|
||||
"""
|
||||
更新当前行的域名信息
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_info_id = request.json['domain_info_id']
|
||||
|
||||
# check data
|
||||
domain_info_row = DomainInfoModel.select().where(
|
||||
DomainInfoModel.id == domain_info_id,
|
||||
DomainInfoModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not domain_info_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
domain_info_service.update_domain_info_row(row=domain_info_row)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_all_domain_info_of_user():
|
||||
"""
|
||||
更新当前用户的域名信息
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_info_service.update_all_domain_info_of_user(user_id=current_user_id)
|
||||
# async_task_service.submit_task(fn=domain_info_service.update_all_domain_info_of_user, user_id=current_user_id)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_all_domain_icp_of_user():
|
||||
"""
|
||||
更新当前用户的域名icp信息
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_info_service.update_all_domain_icp_of_user(current_user_id)
|
||||
|
||||
# async_task_service.submit_task(fn=domain_info_service.update_all_domain_icp_of_user, user_id=current_user_id)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_domain_row_icp():
|
||||
"""
|
||||
更新当前域名icp信息
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_info_id = request.json['domain_info_id']
|
||||
|
||||
# check data
|
||||
domain_info_row = DomainInfoModel.select().where(
|
||||
DomainInfoModel.id == domain_info_id,
|
||||
DomainInfoModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not domain_info_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
domain_info_service.update_domain_row_icp(row=domain_info_row)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def import_domain_info_from_file():
|
||||
"""
|
||||
从文件导入域名
|
||||
支持格式: txt、xlsx、csv
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
update_file = request.files.get('file')
|
||||
|
||||
filename = file_service.save_temp_file(update_file)
|
||||
|
||||
# 导入数据
|
||||
domain_info_service.add_domain_from_file(filename, current_user_id)
|
||||
|
||||
# 异步查询
|
||||
domain_info_service.handle_auto_import_domain_info(current_user_id)
|
||||
# async_task_service.submit_task(fn=domain_info_service.update_all_domain_info_of_user, user_id=current_user_id)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def export_domain_info_file():
|
||||
"""
|
||||
导出域名文件
|
||||
csv格式
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
keyword = request.json.get('keyword')
|
||||
group_ids = request.json.get('group_ids')
|
||||
domain_expire_days = request.json.get('domain_expire_days')
|
||||
role = request.json.get('role')
|
||||
ext = request.json.get('ext', 'csv')
|
||||
|
||||
order_prop = request.json.get('order_prop') or 'domain_expire_days'
|
||||
order_type = request.json.get('order_type') or 'ascending'
|
||||
|
||||
params = {
|
||||
'keyword': keyword,
|
||||
'group_ids': group_ids,
|
||||
'domain_expire_days': domain_expire_days,
|
||||
'role': role,
|
||||
'user_id': current_user_id
|
||||
}
|
||||
|
||||
# 列表数据
|
||||
query = domain_info_service.get_domain_info_query(**params)
|
||||
|
||||
ordering = domain_info_service.get_ordering(order_prop=order_prop, order_type=order_type)
|
||||
|
||||
rows = query.order_by(*ordering)
|
||||
|
||||
lst = [model_to_dict(
|
||||
model=row,
|
||||
extra_attrs=[
|
||||
'domain_start_date',
|
||||
'domain_expire_date',
|
||||
'real_domain_expire_days',
|
||||
'update_time_label',
|
||||
'tags',
|
||||
'tags_str',
|
||||
]
|
||||
) for row in rows]
|
||||
|
||||
# 分组名
|
||||
group_service.load_group_name(lst)
|
||||
|
||||
filename = domain_info_service.export_domain_to_file(ext=ext, rows=lst)
|
||||
|
||||
return {
|
||||
'name': filename,
|
||||
'url': file_service.resolve_temp_url(filename)
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_domain_info_list():
|
||||
"""
|
||||
获取域名列表
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
page = request.json.get('page', 1)
|
||||
size = request.json.get('size', 10)
|
||||
|
||||
keyword = request.json.get('keyword')
|
||||
group_ids = request.json.get('group_ids')
|
||||
domain_expire_days = request.json.get('domain_expire_days')
|
||||
role = request.json.get('role')
|
||||
|
||||
order_prop = request.json.get('order_prop') or 'domain_expire_days'
|
||||
order_type = request.json.get('order_type') or 'ascending'
|
||||
|
||||
group_user_permission_map = {}
|
||||
|
||||
if role == RoleEnum.ADMIN:
|
||||
pass
|
||||
|
||||
else:
|
||||
# 所在分组
|
||||
group_user_rows = GroupUserModel.select().where(
|
||||
GroupUserModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
group_user_list = list(group_user_rows)
|
||||
# 组员权限
|
||||
group_user_permission_map = {row.group_id: row.has_edit_permission for row in group_user_list}
|
||||
|
||||
params = {
|
||||
'keyword': keyword,
|
||||
'group_ids': group_ids,
|
||||
'domain_expire_days': domain_expire_days,
|
||||
'role': role,
|
||||
'user_id': current_user_id
|
||||
}
|
||||
|
||||
# 列表数据
|
||||
query = domain_info_service.get_domain_info_query(**params)
|
||||
|
||||
total = query.count()
|
||||
|
||||
lst = []
|
||||
if total > 0:
|
||||
|
||||
ordering = domain_info_service.get_ordering(order_prop=order_prop, order_type=order_type)
|
||||
|
||||
rows = query.order_by(*ordering).paginate(page, size)
|
||||
|
||||
lst = [model_to_dict(
|
||||
model=row,
|
||||
extra_attrs=[
|
||||
'real_domain_expire_days',
|
||||
'update_time_label',
|
||||
'tags',
|
||||
]
|
||||
) for row in rows]
|
||||
|
||||
domain_list = [row['domain'] for row in lst]
|
||||
|
||||
# 域名证书
|
||||
root_domain_groups = DomainModel.select(
|
||||
DomainModel.root_domain,
|
||||
fn.COUNT(DomainModel.id).alias('count')
|
||||
).where(
|
||||
DomainModel.root_domain.in_(domain_list),
|
||||
DomainModel.user_id == current_user_id
|
||||
).group_by(DomainModel.root_domain)
|
||||
|
||||
root_domain_groups_map = {
|
||||
row.root_domain: row.count
|
||||
for row in root_domain_groups
|
||||
}
|
||||
|
||||
for row in lst:
|
||||
row['ssl_count'] = root_domain_groups_map.get(row['domain'], 0)
|
||||
|
||||
if role == RoleEnum.ADMIN:
|
||||
has_edit_permission = True
|
||||
|
||||
elif row['user_id'] == current_user_id:
|
||||
has_edit_permission = True
|
||||
else:
|
||||
has_edit_permission = group_user_permission_map.get(row['group_id'], False)
|
||||
|
||||
row['has_edit_permission'] = has_edit_permission
|
||||
|
||||
# 分组名
|
||||
group_service.load_group_name(lst)
|
||||
# 用户名
|
||||
common_service.load_user_name(lst)
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': total,
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_domain_info_group_filter():
|
||||
"""
|
||||
获取域名分组筛选器
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
# 分组列表数据
|
||||
query = GroupModel.select().where(
|
||||
GroupModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
lst = []
|
||||
if total > 0:
|
||||
|
||||
# 证书分组统计
|
||||
cert_groups = DomainInfoModel.select(
|
||||
DomainInfoModel.group_id,
|
||||
fn.COUNT(DomainInfoModel.id).alias('count')
|
||||
).group_by(DomainInfoModel.group_id)
|
||||
|
||||
groups_map = {
|
||||
str(row.group_id): row.count
|
||||
for row in cert_groups
|
||||
}
|
||||
|
||||
lst = []
|
||||
for row in query:
|
||||
row_dict = model_to_dict(row)
|
||||
row_dict['domain_count'] = groups_map.get(str(row.id), 0)
|
||||
lst.append(row_dict)
|
||||
|
||||
if groups_map.get('0'):
|
||||
lst.append({
|
||||
'domain_count': groups_map.get('0'),
|
||||
'id': 0,
|
||||
'name': '未分组',
|
||||
})
|
||||
|
||||
lst.sort(key=itemgetter('domain_count'), reverse=True)
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': len(lst),
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_icp():
|
||||
"""
|
||||
查询icp信息
|
||||
"""
|
||||
domain = request.json['domain']
|
||||
|
||||
# 解析域名
|
||||
resolve_domain = domain_util.parse_domain(domain)
|
||||
|
||||
item = domain_icp_service.get_domain_icp(resolve_domain)
|
||||
|
||||
if not item:
|
||||
raise AppException('没有查到icp信息')
|
||||
|
||||
res = item.to_dict()
|
||||
res['resolve_domain'] = resolve_domain
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_sub_domain_cert():
|
||||
"""
|
||||
获取子域证书列表
|
||||
:return:
|
||||
"""
|
||||
keyword = request.json.get('keyword', 1)
|
||||
|
||||
lst = crtsh_api.search(keyword)
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': len(lst)
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def auto_import_subdomain_by_ids():
|
||||
"""
|
||||
批量导入子域证书
|
||||
@since v1.6.30
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
domain_ids = request.json['domain_ids']
|
||||
|
||||
rows = DomainInfoModel.select().where(
|
||||
DomainInfoModel.id.in_(domain_ids),
|
||||
DomainInfoModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
# 异步提交
|
||||
domain_service.auto_import_from_domain_batch_async(rows=rows, user_id=current_user_id)
|
||||
@@ -0,0 +1,295 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, unicode_literals, absolute_import, division
|
||||
from operator import itemgetter
|
||||
|
||||
from flask import request, g
|
||||
from peewee import fn
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from domain_admin.enums.operation_enum import OperationEnum
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.model.domain_info_model import DomainInfoModel
|
||||
from domain_admin.model.domain_model import DomainModel
|
||||
from domain_admin.model.group_model import GroupModel
|
||||
from domain_admin.model.group_user_model import GroupUserModel
|
||||
from domain_admin.service import group_service, operation_service, group_user_service, auth_service
|
||||
from domain_admin.utils.flask_ext.app_exception import ForbiddenAppException, DataNotFoundAppException
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=GroupModel,
|
||||
operation_type_id=OperationEnum.CREATE,
|
||||
primary_key='id'
|
||||
)
|
||||
def add_group():
|
||||
"""
|
||||
添加
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
name = request.json['name']
|
||||
|
||||
row = GroupModel.create(
|
||||
name=name,
|
||||
user_id=current_user_id
|
||||
)
|
||||
|
||||
return {'id': row.id}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=GroupModel,
|
||||
operation_type_id=OperationEnum.UPDATE,
|
||||
primary_key='id'
|
||||
)
|
||||
def update_group_by_id():
|
||||
"""
|
||||
更新数据
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
group_id = request.json['id']
|
||||
name = request.json.get('name')
|
||||
|
||||
# group_service.check_group_permission(group_id, current_user_id)
|
||||
# check data
|
||||
group_row = GroupModel.select().where(
|
||||
GroupModel.id == group_id,
|
||||
GroupModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not group_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
GroupModel.update(
|
||||
name=name,
|
||||
).where(
|
||||
GroupModel.id == group_id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=GroupModel,
|
||||
operation_type_id=OperationEnum.DELETE,
|
||||
primary_key='id'
|
||||
)
|
||||
def delete_group_by_id():
|
||||
"""
|
||||
删除
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
group_id = request.json['id']
|
||||
|
||||
# check data
|
||||
group_row = GroupModel.select().where(
|
||||
GroupModel.id == group_id,
|
||||
GroupModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not group_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
GroupModel.delete_by_id(group_row.id)
|
||||
|
||||
# 重置已分类的证书 和 域名
|
||||
DomainModel.update(
|
||||
group_id=0
|
||||
).where(
|
||||
DomainModel.group_id == group_id
|
||||
).execute()
|
||||
|
||||
DomainInfoModel.update(
|
||||
group_id=0
|
||||
).where(
|
||||
DomainInfoModel.group_id == group_id
|
||||
).execute()
|
||||
|
||||
GroupUserModel.delete().where(
|
||||
GroupUserModel.group_id == group_id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=GroupModel,
|
||||
operation_type_id=OperationEnum.BATCH_DELETE,
|
||||
primary_key='group_ids'
|
||||
)
|
||||
def delete_group_by_ids():
|
||||
"""
|
||||
批量删除
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
group_ids = request.json['group_ids']
|
||||
|
||||
GroupModel.delete().where(
|
||||
GroupModel.id.in_(group_ids),
|
||||
GroupModel.user_id == current_user_id
|
||||
).execute()
|
||||
|
||||
# 重置已分类的证书 和 域名
|
||||
DomainModel.update(
|
||||
group_id=0
|
||||
).where(
|
||||
DomainModel.group_id.in_(group_ids)
|
||||
).execute()
|
||||
|
||||
DomainInfoModel.update(
|
||||
group_id=0
|
||||
).where(
|
||||
DomainInfoModel.group_id.in_(group_ids)
|
||||
).execute()
|
||||
|
||||
GroupUserModel.delete().where(
|
||||
GroupUserModel.group_id.in_(group_ids)
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_group_list():
|
||||
"""
|
||||
获取域名列表
|
||||
:return:
|
||||
"""
|
||||
# page = request.json.get('page', 1)
|
||||
# size = request.json.get('size', 10)
|
||||
keyword = request.json.get('keyword')
|
||||
role = request.json.get('role')
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
# 分组列表数据
|
||||
query = GroupModel.select()
|
||||
|
||||
# 所在分组
|
||||
group_user_list = list(GroupUserModel.select().where(
|
||||
GroupUserModel.user_id == current_user_id
|
||||
))
|
||||
|
||||
user_group_ids = [row.group_id for row in group_user_list]
|
||||
group_user_map = {row.group_id: row.has_edit_permission for row in group_user_list}
|
||||
|
||||
if role == RoleEnum.ADMIN:
|
||||
pass
|
||||
|
||||
elif user_group_ids:
|
||||
query = query.where(
|
||||
(GroupModel.user_id == current_user_id)
|
||||
| (GroupModel.id.in_(user_group_ids))
|
||||
)
|
||||
else:
|
||||
query = query.where(
|
||||
GroupModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
if keyword:
|
||||
query = query.where(GroupModel.name.contains(keyword))
|
||||
|
||||
total = query.count()
|
||||
|
||||
# bugfix: 分组不需要分页
|
||||
rows = query.order_by(
|
||||
GroupModel.create_time.asc(),
|
||||
GroupModel.id.asc()
|
||||
)
|
||||
|
||||
rows = list(rows)
|
||||
|
||||
# 证书分组统计
|
||||
cert_groups = DomainModel.select(
|
||||
DomainModel.group_id,
|
||||
fn.COUNT(DomainModel.id).alias('count')
|
||||
).group_by(DomainModel.group_id)
|
||||
|
||||
cert_groups_map = {
|
||||
row.group_id: row.count
|
||||
for row in cert_groups
|
||||
}
|
||||
|
||||
# 域名分组统计
|
||||
domain_groups = DomainInfoModel.select(
|
||||
DomainInfoModel.group_id,
|
||||
fn.COUNT(DomainInfoModel.id).alias('count')
|
||||
).group_by(DomainInfoModel.group_id)
|
||||
|
||||
domain_groups_map = {
|
||||
row.group_id: row.count
|
||||
for row in domain_groups
|
||||
}
|
||||
|
||||
# 成员分组统计
|
||||
group_ids = [row.id for row in rows]
|
||||
|
||||
domain_groups = GroupUserModel.select(
|
||||
GroupUserModel.group_id,
|
||||
fn.COUNT(GroupUserModel.id).alias('count')
|
||||
).where(
|
||||
GroupUserModel.group_id.in_(group_ids)
|
||||
).group_by(GroupUserModel.group_id)
|
||||
|
||||
group_user_groups_map = {
|
||||
row.group_id: row.count
|
||||
for row in domain_groups
|
||||
}
|
||||
|
||||
lst = []
|
||||
for row in rows:
|
||||
row_dict = model_to_dict(row)
|
||||
row_dict['cert_count'] = cert_groups_map.get(row.id, 0)
|
||||
row_dict['domain_count'] = domain_groups_map.get(row.id, 0)
|
||||
row_dict['group_user_count'] = group_user_groups_map.get(row.id, 0) + 1
|
||||
|
||||
# 组权限
|
||||
if role == RoleEnum.ADMIN:
|
||||
has_edit_permission = True
|
||||
|
||||
elif row.user_id == current_user_id:
|
||||
has_edit_permission = True
|
||||
else:
|
||||
has_edit_permission = group_user_map.get(row.id, False)
|
||||
|
||||
row_dict['has_edit_permission'] = has_edit_permission
|
||||
row_dict['is_leader'] = row.user_id == current_user_id
|
||||
|
||||
lst.append(row_dict)
|
||||
|
||||
lst.sort(key=itemgetter('is_leader', 'has_edit_permission'), reverse=True)
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': total,
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_group_by_id():
|
||||
"""
|
||||
获取
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
group_id = request.json['id']
|
||||
|
||||
# group_service.check_group_permission(group_id, current_user_id)
|
||||
# check data
|
||||
group_row = GroupModel.select().where(
|
||||
GroupModel.id == group_id,
|
||||
GroupModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not group_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
return group_row
|
||||
@@ -0,0 +1,186 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
group_user_api.py
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals, absolute_import, division
|
||||
from flask import request, g
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from domain_admin.enums.operation_enum import OperationEnum
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.model.group_model import GroupModel
|
||||
from domain_admin.model.group_user_model import GroupUserModel
|
||||
from domain_admin.service import group_service, operation_service, common_service, auth_service
|
||||
from domain_admin.utils.flask_ext.app_exception import DataNotFoundAppException
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=GroupUserModel,
|
||||
operation_type_id=OperationEnum.CREATE,
|
||||
primary_key='id'
|
||||
)
|
||||
def add_group_user():
|
||||
"""
|
||||
添加
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
group_id = request.json['group_id']
|
||||
user_id = request.json['user_id']
|
||||
has_edit_permission = request.json.get('has_edit_permission', False)
|
||||
|
||||
# 权限校验
|
||||
group_service.check_group_permission(group_id, current_user_id)
|
||||
|
||||
row = GroupUserModel.create(
|
||||
group_id=group_id,
|
||||
user_id=user_id,
|
||||
has_edit_permission=has_edit_permission
|
||||
)
|
||||
|
||||
return {'id': row.id}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=GroupUserModel,
|
||||
operation_type_id=OperationEnum.UPDATE,
|
||||
primary_key='group_user_id'
|
||||
)
|
||||
def update_group_user_by_id():
|
||||
"""
|
||||
更新数据
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
group_user_id = request.json['group_user_id']
|
||||
has_edit_permission = request.json.get('has_edit_permission', False)
|
||||
|
||||
group_user_row = GroupUserModel.get_by_id(group_user_id)
|
||||
|
||||
group_service.check_group_permission(group_user_row.group_id, current_user_id)
|
||||
|
||||
GroupUserModel.update(
|
||||
has_edit_permission=has_edit_permission,
|
||||
).where(
|
||||
GroupUserModel.id == group_user_id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=GroupUserModel,
|
||||
operation_type_id=OperationEnum.DELETE,
|
||||
primary_key='group_user_id'
|
||||
)
|
||||
def delete_group_user_by_id():
|
||||
"""
|
||||
删除
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
group_user_id = request.json['group_user_id']
|
||||
|
||||
group_user_row = GroupUserModel.get_by_id(group_user_id)
|
||||
|
||||
group_service.check_group_permission(group_user_row.group_id, current_user_id)
|
||||
|
||||
GroupUserModel.delete().where(
|
||||
GroupUserModel.id == group_user_id,
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=GroupUserModel,
|
||||
operation_type_id=OperationEnum.BATCH_DELETE,
|
||||
primary_key='group_user_ids'
|
||||
)
|
||||
def delete_group_user_by_ids():
|
||||
"""
|
||||
批量删除
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
group_user_ids = request.json['group_user_ids']
|
||||
|
||||
group_user_rows = GroupUserModel.select().where(
|
||||
GroupUserModel.id.in_(group_user_ids)
|
||||
)
|
||||
|
||||
for group_user_row in group_user_rows:
|
||||
group_service.check_group_permission(group_user_row.group_id, current_user_id)
|
||||
|
||||
GroupUserModel.delete().where(
|
||||
GroupUserModel.id.in_(group_user_ids),
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_group_user_list():
|
||||
"""
|
||||
获取列表
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
group_id = request.json.get('group_id')
|
||||
|
||||
# 分组列表数据
|
||||
query = GroupUserModel.select().where(
|
||||
GroupUserModel.group_id == group_id
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
|
||||
rows = query.order_by(
|
||||
GroupUserModel.create_time.asc(),
|
||||
GroupUserModel.id.asc()
|
||||
)
|
||||
|
||||
lst = [model_to_dict(row) for row in rows]
|
||||
|
||||
# 添加leader
|
||||
group_row = GroupModel.get_by_id(group_id)
|
||||
|
||||
leader = GroupUserModel()
|
||||
leader.group_id = group_id
|
||||
leader.user_id = group_row.user_id
|
||||
leader.has_edit_permission = True
|
||||
|
||||
lst.insert(0, model_to_dict(leader))
|
||||
|
||||
common_service.load_user_name(lst)
|
||||
|
||||
for row in lst:
|
||||
# add leader
|
||||
if row['user_id'] == group_row.user_id:
|
||||
row['is_leader'] = True
|
||||
else:
|
||||
row['is_leader'] = False
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': len(lst),
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_group_user_by_id():
|
||||
"""
|
||||
获取
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
group_id = request.json['group_id']
|
||||
|
||||
group_service.check_group_permission(group_id, current_user_id)
|
||||
|
||||
return GroupUserModel.get_by_id(group_id)
|
||||
@@ -0,0 +1,173 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@File : host_api.py
|
||||
@Date : 2023-07-29
|
||||
"""
|
||||
from flask import request, g
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from domain_admin.config import DEFAULT_SSH_PORT
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.log import logger
|
||||
from domain_admin.model.host_model import HostModel
|
||||
from domain_admin.service import auth_service
|
||||
from domain_admin.utils.flask_ext.app_exception import DataNotFoundAppException
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def add_host():
|
||||
"""
|
||||
添加主机
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
host = request.json['host']
|
||||
port = request.json.get('port') or DEFAULT_SSH_PORT
|
||||
user = request.json['user']
|
||||
auth_type = request.json['auth_type']
|
||||
private_key = request.json['private_key']
|
||||
password = request.json['password']
|
||||
dns_id = request.json.get('dns_id', 0)
|
||||
|
||||
row = HostModel.create(
|
||||
user_id=current_user_id,
|
||||
host=host,
|
||||
port=int(port),
|
||||
user=user,
|
||||
auth_type=auth_type,
|
||||
private_key=private_key,
|
||||
password=password,
|
||||
dns_id=dns_id
|
||||
)
|
||||
|
||||
return row
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_host_by_id():
|
||||
"""
|
||||
更新主机
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
host_id = request.json['host_id']
|
||||
host = request.json['host']
|
||||
port = request.json.get('port') or DEFAULT_SSH_PORT
|
||||
user = request.json['user']
|
||||
password = request.json['password']
|
||||
auth_type = request.json['auth_type']
|
||||
private_key = request.json['private_key']
|
||||
dns_id = request.json.get('dns_id', 0)
|
||||
|
||||
# check data
|
||||
host_row = HostModel.select().where(
|
||||
HostModel.id == host_id,
|
||||
HostModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not host_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
HostModel.update(
|
||||
host=host,
|
||||
port=int(port),
|
||||
user=user,
|
||||
password=password,
|
||||
auth_type=auth_type,
|
||||
private_key=private_key,
|
||||
dns_id=dns_id
|
||||
).where(
|
||||
HostModel.id == host_row.id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_host_by_id():
|
||||
"""
|
||||
获取主机
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
host_id = request.json['host_id']
|
||||
|
||||
# check data
|
||||
host_row = HostModel.select().where(
|
||||
HostModel.id == host_id,
|
||||
HostModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not host_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
host_row.password = ''
|
||||
host_row.private_key = ''
|
||||
|
||||
return host_row
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def delete_host_by_id():
|
||||
"""
|
||||
移除主机
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
host_id = request.json['host_id']
|
||||
|
||||
# check data
|
||||
host_row = HostModel.select().where(
|
||||
HostModel.id == host_id,
|
||||
HostModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not host_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
return HostModel.delete_by_id(host_row.id)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_host_list():
|
||||
"""
|
||||
主机列表
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
page = request.json.get('page', 1)
|
||||
size = request.json.get('size', 10)
|
||||
keyword = request.json.get('keyword')
|
||||
host = request.json.get('host')
|
||||
|
||||
query = HostModel.select().where(
|
||||
HostModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
if keyword:
|
||||
query = query.where(HostModel.host.contains(keyword))
|
||||
|
||||
if host:
|
||||
query = query.where(HostModel.host == host)
|
||||
|
||||
total = query.count()
|
||||
|
||||
rows = query.order_by(
|
||||
HostModel.create_time.desc(),
|
||||
HostModel.id.desc()
|
||||
).paginate(page, size)
|
||||
|
||||
lst = list(map(lambda m: model_to_dict(model=m), rows))
|
||||
|
||||
for row in lst:
|
||||
row['password'] = ''
|
||||
row['private_key'] = ''
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': total,
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@File : ip_api.py
|
||||
@Date : 2022-10-14
|
||||
@Author : Peng Shiyu
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals, absolute_import, division
|
||||
from flask import request
|
||||
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.service import auth_service
|
||||
from domain_admin.utils import ip_util, dns_util
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_ip_info():
|
||||
"""
|
||||
获取ip地址的信息
|
||||
:return:
|
||||
"""
|
||||
ip = request.json['ip']
|
||||
return ip_util.get_ip_info(ip)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def query_domain_cname():
|
||||
"""
|
||||
查询域名的CNAME记录
|
||||
:return:
|
||||
"""
|
||||
domain = request.json['domain']
|
||||
return dns_util.query_domain_cname(domain)
|
||||
@@ -0,0 +1,740 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@File : issue_certificate_api.py
|
||||
@Date : 2023-07-23
|
||||
"""
|
||||
import json
|
||||
|
||||
import requests
|
||||
from flask import g, request
|
||||
from playhouse.shortcuts import model_to_dict, chunked
|
||||
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.model.dns_model import DnsModel
|
||||
from domain_admin.model.domain_model import DomainModel
|
||||
from domain_admin.model.host_model import HostModel
|
||||
from domain_admin.model.issue_certificate_model import IssueCertificateModel, ChallengeDeployTypeEnum, \
|
||||
SSLDeployTypeEnum, DeployStatusEnum
|
||||
from domain_admin.service import issue_certificate_service, auth_service
|
||||
from domain_admin.utils import ip_util, domain_util, fabric_util, datetime_util, validate_util
|
||||
from domain_admin.utils.acme_util.challenge_type import ChallengeType
|
||||
from domain_admin.utils.acme_util.key_type_enum import KEY_TYPE_OPTIONS, KeyTypeEnum
|
||||
from domain_admin.utils.acme_util.directory_type_enum import DIRECTORY_URL_OPTIONS, DirectoryTypeEnum
|
||||
from domain_admin.utils.flask_ext.app_exception import AppException, DataNotFoundAppException
|
||||
from domain_admin.utils.open_api import aliyun_domain_api
|
||||
from domain_admin.utils.open_api.aliyun_domain_api import RecordTypeEnum
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def issue_certificate():
|
||||
"""
|
||||
发起申请
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
domains = request.json['domains']
|
||||
directory_type = request.json.get('directory_type') or DirectoryTypeEnum.LETS_ENCRYPT
|
||||
key_type = request.json.get('key_type') or KeyTypeEnum.RSA
|
||||
|
||||
issue_certificate_id = issue_certificate_service.issue_certificate(
|
||||
domains=domains,
|
||||
user_id=current_user_id,
|
||||
directory_type=directory_type,
|
||||
key_type=key_type
|
||||
)
|
||||
|
||||
issue_certificate_row = IssueCertificateModel.get_by_id(issue_certificate_id)
|
||||
|
||||
return issue_certificate_row.to_dict()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def verify_certificate():
|
||||
"""
|
||||
通知验证
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
issue_certificate_id = request.json['issue_certificate_id']
|
||||
challenge_type = request.json['challenge_type']
|
||||
# 验证文件部署信息 可选参数
|
||||
verify_deploy_path = request.json.get('verify_deploy_path')
|
||||
host_id = request.json.get('host_id')
|
||||
dns_id = request.json.get('dns_id')
|
||||
|
||||
# 验证成功后,自动添加到证书监控列表
|
||||
issue_certificate_row = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.id == issue_certificate_id,
|
||||
IssueCertificateModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
issue_certificate_service.verify_certificate(
|
||||
issue_certificate_id=issue_certificate_id,
|
||||
challenge_type=challenge_type,
|
||||
verify_deploy_path=verify_deploy_path,
|
||||
host_id=host_id,
|
||||
dns_id=dns_id
|
||||
)
|
||||
|
||||
if host_id and verify_deploy_path:
|
||||
IssueCertificateModel.update(
|
||||
challenge_deploy_type_id=ChallengeDeployTypeEnum.SSH,
|
||||
challenge_deploy_id=host_id,
|
||||
deploy_verify_path=verify_deploy_path,
|
||||
challenge_deploy_status=DeployStatusEnum.SUCCESS
|
||||
).where(
|
||||
IssueCertificateModel.id == issue_certificate_id
|
||||
).execute()
|
||||
if dns_id:
|
||||
IssueCertificateModel.update(
|
||||
challenge_deploy_type_id=ChallengeDeployTypeEnum.DNS,
|
||||
challenge_deploy_id=dns_id,
|
||||
challenge_deploy_status=DeployStatusEnum.SUCCESS
|
||||
).where(
|
||||
IssueCertificateModel.id == issue_certificate_id
|
||||
).execute()
|
||||
|
||||
issue_certificate_service.renew_certificate(issue_certificate_id)
|
||||
|
||||
# fix: 过滤通配符的域名
|
||||
lst = [
|
||||
{
|
||||
'domain': domain,
|
||||
'root_domain': domain_util.get_root_domain(domain),
|
||||
'port': 443,
|
||||
'alias': '',
|
||||
'user_id': current_user_id,
|
||||
'group_id': 0,
|
||||
}
|
||||
for domain in issue_certificate_row.domains
|
||||
if validate_util.is_domain(domain)
|
||||
]
|
||||
|
||||
for batch in chunked(lst, 10):
|
||||
DomainModel.insert_many(batch).on_conflict_ignore().execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_certificate_challenges():
|
||||
current_user_id = g.user_id
|
||||
|
||||
issue_certificate_id = request.json['issue_certificate_id']
|
||||
|
||||
# data check
|
||||
issue_certificate_row = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.id == issue_certificate_id,
|
||||
IssueCertificateModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
lst = issue_certificate_service.get_certificate_challenges(issue_certificate_id)
|
||||
|
||||
return {
|
||||
'total': len(lst),
|
||||
'list': lst
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_domain_host():
|
||||
domain = request.json['domain']
|
||||
host = ip_util.get_domain_ip(domain)
|
||||
|
||||
return {
|
||||
'domain': domain,
|
||||
'host': host
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def deploy_verify_file():
|
||||
"""
|
||||
部署验证文件
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
issue_certificate_id = request.json['issue_certificate_id']
|
||||
verify_deploy_path = request.json['verify_deploy_path']
|
||||
challenges = request.json['challenges']
|
||||
host_id = request.json['host_id']
|
||||
|
||||
if not verify_deploy_path.endswith("/"):
|
||||
raise AppException("verify_deploy_path must endswith '/'")
|
||||
|
||||
# data check
|
||||
issue_certificate_row = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.id == issue_certificate_id,
|
||||
IssueCertificateModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
# deploy
|
||||
issue_certificate_service.deploy_verify_file(
|
||||
host_id=host_id,
|
||||
verify_deploy_path=verify_deploy_path,
|
||||
challenges=challenges
|
||||
)
|
||||
|
||||
IssueCertificateModel.update(
|
||||
challenge_deploy_type_id=ChallengeDeployTypeEnum.SSH,
|
||||
challenge_deploy_id=host_id,
|
||||
deploy_verify_path=verify_deploy_path,
|
||||
challenge_deploy_status=DeployStatusEnum.SUCCESS
|
||||
).where(
|
||||
IssueCertificateModel.id == issue_certificate_id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def deploy_certificate_file():
|
||||
"""
|
||||
ssh方式部署证书文件
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
issue_certificate_id = request.json['issue_certificate_id']
|
||||
host_id = request.json['host_id']
|
||||
|
||||
key_deploy_path = request.json['key_deploy_path']
|
||||
pem_deploy_path = request.json['pem_deploy_path']
|
||||
reload_cmd = request.json['reloadcmd']
|
||||
|
||||
# data check
|
||||
issue_certificate_row = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.id == issue_certificate_id,
|
||||
IssueCertificateModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
# host_row = HostModel.get_by_id(host_id)
|
||||
# data check
|
||||
host_row = HostModel.select().where(
|
||||
HostModel.id == host_id,
|
||||
HostModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not host_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
host = host_row.host
|
||||
user = host_row.user
|
||||
password = host_row.password
|
||||
|
||||
# issue_certificate_row = IssueCertificateModel.get_by_id(issue_certificate_id)
|
||||
|
||||
if not issue_certificate_row.ssl_certificate:
|
||||
issue_certificate_service.renew_certificate(issue_certificate_id)
|
||||
# issue_certificate_row = IssueCertificateModel.get_by_id(issue_certificate_id)
|
||||
|
||||
# deploy key
|
||||
|
||||
issue_certificate_row = IssueCertificateModel.get_by_id(issue_certificate_id)
|
||||
|
||||
issue_certificate_service.deploy_certificate_file(
|
||||
host_id=host_id,
|
||||
key_content=issue_certificate_row.ssl_certificate_key,
|
||||
pem_content=issue_certificate_row.ssl_certificate,
|
||||
key_deploy_path=key_deploy_path,
|
||||
pem_deploy_path=pem_deploy_path,
|
||||
reload_cmd=reload_cmd
|
||||
)
|
||||
|
||||
# update only support file verify
|
||||
# if issue_certificate_row.challenge_type == ChallengeType.HTTP01:
|
||||
# is_auto_renew = True
|
||||
# else:
|
||||
# is_auto_renew = False
|
||||
|
||||
IssueCertificateModel.update(
|
||||
deploy_type_id=SSLDeployTypeEnum.SSH,
|
||||
deploy_host_id=host_id,
|
||||
deploy_key_file=key_deploy_path,
|
||||
deploy_fullchain_file=pem_deploy_path,
|
||||
deploy_reloadcmd=reload_cmd,
|
||||
ssl_deploy_status=DeployStatusEnum.SUCCESS,
|
||||
).where(
|
||||
IssueCertificateModel.id == issue_certificate_id
|
||||
).execute()
|
||||
|
||||
# 验证成功后, check_auto_renew
|
||||
issue_certificate_service.check_auto_renew(
|
||||
issue_certificate_id=issue_certificate_id
|
||||
)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def renew_certificate():
|
||||
"""
|
||||
发起申请
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
issue_certificate_id = request.json['issue_certificate_id']
|
||||
|
||||
# data check
|
||||
issue_certificate_row = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.id == issue_certificate_id,
|
||||
IssueCertificateModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
issue_certificate_service.renew_certificate(issue_certificate_id)
|
||||
|
||||
issue_certificate_row = IssueCertificateModel.get_by_id(issue_certificate_id)
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise AppException('数据不存在')
|
||||
|
||||
return issue_certificate_row.to_dict()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_issue_certificate_list():
|
||||
"""
|
||||
发起申请
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
page = request.json.get('page', 1)
|
||||
size = request.json.get('size', 10)
|
||||
keyword = request.json.get('keyword')
|
||||
|
||||
query = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
if keyword:
|
||||
query = query.where(IssueCertificateModel.domain_raw.contains(keyword))
|
||||
|
||||
total = query.count()
|
||||
|
||||
rows = query.order_by(
|
||||
IssueCertificateModel.update_time.desc(),
|
||||
IssueCertificateModel.id.desc()
|
||||
).paginate(page, size)
|
||||
|
||||
lst = []
|
||||
for row in rows:
|
||||
item = model_to_dict(
|
||||
row,
|
||||
extra_attrs=[
|
||||
'domains',
|
||||
'create_time_label',
|
||||
'update_time_label',
|
||||
'start_date',
|
||||
'expire_date',
|
||||
'has_ssl_certificate',
|
||||
'can_auto_renew',
|
||||
],
|
||||
exclude=[
|
||||
IssueCertificateModel.ssl_certificate,
|
||||
IssueCertificateModel.ssl_certificate_key
|
||||
])
|
||||
|
||||
# 关联信息
|
||||
item['challenge_deploy_name'] = '-'
|
||||
if row.challenge_deploy_type_id == ChallengeDeployTypeEnum.SSH:
|
||||
host = HostModel.get_or_none(HostModel.id == row.challenge_deploy_id)
|
||||
if host:
|
||||
item['challenge_deploy_name'] = host.host
|
||||
elif row.challenge_deploy_type_id == ChallengeDeployTypeEnum.DNS:
|
||||
dns = DnsModel.get_or_none(DnsModel.id == row.challenge_deploy_id)
|
||||
if dns:
|
||||
item['challenge_deploy_name'] = dns.name
|
||||
|
||||
item['deploy_host_name'] = '-'
|
||||
if row.deploy_type_id == SSLDeployTypeEnum.SSH:
|
||||
host = HostModel.get_or_none(HostModel.id == row.deploy_host_id)
|
||||
if host:
|
||||
item['deploy_host_name'] = host.host
|
||||
elif row.deploy_type_id in [SSLDeployTypeEnum.OSS, SSLDeployTypeEnum.CDN, SSLDeployTypeEnum.DCDN]:
|
||||
dns = DnsModel.get_or_none(DnsModel.id == row.deploy_host_id)
|
||||
if dns:
|
||||
item['deploy_host_name'] = dns.name
|
||||
|
||||
lst.append(item)
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': total,
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_issue_certificate_by_id():
|
||||
"""
|
||||
获取
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
issue_certificate_id = request.json['issue_certificate_id']
|
||||
|
||||
# check data
|
||||
issue_certificate_row = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.id == issue_certificate_id,
|
||||
IssueCertificateModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
data = issue_certificate_row.to_dict()
|
||||
data['deploy_dns'] = None
|
||||
data['deploy_host'] = None
|
||||
data['cert_deploy_host'] = None
|
||||
data['cert_deploy_dns'] = None
|
||||
|
||||
if issue_certificate_row.challenge_deploy_type_id == ChallengeDeployTypeEnum.SSH:
|
||||
host_row = HostModel.get_or_none(HostModel.id == issue_certificate_row.challenge_deploy_id)
|
||||
data['deploy_host'] = host_row
|
||||
if host_row and host_row.dns_id:
|
||||
data['deploy_dns'] = DnsModel.get_or_none(DnsModel.id == host_row.dns_id)
|
||||
|
||||
elif issue_certificate_row.challenge_deploy_type_id == ChallengeDeployTypeEnum.DNS:
|
||||
data['deploy_dns'] = DnsModel.get_or_none(DnsModel.id == issue_certificate_row.challenge_deploy_id)
|
||||
|
||||
if issue_certificate_row.deploy_type_id == SSLDeployTypeEnum.SSH:
|
||||
data['cert_deploy_host'] = HostModel.get_or_none(HostModel.id == issue_certificate_row.deploy_host_id)
|
||||
elif issue_certificate_row.deploy_type_id in [SSLDeployTypeEnum.OSS, SSLDeployTypeEnum.CDN, SSLDeployTypeEnum.DCDN]:
|
||||
data['cert_deploy_dns'] = DnsModel.get_or_none(DnsModel.id == issue_certificate_row.deploy_host_id)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def delete_issue_certificate_by_id():
|
||||
"""
|
||||
获取
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
issue_certificate_id = request.json['issue_certificate_id']
|
||||
|
||||
# check data
|
||||
issue_certificate_row = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.id == issue_certificate_id,
|
||||
IssueCertificateModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
IssueCertificateModel.delete_by_id(issue_certificate_row.id)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def delete_certificate_by_batch():
|
||||
"""
|
||||
批量删除
|
||||
@since v1.2.16
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
ids = request.json['ids']
|
||||
|
||||
IssueCertificateModel.delete().where(
|
||||
IssueCertificateModel.id.in_(ids),
|
||||
IssueCertificateModel.user_id == current_user_id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def renew_issue_certificate_by_id():
|
||||
"""
|
||||
手动续期SSL证书
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
issue_certificate_id = request.json['issue_certificate_id']
|
||||
|
||||
# check data
|
||||
issue_certificate_row = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.id == issue_certificate_id,
|
||||
IssueCertificateModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
issue_certificate_service.renew_certificate_row(issue_certificate_row)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_allow_commands():
|
||||
"""
|
||||
命令白名单
|
||||
:return:
|
||||
"""
|
||||
return fabric_util.allow_commands
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def notify_web_hook():
|
||||
"""
|
||||
用户调用webhook
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
issue_certificate_id = request.json['issue_certificate_id']
|
||||
url = request.json['url']
|
||||
headers = request.json.get('headers')
|
||||
|
||||
# data check
|
||||
issue_certificate_row = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.id == issue_certificate_id,
|
||||
IssueCertificateModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
ret = issue_certificate_service.deploy_ssl_by_web_hook(
|
||||
issue_certificate_id=issue_certificate_id,
|
||||
url=url,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
# 更新验证信息
|
||||
IssueCertificateModel.update(
|
||||
deploy_type_id=SSLDeployTypeEnum.WEB_HOOK,
|
||||
deploy_url=url,
|
||||
deploy_header_raw=json.dumps(headers or {}),
|
||||
ssl_deploy_status=DeployStatusEnum.SUCCESS
|
||||
).where(
|
||||
IssueCertificateModel.id == issue_certificate_id
|
||||
).execute()
|
||||
|
||||
# 验证成功后, check_auto_renew
|
||||
issue_certificate_service.check_auto_renew(
|
||||
issue_certificate_id=issue_certificate_id
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def deploy_cert_to_oss():
|
||||
"""
|
||||
部署证书到阿里云oss
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
issue_certificate_id = request.json['issue_certificate_id']
|
||||
dns_id = request.json['dns_id']
|
||||
|
||||
# data check
|
||||
issue_certificate_row = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.id == issue_certificate_id,
|
||||
IssueCertificateModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
ret = issue_certificate_service.deploy_cert_to_oss(
|
||||
issue_certificate_id=issue_certificate_id,
|
||||
dns_id=dns_id,
|
||||
)
|
||||
|
||||
# 更新验证信息
|
||||
IssueCertificateModel.update(
|
||||
deploy_type_id=SSLDeployTypeEnum.OSS,
|
||||
deploy_host_id=dns_id,
|
||||
ssl_deploy_status=DeployStatusEnum.SUCCESS
|
||||
).where(
|
||||
IssueCertificateModel.id == issue_certificate_id
|
||||
).execute()
|
||||
|
||||
# 验证成功后, check_auto_renew
|
||||
issue_certificate_service.check_auto_renew(
|
||||
issue_certificate_id=issue_certificate_id
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def deploy_cert_to_cdn():
|
||||
"""
|
||||
部署证书到阿里云cdn
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
issue_certificate_id = request.json['issue_certificate_id']
|
||||
dns_id = request.json['dns_id']
|
||||
|
||||
# data check
|
||||
issue_certificate_row = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.id == issue_certificate_id,
|
||||
IssueCertificateModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
ret = issue_certificate_service.deploy_cert_to_cdn(
|
||||
issue_certificate_id=issue_certificate_id,
|
||||
dns_id=dns_id,
|
||||
)
|
||||
|
||||
# 更新验证信息
|
||||
IssueCertificateModel.update(
|
||||
deploy_type_id=SSLDeployTypeEnum.CDN,
|
||||
deploy_host_id=dns_id,
|
||||
ssl_deploy_status=DeployStatusEnum.SUCCESS
|
||||
).where(
|
||||
IssueCertificateModel.id == issue_certificate_id
|
||||
).execute()
|
||||
|
||||
# 验证成功后, check_auto_renew
|
||||
issue_certificate_service.check_auto_renew(
|
||||
issue_certificate_id=issue_certificate_id
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def deploy_cert_to_dcdn():
|
||||
"""
|
||||
部署证书到阿里云dcdn
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
issue_certificate_id = request.json['issue_certificate_id']
|
||||
dns_id = request.json['dns_id']
|
||||
|
||||
# data check
|
||||
issue_certificate_row = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.id == issue_certificate_id,
|
||||
IssueCertificateModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
ret = issue_certificate_service.deploy_cert_to_dcdn(
|
||||
issue_certificate_id=issue_certificate_id,
|
||||
dns_id=dns_id,
|
||||
)
|
||||
|
||||
# 更新验证信息
|
||||
IssueCertificateModel.update(
|
||||
deploy_type_id=SSLDeployTypeEnum.DCDN,
|
||||
deploy_host_id=dns_id,
|
||||
ssl_deploy_status=DeployStatusEnum.SUCCESS
|
||||
).where(
|
||||
IssueCertificateModel.id == issue_certificate_id
|
||||
).execute()
|
||||
|
||||
# 验证成功后, check_auto_renew
|
||||
issue_certificate_service.check_auto_renew(
|
||||
issue_certificate_id=issue_certificate_id
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def add_dns_domain_record():
|
||||
"""
|
||||
添加dns记录
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
dns_id = request.json['dns_id']
|
||||
issue_certificate_id = request.json['issue_certificate_id']
|
||||
|
||||
# data check
|
||||
issue_certificate_row = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.id == issue_certificate_id,
|
||||
IssueCertificateModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
challenges = issue_certificate_service.get_certificate_challenges(issue_certificate_id)
|
||||
|
||||
# 添加txt记录
|
||||
issue_certificate_service.add_dns_domain_record(
|
||||
dns_id=dns_id,
|
||||
challenges=challenges
|
||||
)
|
||||
|
||||
# 更新验证信息
|
||||
IssueCertificateModel.update(
|
||||
challenge_deploy_type_id=ChallengeDeployTypeEnum.DNS,
|
||||
challenge_deploy_id=dns_id,
|
||||
challenge_deploy_status=DeployStatusEnum.SUCCESS
|
||||
).where(
|
||||
IssueCertificateModel.id == issue_certificate_id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_row_auto_renew():
|
||||
"""
|
||||
修改自动更新字段
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
issue_certificate_id = request.json['issue_certificate_id']
|
||||
is_auto_renew = request.json['is_auto_renew']
|
||||
|
||||
# check data
|
||||
issue_certificate_row = IssueCertificateModel.select().where(
|
||||
IssueCertificateModel.id == issue_certificate_id,
|
||||
IssueCertificateModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not issue_certificate_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
if issue_certificate_row and issue_certificate_row.can_auto_renew:
|
||||
# 更新验证信息
|
||||
IssueCertificateModel.update(
|
||||
is_auto_renew=is_auto_renew
|
||||
).where(
|
||||
IssueCertificateModel.id == issue_certificate_id
|
||||
).execute()
|
||||
else:
|
||||
raise AppException("不支持自动续期")
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_issue_certificate_options():
|
||||
"""
|
||||
获取常量
|
||||
:return:
|
||||
"""
|
||||
return {
|
||||
'KEY_TYPE_OPTIONS': KEY_TYPE_OPTIONS,
|
||||
'DIRECTORY_URL_OPTIONS': DIRECTORY_URL_OPTIONS
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
log_async_task_api.py
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals, absolute_import, division
|
||||
from flask import request, g
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.model.log_async_task_model import AsyncTaskModel
|
||||
from domain_admin.model.log_operation_model import LogOperationModel
|
||||
from domain_admin.service import common_service, auth_service
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.ADMIN)
|
||||
def get_async_task_log_list():
|
||||
"""
|
||||
获取操作日志列表
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
page = request.json.get('page', 1)
|
||||
size = request.json.get('size', 10)
|
||||
|
||||
query = AsyncTaskModel.select()
|
||||
|
||||
total = query.count()
|
||||
|
||||
lst = []
|
||||
|
||||
if total > 0:
|
||||
rows = query.order_by(
|
||||
AsyncTaskModel.create_time.desc(),
|
||||
AsyncTaskModel.id.desc(),
|
||||
).paginate(page, size)
|
||||
|
||||
lst = [model_to_dict(model=row, extra_attrs=[
|
||||
'total_time',
|
||||
'total_time_label',
|
||||
'create_time_label',
|
||||
]) for row in rows]
|
||||
|
||||
common_service.load_user_name(lst)
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': total
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.ADMIN)
|
||||
def clear_async_task_log_list():
|
||||
"""
|
||||
清空日志
|
||||
:return:
|
||||
"""
|
||||
AsyncTaskModel.truncate_table()
|
||||
@@ -0,0 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@File : log_monitor_api.py
|
||||
@Date : 2024-01-28
|
||||
@Author : Peng Shiyu
|
||||
"""
|
||||
from flask import g, request
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.model.log_monitor_model import LogMonitorModel
|
||||
from domain_admin.service import auth_service
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_log_monitor_list():
|
||||
"""
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_user_id = g.user_id
|
||||
|
||||
page = request.json.get('page', 1)
|
||||
size = request.json.get('size', 10)
|
||||
monitor_id = request.json.get('monitor_id')
|
||||
|
||||
query = LogMonitorModel.select()
|
||||
if monitor_id:
|
||||
query = query.where(LogMonitorModel.monitor_id == monitor_id)
|
||||
|
||||
total = query.count()
|
||||
|
||||
lst = []
|
||||
|
||||
if total > 0:
|
||||
rows = query.order_by(LogMonitorModel.id.desc()).paginate(page, size)
|
||||
|
||||
lst = [
|
||||
model_to_dict(
|
||||
model=row,
|
||||
extra_attrs=[
|
||||
'create_time_label',
|
||||
'update_time_label',
|
||||
'total_time_label',
|
||||
'total_microsecond_time',
|
||||
]
|
||||
) for row in rows]
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': total
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def clear_log_monitor():
|
||||
"""
|
||||
:return:
|
||||
"""
|
||||
monitor_id = request.json.get('monitor_id')
|
||||
|
||||
LogMonitorModel.delete().where(
|
||||
LogMonitorModel.monitor_id == monitor_id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def clear_all_log_monitor():
|
||||
"""
|
||||
:return:
|
||||
"""
|
||||
LogMonitorModel.truncate_table()
|
||||
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
log_operation_api.py
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals, absolute_import, division
|
||||
from flask import request, g
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.model.log_operation_model import LogOperationModel
|
||||
from domain_admin.service import common_service, auth_service
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.ADMIN)
|
||||
def get_operation_log_list():
|
||||
"""
|
||||
获取操作日志列表
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
page = request.json.get('page', 1)
|
||||
size = request.json.get('size', 10)
|
||||
|
||||
query = LogOperationModel.select()
|
||||
|
||||
total = query.count()
|
||||
|
||||
lst = []
|
||||
if total > 0:
|
||||
rows = query.order_by(
|
||||
LogOperationModel.create_time.desc(),
|
||||
LogOperationModel.id.desc(),
|
||||
).paginate(page, size)
|
||||
|
||||
lst = [model_to_dict(model=row, extra_attrs=[
|
||||
'type_label',
|
||||
'create_time_label',
|
||||
]) for row in rows]
|
||||
|
||||
common_service.load_user_name(lst)
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': total
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.ADMIN)
|
||||
def clear_log_operation_list():
|
||||
"""
|
||||
清空日志
|
||||
:return:
|
||||
"""
|
||||
LogOperationModel.truncate_table()
|
||||
@@ -0,0 +1,56 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
log_scheduler_api.py
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals, absolute_import, division
|
||||
from flask import request, g
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.model.log_scheduler_model import LogSchedulerModel
|
||||
from domain_admin.service import auth_service
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.ADMIN)
|
||||
def get_log_scheduler_list():
|
||||
"""
|
||||
获取调度日志列表
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
page = request.json.get('page', 1)
|
||||
size = request.json.get('size', 10)
|
||||
|
||||
query = LogSchedulerModel.select()
|
||||
|
||||
total = query.count()
|
||||
|
||||
lst = []
|
||||
if total > 0:
|
||||
rows = query.order_by(
|
||||
LogSchedulerModel.create_time.desc(),
|
||||
LogSchedulerModel.id.desc(),
|
||||
).paginate(page, size)
|
||||
|
||||
lst = list(map(lambda m: model_to_dict(
|
||||
model=m,
|
||||
extra_attrs=[
|
||||
'total_time',
|
||||
'total_time_label',
|
||||
]
|
||||
), rows))
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': total
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.ADMIN)
|
||||
def clear_log_scheduler_list():
|
||||
"""
|
||||
清空日志
|
||||
:return:
|
||||
"""
|
||||
LogSchedulerModel.truncate_table()
|
||||
@@ -0,0 +1,305 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@File : monitor_api.py
|
||||
@Date : 2024-01-28
|
||||
@Author : Peng Shiyu
|
||||
"""
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from flask import request, g
|
||||
from peewee import SQL, fn
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from domain_admin.enums.monitor_type_enum import MonitorTypeEnum
|
||||
from domain_admin.enums.operation_enum import OperationEnum
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.enums.time_unit_enum import TimeUnitEnum
|
||||
from domain_admin.model.log_monitor_model import LogMonitorModel
|
||||
from domain_admin.model.monitor_model import MonitorModel
|
||||
from domain_admin.service import monitor_service, file_service, async_task_service, operation_service, auth_service
|
||||
from domain_admin.service.scheduler_service import scheduler_main
|
||||
from domain_admin.utils.flask_ext.app_exception import DataNotFoundAppException
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def add_monitor():
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
title = request.json['title']
|
||||
monitor_type = request.json['monitor_type']
|
||||
allow_error_count = request.json.get('allow_error_count') or 0
|
||||
content = request.json['content']
|
||||
interval = request.json['interval']
|
||||
interval_unit = request.json.get('interval_unit', TimeUnitEnum.Minute)
|
||||
|
||||
monitor_row = MonitorModel.create(
|
||||
user_id=current_user_id,
|
||||
title=title,
|
||||
allow_error_count=allow_error_count,
|
||||
monitor_type=monitor_type,
|
||||
content=json.dumps(content),
|
||||
interval=interval,
|
||||
interval_unit=interval_unit
|
||||
)
|
||||
|
||||
scheduler_main.run_one_monitor_task(MonitorModel.get_by_id(monitor_row.id))
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_monitor_by_id():
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
monitor_id = request.json['monitor_id']
|
||||
title = request.json['title']
|
||||
content = request.json['content']
|
||||
interval = request.json['interval']
|
||||
allow_error_count = request.json.get('allow_error_count') or 0
|
||||
interval_unit = request.json.get('interval_unit', TimeUnitEnum.Minute)
|
||||
|
||||
monitor_row = MonitorModel.select().where(
|
||||
MonitorModel.id == monitor_id,
|
||||
MonitorModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not monitor_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
MonitorModel.update(
|
||||
title=title,
|
||||
content=json.dumps(content),
|
||||
interval=interval,
|
||||
interval_unit=interval_unit,
|
||||
allow_error_count=allow_error_count,
|
||||
).where(
|
||||
MonitorModel.id == monitor_id
|
||||
).execute()
|
||||
|
||||
scheduler_main.run_one_monitor_task(MonitorModel.get_by_id(monitor_id))
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def update_monitor_active():
|
||||
"""
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
monitor_id = request.json['monitor_id']
|
||||
is_active = request.json['is_active']
|
||||
|
||||
if is_active:
|
||||
next_run_time = datetime.now()
|
||||
else:
|
||||
next_run_time = None
|
||||
|
||||
# data check
|
||||
monitor_row = MonitorModel.select().where(
|
||||
MonitorModel.id == monitor_id,
|
||||
MonitorModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not monitor_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
MonitorModel.update(
|
||||
is_active=is_active,
|
||||
next_run_time=next_run_time
|
||||
).where(
|
||||
MonitorModel.id == monitor_row.id
|
||||
).execute()
|
||||
|
||||
if is_active:
|
||||
scheduler_main.run_one_monitor_task(MonitorModel.get_by_id(monitor_id))
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def remove_monitor_by_id():
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
monitor_id = request.json['monitor_id']
|
||||
|
||||
# data check
|
||||
monitor_row = MonitorModel.select().where(
|
||||
MonitorModel.id == monitor_id,
|
||||
MonitorModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not monitor_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
MonitorModel.delete_by_id(monitor_row.id)
|
||||
|
||||
# remote log
|
||||
LogMonitorModel.delete().where(
|
||||
LogMonitorModel.monitor_id == monitor_row.id,
|
||||
LogMonitorModel.monitor_type == MonitorTypeEnum.HTTP
|
||||
).execute()
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=MonitorModel,
|
||||
operation_type_id=OperationEnum.BATCH_DELETE,
|
||||
primary_key='monitor_ids'
|
||||
)
|
||||
def delete_monitor_by_ids():
|
||||
"""
|
||||
批量删除
|
||||
@since v1.6.12
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
monitor_ids = request.json['monitor_ids']
|
||||
|
||||
MonitorModel.delete().where(
|
||||
MonitorModel.id.in_(monitor_ids),
|
||||
MonitorModel.user_id == current_user_id
|
||||
).execute()
|
||||
|
||||
# remote log
|
||||
LogMonitorModel.delete().where(
|
||||
LogMonitorModel.monitor_id.in_(monitor_ids),
|
||||
LogMonitorModel.monitor_type == MonitorTypeEnum.HTTP
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_monitor_by_id():
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
monitor_id = request.json['monitor_id']
|
||||
|
||||
# data check
|
||||
monitor_row = MonitorModel.select().where(
|
||||
MonitorModel.id == monitor_id,
|
||||
MonitorModel.user_id == current_user_id
|
||||
).first()
|
||||
|
||||
if not monitor_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
return monitor_row.to_dict()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_monitor_list():
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
page = request.json.get('page', 1)
|
||||
size = request.json.get('size', 10)
|
||||
order_prop = request.json.get('order_prop') or 'create_time'
|
||||
order_type = request.json.get('order_type') or 'desc'
|
||||
keyword = request.json.get('keyword')
|
||||
status = request.json.get('status')
|
||||
|
||||
query = MonitorModel.select().where(
|
||||
MonitorModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
if keyword:
|
||||
query = query.where(MonitorModel.title.contains(keyword))
|
||||
|
||||
if isinstance(status, int):
|
||||
query = query.where(MonitorModel.status == status)
|
||||
|
||||
total = query.count()
|
||||
|
||||
lst = []
|
||||
|
||||
if total > 0:
|
||||
ordering = [
|
||||
SQL(f"`{order_prop}` {order_type}"),
|
||||
MonitorModel.id.desc()
|
||||
]
|
||||
|
||||
rows = query.order_by(*ordering).paginate(page, size)
|
||||
|
||||
lst = [row.to_dict() for row in rows]
|
||||
|
||||
monitor_service.load_monitor_log_count(lst)
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': total
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def export_monitor_file():
|
||||
"""
|
||||
导出监控文件
|
||||
csv格式
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
keyword = request.json.get('keyword')
|
||||
status = request.json.get('status')
|
||||
ext = request.json.get('ext', 'csv')
|
||||
|
||||
order_prop = request.json.get('order_prop') or 'create_time'
|
||||
order_type = request.json.get('order_type') or 'desc'
|
||||
|
||||
params = {
|
||||
'keyword': keyword,
|
||||
'status': status,
|
||||
'user_id': current_user_id,
|
||||
}
|
||||
|
||||
query = monitor_service.get_monitor_list_query(**params)
|
||||
|
||||
ordering = [
|
||||
SQL(f"`{order_prop}` {order_type}"),
|
||||
MonitorModel.id.desc()
|
||||
]
|
||||
|
||||
rows = query.order_by(*ordering)
|
||||
|
||||
lst = [row.to_dict() for row in rows]
|
||||
|
||||
filename = monitor_service.export_monitor_to_file(rows=lst, ext=ext)
|
||||
|
||||
return {
|
||||
'name': filename,
|
||||
'url': file_service.resolve_temp_url(filename)
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def import_monitor_from_file():
|
||||
"""
|
||||
从文件导入域名
|
||||
支持 xlsx 和 csv格式
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
update_file = request.files.get('file')
|
||||
|
||||
filename = file_service.save_temp_file(update_file)
|
||||
|
||||
# 导入数据
|
||||
monitor_service.import_monitor_from_file(filename, current_user_id)
|
||||
|
||||
# 异步查询
|
||||
monitor_service.run_init_monitor_task_async(user_id=current_user_id)
|
||||
@@ -0,0 +1,356 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@File : notify_api.py
|
||||
@Date : 2022-10-14
|
||||
@Author : Peng Shiyu
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals, absolute_import, division
|
||||
import json
|
||||
import random
|
||||
import traceback
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from flask import request, g
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from domain_admin.enums.operation_enum import OperationEnum
|
||||
from domain_admin.enums.role_enum import RoleEnum
|
||||
from domain_admin.enums.status_enum import StatusEnum
|
||||
from domain_admin.log import logger
|
||||
from domain_admin.model.group_model import GroupModel
|
||||
from domain_admin.model.notify_model import NotifyModel
|
||||
from domain_admin.service import notify_service, operation_service, auth_service
|
||||
from domain_admin.utils import datetime_util
|
||||
from domain_admin.utils.flask_ext.app_exception import DataNotFoundAppException
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_notify_list_of_user():
|
||||
"""
|
||||
获取用户通知配置
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
page = request.json.get('page', 1)
|
||||
size = request.json.get('size', 10)
|
||||
order_prop = request.json.get('order_prop') or 'create_time'
|
||||
order_type = request.json.get('order_type') or 'descending'
|
||||
|
||||
query = NotifyModel.select().where(
|
||||
NotifyModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
|
||||
lst = []
|
||||
|
||||
if total > 0:
|
||||
|
||||
ordering = []
|
||||
|
||||
# order by event_id
|
||||
if order_prop == 'event_id':
|
||||
if order_type == 'descending':
|
||||
ordering.append(NotifyModel.event_id.desc())
|
||||
else:
|
||||
ordering.append(NotifyModel.event_id.asc())
|
||||
|
||||
# order by type_id
|
||||
if order_prop == 'type_id':
|
||||
if order_type == 'descending':
|
||||
ordering.append(NotifyModel.type_id.desc())
|
||||
else:
|
||||
ordering.append(NotifyModel.type_id.asc())
|
||||
|
||||
# order by expire_days
|
||||
if order_prop == 'expire_days':
|
||||
if order_type == 'descending':
|
||||
ordering.append(NotifyModel.expire_days.desc())
|
||||
else:
|
||||
ordering.append(NotifyModel.expire_days.asc())
|
||||
|
||||
# order by status
|
||||
if order_prop == 'status':
|
||||
if order_type == 'descending':
|
||||
ordering.append(NotifyModel.status.desc())
|
||||
else:
|
||||
ordering.append(NotifyModel.status.asc())
|
||||
|
||||
ordering.append(NotifyModel.id.desc())
|
||||
|
||||
rows = query.order_by(*ordering).paginate(page, size)
|
||||
|
||||
lst = list(map(lambda m: model_to_dict(
|
||||
model=m,
|
||||
exclude=[NotifyModel.value_raw],
|
||||
extra_attrs=[
|
||||
'value',
|
||||
'groups',
|
||||
]
|
||||
), rows))
|
||||
|
||||
group_ids = []
|
||||
for row in lst:
|
||||
group_ids.extend(row['groups'])
|
||||
|
||||
group_rows = GroupModel.select().where(
|
||||
GroupModel.id.in_(list(set(group_ids)))
|
||||
)
|
||||
|
||||
group_dict = {
|
||||
row.id: row
|
||||
for row in group_rows
|
||||
}
|
||||
|
||||
for row in lst:
|
||||
group_list = []
|
||||
for group in row['groups']:
|
||||
# bugfix: 如果用户删除分组后,分组数据不存在会出现null问题
|
||||
if group in group_dict:
|
||||
group_list.append(group_dict.get(group))
|
||||
|
||||
row['group_list'] = group_list
|
||||
|
||||
return {
|
||||
'list': lst,
|
||||
'total': total
|
||||
}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=NotifyModel,
|
||||
operation_type_id=OperationEnum.CREATE,
|
||||
primary_key='id'
|
||||
)
|
||||
def add_notify():
|
||||
"""
|
||||
添加用户通知配置
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
type_id = request.json['type_id']
|
||||
event_id = request.json['event_id']
|
||||
value = request.json['value']
|
||||
expire_days = request.json['expire_days']
|
||||
groups = request.json.get('groups') or []
|
||||
comment = request.json.get('comment') or ''
|
||||
|
||||
value_raw = json.dumps(value, ensure_ascii=False)
|
||||
groups_raw = json.dumps(groups, ensure_ascii=False)
|
||||
|
||||
row = NotifyModel.create(
|
||||
user_id=current_user_id,
|
||||
event_id=event_id,
|
||||
type_id=type_id,
|
||||
value_raw=value_raw,
|
||||
groups_raw=groups_raw,
|
||||
expire_days=expire_days,
|
||||
comment=comment,
|
||||
status=StatusEnum.Enabled
|
||||
)
|
||||
|
||||
return {'id': row.id}
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=NotifyModel,
|
||||
operation_type_id=OperationEnum.DELETE,
|
||||
primary_key='notify_id'
|
||||
)
|
||||
def delete_notify_by_id():
|
||||
"""
|
||||
删除用户通知配置
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
notify_id = request.json['notify_id']
|
||||
|
||||
# data check
|
||||
notify_row = NotifyModel.select().where(
|
||||
NotifyModel.id == notify_id,
|
||||
NotifyModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not notify_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
NotifyModel.delete_by_id(notify_row.id)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def get_notify_by_id():
|
||||
"""
|
||||
获取用户通知配置
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
notify_id = request.json['notify_id']
|
||||
|
||||
# data check
|
||||
notify_row = NotifyModel.select().where(
|
||||
NotifyModel.id == notify_id,
|
||||
NotifyModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not notify_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
data = model_to_dict(
|
||||
model=notify_row,
|
||||
exclude=[NotifyModel.value_raw],
|
||||
extra_attrs=[
|
||||
'value',
|
||||
'groups',
|
||||
])
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=NotifyModel,
|
||||
operation_type_id=OperationEnum.UPDATE,
|
||||
primary_key='notify_id'
|
||||
)
|
||||
def update_notify_by_id():
|
||||
"""
|
||||
更新用户通知配置
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
notify_id = request.json['notify_id']
|
||||
|
||||
event_id = request.json['event_id']
|
||||
value = request.json['value']
|
||||
|
||||
expire_days = request.json['expire_days']
|
||||
comment = request.json.get('comment') or ''
|
||||
groups = request.json.get('groups') or []
|
||||
|
||||
value_raw = json.dumps(value, ensure_ascii=False)
|
||||
groups_raw = json.dumps(groups, ensure_ascii=False)
|
||||
|
||||
# data check
|
||||
notify_row = NotifyModel.select().where(
|
||||
NotifyModel.id == notify_id,
|
||||
NotifyModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not notify_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
NotifyModel.update(
|
||||
event_id=event_id,
|
||||
value_raw=value_raw,
|
||||
groups_raw=groups_raw,
|
||||
expire_days=expire_days,
|
||||
comment=comment,
|
||||
).where(
|
||||
NotifyModel.id == notify_row.id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
@operation_service.operation_log_decorator(
|
||||
model=NotifyModel,
|
||||
operation_type_id=OperationEnum.UPDATE,
|
||||
primary_key='notify_id'
|
||||
)
|
||||
def update_notify_status_by_id():
|
||||
"""
|
||||
更新用户通知配置状态
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
|
||||
notify_id = request.json['notify_id']
|
||||
|
||||
status = request.json['status']
|
||||
|
||||
# data check
|
||||
notify_row = NotifyModel.select().where(
|
||||
NotifyModel.id == notify_id,
|
||||
NotifyModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not notify_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
NotifyModel.update(
|
||||
status=status,
|
||||
).where(
|
||||
NotifyModel.id == notify_row.id
|
||||
).execute()
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def handle_test_notify_by_id():
|
||||
"""
|
||||
测试通知配置
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
notify_id = request.json['notify_id']
|
||||
|
||||
# data check
|
||||
notify_row = NotifyModel.select().where(
|
||||
NotifyModel.id == notify_id,
|
||||
NotifyModel.user_id == current_user_id,
|
||||
).first()
|
||||
|
||||
if not notify_row:
|
||||
raise DataNotFoundAppException()
|
||||
|
||||
# days = random.randint(1, 365)
|
||||
# start_date = datetime.now()
|
||||
# expire_date = start_date + timedelta(days=days)
|
||||
|
||||
# lst = [
|
||||
# {
|
||||
# 'domain': 'www.demo.com',
|
||||
# 'start_date': datetime_util.format_date(start_date),
|
||||
# 'expire_date': datetime_util.format_date(expire_date),
|
||||
# 'expire_days': days
|
||||
# }
|
||||
# ]
|
||||
|
||||
# return notify_service.notify_user(notify_row, lst)
|
||||
return notify_service.notify_user_about_some_event(notify_row)
|
||||
|
||||
|
||||
@auth_service.permission(role=RoleEnum.USER)
|
||||
def handle_notify_by_event_id():
|
||||
"""
|
||||
触发用户的某一类通知操作
|
||||
:return:
|
||||
"""
|
||||
current_user_id = g.user_id
|
||||
event_id = request.json['event_id']
|
||||
|
||||
rows = NotifyModel.select().where(
|
||||
NotifyModel.event_id == event_id,
|
||||
NotifyModel.user_id == current_user_id
|
||||
)
|
||||
|
||||
total = 0
|
||||
success = 0
|
||||
|
||||
for row in rows:
|
||||
try:
|
||||
notify_service.notify_user_about_some_event(row)
|
||||
success = success + 1
|
||||
except:
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
total = total + 1
|
||||
|
||||
return {
|
||||
'total': total,
|
||||
'success': success
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user