Showing preview only (6,735K chars total). Download the full file or copy to clipboard to get everything.
Repository: tongchengbin/ocean_ctf
Branch: main
Commit: e1848760aa40
Files: 189
Total size: 6.2 MB
Directory structure:
gitextract_upuwzr0s/
├── .env_example
├── .flake8
├── .github/
│ └── workflows/
│ └── main.yml
├── .gitignore
├── .pylintrc
├── LICENSE
├── Makefile
├── README.md
├── app/
│ ├── __init__.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── admin/
│ │ │ ├── __init__.py
│ │ │ ├── ctf.py
│ │ │ ├── docker.py
│ │ │ ├── schemas/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── ctf.py
│ │ │ │ └── docker.py
│ │ │ ├── system.py
│ │ │ └── vulnerability.py
│ │ ├── health.py
│ │ ├── player/
│ │ │ ├── __init__.py
│ │ │ ├── views.py
│ │ │ ├── vulnerability.py
│ │ │ └── ws.py
│ │ └── route.py
│ ├── core/
│ │ ├── api.py
│ │ ├── command.py
│ │ ├── const.py
│ │ ├── decorators.py
│ │ ├── error_handlers.py
│ │ ├── exceptions.py
│ │ ├── flask_celery.py
│ │ ├── middlewares.py
│ │ └── tools.py
│ ├── extensions.py
│ ├── models/
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── ctf.py
│ │ ├── docker.py
│ │ └── user.py
│ ├── services/
│ │ ├── __init__.py
│ │ ├── docker.py
│ │ ├── player.py
│ │ └── system.py
│ ├── tasks/
│ │ ├── __init__.py
│ │ ├── ctf.py
│ │ ├── docker.py
│ │ ├── player.py
│ │ ├── system.py
│ │ └── vulnerability.py
│ ├── utils/
│ │ ├── security.py
│ │ ├── tools.py
│ │ └── validator.py
│ └── worker.py
├── config/
│ └── config.py
├── docker-compose.yml
├── install/
│ ├── config/
│ │ └── supervisord/
│ │ └── ocean.ini
│ ├── db_init/
│ │ └── ocean.sql
│ ├── docker/
│ │ ├── ocean_web.Dockerfile
│ │ └── wait-for-it.sh
│ ├── frontend/
│ │ └── dist/
│ │ ├── assets/
│ │ │ ├── detail-Beu8V9iA.js
│ │ │ ├── detail-CwA0i7lp.css
│ │ │ ├── index-2Kde3L7C.js
│ │ │ ├── index-BNtu7jJ7.js
│ │ │ ├── index-CUMyn3nz.js
│ │ │ ├── index-Cn092HV2.css
│ │ │ ├── index-CocFE3Kg.css
│ │ │ ├── index-DAXaKKX5.js
│ │ │ ├── index-DSpYhb0F.css
│ │ │ ├── index-DTf6UOgP.js
│ │ │ ├── index-D_63FOFH.js
│ │ │ ├── index-Dst5s0sm.css
│ │ │ ├── index-IBfy7H4-.css
│ │ │ ├── index-M3X3VVYJ.css
│ │ │ ├── index-PyJu_6A4.js
│ │ │ ├── index-qmyYS7Pw.css
│ │ │ ├── register-C1gwrr0Z.css
│ │ │ └── register-CwwKsjmL.js
│ │ └── index.html
│ ├── manager/
│ │ └── dist/
│ │ ├── index.html
│ │ ├── serverConfig.json
│ │ ├── static/
│ │ │ ├── css/
│ │ │ │ ├── LineChart-CAiRPB6S.css
│ │ │ │ ├── PanelGroup-TcT6nE-z.css
│ │ │ │ ├── UserCard-D6QtR3QQ.css
│ │ │ │ ├── addImage-qmRBmbjD.css
│ │ │ │ ├── answer-C7eDAiIE.css
│ │ │ │ ├── config-CZAQQi9l.css
│ │ │ │ ├── config-W0rb9i92.css
│ │ │ │ ├── container-C6zUPM_j.css
│ │ │ │ ├── container-DtRsVCeN.css
│ │ │ │ ├── editResources-Df9j_O02.css
│ │ │ │ ├── frame-C56j9Uki.css
│ │ │ │ ├── host-C9ss6-xf.css
│ │ │ │ ├── hostDetail-DJ2SmXrv.css
│ │ │ │ ├── imageDetail-LDv3EwHR.css
│ │ │ │ ├── images-Tpvwj_u0.css
│ │ │ │ ├── index-B2cknHNH.css
│ │ │ │ ├── index-B5rb6rib.css
│ │ │ │ ├── index-BOEMDA_E.css
│ │ │ │ ├── index-BcTahPvV.css
│ │ │ │ ├── index-C4pPArRS.css
│ │ │ │ ├── index-CQHzbCBB.css
│ │ │ │ ├── index-ChP2PXZC.css
│ │ │ │ ├── index-DNZyiNmE.css
│ │ │ │ ├── index-m7pPGTca.css
│ │ │ │ ├── operator-C9HCk2lz.css
│ │ │ │ ├── question-qN-CSJNX.css
│ │ │ │ ├── questionItem-4bFCFt94.css
│ │ │ │ ├── resource-CM1VvOIh.css
│ │ │ │ ├── resourceForm-p8bnP91q.css
│ │ │ │ ├── resourceItem-DzDzzvBa.css
│ │ │ │ ├── resources-BH1s_vA0.css
│ │ │ │ ├── resources-Ct3K675F.css
│ │ │ │ ├── sysInfo-BohfULNO.css
│ │ │ │ └── user-DerJW7Bd.css
│ │ │ └── js/
│ │ │ ├── 403-DKEdQNMH.js
│ │ │ ├── 404-lPQWHYlH.js
│ │ │ ├── 500-Bi6ZUpQv.js
│ │ │ ├── Account-Bb8H_MKm.js
│ │ │ ├── LineChart-CyBl2sMc.js
│ │ │ ├── PanelGroup-QRQhV6r1.js
│ │ │ ├── RestPass-mWtPbTNp.js
│ │ │ ├── Timeline-CGNLPXAJ.js
│ │ │ ├── Todo-B8eVum4J.js
│ │ │ ├── UserCard-D9xOWVMi.js
│ │ │ ├── addHost-BVYUJp7N.js
│ │ │ ├── addImage-Cex8BYxg.js
│ │ │ ├── addUser-C2q5OAyK.js
│ │ │ ├── admins-BKHsnYfX.js
│ │ │ ├── answer-PdY72vFF.js
│ │ │ ├── audit-DbCqE0TT.js
│ │ │ ├── back_top-B8RoSTAY.js
│ │ │ ├── config-BerqnP1C.js
│ │ │ ├── config-DU8nLchD.js
│ │ │ ├── container--f66ltxq.js
│ │ │ ├── container-62xqkrS2.js
│ │ │ ├── dark-Dv7Dbtg0.js
│ │ │ ├── docker_resource_sync-Dxn6rGPc.js
│ │ │ ├── editAdmin-xd3VaCzj.js
│ │ │ ├── editResources-jJ-omP3I.js
│ │ │ ├── editRole-Dqlx3Hxu.js
│ │ │ ├── frame-CRmNRX0K.js
│ │ │ ├── hooks-CzcwFjli.js
│ │ │ ├── host-g1EgSM4r.js
│ │ │ ├── hostDetail-DeVpx4OE.js
│ │ │ ├── imageDetail-quKI1yqP.js
│ │ │ ├── images-BUkzOY-1.js
│ │ │ ├── index-BLCelF29.js
│ │ │ ├── index-B_eHX4aD.js
│ │ │ ├── index-BqIwwt-c.js
│ │ │ ├── index-BzD9KVPC.js
│ │ │ ├── index-Cj3Ji7Ce.js
│ │ │ ├── index-Cuc6Bha6.js
│ │ │ ├── index-D4veOIBM.js
│ │ │ ├── index-DGss3hGv.js
│ │ │ ├── index-DqeA2Szn.js
│ │ │ ├── index-Lj5hgGY3.js
│ │ │ ├── index-xEKAsWxN.js
│ │ │ ├── notifications-D8ciMJpU.js
│ │ │ ├── operator-BiRoUc-y.js
│ │ │ ├── question-DPBmlVI1.js
│ │ │ ├── questionItem-BnT1RndV.js
│ │ │ ├── questionTypeManage-C4aIrDS_.js
│ │ │ ├── redirect-BNWD9FK6.js
│ │ │ ├── resource-B26noqeQ.js
│ │ │ ├── resourceForm-UYjwLmkY.js
│ │ │ ├── resourceItem-BIlM6slQ.js
│ │ │ ├── resources-D66Xg1B4.js
│ │ │ ├── resources-eAiTMxgh.js
│ │ │ ├── role-Cj_rRiT0.js
│ │ │ ├── runner-C4c3DT0z.js
│ │ │ ├── sysInfo-BJzi9t0G.js
│ │ │ └── user-BBi65gzK.js
│ │ └── version.json
│ └── nginx.conf
├── main.py
├── pyproject.toml
├── requirements/
│ ├── base.txt
│ ├── dev.txt
│ ├── prod.txt
│ └── test.txt
├── requirements.txt
├── run.sh
├── tests/
│ ├── conftest.py
│ ├── test_docker.py
│ ├── test_health_api.py
│ ├── test_tasks.py
│ └── test_validator.py
├── upload/
│ └── .gitignore
└── wsgi.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .env_example
================================================
HTTP_PORT=80
# 数据库配置
DB_HOST=db
DB_PORT=3306
DB_USER=root
DB_PASSWORD=123456
DB_NAME=ocean
# Redis配置
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=123456
# 应用配置
FLASK_APP=main.py
FLASK_ENV=production
SECRET_KEY=your_secure_secret_key
DEBUG=False
# 时区设置
TZ=Asia/Shanghai
# 其他配置
LOG_LEVEL=INFO
ALLOWED_HOSTS=*
# Docker配置
DOCKER_REGISTRY=
DOCKER_USERNAME=
DOCKER_PASSWORD=
# 邮件配置(可选)
MAIL_SERVER=
MAIL_PORT=
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_USE_TLS=
MAIL_USE_SSL=
================================================
FILE: .flake8
================================================
[flake8]
max-line-length = 100
exclude = .git,__pycache__,build,dist,.env,venv,env
ignore = E203, W503, E501
# E203: whitespace before ':' (black formats differently)
# W503: line break before binary operator (black formats differently)
================================================
FILE: .github/workflows/main.yml
================================================
name: Ocean CTF CI/CD
on:
push:
branches: [main, test-cicd]
pull_request:
branches: [main, test-cicd]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Compose
uses: docker/setup-buildx-action@v2
- name: Create .env file (exactly like user would do)
run: |
cp .env_example .env
- name: Run docker compose (like user deployment)
run: |
# 显示当前配置
echo "Current environment configuration:"
cat .env
# 启动服务(与用户部署步骤相同)
docker compose build
echo "Build completed"
docker compose up -d
# 等待服务准备就绪
echo "Waiting for services to be ready..."
sleep 30 # 增加等待时间到 30 秒
# 检查服务状态
docker compose ps
# 检查服务状态
docker compose logs --tail 100
- name: Run tests
run: |
# 在 Docker 容器内运行测试
docker compose exec -T web sh -c "pip install -r requirements/test.txt && pytest tests/ -v"
# 检查测试运行状态
echo "Tests completed with exit code: $?"
================================================
FILE: .gitignore
================================================
# IDE
/.idea/
.vscode
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# 日志和临时文件
*.log
*.pid
celerybeat-schedule
*.db
*.pyc
*.py~
# 环境变量文件 - 不提交实际的环境变量文件
.env
.env.*
!.env.example
!.env.*.example
# 上传目录
/upload/*
# 本地配置
config/local_config.py
# 虚拟环境
venv/
env/
.venv/
.env/
# 其他
.DS_Store
*.isorted
================================================
FILE: .pylintrc
================================================
[MASTER]
# Python version
py-version = 3.9
# Ignore specific directories
ignore=.git,__pycache__,venv,env
# Add files or directories matching the regex patterns to the ignore-list
ignore-patterns=
# Specify a score threshold to be exceeded before program exits with error
fail-under=7.0
[MESSAGES CONTROL]
# Disable specific messages, warnings, errors or refactors
disable=
C0111, # missing-docstring
C0103, # invalid-name
C0303, # trailing-whitespace
C0330, # bad-continuation
C1801, # len-as-condition
W0511, # fixme
W1202, # logging-format-interpolation
W0212, # protected-access
W0703, # broad-except
R0903, # too-few-public-methods
R0913, # too-many-arguments
R0914, # too-many-locals
R0915, # too-many-statements
[FORMAT]
# Maximum number of characters on a single line
max-line-length=100
[DESIGN]
# Maximum number of arguments for function / method
max-args=8
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901)
max-parents=7
# Maximum number of attributes for a class (see R0902)
max-attributes=11
# Minimum number of public methods for a class (see R0903)
min-public-methods=1
# Maximum number of public methods for a class (see R0904)
max-public-methods=20
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 tongcb
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.
================================================
FILE: Makefile
================================================
.PHONY: help lint lint-flake8 format format-black format-isort install-dev install-prod install-test clean test docker-dev docker-build docker-shell docker-test setup-dev setup-test
# Default target
help:
@echo "Available commands:"
@echo " make help - Show this help message"
@echo " make lint - Run all linters (flake8)"
@echo " make lint-flake8 - Run flake8 linter"
@echo " make format - Run all formatters (black, isort)"
@echo " make format-black - Run black formatter"
@echo " make format-isort - Run isort formatter"
@echo " make install-dev - Install development dependencies"
@echo " make install-prod - Install production dependencies"
@echo " make install-test - Install test dependencies"
@echo " make clean - Remove Python cache files"
@echo " make test - Run tests"
@echo " make docker-dev - Start development environment with Docker"
@echo " make docker-build - Build Docker images"
@echo " make docker-shell - Open shell in app container"
@echo " make docker-test - Run tests in Docker environment"
@echo " make setup-dev - Setup development environment (.env.dev)"
@echo " make setup-test - Setup test environment (.env.test)"
# Install development dependencies
install-dev:
pip install -r requirements/dev.txt
@echo "Development dependencies installed successfully"
# Install production dependencies
install-prod:
pip install -r requirements/prod.txt
@echo "Production dependencies installed successfully"
# Install test dependencies
install-test:
pip install -r requirements/test.txt
@echo "Test dependencies installed successfully"
# Linting
lint: lint-flake8
lint-flake8:
@echo "Running flake8..."
flake8 app main.py tests
# Formatting
format: format-black format-isort
format-black:
@echo "Running black..."
black app main.py tests
format-isort:
@echo "Running isort..."
isort app main.py tests
# Clean up
clean:
@echo "Cleaning Python cache files..."
find . -type d -name "__pycache__" -exec rm -rf {} +
find . -type f -name "*.pyc" -delete
find . -type f -name "*.pyo" -delete
find . -type f -name "*.pyd" -delete
find . -type f -name ".coverage" -delete
find . -type d -name "*.egg-info" -exec rm -rf {} +
find . -type d -name "*.egg" -exec rm -rf {} +
find . -type d -name ".pytest_cache" -exec rm -rf {} +
find . -type d -name ".coverage" -exec rm -rf {} +
find . -type d -name "htmlcov" -exec rm -rf {} +
find . -type d -name ".tox" -exec rm -rf {} +
find . -type d -name "dist" -exec rm -rf {} +
find . -type d -name "build" -exec rm -rf {} +
# Testing
test:
@echo "Running tests..."
pytest tests/
# Docker development commands
docker-dev:
@echo "Starting development environment..."
docker-compose -f docker-compose.dev.yml up
docker-build:
@echo "Building Docker images..."
docker-compose -f docker-compose.dev.yml build
docker-shell:
@echo "Opening shell in app container..."
docker-compose -f docker-compose.dev.yml run --rm app bash
docker-test:
@echo "Running tests in Docker..."
docker-compose -f docker-compose.test.yml up --build --exit-code-from app
# Environment setup commands
setup-dev:
@echo "Setting up development environment..."
@if [ ! -f .env.dev ]; then \
echo "Creating .env.dev from example..."; \
cp .env.dev.example .env.dev; \
else \
echo ".env.dev already exists, skipping..."; \
fi
@echo "Run 'make docker-dev' to start the development environment"
setup-test:
@echo "Setting up test environment..."
@if [ ! -f .env.test ]; then \
echo "Creating .env.test from example..."; \
cp .env.test.example .env.test; \
else \
echo ".env.test already exists, skipping..."; \
fi
@echo "Run 'make docker-test' to run tests"
================================================
FILE: README.md
================================================
# 🌊 Ocean CTF
一个开箱即用的 **动态 Flag / 练习靶场 / 比赛平台**,支持容器化题目、题库管理、排行榜、公告通知,以及漏洞复现(Vulhub/自维护库)等能力,适合学校社团、战队训练、内部演练与小型赛事。
## 🔥 立刻开始(推荐)
- **赛事版在线体验**:http://ctf-docs.lostpeach.cn
## 🎯 适用场景
- **训练靶场**:战队日常训练、课程实验、入门练习
- **小型比赛**:校内赛/社团赛/内部赛快速搭建
- **漏洞复现**:沉淀可复现环境,便于演示与复盘
## ✨ 核心特性
- **动态 Flag**:题目容器化运行,Flag 动态生成
- **题库管理**:题目资源中心、编译(Pull/Build)、题库隔离
- **容器管理**:题目容器启停、实例信息展示、多端口开放
- **作弊检测**:基础反作弊能力
- **公告通知**:用户端顶部通知/公告列表
- **排行榜**:积分排行与记录
- **漏洞复现**:支持导入漏洞资源,提供 WebShell/日志等能力(见下文说明)
## 🗺️ Roadmap
- 大屏展示
- compose 容器支持
- 镜像功能页面重构
## 🚀 在线演示
- **用户端**:http://ctf.lostpeach.cn/
- 测试账号:`test/test`
- **管理端**:http://ctf.lostpeach.cn/manager
- 访客账号:`guest/guest`
## 🧩 关联仓库
- 后台管理前端(ocean_manager):https://github.com/tongchengbin/ocean_manager
- 题库仓库(CTFDB):https://github.com/tongchengbin/ctfdb
## 📚 赛事版私有化部署文档
- http://ctf-docs.lostpeach.cn/
## 🏁 赛事版(推荐)
- 在线体验:http://ocean.lostpeach.cn
- 私有化部署文档:http://ctf-docs.lostpeach.cn/
赛事版面向 **真实比赛场景** 做了更偏“赛事运营”的能力组合,适合校赛/社团赛/企业内部赛快速落地。
- **赛事运营更友好**:赛事配置/题库投放/公告与通知/排行榜
- **更适合多人并发**:题目容器化与隔离,支持多端口开放
- **更贴近比赛体验**:统一的参赛入口与交互流程
## 📖 快速开始(Docker Compose)
1) 克隆代码
```bash
git clone https://github.com/tongchengbin/ocean_ctf.git /opt/ocean_ctf
```
2) 启动服务
```bash
cd /opt/ocean_ctf
cp .env_example .env
docker-compose up -d
```
3) 初始化系统
- 访问管理端:`/manager`
- 默认管理账号:`admin/admin`
## 使用说明(题库 / 动态题目)
平台支持通过 **资源中心** 管理题目镜像与题库。
说明:实际运行题目更推荐使用 **Docker 方式**(启动/关闭更快、共享更方便、磁盘释放更直观)。因此当前 **CTF 动态题目** 主要以 Docker 启动为主,后续靶场场景会同时完善 docker-compose 与 docker 两种方式。
### 动态题目
通过虚拟化-资源中心添加题目资源,题目资源可以使用本地镜像、远程镜像、如 CTFDB 项目中提供了默认题目可以通过同步功能拉取题库或在[dockerhub](https://hub.docker.com/r/tongchengbin/easy_web)中查看题目
添加资源后需要进行编译(PULL)、因为编译是个比较耗时的操作(和网络环境有关)、所以建议先本地拉取 image 后点击编译按钮。
后续的操作与之前的版本一样、添加赛事题库选择动态题目和对应的资源环境即可。
## 📃 题库
> 题库仓库:[GitHub - tongchengbin/ctfdb: ctf 题库](https://github.com/tongchengbin/ctfdb)
## 💻 题库格式说明
> TODO
### 漏洞复现
漏洞复现支持 vulhub 部分镜像可以直接导入,但是推荐使用[CTFDB Vulnerability](https://github.com/tongchengbin/ctfdb) 仓库进行维护,内置 export 脚本支持自动扫描导出漏洞文件,该文件可以在平台 漏洞复现-漏洞资源中导入
## 开发
### 依赖管理与代码质量
项目使用 `requirements.txt` 来管理依赖,使用 `pyproject.toml` 配置代码质量工具,并使用 Makefile 来简化常用命令。
#### 依赖文件结构
```
requirements/
├── base.txt # 基础运行时依赖
├── dev.txt # 开发环境依赖
├── prod.txt # 生产环境依赖
└── test.txt # 测试环境依赖
requirements.txt # 生产部署用(指向 prod.txt)
```
#### 安装依赖
```bash
# 安装开发依赖
make install-dev
# 或者直接使用 pip
pip install -r requirements/dev.txt
# 安装生产依赖
make install-prod
# 或者直接使用 pip
pip install -r requirements/prod.txt
# 安装测试依赖
make install-test
# 或者直接使用 pip
pip install -r requirements/test.txt
```
#### 代码质量工具
```bash
# 运行所有代码检查工具
make lint
# 只运行 flake8
make lint-flake8
# 格式化代码(使用 black 和 isort)
make format
# 清理 Python 缓存文件
make clean
# 运行测试
make test
```
#### Docker 构建优化
项目 Dockerfile 已配置使用清华大学 PyPI 镜像源,加速依赖安装:
```bash
# 构建镜像(自动使用清华源)
docker-compose build
# 或者单独构建 web 服务
docker build -f install/docker/ocean_web.Dockerfile -t ocean_web .
```
要查看所有可用命令,请运行:
```bash
make help
```
## 社群
QQ 群: 836641851
<img src="./doc/image/qq.jpg" alt="QQ Image" width="200" height="300">
## License
[MIT](./LICENSE)
Copyright (c) 2022-present tongcb
## 截图
- 管理员登录

- Home

- Docker

- 资源中心

- 编译镜像

- 编译进度

- 镜像列表

- 题目列表

- 赛事环境

- 答题记录

- 用户首页

- 启动容器

- 漏洞复现 Shell

- 靶场

- 漏洞复现

================================================
FILE: app/__init__.py
================================================
import logging
from urllib.parse import urljoin, urlparse
import sqlalchemy.exc
from flask import Flask, g, request, url_for
from app.core.middlewares import before_req_cache_ip, global_admin_required
from config import config
from .api.route import register_blueprints
from .core import error_handlers
from .extensions import cache, db, socketio
from .utils.security import hash_password
logger = logging.getLogger(__name__)
def create_app():
"""
特别注意 因为使用了bp路由会导致 view task main 三个互相依赖 所以在这里要去掉路由 把路由和main 绑定在一起
当然也可以通过app_name 参数实现 但是需要使用单例模式 否者api和task 会启动两个app 一个提供接口 一个提供对celery的连接 感觉不太合理
Application factory.
"""
flask_app = Flask("main")
flask_app.config.from_object(config)
# 注册缓存
cache.init_app(flask_app)
register_extensions(flask_app)
register_blueprints(flask_app)
register_custom_helpers(flask_app)
flask_app.before_request_funcs.setdefault(None, []).append(before_req_cache_ip)
flask_app.before_request_funcs.setdefault(None, []).append(global_admin_required)
# register error handler
error_handlers.register_error_handlers(flask_app)
try:
db.engine.connect().close()
except sqlalchemy.exc.OperationalError as e:
db.session.rollback()
logging.error(e)
logging.error("数据库未就绪")
exit(1)
db.create_all()
create_default_data()
logger.info("数据库初始化完成")
return flask_app
def register_custom_helpers(scope_app):
def url_for_self(**args):
return url_for(request.endpoint, **dict(request.view_args, **args))
def url_for_no_querystring(endpoint, **args):
full_url = url_for(endpoint, **args)
return urljoin(full_url, urlparse(full_url).path)
def is_admin_user():
return bool(getattr(g, "user") and g.user.is_admin())
def is_reviewer():
return getattr(g, "user") and g.user.is_reviewer()
scope_app.jinja_env.globals["url_for_self"] = url_for_self
scope_app.jinja_env.globals["is_admin"] = is_admin_user
scope_app.jinja_env.globals["is_reviewer"] = is_reviewer
scope_app.jinja_env.globals["url_for_no_querystring"] = url_for_no_querystring
def register_extensions(scope_app):
"""异常捕获"""
db.init_app(scope_app)
cache.init_app(scope_app)
# 注册 socketIo
socketio.init_app(scope_app, cors_allowed_origins="*", logger=True)
scope_app.app_context().push()
public_paths = ["/favicon.ico", "/static/"]
def always_authorize():
for path in public_paths:
if request.path.startswith(path):
request._authorized = True
return
scope_app.before_request(always_authorize)
def cors(environ):
environ.headers["Access-Control-Allow-Origin"] = "*"
environ.headers["Access-Control-Allow-MethodS"] = "DELETE,PUT,OPTIONS,GET,POST"
environ.headers["Access-Control-Allow-Headers"] = (
"x-requested-with,content-type,Authorization,Token"
)
return environ
scope_app.after_request(cors)
def create_default_data():
"""
添加默认数据
@return:
"""
from app.models.admin import Admin, Role
# 添加角色 目前角色权限控制作为预留
for role in ("超级管理员", "运维管理员", "审计员", "访客"):
if not db.session.query(Role).filter_by(name=role).first():
db.session.add(Role(name=role))
db.session.commit()
superuser_role_id = db.session.query(Role.id).filter(Role.name == "超级管理员").first()[0]
if not db.session.query(Admin).filter(Admin.username == "admin").first():
db.session.add(
Admin(username="admin", password=hash_password("admin"), role_id=superuser_role_id)
)
db.session.commit()
__all__ = ["create_app"]
================================================
FILE: app/api/__init__.py
================================================
# API 包初始化文件
================================================
FILE: app/api/admin/__init__.py
================================================
================================================
FILE: app/api/admin/ctf.py
================================================
import logging
import os
import uuid
import docker
from docker import errors as docker_error
from flask import Blueprint, g, request
from flask_pydantic import validate
from app.api.admin.schemas import QuestionForm
from app.core.api import api_fail, api_success
from app.extensions import db
from app.models.admin import Config
from app.models.ctf import Answer, Attachment, CtfResource, ImageResource, QType, Question
from app.models.user import User
from app.services.docker import destroy_docker_runner
from app.tasks.ctf import sync_ctf_question_repo
from config import config
logger = logging.getLogger("app")
bp = Blueprint("admin_ctf", __name__, url_prefix="/api/admin/ctf")
@bp.get("/question/type")
def question_type():
"""
题库列表
:return:
"""
data = [i.value for i in QType]
return api_success({"data": data})
@bp.get("/resource")
def resource_list():
"""
:return : 已生成题目容器
"""
page = int(request.args.get("page", 1))
page_size = int(request.args.get("page_size", 10))
username = request.args.get("username")
question_name = request.args.get("question")
query = (
db.session.query(CtfResource, Question, User)
.join(User, CtfResource.user_id == User.id)
.join(Question, Question.id == CtfResource.question_id)
)
if username:
query = query.filter(User.username.ilike("%{}%".format(username)))
if question_name:
query = query.filter(Question.name.ilike("%{}%".format(question_name)))
page = query.order_by(CtfResource.id.desc()).paginate(page=page, per_page=page_size)
data = []
for item in page.items:
resource, question, user_obj = item
data.append(
{
"id": resource.id,
"name": resource.docker_runner.name,
"date_created": (
resource.created_at.strftime("%Y-%m-%d %H:%M:%S")
if resource.created_at
else None
),
"date_modified": (
resource.updated_at.strftime("%Y-%m-%d %H:%M:%S")
if resource.updated_at
else None
),
"container_port": resource.docker_runner.port_info,
"flag": resource.flag,
"destroy_time": (
resource.destroy_time.strftime("%Y-%m-%d %H:%M")
if resource.destroy_time
else None
),
"username": user_obj.username,
"question": {"name": question.name},
}
)
return api_success({"total": page.total, "data": data})
@bp.post("/question/<int:pk>/set_active")
def question_set_active(pk):
"""
设置题目是否可用
:param pk:
:return:
"""
instance = db.session.query(Question).get(pk)
instance.active = not instance.active
db.session.commit()
return api_success()
@bp.put("/question/<int:pk>")
def question_update(pk):
"""
修改题目
:param question: 题目ID
:return:
@param pk:
"""
data = request.get_json()
instance = db.session.query(Question).get(pk)
name = data.get("name")
_type = data.get("type")
active_flag = data.get("active_flag")
score = data.get("score")
flag = data.get("flag")
desc = data.get("desc")
resource_id = data.get("resource_id") or None
if active_flag is not None:
instance.active_flag = active_flag
if name is not None:
instance.name = name
if score is not None:
instance.score = score
if _type is not None:
instance.type = _type
if desc is not None:
instance.desc = desc
attachment = data.get("attachment", [])
active = data.get("active")
if active is not None:
instance.active = active
if active_flag:
if not resource_id:
return api_fail(msg="清选择环境资源")
instance.resource_id = resource_id
instance.attachment = ",".join([str(i) for i in attachment])
if active_flag is not None:
if not active_flag:
instance.flag = flag
db.session.commit()
return api_success()
@bp.post("/containers/<int:container_resource>/refresh")
def ctf_containers_refresh(container_resource):
"""
刷新容器状态 数据库和实际容器状态同步
:param :container_resource :题目容器
:return
"""
container = db.session.query(CtfResource).get(container_resource)
question = container.question
try:
client = docker.DockerClient(question.host.docker_api)
docker_container = client.containers.get(container.container_id)
except docker_error.DockerException:
container.container_status = "Outline".lower()
db.session.commit()
return api_fail(msg="容器不在线")
container.container_status = docker_container.attrs["State"]["Status"].lower()
db.session.commit()
return api_success()
@bp.post("/resource/<int:pk>/remove")
def resource_remove(pk):
"""
删除题目容器 如果容器不在线需要自己手动删除
:param :container_resource 题目容器id
:return
"""
ctf_resource = db.session.query(CtfResource).get(pk)
destroy_docker_runner(ctf_resource.docker_runner_id)
# docker runner 删除会自动删除外键关联的CTF 环境
return api_success(msg="删除成功")
@bp.route("/answers", methods=["get"])
def answers_list():
"""
答题记录
:return:
"""
page = int(request.args.get("page", 1))
page_size = int(request.args.get("page_size", 10))
_type = request.args.get("q_type")
status = request.args.get("status")
question_name = request.args.get("question")
username = request.args.get("username")
query = (
db.session.query(Answer, Question, User)
.join(Question, Question.id == Answer.question_id)
.join(User, User.id == Answer.user_id)
)
if _type:
query = query.filter(Question.type == _type)
if status:
query = query.filter(Answer.status == status)
if username:
query = query.filter(User.username.contains(username))
if question_name:
query = query.filter(Question.name.contains(question_name))
page = query.order_by(Answer.id.desc()).paginate(page=page, per_page=page_size)
data = []
for item in page.items:
answer, question, user = item
data.append(
{
"id": answer.id,
"date_created": (
answer.created_at.strftime("%Y-%m-%d %H:%M:%S") if answer.created_at else None
),
"date_modified": (
answer.updated_at.strftime("%Y-%m-%d %H:%M:%S") if answer.updated_at else None
),
"status": answer.status,
"status_name": answer.status_name,
"question": {"type": question.type, "name": question.name},
"score": answer.score,
"flag": answer.flag,
"username": user.username,
"ip": answer.ip,
}
)
return api_success({"total": page.total, "data": data})
@bp.route("/answers/status_list", methods=["get"])
def answer_status_list():
"""
回答题目的类别
"""
return api_success({"data": list(Answer.status_choices)})
@bp.get("/question")
def question_list():
"""
题库列表 和题库添加
:data :subject 题目分类
:return:
"""
page = int(request.args.get("page", 1))
page_size = int(request.args.get("page_size", 10))
subject = request.args.get("subject")
search = request.args.get("search")
query = db.session.query(Question).filter()
if subject:
query = query.filter(Question.type == subject)
if search:
query = query.filter(Question.name.contains(search))
page = query.order_by(Question.id.desc()).paginate(page=page, per_page=page_size)
data = []
attachment_info = []
for item in page.items:
if item.attachment:
attachment = item.attachment.split(",")
if attachment:
attachment_query = db.session.query(Attachment).filter(
Attachment.id.in_(attachment)
)
attachment_info = [{"filename": i.filename, "uuid": i.id} for i in attachment_query]
data.append(
{
"resource_id": item.resource_id,
"resource_name": item.resource.name if item.resource_id else None,
"attachment": attachment_info,
"id": item.id,
"date_created": (
item.created_at.strftime("%Y-%m-%d %H:%M:%S") if item.created_at else None
),
"date_modified": (
item.updated_at.strftime("%Y-%m-%d %H:%M:%S") if item.updated_at else None
),
"name": item.name,
"type": item.type,
"active": item.active,
"flag": item.flag,
"active_flag": item.active_flag,
"score": item.score,
"desc": item.desc,
}
)
return api_success({"total": page.total, "results": data})
@bp.post("/question")
@validate()
def question_create(body: QuestionForm):
data = request.get_json()
attachment = data.get("attachment", [])
Question.create(
name=body.name,
active=body.active,
active_flag=body.active_flag,
desc=body.desc,
flag=body.flag,
type=body.type,
score=body.score,
resource_id=body.resource_id,
attachment=",".join([str(i) for i in attachment]),
)
return api_success({})
@bp.delete("/question/<int:pk>")
def question_delete(pk):
"""
删除题库 判断是否是动态题库 动态题库删除容器 实体容器 镜像
:param : question 题目ID
"""
# 使用逻辑删除
instance: Question = db.session.query(Question).get(pk)
if instance.active_flag:
containers = db.session.query(CtfResource).filter(CtfResource.question_id == instance.id)
for container in containers:
db.session.delete(container)
client = docker.DockerClient(container.image.host.docker_api)
docker_container = client.containers.get(container.container_id)
docker_container.stop()
container.status = 2
db.session.commit()
# 删除镜像
instance.delete()
return api_success({})
@bp.get("/images")
def images_list():
page = int(request.args.get("page", 1))
page_size = int(request.args.get("page_size", 10))
host_id = request.args.get("host")
status = request.args.get("status")
name = request.args.get("name")
file = request.args.get("file")
query = db.session.query(ImageResource)
if host_id:
query = query.filter(ImageResource.host_id == host_id)
if status:
query = query.filter(ImageResource.status == status)
if name:
query = query.filter(ImageResource.name.ilike("%%%s%%" % name))
if file:
query = query.filter(ImageResource.file.filename.ilike(file))
page = query.order_by(ImageResource.id.desc()).paginate(page=page, per_page=page_size)
data = []
for item in page.items:
_item = item.to_dict()
if item.file:
_item["filename"] = item.file.filename
else:
_item["filename"] = None
_item["ip"] = item.host.ip
_item["host_name"] = item.host.name
data.append(_item)
return api_success(data={"data": data})
@bp.delete("/images/<int:pk>")
def images_delete(pk):
"""
删除镜像 目前仅仅删除数据库数据 判断是否有容器在运行 否则不允许删除
"""
if db.session.query(CtfResource).filter(CtfResource.image_resource_id == pk).count():
return api_fail(msg="无法删除当前镜像、因为相关容器正在运行中!", code=400)
instance = db.session.query(ImageResource).get(pk)
db.session.delete(instance)
db.session.commit()
return api_success()
@bp.post("/images")
def images_create():
# Deprecated
_data = request.get_json()
name = _data.get("name")
host_id = _data.get("host")
version = _data.get("version")
memory = _data.get("memory")
cpu = _data.get("cpu")
instance = ImageResource(
host_id=host_id,
name=name,
version=version,
memory=memory,
cpu=cpu,
file_id=_data["file_id"],
)
db.session.add(instance)
db.session.commit()
return api_success()
@bp.put("/images/<int:pk>")
def image_update(pk):
# Deprecated
_data = request.get_json()
name = _data.get("name")
host_id = _data.get("host_id")
version = _data.get("version")
memory = _data.get("memory")
cpu = _data.get("cpu")
instance = db.session.query(ImageResource).get(pk)
instance.name = name
instance.host_id = host_id
instance.version = version
instance.memory = memory
instance.cpu = cpu
instance.file_id = _data["file_id"]
instance.status = ImageResource.STATUS_BUILDING
db.session.commit()
return api_success()
@bp.post("/upload")
def ctf_upload_attachment():
"""
题目附件上传
"""
file = request.files["file"]
filename = file.filename
ext = filename.split(".")[-1]
upload_dir = config.UPLOAD_DIR
if ".." in filename:
return api_fail(msg="文件名非法!")
# 生成随机文件名
uuid_filename = str(uuid.uuid4()) + "." + ext
file_path = os.path.join(upload_dir, uuid_filename)
file.save(file_path)
# 添加数据库记录
at = Attachment(filename=filename, file_path=uuid_filename)
db.session.add(at)
db.session.commit()
return api_success({"filename": filename, "uuid": at.id})
@bp.post("/sync_repo")
def ctf_sync_repo():
remote_repo = Config.get_config(Config.KEY_CTF_REPOSITORY)
if not remote_repo:
return api_fail(msg="未配置远程漏洞仓库")
sync_ctf_question_repo.apply_async(args=(remote_repo,), kwargs={"admin_id": g.user.id})
return api_success(msg="任务已提交")
================================================
FILE: app/api/admin/docker.py
================================================
import json
import logging
import os
import docker
import requests
import yaml
from docker import APIClient
from docker import errors as docker_error
from docker.errors import ImageNotFound
from flask import Blueprint, g, request
from flask_pydantic import validate
from sqlalchemy import or_
from sqlalchemy.exc import IntegrityError
from app.api.admin.schemas.docker import (
ComposeDBForm,
DockerResourceEditForm,
DockerResourceForm,
PageForm,
)
from app.core.api import api_fail, api_success
from app.core.tools import model2dict
from app.extensions import cache, db
from app.models.admin import Config, TaskList
from app.models.ctf import Question
from app.models.docker import (
ComposeDB,
ComposeRunner,
DockerResource,
)
from app.tasks.docker import build_delay, docker_build_resource
from app.utils.validator import check_image_name
logger = logging.getLogger("app")
bp = Blueprint("admin_docker", __name__, url_prefix="/api/admin/docker")
@bp.get("/info")
def docker_info():
api = Config.get_config(Config.KEY_DOCKER_API)
ip = Config.get_config(Config.KEY_IP)
try:
client = docker.DockerClient(api)
info = client.info()
except docker_error.DockerException:
info = {}
from pprint import pprint
pprint(info)
if info:
"""数据格式化"""
info = {
"name": info["Name"],
"containers": info["Containers"],
"images": info["Images"],
"version": info["KernelVersion"],
"memory": round(info["MemTotal"] / 1024 / 2024 / 1024, 2),
"cpu": info["NCPU"],
"system": info["OperatingSystem"],
"system_time": info["SystemTime"],
}
data = {"docker_api": api, "ip": ip, "info": info}
return api_success({"data": data})
@bp.get("/images")
def docker_images():
"""
获取镜像列表
:return:
"""
docker_api = Config.get_config(Config.KEY_DOCKER_API)
try:
client = docker.DockerClient(docker_api)
images = client.images.list()
except docker_error.DockerException:
images = []
images_list = []
for im in images:
attrs = im.attrs
if not attrs["RepoTags"]:
continue
tmp = {
"created": attrs["Created"].split("T")[0],
"id": attrs["Id"][7:17],
"size": attrs["Size"],
"repo": attrs["RepoTags"][0].split(":")[0],
"tags": [i.split(":")[1] for i in attrs["RepoTags"]],
}
images_list.append(tmp)
return api_success({"data": images_list})
@bp.post("/delete_images")
def image_delete():
tag = request.get_json().get("id")
docker_api = Config.get_config(Config.KEY_DOCKER_API)
try:
client = docker.DockerClient(docker_api)
client.images.remove(tag)
except docker_error.DockerException as e:
error_str = str(e)
logger.info("ERROR %s" % error_str)
if "is using its referenced image" in error_str:
return api_fail(msg="当前镜像被占用,请先删除对应容器!")
if "is being used by running container" in error_str:
return api_fail(msg="当前有对应容器正在运行,请停止对应容器!")
if "image is referenced in multiple repositories" in error_str:
return api_fail(msg="镜像被多个仓库依赖!")
return api_fail(msg=f"删除失败({error_str})")
return api_success({"status": 0})
@bp.get("/containers")
def host_docker_container():
"""
获取镜像列表
:return:
"""
docker_api = Config.get_config(Config.KEY_DOCKER_API)
try:
client = docker.DockerClient(docker_api)
containers = client.containers.list(all=True)
except docker_error.DockerException:
containers = []
containers = [container.attrs for container in containers]
return api_success({"data": containers})
@bp.post("/containerAction")
def container_action():
"""
容器操作
:return:
"""
container_id = request.get_json().get("id")
action = request.get_json().get("action")
docker_api = Config.get_config(Config.KEY_DOCKER_API)
try:
client = docker.DockerClient(docker_api)
container = client.containers.get(container_id)
action_fun = getattr(container, action)
action_fun()
except docker_error.DockerException as e:
return api_fail(msg=f"关闭容器失败:{container_id}:{e}")
return api_success({"status": "ok"})
@bp.route("/image", methods=["post"])
def image_create():
"""
编译是一个比较耗时的任务 这里回采取延迟执行方式
"""
build_type = request.args.get("build_type")
tag = request.args.get("tag")
if len(tag.split(":")) != 2:
return api_fail(msg="images name 格式错误请指定tag")
name, version = tag.split(":")
# check name and version 是否包含特殊字符
if not name or not version:
return api_fail(msg="images name 格式错误请指定tag")
if not check_image_name(tag):
return api_fail(msg="镜像名称不合法")
task_obj = TaskList(admin_id=g.user.id, target_id=None, title="build image for %s" % build_type)
db.session.add(task_obj)
db.session.commit()
args = (task_obj.id, build_type, tag, g.user.id)
if build_type == "tar":
file = request.files.get("files")
pt = os.path.join("upload", file.filename)
file.save(pt)
kwargs = {"pt": pt}
elif build_type == "dockerfile":
kwargs = {"dockerfile": request.get_json().get("dockerfile")}
else:
kwargs = {}
build_delay.apply_async(args=args, kwargs=kwargs)
return api_success({"status": "ok", "data": {"task": task_obj.id}})
@bp.get("/compose_db")
@validate()
def compose_db_list(query: PageForm):
db_query = db.session.query(ComposeDB).filter()
page = db_query.paginate(page=query.page, per_page=query.page_size)
data = []
for item in page.items:
data.append(model2dict(item))
return api_success({"total": page.total, "data": data})
@bp.post("/compose_db")
@validate()
def compose_db_create(body: ComposeDBForm):
if (
db.session.query(ComposeDB)
.filter(or_(ComposeDB.name == body.name, ComposeDB.path == body.path))
.count()
):
return api_fail(msg="compose已存在", code=400)
ComposeDB.create(name=body.name, path=body.path)
return api_success({})
@bp.delete("/compose_db/<int:pk>")
def compose_db_delete(pk):
ComposeDB.get_by_id(pk).delete()
return api_success({})
@bp.get("/compose_runner")
@validate()
def compose_runner_list(query: PageForm):
db_query = db.session.query(ComposeRunner).filter()
page = db_query.paginate(page=query.page, per_page=query.page_size)
data = []
for item in page.items:
data.append(model2dict(item))
return api_success({"total": page.total, "data": data})
@bp.get("/resource")
@validate()
def docker_resource_list(query: PageForm):
"""
:return: 获取docker资源列表
"""
search = request.args.get("search")
db_query = db.session.query(DockerResource)
if search:
db_query = db_query.filter(DockerResource.name.ilike("%%%s%%" % search))
resource_type = request.args.get("type")
if resource_type:
db_query = db_query.filter(DockerResource.resource_type == resource_type)
page = db_query.order_by(DockerResource.id.desc()).paginate(
page=query.page, per_page=query.page_size
)
data = []
for item in page.items:
info = model2dict(item)
info["docker_type_name"] = item.docker_type_name
info["status_name"] = item.status_name
data.append(info)
return api_success({"total": page.total, "data": data})
@bp.post("/resource")
@validate()
def docker_resource_create(body: DockerResourceForm):
DockerResource.create(**body.dict())
return api_success()
@bp.put("/resource/<int:pk>")
@validate()
def docker_resource_update(pk: int, body: DockerResourceEditForm):
instance = DockerResource.get_by_id(pk)
# 判断是否修改了镜像
if instance.image != body.image or instance.docker_type != body.docker_type:
instance.status = DockerResource.STATUS_INIT
instance.name = body.name
# 资源类型无法更新
# instance.resource_type = body.resource_type
instance.image = body.image
instance.docker_type = body.docker_type
instance.description = body.description
instance.save()
return api_success()
@bp.post("/resource/<int:pk>/build")
def docker_resource_build(pk):
"""
资源编译
"""
result = docker_build_resource.apply_async(args=(pk,))
return api_success(msg="任务已提交", data={"task_id": result.id})
@bp.get("/resource/<int:pk>/logs")
def docker_resource_logs(pk):
resource = DockerResource.get_by_id(pk)
start = int(request.args.get("start", 0))
# 支持通过task_id参数获取日志
task_id = request.args.get("task_id")
if task_id:
# 使用任务ID获取日志
key = "DOCKER_BUILD_LOG_%s" % task_id
else:
# 兼容旧的方式
key = "DOCKER_RESOURCE_%s" % pk
data = []
for log in cache.lrange(key, start, -1):
data.insert(0, json.loads(log))
results = {"status": resource.status, "data": data}
return api_success({"data": results})
@bp.post("/resource/sync")
def docker_resource_sync():
url = request.get_json().get("url")
try:
res = requests.get(url, verify=False, timeout=10)
except requests.exceptions.RequestException as e:
logger.error(e)
return api_fail(msg="同步失败、同步服务器连接失败", code=400)
data = yaml.load(res.text, Loader=yaml.SafeLoader)
resources = data.get("resources", [])
res_dict = {}
add_count = 0
if resources:
# 获取当前数据
query = db.session.query(DockerResource).all()
for i in query:
res_dict[i.name] = (i.image, i.resource_type)
for item in resources:
logger.info(item)
old_res = res_dict.get(item["name"])
if old_res and old_res == item["resource_type"]:
continue
if item["resource_type"] not in ("CTF", "VUL"):
continue
DockerResource.create(
name=item["name"],
image=item["image"],
resource_type=item["resource_type"],
description=item["desc"],
)
add_count += 1
results = {
"total": add_count,
}
return api_success(results)
@bp.delete("/resource/<int:pk>")
def resource_delete(pk):
logger.info(db.session.query(Question).filter(Question.resource_id == pk).all())
try:
instance = DockerResource.get_by_id(pk)
image = instance.image
instance.delete()
except IntegrityError as e:
logger.exception(e)
db.session.rollback()
return api_fail(msg="资源占用中、当前状态无法删除,请检查引用对象!", code=400)
# 删除docker images
try:
client = APIClient(Config.get_config(Config.KEY_DOCKER_API))
logger.info("删除镜像:%s" % image)
client.remove_image(image)
except ImageNotFound:
pass
db.session.commit()
return api_success({})
================================================
FILE: app/api/admin/schemas/__init__.py
================================================
from app.api.admin.schemas.ctf import * # noqa
================================================
FILE: app/api/admin/schemas/ctf.py
================================================
from typing import Optional
from pydantic import BaseModel
class QuestionForm(BaseModel):
name: str
active: Optional[bool] = False
active_flag: Optional[bool] = False
flag: Optional[str] = None # Set default to None
desc: str
type: str
score: int
resource_id: Optional[int] = None
================================================
FILE: app/api/admin/schemas/docker.py
================================================
from typing import Optional
from pydantic import BaseModel
class PageForm(BaseModel):
page: int = 1
page_size: int = 10
class ComposeDBForm(BaseModel):
path: str
name: str
class DockerResourceForm(BaseModel):
resource_type: str
docker_type: int
image: str
name: str
description: Optional[str]
class DockerResourceEditForm(BaseModel):
docker_type: int
image: str
name: str
description: Optional[str]
================================================
FILE: app/api/admin/system.py
================================================
import logging
import os
import uuid
from datetime import datetime
from operator import or_
from flask import Blueprint, current_app, g, jsonify, request
from sqlalchemy import desc, func
from app.core.api import api_fail, api_success, response_ok
from app.extensions import cache, db
from app.models.admin import (
Admin,
AdminMessage,
Config,
Notice,
Operator,
RequestState,
Role,
TaskList,
)
from app.models.ctf import CtfResource, Question
from app.models.user import User
from app.services.system import insert_operator
from app.utils.security import check_password, create_token, hash_password
bp = Blueprint("admin", __name__, url_prefix="/api/admin")
logger = logging.getLogger("app")
@bp.route("/rest_pass", methods=["post"])
def admin_rest_pass():
data = request.get_json()
old_pass = data.get("old_pass")
new_pass = data.get("pass")
if not all([old_pass, new_pass]):
return api_fail(msg="参数错误")
user = g.user
if check_password(user.password, old_pass):
user.password = hash_password(new_pass)
db.session.commit()
return api_success()
else:
return api_fail(msg="旧密码错误")
@bp.route("/task/<int:task>/log", methods=["get"])
def task_log(task):
"""
任务执行日志
"""
index = request.args.get("index", 0)
lines = cache.lrange("task_%s" % task, index, -1)
task = db.session.query(TaskList).get(task)
data = [i.decode() for i in lines]
return api_success({"data": data, "end": False if task.status in (1, 3) else True})
@bp.route("/upload", methods=["post"])
def upload_file():
"""
后台文件上传入口 目前存在文件覆盖 后续处理吧
"""
file = request.files["file"]
filename = file.filename
ext = filename.split(".")[-1]
upload_dir = current_app.config.get("UPLOAD_DIR")
if ".." in filename:
return jsonify({"error": "文件名非法!"})
# 生成随机文件名
uuid_filename = str(uuid.uuid4()) + "." + ext
file_path = os.path.join(upload_dir, uuid_filename)
file.save(file_path)
return api_success({"name": filename, "filename": filename + "|" + uuid_filename})
@bp.get("/admin")
def admin_list():
"""
管理员列表
:return:
"""
page = int(request.args.get("page", 1))
page_size = int(request.args.get("page_size", 10))
search = request.args.get("search")
query = db.session.query(Admin).filter(Admin.username != "superuser")
if search:
query = query.filter(Admin.username.contains(search))
page = query.paginate(page=page, per_page=page_size)
data = []
for item in page.items:
data.append(
{
"id": item.id,
"login_time": (
item.login_time.strftime("%Y-%m-%d %H:%M:%S") if item.login_time else None
),
"username": item.username,
"role": item.role_id,
"role_name": item.role.name if item.role else None,
}
)
return api_success({"total": page.total, "data": data})
@bp.route("/admin/<int:pk>", methods=["put"])
def admin_update(pk):
# 修改信息
data = request.get_json()
username = data.get("username")
password = data.get("password")
role = data.get("role")
admin = db.session.query(Admin).get(pk)
if username:
admin.username = username
if role:
admin.role_id = role
if password:
admin.password = hash_password(password)
db.session.commit()
return api_success({})
@bp.route("/admin", methods=["post"])
def admin_create():
data = request.get_json()
username = data.get("username")
password = data.get("password")
role = data.get("role")
if db.session.query(Admin).filter(Admin.username == username).count():
return api_fail(msg="管理员已存在")
safe_password = hash_password(password)
admin = Admin(username=username, password=safe_password, role_id=role)
db.session.add(admin)
db.session.commit()
return api_success({})
@bp.delete("/admin/<int:pk>")
def admin_delete(pk):
admin = db.session.query(Admin).get(pk)
db.session.delete(admin)
db.session.commit()
return api_success({})
@bp.route("/user", methods=["get"])
def user_list():
"""
用户列表
"""
page = int(request.args.get("page", 1))
page_size = int(request.args.get("page_size", 10))
search = request.args.get("search")
query = db.session.query(User)
if search:
query = query.filter(User.username.contains(search))
page = query.order_by(User.id.desc()).paginate(page=page, per_page=page_size)
data = []
for item in page.items:
data.append(
{
"id": item.id,
"username": item.username,
"date_created": (
item.created_at.strftime("%Y-%m-%d %H:%M:%S") if item.created_at else None
),
"date_modified": (
item.updated_at.strftime("%Y-%m-%d %H:%M:%S") if item.updated_at else None
),
"active": item.active,
}
)
return api_success({"total": page.total, "data": data})
@bp.route("/user", methods=["post"])
def user_create():
"""
添加用户
"""
data = request.get_json()
username = data.get("username")
password = data.get("password")
if db.session.query(User).filter(User.username == username).one_or_none():
return api_fail(msg="用户名已存在")
safe_password = hash_password(password)
db.session.add(User(username=username, password=safe_password))
db.session.commit()
return api_success({})
@bp.route("/user/<int:pk>", methods=["put"])
def user_update(pk):
data = request.get_json()
password = data.get("password")
user = db.session.query(User).get(pk)
if password:
user.password = hash_password(password)
db.session.commit()
return api_success({})
@bp.route("/user/<int:pk>", methods=["delete"])
def user_delete(pk):
"""
删除用户
@return:
"""
user = db.session.query(User).get(pk)
db.session.delete(user)
db.session.commit()
return api_success({})
@bp.route("/index/state", methods=["get"])
def index_state():
"""
:return:今日容器启动数量、今日IP数量、题库数量
"""
today_query = db.session.query(CtfResource).filter(
func.date(CtfResource.created_at) == datetime.today().date()
)
challenges_cnt = db.session.query(Question).count()
user_cnt = db.session.query(User).count()
today_register = (
db.session.query(User).filter(func.date(User.created_at) == datetime.today().date()).count()
)
today_create_cnt = today_query.count()
today = datetime.today().strftime("%Y%m%d")
ip_count = cache.scard("ip-%s" % today)
req_count = cache.get("req-%s" % today).decode() or 0
# 统计半月用户访问情况
req_state = db.session.query(RequestState).order_by(RequestState.day)
req_data = {
"x_data": [],
"lines": [{"label": "活跃IP", "data": []}, {"label": "处理请求", "data": []}],
}
for dy in req_state:
req_data["x_data"].append(dy.day.strftime("%m-%d"))
req_data["lines"][0]["data"].append(dy.ip_count)
req_data["lines"][1]["data"].append(dy.req_count)
req_data["x_data"].append(datetime.today().strftime("%m-%d"))
req_data["lines"][0]["data"].append(ip_count)
req_data["lines"][1]["data"].append(req_count)
return api_success(
{
"data": {
"req_data": req_data,
"today_create_cnt": today_create_cnt,
"ip_cnt": ip_count,
"req_count": req_count,
"challenges_cnt": challenges_cnt,
"today_register": today_register,
"user_cnt": user_cnt,
}
}
)
@bp.route("/notice", methods=["get"])
def notice_list():
page = int(request.args.get("page", 1))
page_size = int(request.args.get("page_size", 10))
search = request.args.get("search")
is_top = request.args.get("is_top")
active = request.args.get("active")
query = db.session.query(Notice)
if is_top:
query = query.filter(Notice.is_top == is_top)
if active:
query = query.filter(Notice.active == active)
if search:
query = query.filter(Notice.content.contains(search))
query = query.order_by(desc(Notice.active), desc(Notice.is_top), desc(Notice.id))
page = query.paginate(page=page, per_page=page_size)
data = []
for item in page.items:
data.append(
{
"id": item.id,
"updated_at": (
item.updated_at.strftime("%Y-%m-%d %H:%M:%S") if item.created_at else None
),
"content": item.content,
"is_top": item.is_top,
"active": item.active,
}
)
return api_success({"total": page.total, "data": data})
@bp.route("/notice", methods=["post"])
def notice_create():
data = request.get_json()
content = data.get("content")
is_top = data.get("isTop")
active = data.get("active")
instance = Notice(content=content, active=active, is_top=is_top)
db.session.add(instance)
db.session.commit()
return api_success({})
@bp.route("/notice/<int:pk>", methods=["put"])
def notice_update(pk):
"""
update 公告
"""
data = request.get_json()
content = data.get("content")
is_top = data.get("isTop")
active = data.get("active")
instance = db.session.query(Notice).get(pk)
if is_top is not None:
instance.is_top = is_top
if active is not None:
instance.active = active
if content is not None:
instance.content = content
db.session.commit()
return api_success({})
@bp.route("/notice/<int:pk>", methods=["delete"])
def notice_delete(pk):
"""
delete 公告
"""
instance = db.session.query(Notice).get(pk)
db.session.delete(instance)
db.session.commit()
return api_success({})
@bp.get("/userinfo")
def login_info():
admin = g.user
ret = {
"role": admin.role_id,
"role_name": admin.role.name if admin.role else None,
"username": admin.username,
"id": admin.id,
}
return response_ok(ret)
@bp.post("/login")
def login():
data = request.get_json()
username = data.get("username")
password = data.get("password")
admin = db.session.query(Admin).filter(Admin.username == username).one_or_none()
if admin is None:
insert_operator(code=False, content="登录失败", username=username, role_name=None)
return api_fail(code=403, msg="用户名或密码错误")
if check_password(admin.password, password):
token = create_token()
admin.token = token
admin.login_time = datetime.now()
db.session.commit()
ret = {
"token": token,
"role": admin.role_id,
"role_name": admin.role.name if admin.role else None,
"username": admin.username,
"id": admin.id,
}
insert_operator(
code=True, content="登录成功", username=admin.username, role_name=admin.role_name
)
return api_success({"data": ret})
else:
insert_operator(code=False, content="登录失败", username=username, role_name=None)
return api_fail(code=403, msg="用户名或密码错误")
@bp.get("/role")
def role_list():
page = int(request.args.get("page", 1))
page_size = int(request.args.get("page_size", 10))
query = db.session.query(Role)
page_query = query.paginate(page=page, per_page=page_size)
data = []
for item in page_query.items:
_item = dict()
_item["id"] = item.id
_item["name"] = item.name
data.append(_item)
return api_success({"data": data, "total": page_query.total})
@bp.post("/role")
def role_create():
data = request.get_json()
name = data.get("name")
if db.session.query(Role).filter(Role.name == name).count():
return api_fail(msg="角色已存在")
instance = Role(name=name)
db.session.add(instance)
db.session.commit()
return api_success({})
@bp.put("/role")
def role_update():
data = request.get_json()
pk = data.get("id")
name = data.get("name").strip()
instance = db.session.query(Role).filter(Role.id == pk).first()
if not instance:
return api_fail(msg="资源不存在")
if instance.name == name:
return api_success({})
if db.session.query(Role).filter(Role.name == name).count():
return api_fail(msg="角色已存在")
instance.name = name
db.session.commit()
return api_success({})
@bp.delete("/role/<int:pk>")
def role_delete(pk):
instance = db.session.query(Role).filter(Role.id == pk).first()
if not instance:
return api_fail(msg="资源不存在")
db.session.delete(instance)
db.session.commit()
return api_success({})
@bp.post("/logout")
def logout():
"""
登出
:return:
"""
current_user = g.user
current_user.token = None
db.session.commit()
insert_operator(
code=True,
content="登出成功",
username=current_user.username,
role_name=current_user.role_name,
)
return api_success({})
@bp.post("/config")
def set_config():
data = request.get_json()
for k, v in data.items():
if k not in Config.CONFIG_MAP:
logger.warning(f"未知配置键: {k}")
continue
# 校验数据
val_type = Config.CONFIG_MAP[k][0]
if not isinstance(v, val_type):
return api_fail(msg="数据格式错误")
if isinstance(v, str) and len(v) > 2000:
return api_fail(msg="配置内容过长")
old = db.session.query(Config).filter(Config.key == k).first()
if old:
old.val = v
old.type = val_type.__name__
else:
db.session.add(Config(key=k, val=v))
db.session.commit()
return api_success()
@bp.get("/config")
def get_config():
config_list = db.session.query(Config).all()
data = {}
for ite in config_list:
try:
val_type = Config.CONFIG_MAP[ite.key][0]
except KeyError:
continue
data[ite.key] = val_type(ite.val)
for k, v in Config.CONFIG_MAP.items():
if k not in data:
data[k] = v[1]
return api_success({"data": data})
@bp.get("/operator")
def operator_list():
"""
审计日志
:return:
"""
page = int(request.args.get("page", 1))
page_size = int(request.args.get("page_size", 10))
search = request.args.get("search")
code = request.args.get("code")
query = db.session.query(Operator)
if code:
query = query.filter(Operator.code == code)
if search:
query = query.filter(
or_(Operator.username.contains(search), Operator.content.contains(search))
)
query = query.order_by(desc(Operator.id))
page_query = query.paginate(page=page, per_page=page_size)
data = []
for item in page_query.items:
_item = dict()
_item["id"] = item.id
_item["content"] = item.content
_item["role"] = item.role
_item["username"] = item.username
_item["ip"] = item.ip
_item["create_time"] = item.create_time_format
_item["code"] = item.code
data.append(_item)
return api_success({"data": data, "total": page_query.total})
@bp.get("/message")
def message_notice():
"""
获取管理员消息 通知 代办
"""
page = int(request.args.get("page", 1))
page_size = int(request.args.get("page_size", 10))
read = request.args.get("read")
query = db.session.query(AdminMessage).filter(AdminMessage.admin_id == g.user.id)
if read == "0":
query = query.filter(AdminMessage.read.is_(False))
query = query.order_by(desc(AdminMessage.id))
page_query = query.paginate(page=page, per_page=page_size)
messages = []
for item in page_query.items:
messages.append(
{
"id": item.id,
"read": item.read,
"avatar": "https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png",
"title": item.content,
"datetime": item.created_at.strftime("%Y-%m-%d %H:%M"),
"description": "",
"type": 2,
}
)
return response_ok(results=messages, total=page_query.total)
@bp.post("/message/read")
def message_read():
"""
消息已读
:return:
"""
ids = request.get_json().get("ids")
db.session.query(AdminMessage).filter(AdminMessage.id.in_(ids)).update({"read": True})
db.session.commit()
return api_success()
@bp.delete("/message/<int:pk>")
def message_delete(pk: int):
"""
消息删除
:return:
"""
db.session.query(AdminMessage).filter(AdminMessage.id == pk).delete()
db.session.commit()
return api_success()
@bp.post("/message/read_all")
def message_read_all():
"""
消息删除
:return:
"""
db.session.query(AdminMessage).filter(AdminMessage.admin_id == g.user.id).update({"read": True})
db.session.commit()
return api_success()
================================================
FILE: app/api/admin/vulnerability.py
================================================
import logging
import docker
import yaml
from docker.errors import NotFound
from flask import Blueprint, g, request
from sqlalchemy import or_
from app.core.api import api_fail, api_success
from app.core.tools import model2dict
from app.extensions import cache, db
from app.models.admin import Config
from app.models.docker import DockerResource, DockerRunner
from app.tasks.vulnerability import start_vuln_resource, sync_remote_vulnerability_repo
logger = logging.getLogger(__name__)
bp = Blueprint("admin_vuln", __name__, url_prefix="/api/admin")
@bp.get("/vulnerability")
def vuln_list():
"""
漏洞列表
"""
page = int(request.args.get("page", 1))
page_size = int(request.args.get("page_size", 10))
search = request.args.get("search")
db_query = db.session.query(DockerResource).filter(DockerResource.resource_type == "VUL")
if search:
db_query = db_query.filter(
or_(
DockerResource.name.contains(search)
| DockerResource.app.contains(search)
| DockerResource.cve.contains(search)
)
)
page = db_query.order_by(DockerResource.updated_at.desc()).paginate(
page=page, per_page=page_size
)
data = []
for item in page.items:
info = model2dict(item)
info["docker_type_name"] = item.docker_type_name
info["status_name"] = item.status_name
# 判断key 是否存在
if cache.exists("DOCKER_RESOURCE_%s" % item.id):
info["building"] = True
else:
info["building"] = False
data.append(info)
return api_success({"total": page.total, "data": data})
@bp.get("/vulnerability/<int:pk>")
def vuln_detail(pk):
"""
漏洞列表
"""
item = DockerResource.get_by_id(pk)
return api_success({"data": model2dict(item)})
@bp.delete("/vulnerability/<int:pk>")
def vuln_delete(pk):
instance = DockerResource.get_by_id(pk)
instance.delete()
return api_success()
@bp.put("/vulnerability/<int:pk>")
def vuln_update(pk):
"""
漏洞列表
"""
item = DockerResource.get_by_id(pk)
data = request.get_json()
item.description = data["description"]
item.docker_type = data["docker_type"]
item.image = data["image"]
# 检查镜像是否存在
client = docker.DockerClient(Config.get_config(Config.KEY_DOCKER_API))
if data.get("image"):
try:
client.images.get(data["image"])
item.status = DockerResource.STATUS_BUILD
except NotFound:
item.status = DockerResource.STATUS_INIT
else:
item.status = DockerResource.STATUS_INIT
item.name = data["name"]
item.cve = data.get("cve", [])
item.app = data.get("app")
item.save()
return api_success({"data": model2dict(item)})
@bp.post("/vulnerability")
def vuln_create():
"""
添加漏洞
"""
data = request.get_json()
description = data["description"]
docker_type = data["docker_type"]
image = data["image"]
name = data["name"]
cve = data.get("cve", [])
app = data.get("app")
DockerResource.create(
description=description,
docker_type=docker_type,
image=image,
name=name,
resource_type="VUL",
app=app,
cve=cve,
)
return api_success({})
@bp.post("/vulnerability/<int:pk>/run")
def vuln_run(pk):
"""
添加漏洞
"""
try:
start_vuln_resource(pk, user_id=None, admin_id=g.user.id)
except ValueError as e:
return api_fail(msg=str(e))
return api_success({})
@bp.get("/vulnerability/runner")
def vuln_runner():
"""
添加漏洞
"""
page = int(request.args.get("page", 1))
page_size = int(request.args.get("page_size", 10))
db_query = db.session.query(DockerRunner).filter()
page = db_query.paginate(page=page, per_page=page_size)
data = []
for item in page.items:
info = model2dict(item)
info["resource"] = model2dict(item.resource)
if item.user_id:
info["username"] = item.user.username
else:
info["username"] = item.admin.username
info["container_id"] = item.container_id[:8]
data.append(info)
return api_success({"total": page.total, "data": data})
@bp.post("/vulnerability/runner/destroy")
def vulnerability_runner_destroy():
rid = request.get_json().get("id")
instance: DockerRunner = db.session.query(DockerRunner).get(rid)
docker_api = Config.get_config(Config.KEY_DOCKER_API)
if not instance:
return api_fail(msg="资源不存在")
client = docker.DockerClient(docker_api)
try:
docker_container = client.containers.get(instance.container_id)
docker_container.stop()
docker_container.remove()
except NotFound:
pass
instance.delete()
return api_success()
@bp.delete("/vulnerability/<int:pk>")
def resource_delete(pk):
"""
销毁容器
"""
instance: DockerRunner = db.session.query(DockerResource).get(pk)
docker_api = Config.get_config(Config.KEY_DOCKER_API)
client = docker.DockerClient(docker_api)
try:
image = client.images.get(instance.image)
image.remove(force=True)
except NotFound as e:
logger.error(e)
db.session.delete(instance)
db.session.commit()
return api_success({})
@bp.post("/vulnerability/import")
def vuln_import():
file = request.files["file"]
filename = file.filename
ext = filename.split(".")[-1]
if ext != "yaml":
return api_fail(msg="请上传yaml文件格式")
try:
yaml_data = yaml.safe_load(file)
except yaml.YAMLError:
return api_fail(msg="Error while parsing YAML file")
currentImage = [i[0] for i in db.session.query(DockerResource.image)]
bulk_create = []
# 获取当前主机镜像
client = docker.DockerClient(Config.get_config(Config.KEY_DOCKER_API))
docker_images = client.images.list()
tags = []
for im in docker_images:
tags += im.tags
for item in yaml_data:
image = item.get("image")
if not image:
continue
if image in currentImage:
logger.info("Pass Image:{}".format(item["image"]))
continue
if ":" in image:
image_diff = image
else:
image_diff = f"{image}:latest"
if image_diff in tags:
status = DockerResource.STATUS_BUILD
else:
status = DockerResource.STATUS_INIT
bulk_create.append(
DockerResource(
resource_type="CTF",
name=item["name"],
app=item.get("app"),
image=item["image"],
cve=item.get("cve", []),
status=status,
description=item.get("description"),
)
)
db.session.bulk_save_objects(bulk_create)
return api_success()
@bp.post("vulnerability/sync_vulnerability")
def sync_vulnerability():
"""
同步远程漏洞
"""
remote_repo = Config.get_config(Config.KEY_REMOTE_VULNERABILITY_REPOSITORY)
if not remote_repo:
return api_fail(msg="未配置远程漏洞仓库")
sync_remote_vulnerability_repo.apply_async(args=(remote_repo,), kwargs={"admin_id": g.user.id})
return api_success(msg="任务已提交")
================================================
FILE: app/api/health.py
================================================
import os
import time
from flask import Blueprint, jsonify
from app.extensions import cache, db
health_bp = Blueprint("health", __name__, url_prefix="/api/health")
@health_bp.route("/ping", methods=["GET"])
def ping():
"""简单的 ping 接口,用于检查 API 是否在线"""
return jsonify({"status": "success", "message": "pong", "timestamp": int(time.time())}), 200
@health_bp.route("/status", methods=["GET"])
def status():
"""检查所有服务的状态"""
try:
# 初始化结果对象
result = {
"status": "success",
"timestamp": int(time.time()),
"services": {
"api": {"status": "up"},
},
}
# 检查数据库连接
try:
db.session.execute("SELECT 1")
result["services"]["database"] = {
"status": "up",
"type": "mysql",
"host": os.environ.get("DB_HOST", "db"),
}
except Exception as e:
result["services"]["database"] = {
"status": "down",
"error": str(e),
"type": "mysql",
"host": os.environ.get("DB_HOST", "db"),
}
result["status"] = "partial_outage"
# 检查 Redis 连接
try:
cache.ping()
result["services"]["redis"] = {
"status": "up",
"host": os.environ.get("REDIS_HOST", "redis"),
}
except Exception as e:
result["services"]["redis"] = {
"status": "down",
"error": str(e),
"host": os.environ.get("REDIS_HOST", "redis"),
}
result["status"] = "partial_outage"
# 检查 Celery Worker
try:
# 在导入时可能出现问题,使用更安全的方式
try:
from app.extensions import celery
i = celery.control.inspect()
stats = i.stats()
if stats:
result["services"]["celery_worker"] = {
"status": "up",
"workers": list(stats.keys()),
}
else:
result["services"]["celery_worker"] = {
"status": "down",
"error": "No workers found",
}
result["status"] = "partial_outage"
except ImportError:
# 如果无法导入 celery,返回更安全的错误信息
result["services"]["celery_worker"] = {
"status": "unknown",
"error": "Celery module not available",
}
result["status"] = "partial_outage"
except Exception as e:
# 确保错误消息是字符串,避免 JSON 序列化问题
error_msg = str(e)
result["services"]["celery_worker"] = {"status": "down", "error": error_msg}
result["status"] = "partial_outage"
# 返回结果
status_code = 200 if result["status"] == "success" else 500
return jsonify(result), status_code
except Exception as e:
# 最外层异常处理,确保始终返回有效的 JSON
error_response = {
"status": "error",
"timestamp": int(time.time()),
"message": "Health check failed",
"error": str(e),
"services": {"api": {"status": "up"}},
}
return jsonify(error_response), 500
================================================
FILE: app/api/player/__init__.py
================================================
================================================
FILE: app/api/player/views.py
================================================
import logging
import random
import string
from datetime import datetime, timedelta
import docker
from docker.errors import NotFound
from flask import Blueprint, g, request, send_from_directory
from sqlalchemy import desc, func
from app.core.api import api_fail, api_success
from app.core.decorators import user_required
from app.core.tools import get_ip
from app.services.docker import start_docker_resource
from app.extensions import db
from app.models.admin import Config, Notice
from app.models.ctf import Answer, Attachment, CtfResource, Question
from app.models.user import User
from app.utils.security import check_password, create_token, hash_password
from app.tasks.player import ctf_finish_container
from app.services import player as player_service
bp = Blueprint("view", __name__, url_prefix="/api")
logger = logging.getLogger(__name__)
@bp.get("/upload/<path:filename>")
def send_upload_file(filename):
name = request.args.get("filename")
manager_folder = "upload"
return send_from_directory(manager_folder, filename, as_attachment=True, download_name=name)
def generate_flag():
"""
生成flag
return generate flag
"""
rd_str = "".join(random.sample(string.ascii_letters + string.digits, 32))
return "flag{ocean%s}" % rd_str
@bp.post("/login")
def login():
"""
用户登录
"""
data = request.get_json()
username = data.get("username")
password = data.get("password")
user = db.session.query(User).filter(User.username == username).one_or_none()
if user and check_password(user.password, password):
token = create_token()
user.token = token
db.session.commit()
return api_success({"token": token})
else:
return api_fail(msg="用户名不存在或密码错误!")
@bp.get("/info")
@user_required()
def info():
"""
获取用户信息
:return:
"""
user = g.user
data = {"username": user.username, "id": user.id}
return api_success({"data": data})
@bp.route("/register", methods=["get", "post"])
def register():
"""
用户注册
"""
data = request.get_json()
username = data.get("username")
password = data.get("password")
if not all([username, password]):
return api_fail(msg="用户名或密码不允许为空")
user = db.session.query(User).filter(User.username == username).one_or_none()
if user:
return api_fail(msg="该用户名已注册")
token = create_token()
user = User(username=username, password=hash_password(password), active=True, token=token)
db.session.add(user)
db.session.commit()
return api_success()
@bp.post("/logout")
@user_required()
def logout():
"""
用户登出
"""
user = g.user
user.token = None
db.session.commit()
return api_success()
@bp.get("/announcement")
def announcement():
"""用户端首页顶部公告(由后台配置控制)"""
enabled = Config.get_config(Config.KEY_HOME_ANNOUNCEMENT_ENABLED)
content = Config.get_config(Config.KEY_HOME_ANNOUNCEMENT_CONTENT)
link = Config.get_config(Config.KEY_HOME_ANNOUNCEMENT_LINK)
return api_success(
{
"data": {
"enabled": int(enabled),
"content": content,
"link": link,
}
}
)
@bp.post("/rest_pass")
@user_required()
def rest_pass():
"""
修改密码
:return:
"""
user = g.user
data = request.get_json()
old_password = data.get("old_password")
password = data.get("password")
# 校验当前密码
if not check_password(user.password, old_password):
return api_fail(msg="当前密码不匹配")
# 校验密码规则
user.password = hash_password(password)
# 推出登录
# user.token = None
db.session.commit()
return api_success()
@bp.get("/challenge")
@user_required(required=False)
def challenge_list():
"""
题目列表
:return:
"""
user = g.user
if user:
solved = [
i[0]
for i in db.session.query(Answer.question_id).filter(
Answer.user_id == user.id, Answer.status == Answer.status_ok
)
]
else:
solved = []
# 每个题目的解题人数
solved_query = (
db.session.query(Answer.question_id, func.count(Answer.id))
.filter(Answer.status == Answer.status_ok)
.group_by(Answer.question_id)
.all()
)
solved_cnt_dict = {i[0]: i[1] for i in solved_query}
subjects = request.args.get("subject")
query = db.session.query(Question).filter(Question.active.is_(True))
if subjects:
query = query.filter(Question.type == subjects)
data = []
for item in query:
data.append(
{
"id": item.id,
"type": item.type,
"name": item.name,
"score": item.score,
"desc": item.desc,
"active_flag": item.active_flag,
"solved_cnt": solved_cnt_dict.get(item.id, 0),
"is_solved": bool(item.id in solved),
}
)
return api_success({"data": data})
@bp.get("/challenge/<int:question>")
@user_required()
def challenge_detail(question):
"""
题目详情 包括已解决的用户情况 点赞情况
:param question:
:return:
"""
instance = db.session.query(Question).get(question)
if not instance:
return api_fail(msg="题目不存在、请刷新页面!")
answer_object = (
db.session.query(Answer)
.filter(
Answer.user_id == g.user.id,
Answer.status == Answer.status_ok,
Answer.question_id == question,
)
.first()
)
# 获取前三名
ans = (
db.session.query(User.username)
.select_from(Answer)
.filter(
Answer.question_id == question,
Answer.status == Answer.status_ok,
)
.join(User, User.id == Answer.user_id)
)
ans = [i[0] for i in ans][:3]
ans = list(ans) + [None] * (3 - len(list(ans)))
first_blood, second_blood, third_blood = ans
resource = (
db.session.query(CtfResource)
.filter(
CtfResource.user_id == g.user.id,
CtfResource.question_id == instance.id,
CtfResource.destroy_time > datetime.now(),
)
.first()
)
ip = Config.get_config(Config.KEY_IP)
if resource:
urls = []
for origin, port in resource.docker_runner.port_info.items():
urls.append({"url": "http://{}:{}".format(ip, port), "origin": origin})
container_data = {
"timeout": (resource.destroy_time - resource.updated_at).total_seconds(),
"create_time": resource.updated_at.strftime("%Y-%m-%d %H:%M:%S"),
"urls": urls,
}
else:
container_data = None
get_score = answer_object.score if answer_object else None
if instance.attachment:
attachment = instance.attachment.split(",")
attachment_query = db.session.query(Attachment).filter(Attachment.id.in_(attachment))
attachment_info = [
{"filename": i.filename, "uuid": i.id, "file_path": i.file_path}
for i in attachment_query
]
else:
attachment_info = []
data = {
"first_blood": first_blood,
"second_blood": second_blood,
"third_blood": third_blood,
"container": container_data,
"get_score": get_score,
"score": instance.score,
"id": instance.id,
"name": instance.name,
"attachment": [
{
"name": i["filename"],
"url": "/api/upload/{}?filename={}".format(i["file_path"], i["filename"]),
}
for i in attachment_info
],
"desc": instance.desc,
"active_flag": instance.active_flag,
"type": instance.type,
"solved": db.session.query(Answer)
.filter(Answer.question_id == instance.id, Answer.status == Answer.status_ok)
.count(),
"date_created": instance.updated_at.strftime("%y-%m-%d"),
}
return api_success({"data": data})
@bp.post("/challenge/<int:question>/start")
@user_required()
def question_start(question):
"""
创建一个题目容器
:param question:
:return:
"""
user = g.user
instance = db.session.query(Question).get(question)
if not instance.active_flag:
return api_fail(msg="静态题库无需动态生成")
if not instance.resource_id or not instance.active_flag:
return api_fail(msg="服务器没有资源")
flag = generate_flag()
logger.info(flag)
try:
docker_runner = start_docker_resource(instance.resource_id, user.id, flag=flag)
except ValueError as e:
logger.exception(e)
return api_fail(msg=str(e))
sec = Config.get_config(Config.KEY_CTF_TIMEOUT)
obj = CtfResource(
docker_runner_id=docker_runner.id,
flag=flag,
user_id=user.id,
question_id=instance.id,
destroy_time=datetime.now() + timedelta(seconds=sec),
)
db.session.add(obj)
db.session.flush()
db.session.commit()
# 延迟清除
ctf_finish_container.apply_async(args=(obj.id,), countdown=sec + 1)
return api_success({})
@bp.post("/challenge/<int:question>/delayed")
@user_required()
def question_delayed(question):
"""
延长容器时间
:param question:
:return:
"""
container = (
db.session.query(CtfResource)
.filter(CtfResource.user_id == g.user.id, CtfResource.question_id == question)
.order_by(CtfResource.id.desc())
.first()
)
if not container:
return api_fail(msg="当前状态无法延长题目时间")
# 最多延长三小时
if (container.destroy_time - timedelta(hours=3)) > datetime.now():
return api_fail(msg="时间已达上限")
container.destroy_time = container.destroy_time + timedelta(minutes=10)
container.save()
return api_success()
@bp.post("/challenge/<int:question>/destroy")
@user_required()
def question_destroy(question):
"""
销毁容器
:param question:
:return:
"""
instance = db.session.query(Question).get(question)
if not instance.active_flag:
return api_fail("静态题库无需动态生成")
ctf_resource = (
db.session.query(CtfResource)
.filter(CtfResource.question_id == instance.id, CtfResource.user_id == g.user.id)
.first()
)
if not ctf_resource:
return api_fail(msg="环境已销毁")
client = docker.DockerClient(Config.get_config(Config.KEY_DOCKER_API))
# 默认一个docker run 只能绑定一个用户吧 所以直接删除docker run 采用数据库的连表删除自动删除其他索引
try:
container = client.containers.get(ctf_resource.docker_runner.container_id)
container.stop()
container.remove()
except NotFound:
logger.warning("环境异常:{}".format(ctf_resource.docker_runner.container_id))
docker_run = ctf_resource.docker_runner
db.session.delete(ctf_resource)
db.session.delete(docker_run)
db.session.commit()
return api_success()
@bp.post("/user")
@user_required()
def user_center():
"""
修改用户信息
"""
data = request.get_json()
username = data.get("username")
user = g.user
# 检测用户名是否被占用
if db.session.query(User).filter(User.username == username, User.id != user.id).first():
return api_fail("用户名已被占用")
user.username = username
db.session.commit()
return api_success()
@bp.post("challenge/submit")
@user_required()
def challenge_submit():
ip = get_ip()
data = request.get_json()
question_id = data.get("id")
flag = data.get("flag", "").strip()
# 判断是否有提交记录
challenge = Question.get_by_id(question_id)
answer = (
db.session.query(Answer)
.filter(
Answer.question_id == question_id,
Answer.status == Answer.status_ok,
Answer.user_id == g.user.id,
)
.count()
)
if answer:
return api_fail(msg="请勿重复提交")
# 判断是否是别人的答案
copy_resource = (
db.session.query(CtfResource)
.filter(CtfResource.flag == flag, CtfResource.user_id != g.user.id)
.one_or_none()
)
if copy_resource:
# todo 添加作弊记录
Answer.create(
question_id=question_id, user_id=g.user.id, flag=flag, ip=ip, status=Answer.status_cheat
)
return api_fail(msg="检测到作弊、本次答题无效!")
current_ctf_resource = None
if challenge.active_flag:
current_ctf_resource = (
db.session.query(CtfResource)
.filter(CtfResource.user_id == g.user.id, CtfResource.question_id == question_id)
.order_by(CtfResource.updated_at.desc())
.first()
)
if current_ctf_resource:
ok_flag = current_ctf_resource.flag.strip()
else:
return api_fail(msg="当前状态无法作答、请启动环境!")
else:
ok_flag = challenge.flag.strip()
if ok_flag == flag:
Answer.create(
question_id=question_id,
user_id=g.user.id,
flag=flag,
ip=ip,
status=Answer.status_ok,
score=challenge.score,
)
if current_ctf_resource:
# 停止容器
ctf_finish_container.apply_async(
args=(current_ctf_resource.id,), kwargs={"current": True}
)
return api_success(msg="答案正确、获得{}积分".format(challenge.score))
else:
Answer.create(
question_id=question_id, user_id=g.user.id, flag=flag, ip=ip, status=Answer.status_error
)
return api_fail(msg="答案错误")
@bp.get("notice")
def notice():
"""
公告列表
"""
# 公告
notices = []
query = db.session.query(Notice)
query = query.filter(Notice.active.is_(True))
notice_query = query.order_by(desc(Notice.is_top), desc(Notice.id)).all()
for item in notice_query:
notices.append(
{
"id": item.id,
"is_top": item.is_top,
"content": item.content,
"create_time": item.updated_at.strftime("%Y-%m-%d %H:%M"),
}
)
return api_success({"data": notices})
@bp.get("rank/score")
def score_rank():
"""
积分排行
"""
# 公告
code, data = player_service.score_rank(**request.args)
return api_success(data=data)
================================================
FILE: app/api/player/vulnerability.py
================================================
import logging
import docker
from docker.errors import NotFound
from flask import Blueprint, g, request
from sqlalchemy import func, or_
from sqlalchemy.exc import IntegrityError
from app.core.api import api_fail, api_success
from app.core.decorators import user_required
from app.core.tools import model2dict
from app.extensions import db
from app.models.admin import Config
from app.models.docker import DockerResource, DockerRunner
from app.tasks.vulnerability import start_vuln_resource, stop_vulnerability_resource
logger = logging.getLogger(__name__)
bp = Blueprint("user_vuln", __name__, url_prefix="/api/")
@bp.get("/vulnerability")
@user_required(required=False)
def vuln_list():
"""
漏洞列表
"""
page = int(request.args.get("page", 1))
page_size = int(request.args.get("page_size", 100))
search = request.args.get("search")
out_ip = Config.get_config(Config.KEY_IP)
db_query = db.session.query(DockerResource).filter(
DockerResource.status == DockerResource.STATUS_BUILD, DockerResource.resource_type == "VUL"
)
if search:
db_query = db_query.filter(
or_(
DockerResource.name.contains(search)
| DockerResource.app.contains(search)
| DockerResource.cve.contains(search)
)
)
if g.user:
runner = db.session.query(DockerRunner).filter(DockerRunner.user_id == g.user.id)
rids = {i.resource_id: i for i in runner}
else:
rids = {}
page = db_query.order_by(DockerResource.created_at.desc()).paginate(
page=page, per_page=page_size
)
data = []
for item in page.items:
info = model2dict(item)
info["docker_type_name"] = item.docker_type_name
info["status_name"] = item.status_name
rid: DockerRunner = rids.get(item.id)
if rid:
info["runner"] = {"id": rid.id, "out_ip": out_ip, "port_info": rid.port_info}
else:
info["runner"] = {}
data.append(info)
return api_success({"total": page.total, "data": data})
@bp.get("/vulnerability/apps")
def vuln_apps():
query = (
db.session.query(DockerResource.app, func.count(DockerResource.id))
.filter(DockerResource.resource_type == "VUL")
.group_by(DockerResource.app)
)
data = []
for app, cnt in query.all():
data.append({"app": app, "cnt": cnt})
return api_success({"data": data})
@bp.get("/vulnerability/<int:pk>")
def vuln_detail(pk):
instance = DockerResource.get_by_id(pk)
return api_success({"data": model2dict(instance)})
@bp.post("/vulnerability/<int:pk>/start")
@user_required()
def vuln_start(pk):
try:
rid = start_vuln_resource(pk, user_id=g.user.id)
if rid:
etc = Config.get_config(Config.KEY_VULNERABILITY_TIMEOUT)
logger.info(etc)
stop_vulnerability_resource.apply_async(args=(rid,), countdown=etc)
except IntegrityError:
return api_fail(msg="请勿重复创建环境")
except ValueError:
return api_fail(msg="资源启动失败,请联系管理员!")
return api_success()
@bp.post("/vulnerability/<int:pk>/stop")
@user_required()
def vuln_stop(pk):
docker_api = Config.get_config(Config.KEY_DOCKER_API)
instance: DockerRunner = (
db.session.query(DockerRunner)
.filter(DockerRunner.user_id == g.user.id, DockerRunner.resource_id == pk)
.first()
)
if not instance:
return api_success()
client = docker.DockerClient(docker_api)
try:
docker_container = client.containers.get(instance.container_id)
docker_container.stop()
docker_container.remove()
except NotFound:
pass
except docker.errors.APIError as e:
logger.exception(e)
return api_fail(msg="操作失败,请稍后再试")
instance.delete()
return api_success()
================================================
FILE: app/api/player/ws.py
================================================
import logging
import threading
import typing
import docker
from docker.models.containers import Container
from flask import request
from flask_socketio import disconnect, emit
from app.extensions import db, socketio
from app.models.admin import Config
from app.models.docker import DockerRunner
from app.models.user import User
logger = logging.getLogger(__name__)
class SocketClient:
def __init__(self, sid, user):
self.sid = sid
self.user = user
def close(self):
raise NotImplementedError
class LogsSession(SocketClient):
def __init__(self, sid, user, container: Container):
super().__init__(sid, user)
self.log_thread = None
self.logs_stream = None
self.container = container
self.socket = None
self.stop_thread = False
def init_socket(self):
try:
self.logs_stream = self.container.logs(stream=True, follow=True)
self.log_thread = threading.Thread(target=self.stream_logs)
self.log_thread.start()
except Exception as e:
logger.exception("Failed to initialize log stream")
raise e
def stream_logs(self):
try:
for log in self.logs_stream:
print(123, log)
if self.stop_thread:
break
# 发送日志到前端
socketio.emit("message", {"logs": {"output": log.decode("utf-8")}}, room=self.sid)
except Exception:
logger.exception("Error in log streaming")
def close(self):
self.stop_thread = True
if hasattr(self, "log_thread"):
self.log_thread.join()
class TerminalSession(SocketClient):
def __init__(self, sid, user, container):
super().__init__(sid, user)
self.container = container
self.exec_id = None
self.socket = None
self._create_exec()
def _create_exec(self):
try:
# 使用 script 命令来确保输出格式正确
exec_create = self.container.client.api.exec_create(
self.container.id,
'script -q -c "/bin/bash" /dev/null', # 使用 script 命令包装 bash
tty=True,
stdin=True,
stdout=True,
stderr=True,
environment={"TERM": "xterm", "COLUMNS": "80", "LINES": "24"},
)
self.exec_id = exec_create["Id"]
self.socket = self.container.client.api.exec_start(
self.exec_id, tty=True, socket=True, demux=False
)._sock
except Exception as e:
logger.exception("Failed to create exec instance")
raise e
def handle_input(self, input_data):
if not self.socket:
raise Exception("Shell session not initialized")
try:
# Send input to container
self.socket.send(input_data.encode("utf-8"))
# Read response with timeout
output = b""
self.socket.settimeout(0.1)
try:
while True:
chunk = self.socket.recv(4096)
if not chunk:
break
output += chunk
except Exception:
# Timeout or no more data
pass
# 处理输出,移除可能的控制字符
text = output.decode("utf-8", errors="replace")
# 移除 ANSI 转义序列
import re
text = re.sub(r"\x1b\[[0-9;]*[mGKHF]", "", text)
# 确保每行左对齐
lines = text.splitlines()
cleaned_lines = [line.lstrip() for line in lines]
return "\n".join(cleaned_lines)
except Exception as e:
logger.exception("Error handling terminal input")
raise e
def close(self):
try:
if self.socket:
self.socket.close()
except Exception:
logger.exception("Error closing terminal session")
session_clients: typing.Dict[str, typing.Union[LogsSession, TerminalSession]] = {}
@socketio.on("connect")
def handle_connect(auth):
# 身份绑定
namespace = request.args.get("namespace")
runner_id = request.args.get("container_id")
sid = getattr(request, "sid", None)
token = auth.get("token")
logger.info(f"Client connected: {token}")
if not token:
disconnect()
return False
user = db.session.query(User).filter(User.token == token).one_or_none()
if not user:
logger.error(f"Invalid token: {token}")
disconnect()
return False
if sid:
if sid in session_clients:
# 重复登录,关闭旧连接
session_clients[sid].close()
try:
# 创建 Docker 客户端
docker_api = Config.get_config(Config.KEY_DOCKER_API)
client = docker.DockerClient(docker_api)
if not runner_id:
logger.error("No container_id provided")
disconnect()
return False
# 获取容器实例并验证权限
runner = DockerRunner.get_by_id(runner_id)
if not runner or (runner.user_id != user.id):
logger.error(f"Invalid container access: {runner_id}")
disconnect()
return False
container = client.containers.get(runner.container_id)
if namespace == "logs":
s = LogsSession(sid, user, container)
s.init_socket()
session_clients[sid] = s
out = None
else:
logger.info(
f"Initializing terminal session for user: {user.username}, container: {runner.container_id}"
)
# 创建新的终端会话
session_clients[sid] = TerminalSession(sid, user, container)
# send black command
out = session_clients[sid].handle_input("")
if out:
emit("message", {"terminal": {"output": out}})
logger.info(
f"Terminal session created for user: {user.username}, container: {runner.container_id}"
)
return True
except Exception as e:
logger.exception(f"Failed to initialize terminal session: {str(e)}")
disconnect()
return False
return False
@socketio.on("disconnect")
def handle_disconnect():
sid = getattr(request, "sid", None)
if sid and sid in session_clients:
session = session_clients[sid]
logger.info(f"Client disconnected: {session.user.username}")
session.close()
del session_clients[sid]
@socketio.on("terminal")
def handle_message(message):
sid = getattr(request, "sid", None)
session = session_clients.get(sid)
if not session:
logger.warning(f"No session found for SID: {sid}")
disconnect()
return
try:
out = session.handle_input(message)
if out:
emit("message", {"terminal": {"output": out}})
except Exception as e:
logger.exception("Error processing terminal message")
emit("message", {"terminal": {"error": str(e)}})
session.close()
del session_clients[sid]
disconnect()
================================================
FILE: app/api/route.py
================================================
from app.extensions import db
def register_blueprints(flask_app):
"""
这里如果在开头引用回出现循环引用的问题
app_factory 引入了bp bp中引入了task task 引入了app_factory中的celery 导致无法启动celery
"""
# register admin
from app.api.admin import ctf, docker, system, vulnerability
flask_app.register_blueprint(ctf.bp)
flask_app.register_blueprint(docker.bp)
flask_app.register_blueprint(system.bp)
flask_app.register_blueprint(vulnerability.bp)
# register user
from app.api.player import views, vulnerability
flask_app.register_blueprint(vulnerability.bp)
# flask_app.register_blueprint(ws.bp)
flask_app.register_blueprint(views.bp)
# 健康检查API
from app.api.health import health_bp
flask_app.register_blueprint(health_bp)
def remove_db_session(_) -> None:
# 需要手动删除session 不然多线程会遇到读取旧数据的问题
try:
db.session.remove()
except AttributeError:
pass
flask_app.teardown_request_funcs.setdefault(None, []).append(remove_db_session)
================================================
FILE: app/core/api.py
================================================
import typing
from flask import jsonify
def api_success(
data: typing.Union[typing.Dict, None] = None,
msg="",
results: typing.List[typing.Dict] = None,
total=None,
):
if data is None:
data = {}
if "code" in data:
raise AssertionError("无法使用关键字:code")
data["code"] = 0
if msg:
data["message"] = msg
if results:
data["results"] = results
if total is not None:
data["total"] = total
return jsonify(data)
def response_ok(
data: typing.Union[typing.Dict, None] = None,
msg="",
results: typing.List[typing.Dict] = None,
total=None,
):
response = {"code": 0, "message": msg}
if msg is not None:
response["message"] = msg
if isinstance(data, dict) or data is None:
response["data"] = data
if total is not None:
response["total"] = total
if results is not None:
response["results"] = results
return response
def api_fail(code=1, msg="参数错误", data: typing.Union[typing.Dict, None] = None):
if data is None:
data = {}
if "code" in data:
raise AssertionError("无法使用关键字:code")
data["code"] = code
data["message"] = msg
return jsonify(data)
================================================
FILE: app/core/command.py
================================================
import click
from flask.cli import with_appcontext
from app.extensions import db
from app.utils.security import hash_password
@click.command("init-db")
@with_appcontext
def init_db_command():
"""Clear existing data and create new tables."""
click.echo("Initialized the database.")
@click.command("init-app")
@with_appcontext
def init_superuser():
"""
初始化管理员账号 admin:superuser/admin
"""
from app.models.admin import Admin
admin = db.session.query(Admin).filter(Admin.username == "superuser").one_or_none()
if admin:
admin.password = hash_password("admin")
db.session.commit()
else:
pass
@click.command("init-data")
@with_appcontext
def init_data():
"""
初始化数据 删除表数据 重新创建
"""
from app.models.admin import Admin, Role
db.drop_all()
db.create_all()
db.session.commit()
# 创建角色
for name in ("超级管理员", "运维管理员"):
db.session.add(Role(name=name))
# 添加超级管理员
db.session.add(Admin(username="superuser", role_id=1, password=hash_password("admin")))
db.session.commit()
def init_app(app):
"""Register database functions with the Flask app. This is called by
the application factory.
"""
app.cli.add_command(init_db_command)
app.cli.add_command(init_superuser)
app.cli.add_command(init_data)
================================================
FILE: app/core/const.py
================================================
class ConstCacheKey:
IP_DAY_SET = "ip-%s"
REQ_DAY_COUNT = "req-%s"
# tar包编译日志存储 1 day
TASK_BUILD_LOG = "task-build-%s"
================================================
FILE: app/core/decorators.py
================================================
import logging
from functools import wraps
from flask import g, request
from app.core.api import api_fail
from app.extensions import db
from app.models.user import User
logger = logging.getLogger("app")
def user_required(required=True):
"""
管理员权限认证
:return:
"""
def decorator(fn):
@wraps(fn)
def inner(*args, **kwargs):
authorization = request.headers.get("Authorization")
if not authorization:
if required:
return api_fail(msg="权限验证失败!", code=401)
else:
g.user = None
return fn(*args, **kwargs)
user = db.session.query(User).filter(User.token == authorization).one_or_none()
if user:
g.user = user
return fn(*args, **kwargs)
else:
return api_fail(msg="认证已过期、请重新登录!", code=401)
return inner
return decorator
================================================
FILE: app/core/error_handlers.py
================================================
import logging
import redis
from flask import Flask, jsonify, make_response, request
from flask_pydantic import ValidationError
from werkzeug.exceptions import NotFound
from app.core.exceptions import RestExceptions
logger = logging.getLogger(__name__)
def register_error_handlers(app: Flask):
app.register_error_handler(404, exception_handle)
app.register_error_handler(RestExceptions, exception_handle)
app.register_error_handler(ValidationError, handle_pydantic_validation_error)
def handle_pydantic_validation_error(error: ValidationError):
# 格式化错误信息
response = {"code": 400}
if error.body_params:
err = error.body_params[0]
msg = f"{','.join(err['loc'])} {err['msg']}"
response["message"] = msg
return make_response(jsonify(response), 400)
def exception_handle(e):
if isinstance(e, redis.exceptions.ConnectionError):
return make_response(jsonify({"message": "缓存服务不可用"}), 503)
if isinstance(e, RestExceptions):
return make_response(jsonify({"message": e.msg, "code": e.code}), e.status)
if isinstance(e, NotFound):
logger.warning(f"Resource not found: {request.remote_addr} {request.path} {e}")
return make_response(jsonify({"message": "资源不存在", "code": 404}), 200)
# 返回通用错误响应
logger.error("Exception occurred", exc_info=True)
return make_response(jsonify({"message": "服务器内部错误", "code": 500, "error": str(e)}), 500)
================================================
FILE: app/core/exceptions.py
================================================
class RestExceptions(Exception):
default_msg = "error message"
default_code = 400
default_status = 400
def __init__(self, msg=None, code=None, status=None):
self.msg = msg or self.default_msg
self.code = code or self.default_code
self.status = status or self.default_status or self.default_code
================================================
FILE: app/core/flask_celery.py
================================================
from celery import Task as TaskBase
from flask import current_app
from app.extensions import db
class ContextTask(TaskBase):
abstract = True
"""
celery 默认使用的是进程 所以这里可以使用全局变量db
"""
def after_return(self, status, retval, task_id, args, kwargs, info):
"""
After each Celery task, teardown our db session.
FMI: https://gist.github.com/twolfson/a1b329e9353f9b575131
Flask-SQLAlchemy uses create_scoped_session at startup which avoids any setup on a
per-request basis. This means Celery can piggyback off of this initialization.
"""
if current_app.config.get("SQLALCHEMY_COMMIT_ON_TEARDOWN"):
if not isinstance(retval, Exception):
db.session.commit()
db.session.remove()
================================================
FILE: app/core/middlewares.py
================================================
"""
中间件
"""
import logging
from datetime import datetime
from flask import g, request
from app.core.api import api_fail
from app.extensions import cache, db
from app.models.admin import Admin
logger = logging.getLogger(__name__)
def before_req_cache_ip():
"""
缓存IP
"""
if request.access_route:
ip = request.access_route[0]
else:
ip = request.remote_addr or "127.0.0.1"
today = datetime.today().strftime("%Y%m%d")
cache.sadd("ip-%s" % today, ip)
cache.incr("req-%s" % today)
WHITE_PATH_LIST = ("/api/admin/login",)
def global_admin_required():
"""
admin 请求拦截器 不需要针对每个接口使用装饰器
"""
if request.method == "OPTIONS":
return
if request.path in WHITE_PATH_LIST:
return
if request.path.startswith("/api/admin"):
# 检查管理员权限
authorization = request.environ.get("HTTP_AUTHORIZATION")
if not authorization:
return api_fail(msg="Forbidden", code=401)
admin = db.session.query(Admin).filter(Admin.token == authorization).first()
if admin:
g.user = admin
# 检查用户权限
if request.method in ["POST", "PUT", "PATCH", "DELETE"] and admin.role_name == "访客":
return api_fail(msg="访客无权操作", code=403)
else:
return api_fail(msg="Forbidden", code=401)
================================================
FILE: app/core/tools.py
================================================
import datetime
from flask import request
def get_ip():
if request.access_route:
ip = request.access_route[0]
else:
ip = request.remote_addr or "127.0.0.1"
return ip
def model2dict(instance):
data = {}
for k, v in instance.__dict__.items():
if k.startswith("_"):
continue
if isinstance(v, datetime.datetime):
data[k] = v.strftime("%Y-%m-%d %H:%M:%S")
else:
data[k] = v
return data
================================================
FILE: app/extensions.py
================================================
from celery import Celery
from flask_redis import FlaskRedis
from flask_socketio import SocketIO
# noinspection PyUnresolvedReferences
from app.models import db # noqa
from config import config
cache = FlaskRedis()
celery = Celery(__name__)
celery.config_from_object(config)
socketio = SocketIO(cors_allowed_origins="*")
================================================
FILE: app/models/__init__.py
================================================
# -*- coding: utf-8 -*-
import logging
from datetime import datetime
from typing import Optional, Type, TypeVar
from flask_sqlalchemy import SQLAlchemy as SQLAlchemyBase # type: ignore
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
T = TypeVar("T", bound="Model")
log = logging.getLogger(__name__)
DEFAULT_DATETIME_FORMAT = "%Y-%m-%d %H:%M"
class Base(DeclarativeBase):
pass
db = SQLAlchemyBase(model_class=Base)
Column = db.Column
relationship = db.relationship
class CRUDMixin(db.Model):
"""Mixin that adds convenience methods for CRUD (create, read, update, delete) operations."""
__abstract__ = True
@classmethod
def create(cls, **kwargs):
"""Create a new record and save it the database."""
instance = cls(**kwargs)
return instance.save()
def update(self, commit=True, **kwargs):
"""Update specific fields of a record."""
for attr, value in kwargs.items():
setattr(self, attr, value)
if commit:
return self.save()
return self
def save(self, commit=True):
"""Save the record."""
db.session.add(self)
if commit:
db.session.commit()
return self
def delete(self, commit: bool = True) -> None:
"""Remove the record from the database."""
if hasattr(self, "deleted"):
setattr(self, "deleted", True)
self.save()
return
db.session.delete(self)
if commit:
return db.session.commit()
return
class Model(CRUDMixin):
"""Base model class that includes CRUD convenience methods."""
__abstract__ = True
id = Column(db.Integer, primary_key=True)
created_at: Mapped[datetime] = mapped_column(db.DateTime, default=datetime.now)
updated_at: Mapped[datetime] = mapped_column(
db.DateTime,
default=datetime.now,
onupdate=datetime.now,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@classmethod
def get_by_id(cls: Type[T], record_id) -> Optional[T]:
"""Get record by ID."""
if any(
(
isinstance(record_id, str) and record_id.isdigit(),
isinstance(record_id, (int, float)),
)
):
return db.session.get(cls, int(record_id))
return None
@property
def create_time_format(self):
return self.updated_at.strftime("%Y-%m-%d %H:%M")
# 把SQLAlchemy查询对象转换成字典
def to_dict(self):
_dict = {}
for col in self.__table__.columns:
val = getattr(self, col.name)
if isinstance(val, datetime):
_dict[col.name] = val.strftime(DEFAULT_DATETIME_FORMAT)
else:
_dict[col.name] = val
return _dict
================================================
FILE: app/models/admin.py
================================================
import datetime
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from . import Model, db
class Role(Model):
__tablename__ = "role"
"""
角色 后面会关联权限
"""
id: Mapped[int] = mapped_column(db.Integer, primary_key=True)
name: Mapped[str] = mapped_column(
db.String(256),
nullable=False,
unique=True,
)
class Admin(Model):
__tablename__ = "admin"
username: Mapped[str] = mapped_column(
db.String(256), unique=True, nullable=False, comment="用户名"
)
password: Mapped[str] = mapped_column(
db.String(512),
nullable=False,
comment="密码",
)
role_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey("role.id"))
role: Mapped["Role"] = relationship("Role")
active: Mapped[bool] = mapped_column(
db.Boolean(),
comment="是否启用",
default=True,
)
login_time: Mapped[datetime.datetime] = mapped_column(
db.DateTime,
default=None,
nullable=True,
)
token: Mapped[str] = mapped_column(
db.String(64),
comment="token",
nullable=True,
unique=True,
)
# task_list: Mapped['TaskList']
# = relationship('TaskList')
# backref='admin'
@property
def role_name(self):
return self.role.name if self.role else ""
class TaskList(Model):
__tablename__ = "task_list"
STATUS_WAIT = 1 # 排队
STATUS_CANCEL = 2 # 取消
STATUS_RUN = 3 # 执行中
STATUS_ERROR = 4 # 执行错误
STATUS_DONE = 5 # 执行完成
STATUS_CHOICES = (
(STATUS_WAIT, "排队"),
(STATUS_CANCEL, "取消"),
(STATUS_RUN, "执行中"),
(STATUS_ERROR, "执行错误"),
(STATUS_DONE, "执行完成"),
)
admin_id = mapped_column(
db.Integer,
ForeignKey("admin.id"),
comment="操作用户",
)
status = mapped_column(db.Integer, default=STATUS_WAIT, comment="任务状况")
title = mapped_column(db.String(64), comment="任务标题")
target_id = mapped_column(
db.String(32),
comment="操作对象ID 可以是主机 容器 镜像 题库等",
)
remark = mapped_column(db.Text, comment="备注 报错错误信息记录")
@property
def status_name(self):
return dict(self.STATUS_CHOICES)[self.status]
class TaskLog(Model):
__tablename__ = "task_log"
task_id = mapped_column(db.Integer, ForeignKey("task_list.id"))
content = mapped_column(db.String(1024), comment="内容")
class RequestState(Model):
"""
每日请求统计
"""
__tablename__ = "request_state"
ip_count: Mapped[int] = mapped_column(db.Integer)
req_count: Mapped[int] = mapped_column(db.Integer)
day: Mapped[datetime.date] = mapped_column(db.Date, comment="日期")
class Notice(Model):
__tablename__ = "notice"
active: Mapped[bool] = mapped_column(db.Boolean(), default=True)
is_top: Mapped[bool] = mapped_column(db.Boolean(), default=False)
content: Mapped[str] = mapped_column(db.String(1024), comment="内容")
class Operator(Model):
"""
行为审计日志
"""
__tablename__ = "operator"
username: Mapped[str] = mapped_column(db.String(255), comment="操作人用户名")
code: Mapped[bool] = mapped_column(
db.Boolean(),
default=True,
comment="操作结果",
)
ip: Mapped[str] = mapped_column(db.String(15), comment="操作IP")
content: Mapped[str] = mapped_column(db.String(128), comment="操作内容")
role: Mapped[str] = mapped_column(
db.String(10),
comment="操作人角色",
nullable=True,
)
class Config(Model):
KEY_IP = "ip"
KEY_DOCKER_API = "docker_api"
KEY_PORT_RANGE = "port_range"
KEY_CTF_TIMEOUT = "ctf_timeout"
KEY_VULNERABILITY_TIMEOUT = "vulnerability_timeout"
KEY_REMOTE_VULNERABILITY_REPOSITORY = "remote_vulnerability_repository"
KEY_CTF_REPOSITORY = "ctf_repository"
KEY_HOME_ANNOUNCEMENT_ENABLED = "home_announcement_enabled"
KEY_HOME_ANNOUNCEMENT_CONTENT = "home_announcement_content"
KEY_HOME_ANNOUNCEMENT_LINK = "home_announcement_link"
CONFIG_MAP = {
KEY_IP: (str, "127.0.0.1"),
KEY_DOCKER_API: (str, "unix:///var/run/docker.sock"),
KEY_PORT_RANGE: (str, "40000-50000"),
KEY_CTF_TIMEOUT: (int, 180),
KEY_VULNERABILITY_TIMEOUT: (int, 1800),
KEY_REMOTE_VULNERABILITY_REPOSITORY: (
str,
"https://github.com/tongchengbin/vuldb.git",
),
KEY_CTF_REPOSITORY: (str, "https://github.com/tongchengbin/ctfdb.git"),
KEY_HOME_ANNOUNCEMENT_ENABLED: (int, 0),
KEY_HOME_ANNOUNCEMENT_CONTENT: (str, ""),
KEY_HOME_ANNOUNCEMENT_LINK: (str, ""),
}
__tablename__ = "config"
key: Mapped[str] = mapped_column(db.String(255), comment="键")
val: Mapped[str] = mapped_column(db.Text, comment="值")
@staticmethod
def get_config(key):
if key not in Config.CONFIG_MAP:
raise ValueError("该KEY值不是合法的配置键")
val_type = Config.CONFIG_MAP[key][0]
val_default = Config.CONFIG_MAP[key][1]
config_item = db.session.query(Config).filter(Config.key == key).first()
if config_item:
config_val = config_item.val
return val_type(config_val)
else:
return val_default
class MessageType:
TYPE_ADMIN = 0
TYPE_DOCKER = 1
class MessageLevel:
INFO = 0
WARN = 1
ERROR = 2
class AdminMessage(Model):
"""
管理员消息
"""
__tablename__ = "admin_message"
mtype: Mapped[int] = mapped_column(
db.Integer, comment="消息类型", default=MessageType.TYPE_ADMIN
)
admin_id: Mapped[int] = mapped_column(
db.Integer,
ForeignKey("admin.id"),
comment="关联管理员",
)
content: Mapped[str] = mapped_column(db.String(1024))
read: Mapped[bool] = mapped_column(
db.Boolean(),
default=False,
comment="是否已读",
)
level: Mapped[int] = mapped_column(db.Integer, comment="重要级别")
================================================
FILE: app/models/ctf.py
================================================
"""
用户相关模型
"""
from enum import Enum
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.extensions import db
from app.models import Model
from app.models.docker import DockerResource, DockerRunner
from app.models.user import User
class QType(Enum):
web = "web"
misc = "Misc"
reverse = "Reverse"
pwn = "Pwn"
crypto = "Crypto"
iot = "Iot"
class Attachment(Model):
"""
附件表
"""
__tablename__ = "attachment"
filename: Mapped[str] = mapped_column(db.String(256), comment="文件名")
file_path: Mapped[str] = mapped_column(db.String(256), comment="文件相对路径")
class ImageResource(Model):
__tablename__ = "image_resource"
"""
上传的镜像资源
"""
STATUS_BUILDING = "building"
STATUS_ERROR = "error"
STATUS_SUCCESS = "success"
status: Mapped[str] = mapped_column(db.String(8), default=STATUS_BUILDING, comment="状态")
host_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey("docker_host.id"))
name: Mapped[str] = mapped_column(db.String(32), comment="名称")
version: Mapped[str] = mapped_column(db.String(64), comment="版本")
cpu: Mapped[int] = mapped_column(db.Integer, comment="CPU个数")
memory: Mapped[int] = mapped_column(db.Integer, comment="内存大小M")
file_id: Mapped[int] = mapped_column(
db.Integer, db.ForeignKey("attachment.id"), comment="文件名"
)
file: Mapped[Attachment] = relationship(
"Attachment",
)
build_result: Mapped[str] = mapped_column(db.String(4096), comment="镜像状态说明")
class Question(Model):
__tablename__ = "ctf_question"
name: Mapped[str] = mapped_column(db.String(256), nullable=False, comment="题目名称")
type: Mapped[str] = mapped_column(db.String(16), comment="分类")
active: Mapped[bool] = mapped_column(db.Boolean(), default=True, comment="是否启用")
score: Mapped[int] = mapped_column(db.Integer, default=10, comment="积分")
desc: Mapped[str] = mapped_column(db.String(1024), default="", comment="描述")
flag: Mapped[str] = mapped_column(db.String(64), comment="Flag", nullable=True)
active_flag: Mapped[bool] = mapped_column(db.Boolean(), default=False, comment="是否时动态Flag")
attachment: Mapped[str] = mapped_column(db.String(64), comment="附件", nullable=True)
resource_id: Mapped[int] = mapped_column(
db.Integer, db.ForeignKey("docker_resource.id"), comment="关联compose", nullable=True
)
resource: Mapped[DockerResource] = relationship(DockerResource)
class CtfResource(Model):
__tablename__ = "ctf_resource"
"""
实际的容器资源 不一定是实际的主机容器 主要是用来记录用户对容器的使用 同时绑定Flag
"""
docker_runner_id: Mapped[int] = mapped_column(
db.ForeignKey("docker_runner.id", ondelete="cascade"), comment="compose ID"
)
docker_runner: Mapped[DockerRunner] = relationship(DockerRunner)
flag: Mapped[str] = mapped_column(db.String(64), nullable=True, comment="环境flag")
user_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey("user.id"), comment="关联用户")
user: Mapped[User] = relationship(User, backref="container_ref")
destroy_time: Mapped[db.DateTime] = mapped_column(db.DateTime, comment="销毁时间")
question_id: Mapped[int] = mapped_column(db.ForeignKey("ctf_question.id"), comment="对应的题库")
question: Mapped[Question] = relationship(Question, backref="container_ref")
class Answer(Model):
"""
答题记录表
"""
status_ok = 1 # 有效
status_error = 2 # "无效"
status_cheat = 3 # "作弊"
status_repeat = 4 # "有效不计分"
status_choices = (
(status_ok, "有效"),
(status_error, "无效"),
(status_cheat, "作弊"),
(status_repeat, "有效不计分"),
)
__tablename__ = "ctf_answer"
status: Mapped[int] = mapped_column(default=1, comment="状态")
user_id: Mapped[int] = mapped_column(
db.ForeignKey("user.id", ondelete="CASCADE"), comment="关联用户"
)
question_id: Mapped[int] = mapped_column(
db.ForeignKey("ctf_question.id", ondelete="CASCADE"), comment="对应题目"
)
rank: Mapped[int] = mapped_column(db.Integer, comment="解题名次", default=0)
flag: Mapped[str] = mapped_column(db.String(64), comment="提交内容")
score: Mapped[int] = mapped_column(db.Integer(), default=0, comment="分数")
ip: Mapped[str] = mapped_column(db.String(64), comment="提交答案IP")
@property
def status_name(self):
return dict(self.status_choices).get(self.status)
================================================
FILE: app/models/docker.py
================================================
from datetime import datetime
from sqlalchemy import Column, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
from app.models import Model, relationship
from app.models.admin import Admin
from app.models.user import User
from . import db
class Host(Model):
__tablename__ = "docker_host"
name = Column(db.String(256), unique=True, nullable=False, comment="用户名")
ip = Column(db.String(256), unique=True, nullable=False, comment="外部访问连接")
docker_api = Column(db.String(256), nullable=False, comment="地址")
remark = Column(db.String(256), comment="备注", nullable=True)
online_time = Column(db.DateTime, comment="最后一次在线时间")
active = Column(db.Boolean(), default=True, comment="是否启用")
system = Column(db.JSON(), comment="详情信息")
class ComposeDB(Model):
"""
compose 数据库 用来存储管理本地数据映射
"""
__tablename__ = "compose_db"
STATUS_CREATE = 0
STATUS_BUILD = 1
STATUS_FAILED = 2
status = Column(db.Integer, default=0, comment="状态")
name = Column(db.String(256), unique=True, nullable=False, comment="环境名")
path = Column(db.String(256), unique=True, nullable=False, comment="目录地址")
class ComposeRunner(Model):
"""
启动compose后的管理状态
"""
__tablename__ = "compose_runner"
TYPE_USER = 1
TYPE_ADMIN = 2
name: Mapped[str] = mapped_column(db.String(256), unique=True, comment="名称")
compose_id: Mapped[int] = mapped_column(
db.Integer, db.ForeignKey("compose_db.id"), comment="漏洞环境"
)
compose: Mapped[ComposeDB] = relationship(ComposeDB)
type: Mapped[int] = mapped_column(db.Integer, default=1)
user_id: Mapped[int] = mapped_column(db.Integer, db.ForeignKey("user.id"), comment="启动用户")
user: Mapped[User] = relationship(User)
create_time: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, default=datetime.now)
update_time: Mapped[datetime] = mapped_column(
db.DateTime, nullable=False, default=datetime.now, onupdate=datetime.now
)
port_info: Mapped[dict] = mapped_column(db.JSON, comment="服务端口信息")
project_dir: Mapped[str] = mapped_column(db.String(256), comment="项目目录")
flag: Mapped[str] = mapped_column(db.String(64), comment="flag")
class DockerResource(Model):
"""
docker 镜像资源 包括远程镜像 tar包 和 docker file
"""
__tablename__ = "docker_resource"
DOCKER_TYPE_REMOTE_IMAGE = 1 # 远程docker 仓库 PULL 方式
DOCKER_TYPE_LOCAL_IMAGE = 2
DOCKER_TYPE_MAP = {
DOCKER_TYPE_REMOTE_IMAGE: "远程镜像加载",
DOCKER_TYPE_LOCAL_IMAGE: "本地镜像加载",
}
STATUS_INIT = 0
STATUS_BUILD = 1
STATUS_BUILD_ERROR = 2
STATUS_MAP = {STATUS_INIT: "初始化", STATUS_BUILD: "已就绪", STATUS_BUILD_ERROR: "构建失败"}
status: Mapped[int] = mapped_column(db.Integer, default=STATUS_INIT, comment="资源状态")
resource_type: Mapped[str] = mapped_column(db.String(12), comment="资源类型(CTF|VUL)")
docker_type: Mapped[int] = mapped_column(
db.Integer, default=DOCKER_TYPE_REMOTE_IMAGE, comment="资源类型"
)
name: Mapped[str] = mapped_column(db.String(256), comment="资源名称")
image = mapped_column(db.String(256), comment="镜像名称:tag")
dockerfile = mapped_column(db.Text, comment="Dockerfile", nullable=True)
ports: Mapped[str] = mapped_column(db.String(256), comment="开放端口", nullable=True)
description: Mapped[str] = mapped_column(db.Text, comment="描述信息",nullable=True)
cve: Mapped[str] = mapped_column(db.JSON, comment="关联CVE", nullable=True)
app: Mapped[str] = mapped_column(db.String(128), comment="相关组件", nullable=True)
logs = Column(db.Text, comment="构建过程信息", nullable=True)
__table__args = (
UniqueConstraint("name", "docker_type", name="idx_name_docker_type"),
UniqueConstraint("image", "docker_type", name="idx_image_docker_type"),
)
@property
def docker_type_name(self):
return self.DOCKER_TYPE_MAP[self.docker_type]
@property
def status_name(self):
return self.STATUS_MAP[self.status]
class DockerRunner(Model):
__tablename__ = "docker_runner"
TYPE_USER = 1
TYPE_ADMIN = 2
name: Mapped[str] = mapped_column(db.String(256), unique=True, comment="名称")
resource_id: Mapped[int] = mapped_column(
db.Integer, db.ForeignKey("docker_resource.id"), comment="漏洞环境"
)
resource: Mapped[DockerResource] = relationship(DockerResource)
type: Mapped[int] = mapped_column(db.Integer, default=1)
user_id: Mapped[int] = mapped_column(
db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"), comment="启动用户", nullable=True
)
admin_id: Mapped[int] = mapped_column(
db.Integer, db.ForeignKey("admin.id", ondelete="CASCADE"), comment="启动用户", nullable=True
)
user: Mapped[User] = relationship(User)
admin: Mapped[Admin] = relationship(Admin)
create_time: Mapped[datetime] = mapped_column(db.DateTime, nullable=False, default=datetime.now)
update_time: Mapped[datetime] = mapped_column(
db.DateTime, nullable=False, default=datetime.now, onupdate=datetime.now
)
port_info: Mapped[dict] = mapped_column(db.JSON, comment="服务端口信息")
container_id = mapped_column(db.String(255), comment="实际容器ID")
================================================
FILE: app/models/user.py
================================================
"""
用户相关模型
"""
from sqlalchemy.orm import mapped_column
from app.extensions import db
from app.models import Model
class User(Model):
__tablename__ = "user"
username = mapped_column(db.String(256), unique=True, nullable=False, comment="用户名")
password = mapped_column(db.String(512), nullable=False, comment="密码")
active = mapped_column(db.Boolean(), default=True, comment="是否启用")
token = mapped_column(db.String(64), comment="token", nullable=True)
================================================
FILE: app/services/__init__.py
================================================
================================================
FILE: app/services/docker.py
================================================
import logging
import random
import docker
from docker.errors import ImageNotFound, NotFound
from app.extensions import db
from app.models.admin import Config
from app.models.docker import DockerResource, DockerRunner
logger = logging.getLogger(__name__)
def get_free_port(start_port: int, end_port: int):
# todo 检查端口是否被暂用
random_port = str(random.randint(start_port, end_port))
return random_port
def start_docker_resource(resource_id, user_id, flag=None) -> DockerRunner:
"""
启动docker 资源
"""
resource = DockerResource.get_by_id(resource_id)
client = docker.DockerClient(Config.get_config(Config.KEY_DOCKER_API))
try:
image = client.images.get(resource.image)
except ImageNotFound as e:
logger.warning(e)
raise ValueError("当前题目环境缺失、请联系管理员!")
# 解析镜像端口
logger.info("{}".format(image.attrs))
image_config = image.attrs["Config"]
port_range = Config.get_config(Config.KEY_PORT_RANGE)
try:
start_port, end_port = port_range.split("-")
start_port, end_port = int(start_port), int(end_port)
assert start_port < end_port
except (AssertionError, ValueError, IndexError):
raise ValueError("服务器缺少资源、请联系管理员")
if "ExposedPorts" in image_config:
port_dict = image_config["ExposedPorts"]
for docker_port, host_port in port_dict.items():
port_dict[docker_port] = get_free_port(start_port, end_port)
else:
port_dict = {}
image_name = image.attrs["RepoTags"][0].replace(":", ".")
container_name = f"{image_name}_{user_id}".replace("/", "-")
# 检查docker 是否已存在
try:
c = client.containers.get(container_name)
c.stop()
c.remove()
except docker.errors.NotFound:
pass
try:
docker_container = client.containers.run(
image=image.id, name=container_name, ports=port_dict, detach=True
)
except docker.errors.APIError as e:
logger.exception(e)
raise ValueError("题目启动失败")
if flag:
command = 'sh -c "sh /start.sh \\"{0}\\" || echo \\"{0}\\" >/flag"'.format(flag)
logger.info("run {}".format(command))
output = docker_container.exec_run(cmd=command, detach=True)
logger.info(output)
# 查看是否有历史记录
docker_runner = (
db.session.query(DockerRunner).filter(DockerRunner.name == docker_container.name).first()
)
if docker_runner:
docker_runner.container_id = docker_container.id
docker_runner.port_info = port_dict
docker_runner.save()
else:
docker_runner = DockerRunner.create(
name=docker_container.name,
resource_id=resource_id,
type=DockerRunner.TYPE_USER,
port_info=port_dict,
user_id=user_id,
container_id=docker_container.id,
)
return docker_runner
def destroy_docker_runner(docker_runner_id):
docker_runner = db.session.query(DockerRunner).get(docker_runner_id)
client = docker.DockerClient(Config.get_config(Config.KEY_DOCKER_API))
try:
docker_container = client.containers.get(docker_runner.container_id)
docker_container.stop()
docker_container.remove()
except NotFound:
logger.warning("容器未找到:{}".format(docker_runner.name))
db.session.delete(docker_runner)
db.session.commit()
return True
================================================
FILE: app/services/player.py
================================================
import logging
from sqlalchemy import desc, func
from app.extensions import db
from app.models.ctf import Answer, CtfResource, Question
from app.models.user import User
logger = logging.getLogger(__name__)
def submit(question, flag, user, ip=None):
instance = db.session.query(Question).get(question)
answer = Answer(question_id=instance.id, user_id=user.id, flag=flag, ip=ip)
# 判断是否有正确的提交记录
is_answer = (
db.session.query(Answer)
.filter(
Answer.question_id == instance.id,
Answer.user_id == user.id,
Answer.status == Answer.status_ok,
)
.count()
)
if instance.active_flag:
# 获取镜像资源
resource = (
db.session.query(CtfResource)
.filter(CtfResource.question_id == instance.id, CtfResource.user_id == user.id)
.first()
)
if not resource:
answer.status = answer.status_error
db.session.commit()
return 1, "题库无效,请联系管理员或重新生成!"
# 判断是否是作弊
ok_container = (
db.session.query(CtfResource)
.filter(CtfResource.question_id == instance.id, CtfResource.flag == flag)
.first()
)
if ok_container and ok_container != resource:
# 作弊
answer.status = answer.status_cheat
db.session.add(answer)
db.session.commit()
return 1, "请勿作弊"
if resource.flag == flag:
answer.score = instance.score
# 判断是否作弊
answer.status = answer.status_repeat if is_answer else answer.status_ok
answer.rank = (
db.session.query(Answer.id)
.filter(Answer.question_id == question, Answer.status == Answer.status_ok)
.count()
+ 1
)
return 0, "提交成功"
else:
answer.status = answer.status_error
answer.rank = (
db.session.query(Answer.id)
.filter(Answer.question_id == question, Answer.status == Answer.status_ok)
.count()
+ 1
)
db.session.add(answer)
db.session.commit()
return 1, "Flag错误!"
elif instance.flag == flag:
answer.score = instance.score
answer.status = answer.status_repeat if is_answer else answer.status_ok
answer.rank = (
db.session.query(Answer.id)
.filter(Answer.question_id == question, Answer.status == Answer.status_ok)
.count()
+ 1
)
db.session.add(answer)
db.session.commit()
return 0, "提交成功"
else:
answer.status = answer.status_error
db.session.add(answer)
db.session.commit()
return 1, "flag错误!"
def score_rank(username=None, page=1, page_size=20):
page = int(page)
page_size = int(page_size)
# 第一步查询只需要获取排名即可 和过滤条件即可
base_arg_query = (
db.session.query(
Answer.user_id,
func.sum(Answer.score).label("sum_score"),
func.count(Answer.id).label("cnt"),
func.max(Answer.updated_at).label("last_time"),
func.aggregate_strings(Question.type, ",").label("strong"),
)
.join(Question, Question.id == Answer.question_id)
.filter(Answer.status == Answer.status_ok)
)
logger.info(base_arg_query)
arg_query = base_arg_query.group_by(Answer.user_id).subquery("slr")
sub_query = (
db.session.query(
arg_query.c.user_id,
arg_query.c.sum_score.label("sum_score"),
arg_query.c.cnt,
arg_query.c.last_time,
arg_query.c.strong,
)
.select_from(arg_query)
.add_columns(db.func.rank().over(order_by=desc(arg_query.c.sum_score)).label("rank"))
.subquery("rl")
)
query = (
db.session.query(
User,
sub_query.c.user_id,
sub_query.c.sum_score,
sub_query.c.cnt,
sub_query.c.rank,
sub_query.c.last_time,
sub_query.c.strong,
)
.select_from(sub_query)
.filter()
.join(User, User.id == sub_query.c.user_id)
)
if username:
query = query.filter(User.username.ilike("%{}%".format(username)))
page = query.order_by(
(desc(sub_query.c.sum_score)),
).paginate(page=page, per_page=page_size)
data = []
for item in page.items:
types = item.strong.split(",")
strong = max([i for i in tuple(types)], key=lambda x: types.count(x))
data.append(
{
"strong": strong,
"last_time": item.last_time.strftime("%Y-%m-%d %H:%M:%S"),
"username": item.User.username,
"score": float(item.sum_score),
"rank": item.rank,
"cnt": item.cnt,
}
)
return True, {"total": page.total, "data": data}
================================================
FILE: app/services/system.py
================================================
from flask import g, request
from app.extensions import db
from app.models.admin import AdminMessage, Config, MessageLevel, MessageType, Operator
def create_admin_message(admin_id, content, level=MessageLevel.ERROR):
db.session.add(
AdminMessage(admin_id=admin_id, content=content, mtype=MessageType.TYPE_ADMIN, level=level)
)
db.session.commit()
def insert_operator(code, content, username=None, role_name=None):
"""
插入操作日志
@:param code 是否成功
@:param content 操作内容
:return:
"""
if username is None:
user = g.user
username = user.username
ip = request.remote_addr
db.session.add(Operator(code=code, content=content, username=username, role=role_name, ip=ip))
db.session.commit()
def get_config_val(key):
if key not in Config.CONFIG_MAP:
raise ValueError("该KEY值不是合法的配置键")
val_type = Config.CONFIG_MAP[key][0]
val_default = Config.CONFIG_MAP[key][1]
config_item = db.session.query(Config).filter(Config.key == key).first()
if config_item:
config_val = config_item.val
if val_type == "int":
return int(config_val)
else:
return val_default
================================================
FILE: app/tasks/__init__.py
================================================
================================================
FILE: app/tasks/ctf.py
================================================
import logging
import os
from datetime import datetime
from urllib.parse import urlparse
import docker
import git
import requests
import yaml
from docker.errors import NotFound
from app.core.const import ConstCacheKey
from app.core.flask_celery import ContextTask
from app.extensions import cache, celery, db
from app.models.admin import Config
from app.models.ctf import CtfResource, ImageResource
from app.models.docker import DockerResource
from app.services.system import create_admin_message
from app.utils.tools import find_directories_with_filename
from config import config
logger = logging.getLogger()
@celery.task(base=ContextTask)
def beat_destroy_container():
"""
清楚过期容器 容错
"""
for ctf_resource in db.session.query(CtfResource).filter(
CtfResource.destroy_time < datetime.now()
):
client = docker.DockerClient(Config.get_config(Config.KEY_DOCKER_API))
# 默认一个docker run 只能绑定一个用户吧 所以直接删除docker run 采用数据库的连表删除自动删除其他索引
try:
container = client.containers.get(ctf_resource.docker_runner.container_id)
container.stop()
container.remove()
except NotFound:
logger.warning("环境异常:{}".format(ctf_resource.docker_runner.container_id))
docker_run = ctf_resource.docker_runner
name = docker_run.name
db.session.delete(docker_run)
db.session.commit()
logger.info("destroy container:{}".format(name))
@celery.task(base=ContextTask)
def build_question_tar(image_id):
"""
编译上传的tar包为image
"""
logger.info("start build image | {}".format(image_id))
image = db.session.query(ImageResource).get(image_id)
if not image:
logger.info("image not found")
if image.host.docker_api.startswith("http"):
_url = urlparse(image.host.docker_api)
address = "{}:{}".format(_url.hostname, _url.port)
else:
address = image.host.docker_api
cli = docker.DockerClient(base_url=address)
dist_file_path = os.path.join(config.BASE_DIR, "upload", image.file.file_path)
try:
for line in cli.build(
fileobj=open(dist_file_path, "rb"),
rm=True,
tag=f"{image.name}:{image.version}",
custom_context=True,
):
logger.info(line)
cache.lpush(ConstCacheKey.TASK_BUILD_LOG % image.id, line)
image.status = ImageResource.STATUS_SUCCESS
except docker.errors.APIError as e:
logger.exception(e)
image.status = ImageResource.STATUS_ERROR
image.build_result = "镜像文件错误"
except requests.exceptions.ConnectionError as e:
logger.exception(e)
image.build_result = "docker api 链接失败"
except FileNotFoundError as e:
logger.exception(e)
image.status = ImageResource.STATUS_ERROR
image.build_result = "镜像文件不存在"
image.file = None
db.session.commit()
logger.info(f"build finish{image.id}")
@celery.task(base=ContextTask)
def sync_ctf_question_repo(repo, admin_id=None):
repo_name = repo.split("/")[-1].split(".")[0]
username = repo.split("/")[-2]
local_repo = f"/opt/vulnerability/{username}/{repo_name}"
# 判断本地目录是否存在
try:
if os.path.exists(local_repo):
logger.info(f"Pull To {local_repo}")
git.Repo(local_repo).git.pull()
else:
logger.info(f"Clone {repo} to {local_repo}")
git.Repo.clone_from(repo, local_repo)
except git.exc.GitCommandError as e:
create_admin_message(admin_id, f"同步远程CTF仓库失败\n{e}")
logger.error(e)
return
docker_api = Config.get_config(Config.KEY_DOCKER_API)
client = docker.DockerClient(docker_api)
vulnerabilities = find_directories_with_filename(local_repo, filename="metadata.yml")
for directory in vulnerabilities:
with open(os.path.join(directory, "metadata.yml")) as f:
yaml_data = yaml.safe_load(f)
image = yaml_data["image"]
name = yaml_data["name"]
docker_file = os.path.join(directory, "Dockerfile")
# 判断镜像是否已经存在
obj = db.session.query(DockerResource).filter(DockerResource.image == image).first()
if not obj:
obj = DockerResource(
name=name,
resource_type="CTF",
image=image,
description=yaml_data["description"],
cve=yaml_data.get("cve", []),
app=yaml_data.get("app"),
)
if os.path.exists(docker_file):
# 本地编译
obj.dockerfile = docker_file
obj.docker_type = DockerResource.DOCKER_TYPE_LOCAL_IMAGE
else:
# 远程镜像
obj.docker_type = DockerResource.DOCKER_TYPE_REMOTE_IMAGE
# 判断镜像是否存在
try:
client.images.get(image)
obj.status = DockerResource.STATUS_BUILD
except docker.errors.ImageNotFound:
obj.status = DockerResource.STATUS_INIT
except docker.errors.DockerException as e:
logger.exception(e)
obj.status = DockerResource.STATUS_INIT
try:
db.session.add(obj)
db.session.commit()
except Exception as e:
db.session.rollback()
logger.error(e)
continue
logger.info("Add Image:{}".format(image))
create_admin_message(admin_id, "同步远程CTF仓库完成")
================================================
FILE: app/tasks/docker.py
================================================
import json
import logging
import os
from datetime import datetime
from io import BytesIO
import docker.errors
from docker import APIClient
from docker import errors as docker_error
from docker.errors import DockerException
from requests import delete
from app.core.flask_celery import ContextTask
from app.extensions import cache, celery
from app.models.admin import Config, TaskList
from app.models.docker import DockerResource
logger = logging.getLogger(__name__)
@celery.task(base=ContextTask, bind=True)
def docker_build_resource(self, resource_id: int):
# 获取当前任务ID
task_id = self.request.id
logger.info(f"docker_build_resource:{resource_id}, task_id:{task_id}")
resource = DockerResource.get_by_id(resource_id)
# 使用任务ID作为日志键
log_key = "DOCKER_BUILD_LOG_%s" % task_id
try:
client = APIClient(Config.get_config(Config.KEY_DOCKER_API))
except DockerException as e:
logger.exception(e)
return
# 删除key
cache.delete(log_key)
if resource.docker_type == DockerResource.DOCKER_TYPE_LOCAL_IMAGE:
# 判断是否有docker file
logs = []
if resource.dockerfile:
try:
directory = os.path.dirname(resource.dockerfile)
logger.info(f"build {resource.image} images <- {directory}")
ret = client.build(
path=directory, tag=resource.image, dockerfile=resource.dockerfile
)
for chunk in ret:
log_item = json.loads(chunk)
stream = log_item.get("stream")
if stream:
logs.append(stream)
err_log = log_item.get("errorDetail")
if err_log:
logs.append(err_log["message"])
resource.logs = "\n".join(logs)
resource.status = DockerResource.STATUS_BUILD_ERROR
resource.save()
# save to redis 使用任务ID作为键
cache.lpush(log_key, chunk)
# 设置过期时间 1小时
cache.expire(log_key, 3600)
logger.info(json.loads(chunk))
logger.info(f"build success {resource.image}")
except docker_error.DockerException as e:
logger.exception(e)
logger.info(resource.image)
img = client.images(resource.image)
else:
# 本地镜像但没有 Dockerfile,直接检查镜像是否存在
img = client.images(resource.image)
if not img:
error_msg = f"镜像 {resource.image} 不存在,请先构建或拉取镜像"
logger.error(error_msg)
resource.logs = error_msg
resource.status = DockerResource.STATUS_BUILD_ERROR
resource.save()
# 保存错误信息到 Redis,使用任务ID作为键
cache.lpush(log_key, json.dumps({
"error": error_msg,
"timestamp": str(datetime.now())
}))
# 设置过期时间 1小时
cache.expire(log_key, 3600)
return
if img:
resource.status = DockerResource.STATUS_BUILD
resource.save()
logger.info(f"镜像 {resource.image} 验证成功")
else:
# 清空 cache
key = "DOCKER_BUILD_%s" % resource_id
cache.delete(key)
try:
for log_dic in client.pull(resource.image, stream=True, decode=True):
# 添加到日志
logger.info(log_dic)
cache.lpush(key, json.dumps(log_dic))
except docker.errors.ImageNotFound as e:
logger.exception(e)
logger.warning("镜像不存在:{}".format(resource.image))
return
resource.status = DockerResource.STATUS_BUILD
resource.save()
logger.info("编译完成:{}".format(resource.name))
def task_add_log(task: int, line: dict):
"""
处理日志存储
"""
if isinstance(line, bytes):
line = json.loads(line)
task_key = "task_%s" % task
if "progress" in line:
cache.rpush(task_key, "%s:%s" % (line["status"], line["progress"]))
elif "error" in line:
cache.rpush(task_key, "%s:%s" % ("ERROR", line["error"]))
elif "status" in line:
cache.rpush(task_key, line["status"])
elif "stream" in line:
cache.rpush(task_key, line["stream"])
else:
cache.rpush(task_key, json.dumps(line))
@celery.task(base=ContextTask)
def build_delay(task_id: int, build_type, tag, admin, pt=None, dockerfile=None):
"""
编译镜像
"""
task = TaskList.get_by_id(task_id)
if not task:
logger.error("任务ID不粗在:{}".format(task_id))
return
try:
client = APIClient(Config.get_config(Config.KEY_DOCKER_API))
except DockerException as e:
logger.exception(e)
return
try:
if build_type == "tar":
f = open(pt, "rb")
for line in client.build(fileobj=f, rm=True, tag=tag, custom_context=True):
logger.info(line)
task_add_log(task.id, line)
task.status = task.STATUS_DONE
elif build_type == "pull":
for line in client.pull(tag, stream=True, decode=True):
task_add_log(task.id, line)
task.status = task.STATUS_DONE
else:
f = BytesIO(dockerfile.encode("utf-8"))
for line in client.build(fileobj=f, rm=True, tag=tag):
logger.info(line)
task_add_log(task.id, line)
task.status = task.STATUS_DONE
except DockerException as e:
task.status = task.STATUS_ERROR
task.remark = str(e)
logger.error(e)
task_add_log(task_id, {"error": str(e)})
task.save()
================================================
FILE: app/tasks/player.py
================================================
import logging
from datetime import datetime
import docker
from docker.errors import DockerException
from app.core.flask_celery import ContextTask
from app.extensions import celery, db
from app.models.admin import Config
from app.models.ctf import CtfResource
logger = logging.getLogger("app")
@celery.task(base=ContextTask)
def ctf_finish_container(container_id, current=False):
"""
启动题目后自动销毁
定时任务 定时结束容器
"""
logger.info("Start CTF Container finish:%s" % container_id)
resource = db.session.query(CtfResource).get(container_id)
if not resource:
logger.info("container not found:{}".format(container_id))
return
if not current and resource.destroy_time > datetime.now():
logger.info(f"{datetime.now()} -> {resource.destroy_time}")
logger.info("container time is delay ")
return
runner = resource.docker_runner
try:
client = docker.DockerClient(Config.get_config(Config.KEY_DOCKER_API), timeout=3)
docker_container = client.containers.get(runner.container_id)
docker_container.kill()
docker_container.remove()
except DockerException:
pass
db.session.delete(runner)
db.session.commit()
logger.info("destroy container:%s" % container_id)
================================================
FILE: app/tasks/system.py
================================================
from __future__ import absolute_import
import logging
from datetime import datetime, timedelta
from app.core.const import ConstCacheKey
from app.core.flask_celery import ContextTask
from app.extensions import cache, celery, db
from app.models.admin import RequestState
logger = logging.getLogger("app")
@celery.task(base=ContextTask)
def day_upload_req():
"""
定时任务 每天执行一次 将redis内的数据写入db
"""
logger.info("start upload requests data")
day = (datetime.today() - timedelta(days=1)).strftime("%Y%m%d")
ip_count = cache.scard(ConstCacheKey.IP_DAY_SET % day)
req_count = int(cache.get(ConstCacheKey.REQ_DAY_COUNT % day) or 0)
instance = RequestState(day=day, ip_count=ip_count, req_count=req_count)
db.session.add(instance)
db.session.commit()
================================================
FILE: app/tasks/vulnerability.py
================================================
import logging
import os
import docker
import git
import yaml
from docker.errors import ImageNotFound, NotFound
from app.core.flask_celery import ContextTask
from app.extensions import celery, db
from app.models.admin import Config
from app.models.docker import DockerResource, DockerRunner
from app.services.docker import get_free_port
from app.services.system import create_admin_message
from app.utils.tools import find_directories_with_filename
logger = logging.getLogger(__name__)
@celery.task(base=ContextTask)
def stop_vulnerability_resource(run_id):
"""
销毁漏洞环境
"""
logger.info("start destroy vulnerability runner {}".format(run_id))
runner: DockerRunner = DockerRunner.get_by_id(run_id)
docker_api = Config.get_config(Config.KEY_DOCKER_API)
if not runner:
return
client = docker.DockerClient(docker_api)
try:
docker_container = client.containers.get(runner.container_id)
docker_container.stop()
docker_container.remove()
except docker.errors.NotFound as e:
logger.error(e)
db.session.delete(runner)
db.session.commit()
logger.info("destroy vulnerability runner:{}".format(run_id))
def start_vuln_resource(resource_id, user_id=None, admin_id=None):
"""
启动docker 资源
"""
if user_id is None and admin_id is None:
raise ValueError()
resource = DockerResource.get_by_id(resource_id)
client = docker.DockerClient(Config.get_config(Config.KEY_DOCKER_API))
try:
image = client.images.get(resource.image)
except ImageNotFound as e:
logger.exception(e)
raise ValueError("当前题目环境缺失、请联系管理员!")
# 解析镜像端口
image_config = image.attrs["Config"]
port_range = Config.get_config(Config.KEY_PORT_RANGE)
try:
start_port, end_port = port_range.split("-")
start_port, end_port = int(start_port), int(end_port)
assert start_port < end_port
except (AssertionError, ValueError, IndexError):
raise ValueError("服务器缺少资源、请联系管理员")
port_dict = {}
if resource.ports:
for docker_port in resource.ports.split(","):
port_dict[docker_port] = get_free_port(start_port, end_port)
elif "ExposedPorts" in image_config:
port_dict = image_config["ExposedPorts"]
for docker_port, host_port in port_dict.items():
port_dict[docker_port] = get_free_port(start_port, end_port)
image_name = image.attrs["RepoTags"][0].replace(":", ".")
container_name = f"{image_name}_{user_id}".replace("/", "-")
# 检查docker 是否已存在
try:
c = client.containers.get(container_name)
c.stop()
c.remove()
except docker.errors.NotFound:
pass
try:
docker_container = client.containers.run(
image=image.id, name=container_name, ports=port_dict, detach=True
)
except docker.errors.APIError as e:
logger.exception(e)
raise ValueError("题目启动失败")
# 查看是否有历史记录
try:
docker_runner = (
db.session.query(DockerRunner)
.filter(DockerRunner.name == docker_container.name)
.first()
)
if docker_runner:
docker_runner.container_id = docker_container.id
docker_runner.port_info = port_dict
docker_runner.save()
else:
docker_runner = DockerRunner.create(
name=docker_container.name,
resource_id=resource_id,
admin_id=admin_id,
user_id=user_id,
port_info=port_dict,
container_id=docker_container.id,
)
return docker_runner.id
except Exception as e:
logger.exception(e)
db.session.rollback()
@celery.task(base=ContextTask)
def sync_remote_vulnerability_repo(repo, admin_id=None, max_retry=3):
logger.info(f"start sync:{repo}")
repo_name = repo.split("/")[-1].split(".")[0]
username = repo.split("/")[-2]
local_repo = f"/opt/vulnerability/{username}/{repo_name}"
# 判断本地目录是否存在
try:
if os.path.exists(local_repo):
logger.info(f"Pull To {local_repo}")
git.Repo(local_repo).git.pull()
else:
logger.info(f"Clone {repo} to {local_repo}")
git.Repo.clone_from(repo, local_repo)
logger.info("Pull Successful")
except git.exc.GitCommandError as e:
logger.error(e)
# retry
if max_retry <= 0:
logger.error("同步仓库失败")
create_admin_message(admin_id, f"同步远程漏洞仓库失败\n{e}")
return
sync_remote_vulnerability_repo.delay(repo, admin_id, max_retry - 1)
return
# scan directory
client = docker.DockerClient(Config.get_config(Config.KEY_DOCKER_API))
vulnerabilities = find_directories_with_filename(local_repo, filename="metadata.yml")
for directory in vulnerabilities:
logger.info("update {} ...".format(directory))
with open(os.path.join(directory, "metadata.yml")) as f:
yaml_data = yaml.safe_load(f)
image = yaml_data["image"]
description = yaml_data["description"]
name = yaml_data["name"]
ports = ",".join([str(i) for i in yaml_data.get("ports", [])])
if description.startswith("file:"):
desc_file = os.path.join(directory, description.lstrip("file:"))
if os.path.exists(desc_file):
with open(desc_file, "r", encoding="UTF-8") as f:
description = f.read()
# build
obj = db.session.query(DockerResource).filter(DockerResource.image == image).first()
if obj:
logger.info(f"Image:{image} Is Already")
obj.description = description
obj.ports = ports
db.session.commit()
continue
else:
obj = db.session.query(DockerResource).filter(DockerResource.name == name).first()
if obj:
obj.ports = ports
obj.image = image
else:
obj = DockerResource(
name=name,
resource_type="VUL",
image=image,
description=description,
ports=ports,
cve=yaml_data.get("cve", []),
app=yaml_data.get("app"),
)
docker_file = os.path.join(directory, "Dockerfile")
if os.path.exists(docker_file):
# build
obj.status = DockerResource.STATUS_BUILD
obj.dockerfile = docker_file
obj.docker_type = DockerResource.DOCKER_TYPE_LOCAL_IMAGE
# 判断镜像是否存在 如果存在就标记状态 已就绪
try:
client.images.get(image)
obj.status = DockerResource.STATUS_BUILD
except NotFound:
obj.status = DockerResource.STATUS_INIT
else:
obj.description = description
obj.status = DockerResource.STATUS_INIT
obj.docker_type = DockerResource.DOCKER_TYPE_REMOTE_IMAGE
db.session.add(obj)
db.session.commit()
logger.info("Add Image:{}".format(image))
create_admin_message(admin_id, "同步远程漏洞仓库完成")
================================================
FILE: app/utils/security.py
================================================
import base64
import hashlib
import os
from random import Random
# Helper function to hash a password
def hash_password(password: str) -> str:
# Create a new salt
salt = os.urandom(16)
# Hash the password with the salt
pwd_hash = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, 100000)
# Return the salt and hash
return base64.b64encode(salt + pwd_hash).decode("utf-8")
def check_password(stored_password: str, provided_password: str) -> bool:
# Decode the stored password from Base64
stored_password_bytes = base64.b64decode(stored_password)
# Extract the salt from the stored password
salt = stored_password_bytes[:16]
# Extract the hash from the stored password
stored_hash = stored_password_bytes[16:]
# Hash the provided password with the extracted salt
pwd_hash = hashlib.pbkdf2_hmac("sha256", provided_password.encode("utf-8"), salt, 100000)
# Compare the hashes
return pwd_hash == stored_hash
def create_token():
"""
生成token
:return: token
"""
length_r = 32
token = ""
random = Random()
for i in range(length_r):
token += random.choice("abcdefghijklmnopqrstuvwxyz0123456789")
return token
================================================
FILE: app/utils/tools.py
================================================
import os
import random
import string
def generate_flag():
"""
生成flag
return generate flag
"""
rd_str = "".join(random.sample(string.ascii_letters + string.digits, 32))
return "flag{ocean%s}" % rd_str
def find_directories_with_filename(directory, filename="Dockerfile"):
directories_with_dockerfile = []
for root, dirs, files in os.walk(directory):
if filename in files:
directories_with_dockerfile.append(root)
return directories_with_dockerfile
================================================
FILE: app/utils/validator.py
================================================
import re
def check_image_name(name: str) -> bool:
if re.search(
r"^[a-z0-9]+(?:[._-][a-z0-9]+)*(?:\/[a-z0-9]+(?:[._-][a-z0-9]+)*)?(?::[a-zA-Z0-9]+(?:[._-][a-zA-Z0-9]+)*)?$",
name,
):
return True
return False
================================================
FILE: app/worker.py
================================================
from app import create_app
from app.extensions import celery
app = create_app()
celery.autodiscover_tasks(["app.ctf", "app.sys", "app.vulnerability"], related_name="tasks")
app.app_context().push()
__all__ = ["celery"]
# celery -A web.celery_worker:celery worker
================================================
FILE: config/config.py
================================================
import os
import logging.config
from dotenv import load_dotenv
from celery.schedules import crontab
BASE_DIR = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
load_dotenv(verbose=False)
DEBUG = False
# db
DB_HOST = os.getenv('DB_HOST')
DB_USER = os.getenv('DB_USER')
DB_PASSWORD = os.getenv('DB_PASSWORD')
DB_PORT = os.getenv('DB_PORT')
DB_NAME = 'ocean'
# end db
# cache
REDIS_HOST = os.getenv("REDIS_HOST")
REDIS_PASSWORD = os.getenv("REDIS_PASSWORD")
# end cache
# 跨域配置
CSRF_ENABLED = True
CSRF_SESSION_KEY = os.getenv("CSRF_SESSION_KEY", "")
THREADS_PER_PAGE = 2
SQLALCHEMY_ECHO = False
SQLALCHEMY_POOL_RECYCLE = 5
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}?charset=utf8mb4".format(
db_user=DB_USER,
db_password=DB_PASSWORD,
db_host=DB_HOST,
db_port=DB_PORT,
db_name=DB_NAME)
SQLALCHEMY_ENGINE_OPTIONS = {
'pool_recycle': 3600
}
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'basic': {
'format': '%(levelname)-4.4s [%(name)s] %(message)s',
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'basic',
'level': logging.INFO,
'stream': 'ext://sys.stdout',
},
'none': {
'class': 'logging.StreamHandler',
'formatter': 'basic',
'level': logging.NOTSET,
'stream': 'ext://sys.stdout',
}
},
'loggers': {
'': {
'propagate': False,
'level': 'INFO',
'handlers': ['console'],
},
'app': {
'propagate': False,
'handlers': ['console']
},
'werkzeug': {
'handlers': ['none'],
'propagate': False,
}
}
}
# 定时任务
REDIS_URL = 'redis://:{REDIS_PASSWORD}@{REDIS_HOST}:6379'.format(REDIS_HOST=REDIS_HOST,
REDIS_PASSWORD=REDIS_PASSWORD)
UPLOAD_DIR = os.path.join(BASE_DIR, 'upload')
enable_utc = False
logging.config.dictConfig(LOGGING)
# 应用初始化 db 和 redis 初始化有延迟
API_INIT = False
# celery
broker_url = REDIS_URL
beat_schedule = {
"day_upload_req": {
"task": "app.tasks.sys.day_upload_req",
"schedule": crontab(hour="2", minute="4"),
},
"beat_destroy_container": {
"task": "app.tasks.ctf.beat_destroy_container",
"schedule": crontab(minute="*/10"),
},
# "crontab_monitoring_docker_api": {
# "task": "app.tasks.ctf.crontab_monitoring_docker_api",
# "schedule": crontab(minute="*/1")
# }
}
timezone = 'Asia/Shanghai'
CELERY_ENABLE_UTC = True
FLASK_PYDANTIC_VALIDATION_ERROR_RAISE = True
================================================
FILE: docker-compose.yml
================================================
services:
db:
image: mysql:8.0
command: --default-authentication-plugin=mysql_native_password
container_name: db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: ${DB_NAME:-ocean}
LANG: C.UTF-8
TZ: ${TZ:-Asia/Shanghai}
volumes:
- mysql_data:/var/lib/mysql
privileged: true
redis:
image: redis:6
logging:
driver: "json-file"
options:
max-size: "10m"
restart: unless-stopped
privileged: true
command: redis-server --requirepass ${REDIS_PASSWORD}
environment:
TZ: ${TZ:-Asia/Shanghai}
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
web:
build:
context: .
dockerfile: ./install/docker/ocean_web.Dockerfile
image: ${DOCKER_REGISTRY}ocean_web:${APP_VERSION:-latest}
container_name: web
volumes:
- .:/opt/ocean_ctf
- /var/run/docker.sock:/var/run/docker.sock
- /opt/vulnerability:/opt/vulnerability
- /var/log/gunicorn:/var/log/gunicorn
depends_on:
- db
- redis
restart: always
working_dir: /opt/ocean_ctf
command: >
gunicorn
-b 0.0.0.0:5000
--worker-class=eventlet
-w ${GUNICORN_WORKERS:-2}
--log-level=${LOG_LEVEL:-info}
--access-logfile=/var/log/gunicorn/access.log
--error-logfile=/var/log/gunicorn/error.log
--preload
wsgi:app
environment:
TZ: ${TZ:-Asia/Shanghai}
FLASK_APP: ${FLASK_APP:-main.py}
FLASK_ENV: ${FLASK_ENV:-production}
SECRET_KEY: ${SECRET_KEY}
DB_HOST: ${DB_HOST:-db}
DB_PORT: ${DB_PORT:-3306}
DB_USER: ${DB_USER:-root}
DB_PASSWORD: ${DB_PASSWORD}
DB_NAME: ${DB_NAME:-ocean}
REDIS_HOST: ${REDIS_HOST:-redis}
REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_PASSWORD: ${REDIS_PASSWORD}
worker:
build:
context: .
dockerfile: ./install/docker/ocean_web.Dockerfile
image: ${DOCKER_REGISTRY}ocean_web:${APP_VERSION:-latest}
container_name: worker
depends_on:
- db
- redis
volumes:
- .:/opt/ocean_ctf
- /var/run/docker.sock:/var/run/docker.sock
- /opt/vulnerability:/opt/vulnerability
restart: unless-stopped
working_dir: /opt/ocean_ctf
command: celery -A app.worker:celery worker -l ${LOG_LEVEL:-info}
environment:
TZ: ${TZ:-Asia/Shanghai}
FLASK_APP: ${FLASK_APP:-main.py}
FLASK_ENV: ${FLASK_ENV:-production}
SECRET_KEY: ${SECRET_KEY}
DB_HOST: ${DB_HOST:-db}
DB_PORT: ${DB_PORT:-3306}
DB_USER: ${DB_USER:-root}
DB_PASSWORD: ${DB_PASSWORD}
DB_NAME: ${DB_NAME:-ocean}
REDIS_HOST: ${REDIS_HOST:-redis}
REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_PASSWORD: ${REDIS_PASSWORD}
beat:
build:
context: .
dockerfile: ./install/docker/ocean_web.Dockerfile
image: ${DOCKER_REGISTRY}ocean_web:${APP_VERSION:-latest}
container_name: beat
depends_on:
- db
- redis
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- .:/opt/ocean_ctf
- /opt/vulnerability:/opt/vulnerability
restart: unless-stopped
working_dir: /opt/ocean_ctf
command: celery -A app.worker:celery beat -l ${LOG_LEVEL:-info}
environment:
TZ: ${TZ:-Asia/Shanghai}
FLASK_APP: ${FLASK_APP:-main.py}
FLASK_ENV: ${FLASK_ENV:-production}
SECRET_KEY: ${SECRET_KEY}
DB_HOST: ${DB_HOST:-db}
DB_PORT: ${DB_PORT:-3306}
DB_USER: ${DB_USER:-root}
DB_PASSWORD: ${DB_PASSWORD}
DB_NAME: ${DB_NAME:-ocean}
REDIS_HOST: ${REDIS_HOST:-redis}
REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_PASSWORD: ${REDIS_PASSWORD}
nginx:
image: nginx:${NGINX_VERSION:-latest}
ports:
- "${HTTP_PORT:-80}:80"
- "${HTTPS_PORT:-443}:443"
container_name: nginx
volumes:
- ./install/nginx.conf:/etc/nginx/nginx.conf
- .:/opt/ocean_ctf
- ./install/ssl:/etc/nginx/ssl:ro
depends_on:
- web
restart: unless-stopped
environment:
TZ: ${TZ:-Asia/Shanghai}
volumes:
mysql_data:
redis_data:
================================================
FILE: install/config/supervisord/ocean.ini
================================================
[group:partition1]
programs=api,ocean_worker,ocean_beat
[program:api]
directory = /opt/ocean_ctf
command = /opt/ocean_ctf/venv/bin/gunicorn -b 0.0.0.0:5000 --worker-class=eventlet -w 2 --log-level=info --access-logfile=/var/log/gunicorn/access.log --error-logfile=/var/log/gunicorn/error.log main:app
autorestart = true ;程序异常退出后自动重启
startsecs = 5 ;启动失败自动重试次数,默认是3
autorestart = true ;程序异常退出后自动重启
startretries = 3
user = root ;哪个用户启动
redirect_stderr = true ;把stderr重定向到stdout,默认false
stdout_logfile_maxbytes = 20MB ;stdout日志文件大小,默认50MB
stdout_logfile_backups = 20 ;stdout日志文件备份数
stdout_logfile = /var/log/ocean/api.log
[program:worker]
directory = /opt/ocean_ctf ;启动目录
command = /opt/ocean_ctf/venv/bin/celery -A app.worker:celery worker -l info ;启动命令
autostart = true ;在supervisord启动的时候也启动
startsecs = 5 ;启动5秒后没有异常退出,就当作已经正常启动了
autorestart = true ;程序异常退出后自动重启
startretries = 3 ;启动失败自动重试次数,默认是3
user = root ;哪个用户启动
redirect_stderr = true ;把stderr重定向到stdout,默认false
stdout_logfile_maxbytes = 20MB ;stdout日志文件大小,默认50MB
stdout_logfile_backups = 20 ;stdout日志文件备份数
stdout_logfile = /var/log/ocean/worker.log
[program:beat]
directory = /opt/ocean_ctf ;启动目录
command = /opt/ocean_ctf/venv/bin/celery -A app.worker:celery beat -l info ;启动命令
autostart = true ;在supervisord启动的时候也启动
startsecs = 5 ;启动5秒后没有异常退出,就当作已经正常启动了
autorestart = true ;程序异常退出后自动重启
startretries = 3
user = root ;哪个用户启动
redirect_stderr = true ;把stderr重定向到stdout,默认false
stdout_logfile_maxbytes = 20MB ;stdout日志文件大小,默认50MB
stdout_logfile_backups = 20 ;stdout日志文件备份数
stdout_logfile = /var/log/ocean/beatlog
================================================
FILE: install/db_init/ocean.sql
================================================
/*
Navicat Premium Data Transfer
Source Server Type : MariaDB
Source Server Version : 50568
Source Schema : ocean
Target Server Type : MariaDB
Target Server Version : 50568
File Encoding : 65001
Date: 04/03/2021 14:55:37
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
create database if not exists ocean;
================================================
FILE: install/docker/ocean_web.Dockerfile
================================================
FROM python:3.13-alpine
# 安装必要的构建工具
RUN apk add --no-cache gcc musl-dev linux-headers make git
WORKDIR /opt/ocean_ctf
# 配置 pip 使用清华源
RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/ && \
pip config set global.trusted-host pypi.tuna.tsinghua.edu.cn && \
pip install --upgrade pip
# 复制 requirements 文件并安装依赖
COPY requirements/ ./requirements/
COPY requirements.txt .
# 安装生产依赖(使用清华源)
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码 使用挂载方式运行
# COPY . /app/
# 默认命令 - 使用 Gunicorn 启动应用
# 确保使用 wsgi.py 启动应用
CMD ["gunicorn", "--worker-class=eventlet", "--workers=2", "--bind=0.0.0.0:5000", "--preload", "wsgi:app"]
================================================
FILE: install/docker/wait-for-it.sh
================================================
#!/usr/bin/env bash
# Use this script to test if a given TCP host/port are available
WAITFORIT_cmdname=${0##*/}
echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
usage()
{
cat << USAGE >&2
Usage:
$WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Alternatively, you specify the host and port as host:port
-s | --strict Only execute subcommand if the test succeeds
-q | --quiet Don't output any status messages
-t TIMEOUT | --timeout=TIMEOUT
Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit 1
}
wait_for()
{
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
else
echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout"
fi
WAITFORIT_start_ts=$(date +%s)
while :
do
if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then
nc -z $WAITFORIT_HOST $WAITFORIT_PORT
WAITFORIT_result=$?
else
(echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1
WAITFORIT_result=$?
fi
if [[ $WAITFORIT_result -eq 0 ]]; then
WAITFORIT_end_ts=$(date +%s)
echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds"
break
fi
sleep 1
done
return $WAITFORIT_result
}
wait_for_wrapper()
{
# In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
if [[ $WAITFORIT_QUIET -eq 1 ]]; then
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
else
timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
fi
WAITFORIT_PID=$!
trap "kill -INT -$WAITFORIT_PID" INT
wait $WAITFORIT_PID
WAITFORIT_RESULT=$?
if [[ $WAITFORIT_RESULT -ne 0 ]]; then
echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
fi
return $WAITFORIT_RESULT
}
# process arguments
while [[ $# -gt 0 ]]
do
case "$1" in
*:* )
WAITFORIT_hostport=(${1//:/ })
WAITFORIT_HOST=${WAITFORIT_hostport[0]}
WAITFORIT_PORT=${WAITFORIT_hostport[1]}
shift 1
;;
--child)
WAITFORIT_CHILD=1
shift 1
;;
-q | --quiet)
WAITFORIT_QUIET=1
shift 1
;;
-s | --strict)
WAITFORIT_STRICT=1
shift 1
;;
-h)
WAITFORIT_HOST="$2"
if [[ $WAITFORIT_HOST == "" ]]; then break; fi
shift 2
;;
--host=*)
WAITFORIT_HOST="${1#*=}"
shift 1
;;
-p)
WAITFORIT_PORT="$2"
if [[ $WAITFORIT_PORT == "" ]]; then break; fi
shift 2
;;
--port=*)
WAITFORIT_PORT="${1#*=}"
shift 1
;;
-t)
WAITFORIT_TIMEOUT="$2"
if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi
shift 2
;;
--timeout=*)
WAITFORIT_TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
WAITFORIT_CLI=("$@")
break
;;
--help)
usage
;;
*)
echoerr "Unknown argument: $1"
usage
;;
esac
done
if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then
echoerr "Error: you need to provide a host and port to test."
usage
fi
WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
WAITFORIT_CHILD=${WAITFORIT_CHILD:-0}
WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}
# Check to see if timeout is from busybox?
WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)
WAITFORIT_BUSYTIMEFLAG=""
if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
WAITFORIT_ISBUSY=1
# Check if busybox timeout uses -t flag
# (recent Alpine versions don't support -t anymore)
if timeout &>/dev/stdout | grep -q -e '-t '; then
WAITFORIT_BUSYTIMEFLAG="-t"
fi
else
WAITFORIT_ISBUSY=0
fi
if [[ $WAITFORIT_CHILD -gt 0 ]]; then
wait_for
WAITFORIT_RESULT=$?
exit $WAITFORIT_RESULT
else
if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
wait_for_wrapper
WAITFORIT_RESULT=$?
else
wait_for
WAITFORIT_RESULT=$?
fi
fi
if [[ $WAITFORIT_CLI != "" ]]; then
if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
exit $WAITFORIT_RESULT
fi
exec "${WAITFORIT_CLI[@]}"
else
exit $WAITFORIT_RESULT
fi
================================================
FILE: install/frontend/dist/assets/detail-Beu8V9iA.js
================================================
import{K as Vl,_ as zu,L as Wu,c as $u,a as nt,t as Ku,s as Qu,M as Xu,h as Pi,o as Zu}from"./index-CUMyn3nz.js";const ki={};function Ju(t){let e=ki[t];if(e)return e;e=ki[t]=[];for(let n=0;n<128;n++){const r=String.fromCharCode(n);e.push(r)}for(let n=0;n<t.length;n++){const r=t.charCodeAt(n);e[r]="%"+("0"+r.toString(16).toUpperCase()).slice(-2)}return e}function Fe(t,e){typeof e!="string"&&(e=Fe.defaultChars);const n=Ju(e);return t.replace(/(%[a-f0-9]{2})+/gi,function(r){let a="";for(let i=0,s=r.length;i<s;i+=3){const o=parseInt(r.slice(i+1,i+3),16);if(o<128){a+=n[o];continue}if((o&224)===192&&i+3<s){const c=parseInt(r.slice(i+4,i+6),16);if((c&192)===128){const l=o<<6&1984|c&63;l<128?a+="��":a+=String.fromCharCode(l),i+=3;continue}}if((o&240)===224&&i+6<s){const c=parseInt(r.slice(i+4,i+6),16),l=parseInt(r.slice(i+7,i+9),16);if((c&192)===128&&(l&192)===128){const u=o<<12&61440|c<<6&4032|l&63;u<2048||u>=55296&&u<=57343?a+="���":a+=String.fromCharCode(u),i+=6;continue}}if((o&248)===240&&i+9<s){const c=parseInt(r.slice(i+4,i+6),16),l=parseInt(r.slice(i+7,i+9),16),u=parseInt(r.slice(i+10,i+12),16);if((c&192)===128&&(l&192)===128&&(u&192)===128){let _=o<<18&1835008|c<<12&258048|l<<6&4032|u&63;_<65536||_>1114111?a+="����":(_-=65536,a+=String.fromCharCode(55296+(_>>10),56320+(_&1023))),i+=9;continue}}a+="�"}return a})}Fe.defaultChars=";/?:@&=+$,#";Fe.componentChars="";const Fi={};function ju(t){let e=Fi[t];if(e)return e;e=Fi[t]=[];for(let n=0;n<128;n++){const r=String.fromCharCode(n);/^[0-9a-z]$/i.test(r)?e.push(r):e.push("%"+("0"+n.toString(16).toUpperCase()).slice(-2))}for(let n=0;n<t.length;n++)e[t.charCodeAt(n)]=t[n];return e}function Xe(t,e,n){typeof e!="string"&&(n=e,e=Xe.defaultChars),typeof n>"u"&&(n=!0);const r=ju(e);let a="";for(let i=0,s=t.length;i<s;i++){const o=t.charCodeAt(i);if(n&&o===37&&i+2<s&&/^[0-9a-f]{2}$/i.test(t.slice(i+1,i+3))){a+=t.slice(i,i+3),i+=2;continue}if(o<128){a+=r[o];continue}if(o>=55296&&o<=57343){if(o>=55296&&o<=56319&&i+1<s){const c=t.charCodeAt(i+1);if(c>=56320&&c<=57343){a+=encodeURIComponent(t[i]+t[i+1]),i++;continue}}a+="%EF%BF%BD";continue}a+=encodeURIComponent(t[i])}return a}Xe.defaultChars=";/?:@&=+$,-_.!~*'()#";Xe.componentChars="-_.!~*'()";function si(t){let e="";return e+=t.protocol||"",e+=t.slashes?"//":"",e+=t.auth?t.auth+"@":"",t.hostname&&t.hostname.indexOf(":")!==-1?e+="["+t.hostname+"]":e+=t.hostname||"",e+=t.port?":"+t.port:"",e+=t.pathname||"",e+=t.search||"",e+=t.hash||"",e}function ot(){this.protocol=null,this.slashes=null,this.auth=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.pathname=null}const e_=/^([a-z0-9.+-]+:)/i,t_=/:[0-9]*$/,n_=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,r_=["<",">",'"',"`"," ","\r",`
`," "],a_=["{","}","|","\\","^","`"].concat(r_),i_=["'"].concat(a_),Ui=["%","/","?",";","#"].concat(i_),Bi=["/","?","#"],o_=255,Gi=/^[+a-z0-9A-Z_-]{0,63}$/,s_=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,Yi={javascript:!0,"javascript:":!0},qi={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0};function ci(t,e){if(t&&t instanceof ot)return t;const n=new ot;return n.parse(t,e),n}ot.prototype.parse=function(t,e){let n,r,a,i=t;if(i=i.trim(),!e&&t.split("#").length===1){const l=n_.exec(i);if(l)return this.pathname=l[1],l[2]&&(this.search=l[2]),this}let s=e_.exec(i);if(s&&(s=s[0],n=s.toLowerCase(),this.protocol=s,i=i.substr(s.length)),(e||s||i.match(/^\/\/[^@\/]+@[^@\/]+/))&&(a=i.substr(0,2)==="//",a&&!(s&&Yi[s])&&(i=i.substr(2),this.slashes=!0)),!Yi[s]&&(a||s&&!qi[s])){let l=-1;for(let d=0;d<Bi.length;d++)r=i.indexOf(Bi[d]),r!==-1&&(l===-1||r<l)&&(l=r);let u,_;l===-1?_=i.lastIndexOf("@"):_=i.lastIndexOf("@",l),_!==-1&&(u=i.slice(0,_),i=i.slice(_+1),this.auth=u),l=-1;for(let d=0;d<Ui.length;d++)r=i.indexOf(Ui[d]),r!==-1&&(l===-1||r<l)&&(l=r);l===-1&&(l=i.length),i[l-1]===":"&&l--;const p=i.slice(0,l);i=i.slice(l),this.parseHost(p),this.hostname=this.hostname||"";const m=this.hostname[0]==="["&&this.hostname[this.hostname.length-1]==="]";if(!m){const d=this.hostname.split(/\./);for(let S=0,g=d.length;S<g;S++){const T=d[S];if(T&&!T.match(Gi)){let b="";for(let f=0,R=T.length;f<R;f++)T.charCodeAt(f)>127?b+="x":b+=T[f];if(!b.match(Gi)){const f=d.slice(0,S),R=d.slice(S+1),N=T.match(s_);N&&(f.push(N[1]),R.unshift(N[2])),R.length&&(i=R.join(".")+i),this.hostname=f.join(".");break}}}}this.hostname.length>o_&&(this.hostname=""),m&&(this.hostname=this.hostname.substr(1,this.hostname.length-2))}const o=i.indexOf("#");o!==-1&&(this.hash=i.substr(o),i=i.slice(0,o));const c=i.indexOf("?");return c!==-1&&(this.search=i.substr(c),i=i.slice(0,c)),i&&(this.pathname=i),qi[n]&&this.hostname&&!this.pathname&&(this.pathname=""),this};ot.prototype.parseHost=function(t){let e=t_.exec(t);e&&(e=e[0],e!==":"&&(this.port=e.substr(1)),t=t.substr(0,t.length-e.length)),t&&(this.hostname=t)};const c_=Object.freeze(Object.defineProperty({__proto__:null,decode:Fe,encode:Xe,format:si,parse:ci},Symbol.toStringTag,{value:"Module"})),zl=/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,Wl=/[\0-\x1F\x7F-\x9F]/,l_=/[\xAD\u0600-\u0605\u061C\u06DD\u070F\u0890\u0891\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804[\uDCBD\uDCCD]|\uD80D[\uDC30-\uDC3F]|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]/,li=/[!-#%-\*,-\/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061D-\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C77\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1B7D\u1B7E\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4F\u2E52-\u2E5D\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDEAD\uDF55-\uDF59\uDF86-\uDF89]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5A\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDEB9\uDF3C-\uDF3E]|\uD806[\uDC3B\uDD44-\uDD46\uDDE2\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2\uDF00-\uDF09]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8\uDF43-\uDF4F\uDFFF]|\uD809[\uDC70-\uDC74]|\uD80B[\uDFF1\uDFF2]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A\uDFE2]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/,$l=/[\$\+<->\^`\|~\xA2-\xA6\xA8\xA9\xAC\xAE-\xB1\xB4\xB8\xD7\xF7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u02FF\u0375\u0384\u0385\u03F6\u0482\u058D-\u058F\u0606-\u0608\u060B\u060E\u060F\u06DE\u06E9\u06FD\u06FE\u07F6\u07FE\u07FF\u0888\u09F2\u09F3\u09FA\u09FB\u0AF1\u0B70\u0BF3-\u0BFA\u0C7F\u0D4F\u0D79\u0E3F\u0F01-\u0F03\u0F13\u0F15-\u0F17\u0F1A-\u0F1F\u0F34\u0F36\u0F38\u0FBE-\u0FC5\u0FC7-\u0FCC\u0FCE\u0FCF\u0FD5-\u0FD8\u109E\u109F\u1390-\u1399\u166D\u17DB\u1940\u19DE-\u19FF\u1B61-\u1B6A\u1B74-\u1B7C\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2044\u2052\u207A-\u207C\u208A-\u208C\u20A0-\u20C0\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F\u218A\u218B\u2190-\u2307\u230C-\u2328\u232B-\u2426\u2440-\u244A\u249C-\u24E9\u2500-\u2767\u2794-\u27C4\u27C7-\u27E5\u27F0-\u2982\u2999-\u29D7\u29DC-\u29FB\u29FE-\u2B73\u2B76-\u2B95\u2B97-\u2BFF\u2CE5-\u2CEA\u2E50\u2E51\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFF\u3004\u3012\u3013\u3020\u3036\u3037\u303E\u303F\u309B\u309C\u3190\u3191\u3196-\u319F\u31C0-\u31E3\u31EF\u3200-\u321E\u322A-\u3247\u3250\u3260-\u327F\u328A-\u32B0\u32C0-\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA700-\uA716\uA720\uA721\uA789\uA78A\uA828-\uA82B\uA836-\uA839\uAA77-\uAA79\uAB5B\uAB6A\uAB6B\uFB29\uFBB2-\uFBC2\uFD40-\uFD4F\uFDCF\uFDFC-\uFDFF\uFE62\uFE64-\uFE66\uFE69\uFF04\uFF0B\uFF1C-\uFF1E\uFF3E\uFF40\uFF5C\uFF5E\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFFC\uFFFD]|\uD800[\uDD37-\uDD3F\uDD79-\uDD89\uDD8C-\uDD8E\uDD90-\uDD9C\uDDA0\uDDD0-\uDDFC]|\uD802[\uDC77\uDC78\uDEC8]|\uD805\uDF3F|\uD807[\uDFD5-\uDFF1]|\uD81A[\uDF3C-\uDF3F\uDF45]|\uD82F\uDC9C|\uD833[\uDF50-\uDFC3]|\uD834[\uDC00-\uDCF5\uDD00-\uDD26\uDD29-\uDD64\uDD6A-\uDD6C\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDDEA\uDE00-\uDE41\uDE45\uDF00-\uDF56]|\uD835[\uDEC1\uDEDB\uDEFB\uDF15\uDF35\uDF4F\uDF6F\uDF89\uDFA9\uDFC3]|\uD836[\uDC00-\uDDFF\uDE37-\uDE3A\uDE6D-\uDE74\uDE76-\uDE83\uDE85\uDE86]|\uD838[\uDD4F\uDEFF]|\uD83B[\uDCAC\uDCB0\uDD2E\uDEF0\uDEF1]|\uD83C[\uDC00-\uDC2B\uDC30-\uDC93\uDCA0-\uDCAE\uDCB1-\uDCBF\uDCC1-\uDCCF\uDCD1-\uDCF5\uDD0D-\uDDAD\uDDE6-\uDE02\uDE10-\uDE3B\uDE40-\uDE48\uDE50\uDE51\uDE60-\uDE65\uDF00-\uDFFF]|\uD83D[\uDC00-\uDED7\uDEDC-\uDEEC\uDEF0-\uDEFC\uDF00-\uDF76\uDF7B-\uDFD9\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDC00-\uDC0B\uDC10-\uDC47\uDC50-\uDC59\uDC60-\uDC87\uDC90-\uDCAD\uDCB0\uDCB1\uDD00-\uDE53\uDE60-\uDE6D\uDE70-\uDE7C\uDE80-\uDE88\uDE90-\uDEBD\uDEBF-\uDEC5\uDECE-\uDEDB\uDEE0-\uDEE8\uDEF0-\uDEF8\uDF00-\uDF92\uDF94-\uDFCA]/,Kl=/[ \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/,u_=Object.freeze(Object.defineProperty({__proto__:null,Any:zl,Cc:Wl,Cf:l_,P:li,S:$l,Z:Kl},Symbol.toStringTag,{value:"Module"})),__=new Uint16Array('ᵁ<Õıʊҝջאٵ۞ޢߖࠏઑඡ༉༦ረዡᐕᒝᓃᓟᔥ\0\0\0\0\0\0ᕫᛍᦍᰒᷝ↰⊍⏀⏻⑂⠤⤒ⴈ⹈⿎〖㊺㘹㞬㣾㨨㩱㫠㬮ࠀEMabcfglmnoprstu\\bfms¦³¹ÈÏlig耻Æ䃆P耻&䀦cute耻Á䃁reve;䄂Āiyx}rc耻Â䃂;䐐r;쀀𝔄rave耻À䃀pha;䎑acr;䄀d;橓Āgp¡on;䄄f;쀀𝔸plyFunction;恡ing耻Å䃅Ācs¾Ãr;쀀𝒜ign;扔ilde耻Ã䃃ml耻Ä䃄ЀaceforsuåûþėĜĢħĪĀcrêòkslash;或Ŷöø;櫧ed;挆y;䐑ƀcrtąċĔause;戵noullis;愬a;䎒r;쀀𝔅pf;쀀𝔹eve;䋘còēmpeq;扎܀HOacdefhilorsuōőŖƀƞƢƵƷƺǜȕɳɸɾcy;䐧PY耻©䂩ƀcpyŝŢźute;䄆Ā;iŧŨ拒talDifferentialD;慅leys;愭ȀaeioƉƎƔƘron;䄌dil耻Ç䃇rc;䄈nint;戰ot;䄊ĀdnƧƭilla;䂸terDot;䂷òſi;䎧rcleȀDMPTLJNjǑǖot;抙inus;抖lus;投imes;抗oĀcsǢǸkwiseContourIntegral;戲eCurlyĀDQȃȏoubleQuote;思uote;怙ȀlnpuȞȨɇɕonĀ;eȥȦ户;橴ƀgitȯȶȺruent;扡nt;戯ourIntegral;戮ĀfrɌɎ;愂oduct;成nterClockwiseContourIntegral;戳oss;樯cr;쀀𝒞pĀ;Cʄʅ拓ap;才րDJSZacefiosʠʬʰʴʸˋ˗ˡ˦̳ҍĀ;oŹʥtrahd;椑cy;䐂cy;䐅cy;䐏ƀgrsʿ˄ˇger;怡r;憡hv;櫤Āayː˕ron;䄎;䐔lĀ;t˝˞戇a;䎔r;쀀𝔇Āaf˫̧Ācm˰̢riticalȀADGT̖̜̀̆cute;䂴oŴ̋̍;䋙bleAcute;䋝rave;䁠ilde;䋜ond;拄ferentialD;慆Ѱ̽\0\0\0͔͂\0Ѕf;쀀𝔻ƀ;DE͈͉͍䂨ot;惜qual;扐blèCDLRUVͣͲϏϢϸontourIntegraìȹoɴ\0\0ͻ»͉nArrow;懓Āeo·ΤftƀARTΐΖΡrrow;懐ightArrow;懔eåˊngĀLRΫτeftĀARγιrrow;柸ightArrow;柺ightArrow;柹ightĀATϘϞrrow;懒ee;抨pɁϩ\0\0ϯrrow;懑ownArrow;懕erticalBar;戥ǹABLRTaВЪаўѿͼrrowƀ;BUНОТ憓ar;椓pArrow;懵reve;䌑eft˒к\0ц\0ѐightVector;楐eeVector;楞ectorĀ;Bљњ憽ar;楖ightǔѧ\0ѱeeVector;楟ectorĀ;BѺѻ懁ar;楗eeĀ;A҆҇护rrow;憧ĀctҒҗr;쀀𝒟rok;䄐ࠀNTacdfglmopqstuxҽӀӄӋӞӢӧӮӵԡԯԶՒ՝ՠեG;䅊H耻Ð䃐cute耻É䃉ƀaiyӒӗӜron;䄚rc耻Ê䃊;䐭ot;䄖r;쀀𝔈rave耻È䃈ement;戈ĀapӺӾcr;䄒tyɓԆ\0\0ԒmallSquare;旻erySmallSquare;斫ĀgpԦԪon;䄘f;쀀𝔼silon;䎕uĀaiԼՉlĀ;TՂՃ橵ilde;扂librium;懌Āci՚r;愰m;橳a;䎗ml耻Ë䃋Āipժկsts;戃onentialE;慇ʀcfiosօֈ֍ֲy;䐤r;쀀𝔉lledɓ֗\0\0֣mallSquare;旼erySmallSquare;斪Ͱֺ\0ֿ\0\0ׄf;쀀𝔽All;戀riertrf;愱còJTabcdfgorstרׯؒؖ؛؝أ٬ٲcy;䐃耻>䀾mmaĀ;d䎓;䏜reve;䄞ƀeiy؇،ؐdil;䄢rc;䄜;䐓ot;䄠r;쀀𝔊;拙pf;쀀𝔾eater̀EFGLSTصلَٖٛ٦qualĀ;Lؾؿ扥ess;招ullEqual;执reater;檢ess;扷lantEqual;橾ilde;扳cr;쀀𝒢;扫ЀAacfiosuڅڋږڛڞڪھۊRDcy;䐪Āctڐڔek;䋇;䁞irc;䄤r;愌lbertSpace;愋ǰگ\0ڲf;愍izontalLine;攀Āctۃۅòکrok;䄦mpńېۘownHumðįqual;扏܀EJOacdfgmnostuۺ۾܃܇ܚܞܡܨ݄ݸދޏޕcy;䐕lig;䄲cy;䐁cute耻Í䃍Āiyܓܘrc耻Î䃎;䐘ot;䄰r;愑rave耻Ì䃌ƀ;apܠܯܿĀcgܴܷr;䄪inaryI;慈lieóϝǴ݉\0ݢĀ;eݍݎ戬Āgrݓݘral;戫section;拂isibleĀCTݬݲomma;恣imes;恢ƀgptݿރވon;䄮f;쀀𝕀a;䎙cr;愐ilde;䄨ǫޚ\0ޞcy;䐆l耻Ï䃏ʀcfosuެ߂ߐĀiyޱrc;䄴;䐙r;쀀𝔍pf;쀀𝕁ǣ߇\0ߌr;쀀𝒥rcy;䐈kcy;䐄HJacfosߤߨ߽߬߱ࠂࠈcy;䐥cy;䐌ppa;䎚Āey߶dil;䄶;䐚r;쀀𝔎pf;쀀𝕂cr;쀀𝒦րJTaceflmostࠥࠩࠬࡐࡣসে্ੇcy;䐉耻<䀼ʀcmnpr࠷࠼ࡁࡄࡍute;䄹bda;䎛g;柪lacetrf;愒r;憞ƀaeyࡗࡡron;䄽dil;䄻;䐛Āfsࡨ॰tԀACDFRTUVarࡾࢩࢱࣦ࣠ࣼयज़ΐ४ĀnrࢃgleBracket;柨rowƀ;BR࢙࢚࢞憐ar;懤ightArrow;懆eiling;挈oǵࢷ\0ࣃbleBracket;柦nǔࣈ\0࣒eeVector;楡ectorĀ;Bࣛࣜ懃ar;楙loor;挊ightĀAV࣯ࣵrrow;憔ector;楎Āerँगeƀ;AVउऊऐ抣rrow;憤ector;楚iangleƀ;BEतथऩ抲ar;槏qual;抴pƀDTVषूौownVector;楑eeVector;楠ectorĀ;Bॖॗ憿ar;楘ectorĀ;B॥०憼ar;楒ightáΜs̀EFGLSTॾঋকঝঢভqualGreater;拚ullEqual;扦reater;扶ess;檡lantEqual;橽ilde;扲r;쀀𝔏Ā;eঽা拘ftarrow;懚idot;䄿ƀnpwਖਛgȀLRlr৷ਂਐeftĀAR০৬rrow;柵ightArrow;柷ightArrow;柶eftĀarγਊightáοightáϊf;쀀𝕃erĀLRਢਬeftArrow;憙ightArrow;憘ƀchtਾੀੂòࡌ;憰rok;䅁;扪Ѐacefiosuਗ਼અઋp;椅y;䐜Ādl੯iumSpace;恟lintrf;愳r;쀀𝔐nusPlus;戓pf;쀀𝕄cò੶;䎜ҀJacefostuણધભીଔଙඑඞcy;䐊cute;䅃ƀaeyહાron;䅇dil;䅅;䐝ƀgswે૰ativeƀMTV૨ediumSpace;怋hiĀcn૦ëeryThiîtedĀGLଆreaterGreateòٳessLesóੈLine;䀊r;쀀𝔑ȀBnptଢନଷreak;恠BreakingSpace;䂠f;愕ڀ;CDEGHLNPRSTV୕ୖ୪௫ఄ಄ದൡඅ櫬Āoungruent;扢pCap;扭oubleVerticalBar;戦ƀlqxஃஊement;戉ualĀ;Tஒஓ扠ilde;쀀≂̸ists;戄reater;EFGLSTஶஷ扯qual;扱ullEqual;쀀≧̸reater;쀀≫̸ess;批lantEqual;쀀⩾̸ilde;扵umpń௲ownHump;쀀≎̸qual;쀀≏̸eĀfsఊధtTriangleƀ;BEచఛడ拪ar;쀀⧏̸qual;括s̀;EGLSTవశ఼ౄోౘ扮qual;扰reater;扸ess;쀀≪̸lantEqual;쀀⩽̸ilde;扴estedĀGL౨౹reaterGreater;쀀⪢̸essLess;쀀⪡̸recedesƀ;ESಒಓಛ技qual;쀀⪯̸lantEqual;拠ĀeiಫಹverseElement;戌ghtTriangleƀ;BEೋೌ拫ar;쀀⧐̸qual;拭ĀquೝഌuareSuĀbp೨setĀ;Eೳ쀀⊏̸qual;拢ersetĀ;Eഃആ쀀⊐̸qual;拣ƀbcpഓതൎsetĀ;Eഛഞ쀀⊂⃒qual;抈ceedsȀ;ESTലള഻െ抁qual;쀀⪰̸lantEqual;拡ilde;쀀≿̸ersetĀ;E൘൛쀀⊃⃒qual;抉ildeȀ;EFT൮൯൵ൿ扁qual;扄ullEqual;扇ilde;扉erticalBar;戤cr;쀀𝒩ilde耻Ñ䃑;䎝܀Eacdfgmoprstuvලෂෛ෧ขภยา฿ไlig;䅒cute耻Ó䃓Āiyීrc耻Ô䃔;䐞blac;䅐r;쀀𝔒rave耻Ò䃒ƀaei෮ෲcr;䅌ga;䎩cron;䎟pf;쀀𝕆enCurlyĀDQฎบoubleQuote;怜uote;怘;橔Āclวฬr;쀀𝒪ash耻Ø䃘iŬืde耻Õ䃕es;樷ml耻Ö䃖erĀBP๋Āar๐๓r;怾acĀek๚;揞et;掴arenthesis;揜ҀacfhilorsງຊຏຒດຝະrtialD;戂y;䐟r;쀀𝔓i;䎦;䎠usMinus;䂱Āipຢອncareplanåڝf;愙Ȁ;eio຺ູ檻cedesȀ;EST່້扺qual;檯lantEqual;扼ilde;找me;怳Ādpuct;戏ortionĀ;aȥl;戝Āci༁༆r;쀀𝒫;䎨ȀUfos༑༖༛༟OT耻"䀢r;쀀𝔔pf;愚cr;쀀𝒬BEacefhiorsu༾གྷཇའཱིྦྷྪྭ႖ႩႴႾarr;椐G耻®䂮ƀcnrཎནབute;䅔g;柫rĀ;tཛྷཝ憠l;椖ƀaeyཧཬཱron;䅘dil;䅖;䐠Ā;vླྀཹ愜erseĀEUྂྙĀlq྇ྎement;戋uilibrium;懋pEquilibrium;楯r»ཹo;䎡ghtЀACDFTUVa࿁ဢဨၛႇϘĀnr࿆࿒gleBracket;柩rowƀ;BL憒ar;懥eftArrow;懄eiling;按oǵ\0စbleBracket;柧nǔည\0နeeVector;楝ectorĀ;Bဝသ懂ar;楕loor;挋Āerိ၃eƀ;AVဵံြ抢rrow;憦ector;楛iangleƀ;BEၐၑၕ抳ar;槐qual;抵pƀDTVၣၮၸownVector;楏eeVector;楜ectorĀ;Bႂႃ憾ar;楔ectorĀ;B႑႒懀ar;楓Āpuႛ႞f;愝ndImplies;楰ightarrow;懛ĀchႹႼr;愛;憱leDelayed;槴ڀHOacfhimoqstuფჱჷჽᄙᄞᅑᅖᅡᅧᆵᆻᆿĀCcჩხHcy;䐩y;䐨FTcy;䐬cute;䅚ʀ;aeiyᄈᄉᄎᄓᄗ檼ron;䅠dil;䅞rc;䅜;䐡r;쀀𝔖ortȀDLRUᄪᄴᄾᅉownArrow»ОeftArrow»࢚ightArrow»pArrow;憑gma;䎣allCircle;战pf;쀀𝕊ɲᅭ\0\0ᅰt;戚areȀ;ISUᅻᅼᆉᆯ斡ntersection;抓uĀbpᆏᆞsetĀ;Eᆗᆘ抏qual;抑ersetĀ;Eᆨᆩ抐qual;抒nion;抔cr;쀀𝒮ar;拆ȀbcmpᇈᇛሉላĀ;sᇍᇎ拐etĀ;Eᇍᇕqual;抆ĀchᇠህeedsȀ;ESTᇭᇮᇴᇿ扻qual;檰lantEqual;扽ilde;承Tháྌ;我ƀ;esሒሓሣ拑rsetĀ;Eሜም抃qual;抇et»ሓրHRSacfhiorsሾቄቕቱቶኟዂወዑORN耻Þ䃞ADE;愢ĀHcቒcy;䐋y;䐦Ābuቚቜ;䀉;䎤ƀaeyብቪቯron;䅤dil;䅢;䐢r;쀀𝔗ĀeiቻDzኀ\0ኇefore;戴a;䎘ĀcnኘkSpace;쀀 Space;怉ldeȀ;EFTካኬኲኼ戼qual;扃ullEqual;扅ilde;扈pf;쀀𝕋ipleDot;惛Āctዖዛr;쀀𝒯rok;䅦ૡዷጎጚጦ\0ጬጱ\0\0\0\0\0ጸጽ፷ᎅ\0ᐄᐊᐐĀcrዻጁute耻Ú䃚rĀ;oጇገ憟cir;楉rǣጓ\0y;䐎ve;䅬Āiyጞጣrc耻Û䃛;䐣blac;䅰r;쀀𝔘rave耻Ù䃙acr;䅪Ādiፁ፩erĀBPፈ፝Āarፍፐr;䁟acĀekፗፙ;揟et;掵arenthesis;揝onĀ;P፰፱拃lus;抎Āgp፻on;䅲f;쀀𝕌ЀADETadps᎕ᎮᎸᏄϨᏒᏗᏳrrowƀ;BDᅐᎠᎤar;椒ownArrow;懅ownArrow;憕quilibrium;楮eeĀ;AᏋᏌ报rrow;憥ownáϳerĀLRᏞᏨeftArrow;憖ightArrow;憗iĀ;lᏹᏺ䏒on;䎥ing;䅮cr;쀀𝒰ilde;䅨ml耻Ü䃜ҀDbcdefosvᐧᐬᐰᐳᐾᒅᒊᒐᒖash;披ar;櫫y;䐒ashĀ;lᐻᐼ抩;櫦Āerᑃᑅ;拁ƀbtyᑌᑐᑺar;怖Ā;iᑏᑕcalȀBLSTᑡᑥᑪᑴar;戣ine;䁼eparator;杘ilde;所ThinSpace;怊r;쀀𝔙pf;쀀𝕍cr;쀀𝒱dash;抪ʀcefosᒧᒬᒱᒶᒼirc;䅴dge;拀r;쀀𝔚pf;쀀𝕎cr;쀀𝒲Ȁfiosᓋᓐᓒᓘr;쀀𝔛;䎞pf;쀀𝕏cr;쀀𝒳ҀAIUacfosuᓱᓵᓹᓽᔄᔏᔔᔚᔠcy;䐯cy;䐇cy;䐮cute耻Ý䃝Āiyᔉᔍrc;䅶;䐫r;쀀𝔜pf;쀀𝕐cr;쀀𝒴ml;䅸ЀHacdefosᔵᔹᔿᕋᕏᕝᕠᕤcy;䐖cute;䅹Āayᕄᕉron;䅽;䐗ot;䅻Dzᕔ\0ᕛoWidtèa;䎖r;愨pf;愤cr;쀀𝒵ᖃᖊᖐ\0ᖰᖶᖿ\0\0\0\0ᗆᗛᗫᙟ᙭\0ᚕ᚛ᚲᚹ\0ᚾcute耻á䃡reve;䄃̀;Ediuyᖜᖝᖡᖣᖨᖭ戾;쀀∾̳;房rc耻â䃢te肻´̆;䐰lig耻æ䃦Ā;r²ᖺ;쀀𝔞rave耻à䃠ĀepᗊᗖĀfpᗏᗔsym;愵èᗓha;䎱ĀapᗟcĀclᗤᗧr;䄁g;樿ɤᗰ\0\0ᘊʀ;adsvᗺᗻᗿᘁᘇ戧nd;橕;橜lope;橘;橚;elmrszᘘᘙᘛᘞᘿᙏᙙ戠;榤e»ᘙsdĀ;aᘥᘦ戡ѡᘰᘲᘴᘶᘸᘺᘼᘾ;榨;榩;榪;榫;榬;榭;榮;榯tĀ;vᙅᙆ戟bĀ;dᙌᙍ抾;榝Āptᙔᙗh;戢»¹arr;捼Āgpᙣᙧon;䄅f;쀀𝕒;Eaeiopᙻᙽᚂᚄᚇᚊ;橰cir;橯;扊d;手s;䀧roxĀ;eᚒñᚃing耻å䃥ƀctyᚡᚦᚨr;쀀𝒶;䀪mpĀ;eᚯñʈilde耻ã䃣ml耻ä䃤Āciᛂᛈoninôɲnt;樑ࠀNabcdefiklnoprsu᛭ᛱᜰᝃᝈ០៦ᠹᡐᜍ᥈ᥰot;櫭ĀcrᛶkȀcepsᜀᜅᜍᜓong;扌psilon;䏶rime;怵imĀ;e戽q;拍Ŷᜢᜦee;抽edĀ;gᜬᜭ挅e»ᜭrkĀ;tbrk;掶Āoyᜁᝁ;䐱quo;怞ʀcmprtᝓᝡᝤᝨausĀ;eĊĉptyv;榰séᜌnoõēƀahwᝯᝳ;䎲;愶een;扬r;쀀𝔟gcostuvwឍឝឳេ៕៛ƀaiuបពរðݠrc;旯p»፱ƀdptឤឨឭot;樀lus;樁imes;樂ɱឹ\0\0ើcup;樆ar;昅riangleĀdu៍្own;施p;斳plus;樄eåᑄåᒭarow;植ƀakoᠦᠵĀcn៲ᠣkƀlst֫᠂ozenge;槫riangleȀ;dlr᠒᠓᠘斴own;斾eft;旂ight;斸k;搣Ʊᠫ\0ᠳƲᠯ\0ᠱ;斒;斑4;斓ck;斈ĀeoᠾᡍĀ;qᡃᡆ쀀=⃥uiv;쀀≡⃥t;挐Ȁptwxᡙᡞᡧᡬf;쀀𝕓Ā;tᏋᡣom»Ꮜtie;拈DHUVbdhmptuvᢅᢖᢪᢻᣗᣛᣬᤅᤊᤐᤡȀLRlrᢎᢐᢒᢔ;敗;敔;敖;敓ʀ;DUduᢡᢢᢤᢦᢨ敐;敦;敩;敤;敧ȀLRlrᢳᢵᢷᢹ;敝;敚;敜;教;HLRhlrᣊᣋᣍᣏᣑᣓᣕ救;敬;散;敠;敫;敢;敟ox;槉ȀLRlrᣤᣦᣨᣪ;敕;敒;攐;攌ʀ;DUduڽ;敥;敨;攬;攴inus;抟lus;択imes;抠ȀLRlrᤙᤛᤝ;敛;敘;攘;攔;HLRhlrᤰᤱᤳᤵᤷ᤻᤹攂;敪;敡;敞;攼;攤;攜Āevģbar耻¦䂦Ȁceioᥑᥖᥚᥠr;쀀𝒷mi;恏mĀ;elƀ;bhᥨᥩᥫ䁜;槅sub;柈ŬᥴlĀ;e怢t»pƀ;Eeįᦅᦇ;檮Ā;qۜۛೡᦧ\0᧨ᨑᨕᨲ\0ᨷᩐ\0\0᪴\0\0᫁\0\0ᬡᬮ᭒\0᯽\0ᰌƀcprᦲute;䄇̀;abcdsᦿᧀᧄ᧕᧙戩nd;橄rcup;橉Āau᧒p;橋p;橇ot;橀;쀀∩︀Āeo᧢᧥t;恁îړȀaeiu᧰᧻ᨁᨅǰ᧵\0᧸s;橍on;䄍dil耻ç䃧rc;䄉psĀ;sᨌᨍ橌m;橐ot;䄋ƀdmnᨛᨠᨦil肻¸ƭptyv;榲t脀¢;eᨭᨮ䂢räƲr;쀀𝔠ƀceiᨽᩀᩍy;䑇ckĀ;mᩇᩈ朓ark»ᩈ;䏇r;Ecefms᩠ᩢᩫ᪤᪪旋;槃ƀ;elᩩᩪᩭ䋆q;扗eɡᩴ\0\0᪈rrowĀlr᩼᪁eft;憺ight;憻ʀRSacd᪒᪔᪖»ཇ;擈st;抛irc;抚ash;抝nint;樐id;櫯cir;槂ubsĀ;u᪻᪼晣it»᪼ˬ᫇\0ᬊonĀ;eᫍᫎ䀺Ā;qÇÆɭ\0\0aĀ;t䀬;䁀ƀ;fl戁îᅠeĀmxent»eóɍǧ\0ᬇĀ;dኻᬂot;橭nôɆƀfryᬐᬔᬗ;쀀𝕔oäɔ脀©;sŕᬝr;愗Āaoᬥᬩrr;憵ss;朗Ācuᬲᬷr;쀀𝒸Ābpᬼ᭄Ā;eᭁᭂ櫏;櫑Ā;eᭉᭊ櫐;櫒dot;拯delprvw᭠᭬᭷ᮂᮬᯔarrĀlr᭨᭪;椸;椵ɰ᭲\0\0᭵r;拞c;拟arrĀ;pᮀ憶;椽̀;bcdosᮏᮐᮖᮡᮥᮨ截rcap;橈Āauᮛᮞp;橆p;橊ot;抍r;橅;쀀∪︀Ȁalrv᮵ᮿᯞᯣrrĀ;mᮼᮽ憷;椼yƀevwᯇᯔᯘqɰᯎ\0\0ᯒreã᭳uã᭵ee;拎edge;拏en耻¤䂤earrowĀlrᯮ᯳eft»ᮀight»ᮽeäᯝĀciᰁᰇoninôǷnt;戱lcty;挭ঀAHabcdefhijlorstuwz᰻᰿ᱝᱩᱵᲞᲬᲷᴍᵻᶑᶫᶻ᷆᷍ròar;楥Ȁglrs᱈ᱍ᱒᱔ger;怠eth;愸òᄳhĀ;vᱚᱛ怐»ऊūᱡᱧarow;椏aã̕Āayᱮᱳron;䄏;䐴ƀ;ao̲ᱼᲄĀgrʿᲁr;懊tseq;橷ƀglmᲑᲔᲘ耻°䂰ta;䎴ptyv;榱ĀirᲣᲨsht;楿;쀀𝔡arĀlrᲳᲵ»ࣜ»သʀaegsv᳂᳖᳜᳠mƀ;oș᳔ndĀ;ș᳑uit;晦amma;䏝in;拲ƀ;io᳧᳨᳸䃷de脀÷;o᳧ᳰntimes;拇nø᳷cy;䑒cɯᴆ\0\0ᴊrn;挞op;挍ʀlptuwᴘᴝᴢᵉᵕlar;䀤f;쀀𝕕ʀ;emps̋ᴭᴷᴽᵂqĀ;d͒ᴳot;扑inus;戸lus;戔quare;抡blebarwedgåúnƀadhᄮᵝᵧownarrowóᲃarpoonĀlrᵲᵶefôᲴighôᲶŢᵿᶅkaro÷གɯᶊ\0\0ᶎrn;挟op;挌ƀcotᶘᶣᶦĀryᶝᶡ;쀀𝒹;䑕l;槶rok;䄑Ādrᶰᶴot;拱iĀ;fᶺ᠖斿Āah᷀᷃ròЩaòྦangle;榦Āci᷒ᷕy;䑟grarr;柿ऀDacdefglmnopqrstuxḁḉḙḸոḼṉṡṾấắẽỡἪἷὄĀDoḆᴴoôĀcsḎḔute耻é䃩ter;橮ȀaioyḢḧḱḶron;䄛rĀ;cḭḮ扖耻ê䃪lon;払;䑍ot;䄗ĀDrṁṅot;扒;쀀𝔢ƀ;rsṐṑṗ檚ave耻è䃨Ā;dṜṝ檖ot;檘Ȁ;ilsṪṫṲṴ檙nters;揧;愓Ā;dṹṺ檕ot;檗ƀapsẅẉẗcr;䄓tyƀ;svẒẓẕ戅et»ẓpĀ1;ẝẤijạả;怄;怅怃ĀgsẪẬ;䅋p;怂ĀgpẴẸon;䄙f;쀀𝕖ƀalsỄỎỒrĀ;sỊị拕l;槣us;橱iƀ;lvỚớở䎵on»ớ;䏵ȀcsuvỪỳἋἣĀioữḱrc»Ḯɩỹ\0\0ỻíՈantĀglἂἆtr»ṝess»ṺƀaeiἒἚls;䀽st;扟vĀ;DȵἠD;橸parsl;槥ĀDaἯἳot;打rr;楱ƀcdiἾὁỸr;愯oô͒ĀahὉὋ;䎷耻ð䃰Āmrὓὗl耻ë䃫o;悬ƀcipὡὤὧl;䀡sôծĀeoὬὴctatioîՙnentialåչৡᾒ\0ᾞ\0ᾡᾧ\0\0ῆῌ\0ΐ\0ῦῪ \0 ⁚llingdotseñṄy;䑄male;晀ƀilrᾭᾳ῁lig;耀ffiɩᾹ\0\0᾽g;耀ffig;耀ffl;쀀𝔣lig;耀filig;쀀fjƀaltῙῡt;晭ig;耀flns;斱of;䆒ǰ΅\0ῳf;쀀𝕗ĀakֿῷĀ;vῼ´拔;櫙artint;樍Āao⁕Ācs‑⁒ႉ‸⁅⁈\0⁐β•‥‧\0耻½䂽;慓耻¼䂼;慕;慙;慛Ƴ‴\0‶;慔;慖ʴ‾⁁\0\0⁃耻¾䂾;慗;慜5;慘ƶ⁌\0⁎;慚;慝8;慞l;恄wn;挢cr;쀀𝒻ࢀEabcdefgijlnorstv₂₉₥₰₴⃰℃ℒℸ̗ℾ⅒↞Ā;lٍ₇;檌ƀcmpₐₕute;䇵maĀ;dₜ᳚䎳;檆reve;䄟Āiy₪₮rc;䄝;䐳ot;䄡Ȁ;lqsؾق₽ƀ;qsؾٌlanô٥Ȁ;cdl٥⃒⃥⃕c;檩otĀ;o⃜⃝檀Ā;l⃢⃣檂;檄Ā;e⃪⃭쀀⋛︀s;檔r;쀀𝔤Ā;gٳ؛mel;愷cy;䑓Ȁ;Eajٚℌℎℐ;檒;檥;檤ȀEaesℛℝ℩ℴ;扩pĀ;p℣ℤ檊rox»ℤĀ;q℮ℯ檈Ā;q℮ℛim;拧pf;쀀𝕘Āci⅃ⅆr;愊mƀ;el٫ⅎ⅐;檎;檐茀>;cdlqrⅠⅪⅮⅳⅹĀciⅥⅧ;檧r;橺ot;拗Par;榕uest;橼ʀadelsↄⅪ←ٖ↛ǰ↉\0proør;楸qĀlqؿ↖lesó₈ií٫Āen↣↭rtneqq;쀀≩︀Å↪ԀAabcefkosy⇄⇇⇱⇵⇺∘∝∯≨≽ròΠȀilmr⇐⇔⇗⇛rsðᒄf»․ilôکĀdr⇠⇤cy;䑊ƀ;cwࣴ⇫⇯ir;楈;憭ar;意irc;䄥ƀalr∁∎∓rtsĀ;u∉∊晥it»∊lip;怦con;抹r;쀀𝔥sĀew∣∩arow;椥arow;椦ʀamopr∺∾≃≞≣rr;懿tht;戻kĀlr≉≓eftarrow;憩ightarrow;憪f;쀀𝕙bar;怕ƀclt≯≴≸r;쀀𝒽asè⇴rok;䄧Ābp⊂⊇ull;恃hen»ᱛૡ⊣\0⊪\0⊸⋅⋎\0⋕⋳\0\0⋸⌢⍧⍢⍿\0⎆⎪⎴cute耻í䃭ƀ;iyݱ⊰⊵rc耻î䃮;䐸Ācx⊼⊿y;䐵cl耻¡䂡ĀfrΟ⋉;쀀𝔦rave耻ì䃬Ȁ;inoܾ⋝⋩⋮Āin⋢⋦nt;樌t;戭fin;槜ta;愩lig;䄳ƀaop⋾⌚⌝ƀcgt⌅⌈⌗r;䄫ƀelpܟ⌏⌓inåގarôܠh;䄱f;抷ed;䆵ʀ;cfotӴ⌬⌱⌽⍁are;愅inĀ;t⌸⌹戞ie;槝doô⌙ʀ;celpݗ⍌⍐⍛⍡al;抺Āgr⍕⍙eróᕣã⍍arhk;樗rod;樼Ȁcgpt⍯⍲⍶⍻y;䑑on;䄯f;쀀𝕚a;䎹uest耻¿䂿Āci⎊⎏r;쀀𝒾nʀ;EdsvӴ⎛⎝⎡ӳ;拹ot;拵Ā;v⎦⎧拴;拳Ā;iݷ⎮lde;䄩ǫ⎸\0⎼cy;䑖l耻ï䃯̀cfmosu⏌⏗⏜⏡⏧⏵Āiy⏑⏕rc;䄵;䐹r;쀀𝔧ath;䈷pf;쀀𝕛ǣ⏬\0⏱r;쀀𝒿rcy;䑘kcy;䑔Ѐacfghjos␋␖␢ppaĀ;v␓␔䎺;䏰Āey␛␠dil;䄷;䐺r;쀀𝔨reen;䄸cy;䑅cy;䑜pf;쀀𝕜cr;쀀𝓀ABEHabcdefghjlmnoprstuv⑰⒁⒆⒍⒑┎┽╚▀♎♞♥♹♽⚚⚲⛘❝❨➋⟀⠁⠒ƀart⑷⑺⑼ròòΕail;椛arr;椎Ā;gঔ⒋;檋ar;楢ॣ⒥\0⒪\0⒱\0\0\0\0\0⒵Ⓔ\0ⓆⓈⓍ\0⓹ute;䄺mptyv;榴raîࡌbda;䎻gƀ;dlࢎⓁⓃ;榑åࢎ;檅uo耻«䂫rЀ;bfhlpst࢙ⓞⓦⓩ⓫⓮⓱⓵Ā;f࢝ⓣs;椟s;椝ë≒p;憫l;椹im;楳l;憢ƀ;ae⓿─┄檫il;椙Ā;s┉┊檭;쀀⪭︀ƀabr┕┙┝rr;椌rk;杲Āak┢┬cĀek┨┪;䁻;䁛Āes┱┳;榋lĀdu┹┻;榏;榍Ȁaeuy╆╋╖╘ron;䄾Ādi═╔il;䄼ìࢰâ┩;䐻Ȁcqrs╣╦╭╽a;椶uoĀ;rนᝆĀdu╲╷har;楧shar;楋h;憲ʀ;fgqs▋▌উ◳◿扤tʀahlrt▘▤▷◂◨rrowĀ;t࢙□aé⓶arpoonĀdu▯▴own»њp»०eftarrows;懇ightƀahs◍◖◞rrowĀ;sࣴࢧarpoonóquigarro÷⇰hreetimes;拋ƀ;qs▋ও◺lanôবʀ;cdgsব☊☍☝☨c;檨otĀ;o☔☕橿Ā;r☚☛檁;檃Ā;e☢☥쀀⋚︀s;檓ʀadegs☳☹☽♉♋pproøⓆot;拖qĀgq♃♅ôউgtò⒌ôছiíলƀilr♕࣡♚sht;楼;쀀𝔩Ā;Eজ♣;檑š♩♶rĀdu▲♮Ā;l॥♳;楪lk;斄cy;䑙ʀ;achtੈ⚈⚋⚑⚖rò◁orneòᴈard;楫ri;旺Āio⚟⚤dot;䅀ustĀ;a⚬⚭掰che»⚭ȀEaes⚻⚽⛉⛔;扨pĀ;p⛃⛄檉rox»⛄Ā;q⛎⛏檇Ā;q⛎⚻im;拦Ѐabnoptwz⛩⛴⛷✚✯❁❇❐Ānr⛮⛱g;柬r;懽rëࣁgƀlmr⛿✍✔eftĀar০✇ightá৲apsto;柼ightá৽parrowĀlr✥✩efô⓭ight;憬ƀafl✶✹✽r;榅;쀀𝕝us;樭imes;樴š❋❏st;戗áፎƀ;ef❗❘᠀旊nge»❘arĀ;l❤❥䀨t;榓ʀachmt❳❶❼➅➇ròࢨorneòᶌarĀ;d➃;業;怎ri;抿̀achiqt➘➝ੀ➢➮➻quo;怹r;쀀𝓁mƀ;egল➪➬;檍;檏Ābu┪➳oĀ;rฟ➹;怚rok;䅂萀<;cdhilqrࠫ⟒☹⟜⟠⟥⟪⟰Āci⟗⟙;檦r;橹reå◲mes;拉arr;楶uest;橻ĀPi⟵⟹ar;榖ƀ;ef⠀भ旃rĀdu⠇⠍shar;楊har;楦Āen⠗⠡rtneqq;쀀≨︀Å⠞܀Dacdefhilnopsu⡀⡅⢂⢎⢓⢠⢥⢨⣚⣢⣤ઃ⣳⤂Dot;戺Ȁclpr⡎⡒⡣⡽r耻¯䂯Āet⡗⡙;時Ā;e⡞⡟朠se»⡟Ā;sျ⡨toȀ;dluျ⡳⡷⡻owîҌefôएðᏑker;斮Āoy⢇⢌mma;権;䐼ash;怔asuredangle»ᘦr;쀀𝔪o;愧ƀcdn⢯⢴⣉ro耻µ䂵Ȁ;acdᑤ⢽⣀⣄sôᚧir;櫰ot肻·Ƶusƀ;bd⣒ᤃ⣓戒Ā;uᴼ⣘;横ţ⣞⣡p;櫛ò−ðઁĀdp⣩⣮els;抧f;쀀𝕞Āct⣸⣽r;쀀𝓂pos»ᖝƀ;lm⤉⤊⤍䎼timap;抸ఀGLRVabcdefghijlmoprstuvw⥂⥓⥾⦉⦘⧚⧩⨕⨚⩘⩝⪃⪕⪤⪨⬄⬇⭄⭿⮮ⰴⱧⱼ⳩Āgt⥇⥋;쀀⋙̸Ā;v⥐쀀≫⃒ƀelt⥚⥲⥶ftĀar⥡⥧rrow;懍ightarrow;懎;쀀⋘̸Ā;v⥻ే쀀≪⃒ightarrow;懏ĀDd⦎⦓ash;抯ash;抮ʀbcnpt⦣⦧⦬⦱⧌la»˞ute;䅄g;쀀∠⃒ʀ;Eiop⦼⧀⧅⧈;쀀⩰̸d;쀀≋̸s;䅉roøurĀ;a⧓⧔普lĀ;s⧓ସdz⧟\0⧣p肻 ଷmpĀ;e௹ఀʀaeouy⧴⧾⨃⨐⨓ǰ⧹\0⧻;橃on;䅈dil;䅆ngĀ;dൾ⨊ot;쀀⩭̸p;橂;䐽ash;怓;Aadqsxஒ⨩⨭⨻⩁⩅⩐rr;懗rĀhr⨳⨶k;椤Ā;oᏲᏰot;쀀≐̸uiöୣĀei⩊⩎ar;椨íistĀ;sடr;쀀𝔫ȀEest⩦⩹⩼ƀ;qs⩭ƀ;qs⩴lanôií௪Ā;rஶ⪁»ஷƀAap⪊⪍⪑rò⥱rr;憮ar;櫲ƀ;svྍ⪜ྌĀ;d⪡⪢拼;拺cy;䑚AEadest⪷⪺⪾⫂⫅⫶⫹rò⥦;쀀≦̸rr;憚r;急Ȁ;fqs⫎⫣⫯tĀar⫔⫙rro÷⫁ightarro÷⪐ƀ;qs⪺⫪lanôౕĀ;sౕ⫴»శiíౝĀ;rవ⫾iĀ;eచథiäඐĀpt⬌⬑f;쀀𝕟膀¬;in⬙⬚⬶䂬nȀ;Edvஉ⬤⬨⬮;쀀⋹̸ot;쀀⋵̸ǡஉ⬳⬵;拷;拶iĀ;vಸ⬼ǡಸ⭁⭃;拾;拽ƀaor⭋⭣⭩rȀ;ast⭕⭚⭟lleìl;쀀⫽⃥;쀀∂̸lint;樔ƀ;ceಒ⭰⭳uåಥĀ;cಘ⭸Ā;eಒ⭽ñಘȀAait⮈⮋⮝⮧rò⦈rrƀ;cw⮔⮕⮙憛;쀀⤳̸;쀀↝̸ghtarrow»⮕riĀ;eೋೖchimpqu⮽⯍⯙⬄⯤
gitextract_upuwzr0s/ ├── .env_example ├── .flake8 ├── .github/ │ └── workflows/ │ └── main.yml ├── .gitignore ├── .pylintrc ├── LICENSE ├── Makefile ├── README.md ├── app/ │ ├── __init__.py │ ├── api/ │ │ ├── __init__.py │ │ ├── admin/ │ │ │ ├── __init__.py │ │ │ ├── ctf.py │ │ │ ├── docker.py │ │ │ ├── schemas/ │ │ │ │ ├── __init__.py │ │ │ │ ├── ctf.py │ │ │ │ └── docker.py │ │ │ ├── system.py │ │ │ └── vulnerability.py │ │ ├── health.py │ │ ├── player/ │ │ │ ├── __init__.py │ │ │ ├── views.py │ │ │ ├── vulnerability.py │ │ │ └── ws.py │ │ └── route.py │ ├── core/ │ │ ├── api.py │ │ ├── command.py │ │ ├── const.py │ │ ├── decorators.py │ │ ├── error_handlers.py │ │ ├── exceptions.py │ │ ├── flask_celery.py │ │ ├── middlewares.py │ │ └── tools.py │ ├── extensions.py │ ├── models/ │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── ctf.py │ │ ├── docker.py │ │ └── user.py │ ├── services/ │ │ ├── __init__.py │ │ ├── docker.py │ │ ├── player.py │ │ └── system.py │ ├── tasks/ │ │ ├── __init__.py │ │ ├── ctf.py │ │ ├── docker.py │ │ ├── player.py │ │ ├── system.py │ │ └── vulnerability.py │ ├── utils/ │ │ ├── security.py │ │ ├── tools.py │ │ └── validator.py │ └── worker.py ├── config/ │ └── config.py ├── docker-compose.yml ├── install/ │ ├── config/ │ │ └── supervisord/ │ │ └── ocean.ini │ ├── db_init/ │ │ └── ocean.sql │ ├── docker/ │ │ ├── ocean_web.Dockerfile │ │ └── wait-for-it.sh │ ├── frontend/ │ │ └── dist/ │ │ ├── assets/ │ │ │ ├── detail-Beu8V9iA.js │ │ │ ├── detail-CwA0i7lp.css │ │ │ ├── index-2Kde3L7C.js │ │ │ ├── index-BNtu7jJ7.js │ │ │ ├── index-CUMyn3nz.js │ │ │ ├── index-Cn092HV2.css │ │ │ ├── index-CocFE3Kg.css │ │ │ ├── index-DAXaKKX5.js │ │ │ ├── index-DSpYhb0F.css │ │ │ ├── index-DTf6UOgP.js │ │ │ ├── index-D_63FOFH.js │ │ │ ├── index-Dst5s0sm.css │ │ │ ├── index-IBfy7H4-.css │ │ │ ├── index-M3X3VVYJ.css │ │ │ ├── index-PyJu_6A4.js │ │ │ ├── index-qmyYS7Pw.css │ │ │ ├── register-C1gwrr0Z.css │ │ │ └── register-CwwKsjmL.js │ │ └── index.html │ ├── manager/ │ │ └── dist/ │ │ ├── index.html │ │ ├── serverConfig.json │ │ ├── static/ │ │ │ ├── css/ │ │ │ │ ├── LineChart-CAiRPB6S.css │ │ │ │ ├── PanelGroup-TcT6nE-z.css │ │ │ │ ├── UserCard-D6QtR3QQ.css │ │ │ │ ├── addImage-qmRBmbjD.css │ │ │ │ ├── answer-C7eDAiIE.css │ │ │ │ ├── config-CZAQQi9l.css │ │ │ │ ├── config-W0rb9i92.css │ │ │ │ ├── container-C6zUPM_j.css │ │ │ │ ├── container-DtRsVCeN.css │ │ │ │ ├── editResources-Df9j_O02.css │ │ │ │ ├── frame-C56j9Uki.css │ │ │ │ ├── host-C9ss6-xf.css │ │ │ │ ├── hostDetail-DJ2SmXrv.css │ │ │ │ ├── imageDetail-LDv3EwHR.css │ │ │ │ ├── images-Tpvwj_u0.css │ │ │ │ ├── index-B2cknHNH.css │ │ │ │ ├── index-B5rb6rib.css │ │ │ │ ├── index-BOEMDA_E.css │ │ │ │ ├── index-BcTahPvV.css │ │ │ │ ├── index-C4pPArRS.css │ │ │ │ ├── index-CQHzbCBB.css │ │ │ │ ├── index-ChP2PXZC.css │ │ │ │ ├── index-DNZyiNmE.css │ │ │ │ ├── index-m7pPGTca.css │ │ │ │ ├── operator-C9HCk2lz.css │ │ │ │ ├── question-qN-CSJNX.css │ │ │ │ ├── questionItem-4bFCFt94.css │ │ │ │ ├── resource-CM1VvOIh.css │ │ │ │ ├── resourceForm-p8bnP91q.css │ │ │ │ ├── resourceItem-DzDzzvBa.css │ │ │ │ ├── resources-BH1s_vA0.css │ │ │ │ ├── resources-Ct3K675F.css │ │ │ │ ├── sysInfo-BohfULNO.css │ │ │ │ └── user-DerJW7Bd.css │ │ │ └── js/ │ │ │ ├── 403-DKEdQNMH.js │ │ │ ├── 404-lPQWHYlH.js │ │ │ ├── 500-Bi6ZUpQv.js │ │ │ ├── Account-Bb8H_MKm.js │ │ │ ├── LineChart-CyBl2sMc.js │ │ │ ├── PanelGroup-QRQhV6r1.js │ │ │ ├── RestPass-mWtPbTNp.js │ │ │ ├── Timeline-CGNLPXAJ.js │ │ │ ├── Todo-B8eVum4J.js │ │ │ ├── UserCard-D9xOWVMi.js │ │ │ ├── addHost-BVYUJp7N.js │ │ │ ├── addImage-Cex8BYxg.js │ │ │ ├── addUser-C2q5OAyK.js │ │ │ ├── admins-BKHsnYfX.js │ │ │ ├── answer-PdY72vFF.js │ │ │ ├── audit-DbCqE0TT.js │ │ │ ├── back_top-B8RoSTAY.js │ │ │ ├── config-BerqnP1C.js │ │ │ ├── config-DU8nLchD.js │ │ │ ├── container--f66ltxq.js │ │ │ ├── container-62xqkrS2.js │ │ │ ├── dark-Dv7Dbtg0.js │ │ │ ├── docker_resource_sync-Dxn6rGPc.js │ │ │ ├── editAdmin-xd3VaCzj.js │ │ │ ├── editResources-jJ-omP3I.js │ │ │ ├── editRole-Dqlx3Hxu.js │ │ │ ├── frame-CRmNRX0K.js │ │ │ ├── hooks-CzcwFjli.js │ │ │ ├── host-g1EgSM4r.js │ │ │ ├── hostDetail-DeVpx4OE.js │ │ │ ├── imageDetail-quKI1yqP.js │ │ │ ├── images-BUkzOY-1.js │ │ │ ├── index-BLCelF29.js │ │ │ ├── index-B_eHX4aD.js │ │ │ ├── index-BqIwwt-c.js │ │ │ ├── index-BzD9KVPC.js │ │ │ ├── index-Cj3Ji7Ce.js │ │ │ ├── index-Cuc6Bha6.js │ │ │ ├── index-D4veOIBM.js │ │ │ ├── index-DGss3hGv.js │ │ │ ├── index-DqeA2Szn.js │ │ │ ├── index-Lj5hgGY3.js │ │ │ ├── index-xEKAsWxN.js │ │ │ ├── notifications-D8ciMJpU.js │ │ │ ├── operator-BiRoUc-y.js │ │ │ ├── question-DPBmlVI1.js │ │ │ ├── questionItem-BnT1RndV.js │ │ │ ├── questionTypeManage-C4aIrDS_.js │ │ │ ├── redirect-BNWD9FK6.js │ │ │ ├── resource-B26noqeQ.js │ │ │ ├── resourceForm-UYjwLmkY.js │ │ │ ├── resourceItem-BIlM6slQ.js │ │ │ ├── resources-D66Xg1B4.js │ │ │ ├── resources-eAiTMxgh.js │ │ │ ├── role-Cj_rRiT0.js │ │ │ ├── runner-C4c3DT0z.js │ │ │ ├── sysInfo-BJzi9t0G.js │ │ │ └── user-BBi65gzK.js │ │ └── version.json │ └── nginx.conf ├── main.py ├── pyproject.toml ├── requirements/ │ ├── base.txt │ ├── dev.txt │ ├── prod.txt │ └── test.txt ├── requirements.txt ├── run.sh ├── tests/ │ ├── conftest.py │ ├── test_docker.py │ ├── test_health_api.py │ ├── test_tasks.py │ └── test_validator.py ├── upload/ │ └── .gitignore └── wsgi.py
Showing preview only (927K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (8203 symbols across 110 files)
FILE: app/__init__.py
function create_app (line 15) | def create_app():
function register_custom_helpers (line 50) | def register_custom_helpers(scope_app):
function register_extensions (line 70) | def register_extensions(scope_app):
function create_default_data (line 98) | def create_default_data():
FILE: app/api/admin/ctf.py
function question_type (line 25) | def question_type():
function resource_list (line 35) | def resource_list():
function question_set_active (line 85) | def question_set_active(pk):
function question_update (line 98) | def question_update(pk):
function ctf_containers_refresh (line 141) | def ctf_containers_refresh(container_resource):
function resource_remove (line 162) | def resource_remove(pk):
function answers_list (line 175) | def answers_list():
function answer_status_list (line 225) | def answer_status_list():
function question_list (line 233) | def question_list():
function question_create (line 285) | def question_create(body: QuestionForm):
function question_delete (line 303) | def question_delete(pk):
function images_list (line 325) | def images_list():
function images_delete (line 356) | def images_delete(pk):
function images_create (line 369) | def images_create():
function image_update (line 391) | def image_update(pk):
function ctf_upload_attachment (line 412) | def ctf_upload_attachment():
function ctf_sync_repo (line 434) | def ctf_sync_repo():
FILE: app/api/admin/docker.py
function docker_info (line 40) | def docker_info():
function docker_images (line 68) | def docker_images():
function image_delete (line 96) | def image_delete():
function host_docker_container (line 116) | def host_docker_container():
function container_action (line 132) | def container_action():
function image_create (line 151) | def image_create():
function compose_db_list (line 185) | def compose_db_list(query: PageForm):
function compose_db_create (line 196) | def compose_db_create(body: ComposeDBForm):
function compose_db_delete (line 208) | def compose_db_delete(pk):
function compose_runner_list (line 215) | def compose_runner_list(query: PageForm):
function docker_resource_list (line 226) | def docker_resource_list(query: PageForm):
function docker_resource_create (line 251) | def docker_resource_create(body: DockerResourceForm):
function docker_resource_update (line 258) | def docker_resource_update(pk: int, body: DockerResourceEditForm):
function docker_resource_build (line 274) | def docker_resource_build(pk):
function docker_resource_logs (line 283) | def docker_resource_logs(pk):
function docker_resource_sync (line 304) | def docker_resource_sync():
function resource_delete (line 341) | def resource_delete(pk):
FILE: app/api/admin/schemas/ctf.py
class QuestionForm (line 6) | class QuestionForm(BaseModel):
FILE: app/api/admin/schemas/docker.py
class PageForm (line 6) | class PageForm(BaseModel):
class ComposeDBForm (line 11) | class ComposeDBForm(BaseModel):
class DockerResourceForm (line 16) | class DockerResourceForm(BaseModel):
class DockerResourceEditForm (line 24) | class DockerResourceEditForm(BaseModel):
FILE: app/api/admin/system.py
function admin_rest_pass (line 32) | def admin_rest_pass():
function task_log (line 48) | def task_log(task):
function upload_file (line 60) | def upload_file():
function admin_list (line 79) | def admin_list():
function admin_update (line 108) | def admin_update(pk):
function admin_create (line 126) | def admin_create():
function admin_delete (line 141) | def admin_delete(pk):
function user_list (line 149) | def user_list():
function user_create (line 179) | def user_create():
function user_update (line 195) | def user_update(pk):
function user_delete (line 206) | def user_delete(pk):
function index_state (line 218) | def index_state():
function notice_list (line 264) | def notice_list():
function notice_create (line 296) | def notice_create():
function notice_update (line 308) | def notice_update(pk):
function notice_delete (line 328) | def notice_delete(pk):
function login_info (line 339) | def login_info():
function login (line 351) | def login():
function role_list (line 381) | def role_list():
function role_create (line 396) | def role_create():
function role_update (line 408) | def role_update():
function role_delete (line 425) | def role_delete(pk):
function logout (line 435) | def logout():
function set_config (line 453) | def set_config():
function get_config (line 476) | def get_config():
function operator_list (line 492) | def operator_list():
function message_notice (line 525) | def message_notice():
function message_read (line 554) | def message_read():
function message_delete (line 566) | def message_delete(pk: int):
function message_read_all (line 577) | def message_read_all():
FILE: app/api/admin/vulnerability.py
function vuln_list (line 21) | def vuln_list():
function vuln_detail (line 55) | def vuln_detail(pk):
function vuln_delete (line 64) | def vuln_delete(pk):
function vuln_update (line 71) | def vuln_update(pk):
function vuln_create (line 99) | def vuln_create():
function vuln_run (line 123) | def vuln_run(pk):
function vuln_runner (line 135) | def vuln_runner():
function vulnerability_runner_destroy (line 157) | def vulnerability_runner_destroy():
function resource_delete (line 175) | def resource_delete(pk):
function vuln_import (line 194) | def vuln_import():
function sync_vulnerability (line 243) | def sync_vulnerability():
FILE: app/api/health.py
function ping (line 12) | def ping():
function status (line 18) | def status():
FILE: app/api/player/views.py
function send_upload_file (line 29) | def send_upload_file(filename):
function generate_flag (line 35) | def generate_flag():
function login (line 45) | def login():
function info (line 64) | def info():
function register (line 76) | def register():
function logout (line 98) | def logout():
function announcement (line 109) | def announcement():
function rest_pass (line 127) | def rest_pass():
function challenge_list (line 149) | def challenge_list():
function challenge_detail (line 195) | def challenge_detail(question):
function question_start (line 289) | def question_start(question):
function question_delayed (line 326) | def question_delayed(question):
function question_destroy (line 350) | def question_destroy(question):
function user_center (line 384) | def user_center():
function challenge_submit (line 401) | def challenge_submit():
function notice (line 469) | def notice():
function score_rank (line 491) | def score_rank():
FILE: app/api/player/vulnerability.py
function vuln_list (line 23) | def vuln_list():
function vuln_apps (line 65) | def vuln_apps():
function vuln_detail (line 78) | def vuln_detail(pk):
function vuln_start (line 85) | def vuln_start(pk):
function vuln_stop (line 101) | def vuln_stop(pk):
FILE: app/api/player/ws.py
class SocketClient (line 18) | class SocketClient:
method __init__ (line 19) | def __init__(self, sid, user):
method close (line 23) | def close(self):
class LogsSession (line 27) | class LogsSession(SocketClient):
method __init__ (line 28) | def __init__(self, sid, user, container: Container):
method init_socket (line 36) | def init_socket(self):
method stream_logs (line 45) | def stream_logs(self):
method close (line 56) | def close(self):
class TerminalSession (line 62) | class TerminalSession(SocketClient):
method __init__ (line 63) | def __init__(self, sid, user, container):
method _create_exec (line 70) | def _create_exec(self):
method handle_input (line 90) | def handle_input(self, input_data):
method close (line 127) | def close(self):
function handle_connect (line 139) | def handle_connect(auth):
function handle_disconnect (line 200) | def handle_disconnect():
function handle_message (line 210) | def handle_message(message):
FILE: app/api/route.py
function register_blueprints (line 4) | def register_blueprints(flask_app):
FILE: app/core/api.py
function api_success (line 6) | def api_success(
function response_ok (line 26) | def response_ok(
function api_fail (line 44) | def api_fail(code=1, msg="参数错误", data: typing.Union[typing.Dict, None] =...
FILE: app/core/command.py
function init_db_command (line 10) | def init_db_command():
function init_superuser (line 17) | def init_superuser():
function init_data (line 33) | def init_data():
function init_app (line 50) | def init_app(app):
FILE: app/core/const.py
class ConstCacheKey (line 1) | class ConstCacheKey:
FILE: app/core/decorators.py
function user_required (line 13) | def user_required(required=True):
FILE: app/core/error_handlers.py
function register_error_handlers (line 13) | def register_error_handlers(app: Flask):
function handle_pydantic_validation_error (line 19) | def handle_pydantic_validation_error(error: ValidationError):
function exception_handle (line 29) | def exception_handle(e):
FILE: app/core/exceptions.py
class RestExceptions (line 1) | class RestExceptions(Exception):
method __init__ (line 6) | def __init__(self, msg=None, code=None, status=None):
FILE: app/core/flask_celery.py
class ContextTask (line 7) | class ContextTask(TaskBase):
method after_return (line 13) | def after_return(self, status, retval, task_id, args, kwargs, info):
FILE: app/core/middlewares.py
function before_req_cache_ip (line 17) | def before_req_cache_ip():
function global_admin_required (line 33) | def global_admin_required():
FILE: app/core/tools.py
function get_ip (line 6) | def get_ip():
function model2dict (line 14) | def model2dict(instance):
FILE: app/models/__init__.py
class Base (line 16) | class Base(DeclarativeBase):
class CRUDMixin (line 25) | class CRUDMixin(db.Model):
method create (line 31) | def create(cls, **kwargs):
method update (line 36) | def update(self, commit=True, **kwargs):
method save (line 44) | def save(self, commit=True):
method delete (line 51) | def delete(self, commit: bool = True) -> None:
class Model (line 63) | class Model(CRUDMixin):
method __init__ (line 75) | def __init__(self, *args, **kwargs):
method get_by_id (line 79) | def get_by_id(cls: Type[T], record_id) -> Optional[T]:
method create_time_format (line 91) | def create_time_format(self):
method to_dict (line 95) | def to_dict(self):
FILE: app/models/admin.py
class Role (line 9) | class Role(Model):
class Admin (line 22) | class Admin(Model):
method role_name (line 56) | def role_name(self):
class TaskList (line 60) | class TaskList(Model):
method status_name (line 88) | def status_name(self):
class TaskLog (line 92) | class TaskLog(Model):
class RequestState (line 98) | class RequestState(Model):
class Notice (line 109) | class Notice(Model):
class Operator (line 116) | class Operator(Model):
class Config (line 137) | class Config(Model):
method get_config (line 169) | def get_config(key):
class MessageType (line 182) | class MessageType:
class MessageLevel (line 187) | class MessageLevel:
class AdminMessage (line 193) | class AdminMessage(Model):
FILE: app/models/ctf.py
class QType (line 15) | class QType(Enum):
class Attachment (line 24) | class Attachment(Model):
class ImageResource (line 34) | class ImageResource(Model):
class Question (line 57) | class Question(Model):
class CtfResource (line 73) | class CtfResource(Model):
class Answer (line 91) | class Answer(Model):
method status_name (line 121) | def status_name(self):
FILE: app/models/docker.py
class Host (line 13) | class Host(Model):
class ComposeDB (line 24) | class ComposeDB(Model):
class ComposeRunner (line 38) | class ComposeRunner(Model):
class DockerResource (line 63) | class DockerResource(Model):
method docker_type_name (line 98) | def docker_type_name(self):
method status_name (line 102) | def status_name(self):
class DockerRunner (line 106) | class DockerRunner(Model):
FILE: app/models/user.py
class User (line 11) | class User(Model):
FILE: app/services/docker.py
function get_free_port (line 14) | def get_free_port(start_port: int, end_port: int):
function start_docker_resource (line 20) | def start_docker_resource(resource_id, user_id, flag=None) -> DockerRunner:
function destroy_docker_runner (line 89) | def destroy_docker_runner(docker_runner_id):
FILE: app/services/player.py
function submit (line 12) | def submit(question, flag, user, ip=None):
function score_rank (line 92) | def score_rank(username=None, page=1, page_size=20):
FILE: app/services/system.py
function create_admin_message (line 7) | def create_admin_message(admin_id, content, level=MessageLevel.ERROR):
function insert_operator (line 14) | def insert_operator(code, content, username=None, role_name=None):
function get_config_val (line 29) | def get_config_val(key):
FILE: app/tasks/ctf.py
function beat_destroy_container (line 26) | def beat_destroy_container():
function build_question_tar (line 50) | def build_question_tar(image_id):
function sync_ctf_question_repo (line 92) | def sync_ctf_question_repo(repo, admin_id=None):
FILE: app/tasks/docker.py
function docker_build_resource (line 22) | def docker_build_resource(self, resource_id: int):
function task_add_log (line 110) | def task_add_log(task: int, line: dict):
function build_delay (line 131) | def build_delay(task_id: int, build_type, tag, admin, pt=None, dockerfil...
FILE: app/tasks/player.py
function ctf_finish_container (line 16) | def ctf_finish_container(container_id, current=False):
FILE: app/tasks/system.py
function day_upload_req (line 15) | def day_upload_req():
FILE: app/tasks/vulnerability.py
function stop_vulnerability_resource (line 21) | def stop_vulnerability_resource(run_id):
function start_vuln_resource (line 42) | def start_vuln_resource(resource_id, user_id=None, admin_id=None):
function sync_remote_vulnerability_repo (line 115) | def sync_remote_vulnerability_repo(repo, admin_id=None, max_retry=3):
FILE: app/utils/security.py
function hash_password (line 8) | def hash_password(password: str) -> str:
function check_password (line 17) | def check_password(stored_password: str, provided_password: str) -> bool:
function create_token (line 30) | def create_token():
FILE: app/utils/tools.py
function generate_flag (line 6) | def generate_flag():
function find_directories_with_filename (line 15) | def find_directories_with_filename(directory, filename="Dockerfile"):
FILE: app/utils/validator.py
function check_image_name (line 4) | def check_image_name(name: str) -> bool:
FILE: install/frontend/dist/assets/detail-Beu8V9iA.js
function Ju (line 1) | function Ju(t){let e=ki[t];if(e)return e;e=ki[t]=[];for(let n=0;n<128;n+...
function Fe (line 1) | function Fe(t,e){typeof e!="string"&&(e=Fe.defaultChars);const n=Ju(e);r...
function ju (line 1) | function ju(t){let e=Fi[t];if(e)return e;e=Fi[t]=[];for(let n=0;n<128;n+...
function Xe (line 1) | function Xe(t,e,n){typeof e!="string"&&(n=e,e=Xe.defaultChars),typeof n>...
function si (line 1) | function si(t){let e="";return e+=t.protocol||"",e+=t.slashes?"//":"",e+...
function ot (line 1) | function ot(){this.protocol=null,this.slashes=null,this.auth=null,this.p...
function ci (line 2) | function ci(t,e){if(t&&t instanceof ot)return t;const n=new ot;return n....
function E_ (line 2) | function E_(t){var e;return t>=55296&&t<=57343||t>1114111?65533:(e=p_.ge...
function ri (line 2) | function ri(t){return t>=ie.ZERO&&t<=ie.NINE}
function S_ (line 2) | function S_(t){return t>=ie.UPPER_A&&t<=ie.UPPER_F||t>=ie.LOWER_A&&t<=ie...
function b_ (line 2) | function b_(t){return t>=ie.UPPER_A&&t<=ie.UPPER_Z||t>=ie.LOWER_A&&t<=ie...
function f_ (line 2) | function f_(t){return t===ie.EQUALS||b_(t)}
class T_ (line 2) | class T_{constructor(e,n,r){this.decodeTree=e,this.emitCodePoint=n,this....
method constructor (line 2) | constructor(e,n,r){this.decodeTree=e,this.emitCodePoint=n,this.errors=...
method startEntity (line 2) | startEntity(e){this.decodeMode=e,this.state=ae.EntityStart,this.result...
method write (line 2) | write(e,n){switch(this.state){case ae.EntityStart:return e.charCodeAt(...
method stateNumericStart (line 2) | stateNumericStart(e,n){return n>=e.length?-1:(e.charCodeAt(n)|g_)===ie...
method addToNumericResult (line 2) | addToNumericResult(e,n,r,a){if(n!==r){const i=r-n;this.result=this.res...
method stateNumericHex (line 2) | stateNumericHex(e,n){const r=n;for(;n<e.length;){const a=e.charCodeAt(...
method stateNumericDecimal (line 2) | stateNumericDecimal(e,n){const r=n;for(;n<e.length;){const a=e.charCod...
method emitNumericEntity (line 2) | emitNumericEntity(e,n){var r;if(this.consumed<=n)return(r=this.errors)...
method stateNamedEntity (line 2) | stateNamedEntity(e,n){const{decodeTree:r}=this;let a=r[this.treeIndex]...
method emitNotTerminatedNamedEntity (line 2) | emitNotTerminatedNamedEntity(){var e;const{result:n,decodeTree:r}=this...
method emitNamedEntityData (line 2) | emitNamedEntityData(e,n,r){const{decodeTree:a}=this;return this.emitCo...
method end (line 2) | end(){var e;switch(this.state){case ae.NamedEntity:return this.result!...
function Ql (line 2) | function Ql(t){let e="";const n=new T_(t,r=>e+=m_(r));return function(a,...
function R_ (line 2) | function R_(t,e,n,r){const a=(e&Ne.BRANCH_LENGTH)>>7,i=e&Ne.JUMP_TABLE;i...
function Xl (line 2) | function Xl(t,e=Ce.Legacy){return C_(t,e)}
function N_ (line 2) | function N_(t){return Object.prototype.toString.call(t)}
function ui (line 2) | function ui(t){return N_(t)==="[object String]"}
function h_ (line 2) | function h_(t,e){return O_.call(t,e)}
function ut (line 2) | function ut(t){return Array.prototype.slice.call(arguments,1).forEach(fu...
function Zl (line 2) | function Zl(t,e,n){return[].concat(t.slice(0,e),n,t.slice(e+1))}
function _i (line 2) | function _i(t){return!(t>=55296&&t<=57343||t>=64976&&t<=65007||(t&65535)...
function st (line 2) | function st(t){if(t>65535){t-=65536;const e=55296+(t>>10),n=56320+(t&102...
function y_ (line 2) | function y_(t,e){if(e.charCodeAt(0)===35&&v_.test(e)){const r=e[1].toLow...
function D_ (line 2) | function D_(t){return t.indexOf("\\")<0?t:t.replace(Jl,"$1")}
function Ue (line 2) | function Ue(t){return t.indexOf("\\")<0&&t.indexOf("&")<0?t:t.replace(I_...
function w_ (line 2) | function w_(t){return L_[t]}
function he (line 2) | function he(t){return x_.test(t)?t.replace(M_,w_):t}
function k_ (line 2) | function k_(t){return t.replace(P_,"\\$&")}
function Q (line 2) | function Q(t){switch(t){case 9:case 32:return!0}return!1}
function Ve (line 2) | function Ve(t){if(t>=8192&&t<=8202)return!0;switch(t){case 9:case 10:cas...
function ze (line 2) | function ze(t){return li.test(t)||$l.test(t)}
function We (line 2) | function We(t){switch(t){case 33:case 34:case 35:case 36:case 37:case 38...
function _t (line 2) | function _t(t){return t=t.trim().replace(/\s+/g," "),"ẞ".toLowerCase()==...
function B_ (line 2) | function B_(t,e,n){let r,a,i,s;const o=t.posMax,c=t.pos;for(t.pos=e+1,r=...
function G_ (line 2) | function G_(t,e,n){let r,a=e;const i={ok:!1,pos:0,str:""};if(t.charCodeA...
function Y_ (line 2) | function Y_(t,e,n,r){let a,i=e;const s={ok:!1,can_continue:!1,pos:0,str:...
function Ge (line 11) | function Ge(){this.rules=ut({},be)}
function le (line 14) | function le(){this.__rules__=[],this.__cache__=null}
function me (line 14) | function me(t,e,n){this.type=t,this.tag=e,this.attrs=null,this.map=null,...
function jl (line 14) | function jl(t,e,n){this.src=t,this.env=n,this.tokens=[],this.inlineMode=...
function z_ (line 14) | function z_(t){let e;e=t.src.replace(H_,`
function W_ (line 15) | function W_(t){let e;t.inlineMode?(e=new t.Token("inline","",0),e.conten...
function $_ (line 15) | function $_(t){const e=t.tokens;for(let n=0,r=e.length;n<r;n++){const a=...
function K_ (line 15) | function K_(t){return/^<a[>\s]/i.test(t)}
function Q_ (line 15) | function Q_(t){return/^<\/a\s*>/i.test(t)}
function X_ (line 15) | function X_(t){const e=t.tokens;if(t.md.options.linkify)for(let n=0,r=e....
function ed (line 15) | function ed(t,e){return j_[e.toLowerCase()]}
function td (line 15) | function td(t){let e=0;for(let n=t.length-1;n>=0;n--){const r=t[n];r.typ...
function nd (line 15) | function nd(t){let e=0;for(let n=t.length-1;n>=0;n--){const r=t[n];r.typ...
function rd (line 15) | function rd(t){let e;if(t.md.options.typographer)for(e=t.tokens.length-1...
function rt (line 15) | function rt(t,e,n){return t.slice(0,e)+n+t.slice(e+1)}
function id (line 15) | function id(t,e){let n;const r=[];for(let a=0;a<t.length;a++){const i=t[...
function od (line 15) | function od(t){if(t.md.options.typographer)for(let e=t.tokens.length-1;e...
function sd (line 15) | function sd(t){let e,n;const r=t.tokens,a=r.length;for(let i=0;i<a;i++){...
function di (line 15) | function di(){this.ruler=new le;for(let t=0;t<bt.length;t++)this.ruler.p...
function fe (line 15) | function fe(t,e,n,r){this.src=t,this.md=e,this.env=n,this.tokens=r,this....
function ft (line 15) | function ft(t,e){const n=t.bMarks[e]+t.tShift[e],r=t.eMarks[e];return t....
function zi (line 15) | function zi(t){const e=[],n=t.length;let r=0,a=t.charCodeAt(r),i=!1,s=0,...
function ld (line 15) | function ld(t,e,n,r){if(e+2>n)return!1;let a=e+1;if(t.sCount[a]<t.blkInd...
function ud (line 15) | function ud(t,e,n){if(t.sCount[e]-t.blkIndent<4)return!1;let r=e+1,a=r;f...
function _d (line 16) | function _d(t,e,n,r){let a=t.bMarks[e]+t.tShift[e],i=t.eMarks[e];if(t.sC...
function dd (line 16) | function dd(t,e,n,r){let a=t.bMarks[e]+t.tShift[e],i=t.eMarks[e];const s...
function pd (line 16) | function pd(t,e,n,r){const a=t.eMarks[e];if(t.sCount[e]-t.blkIndent>=4)r...
function Wi (line 16) | function Wi(t,e){const n=t.eMarks[e];let r=t.bMarks[e]+t.tShift[e];const...
function $i (line 16) | function $i(t,e){const n=t.bMarks[e]+t.tShift[e],r=t.eMarks[e];let a=n;i...
function md (line 16) | function md(t,e){const n=t.level+2;for(let r=e+2,a=t.tokens.length-2;r<a...
function Ed (line 16) | function Ed(t,e,n,r){let a,i,s,o,c=e,l=!0;if(t.sCount[c]-t.blkIndent>=4|...
function gd (line 16) | function gd(t,e,n,r){let a=t.bMarks[e]+t.tShift[e],i=t.eMarks[e],s=e+1;i...
function Dd (line 16) | function Dd(t,e,n,r){let a=t.bMarks[e]+t.tShift[e],i=t.eMarks[e];if(t.sC...
function xd (line 16) | function xd(t,e,n,r){let a=t.bMarks[e]+t.tShift[e],i=t.eMarks[e];if(t.sC...
function Md (line 16) | function Md(t,e,n){const r=t.md.block.ruler.getRules("paragraph");if(t.s...
function Ld (line 16) | function Ld(t,e,n){const r=t.md.block.ruler.getRules("paragraph"),a=t.pa...
function dt (line 16) | function dt(){this.ruler=new le;for(let t=0;t<at.length;t++)this.ruler.p...
function Ze (line 16) | function Ze(t,e,n,r){this.src=t,this.env=n,this.md=e,this.tokens=r,this....
function wd (line 16) | function wd(t){switch(t){case 10:case 33:case 35:case 36:case 37:case 38...
function Pd (line 16) | function Pd(t,e){let n=t.pos;for(;n<t.posMax&&!wd(t.src.charCodeAt(n));)...
function Fd (line 16) | function Fd(t,e){if(!t.md.options.linkify||t.linkLevel>0)return!1;const ...
function Ud (line 16) | function Ud(t,e){let n=t.pos;if(t.src.charCodeAt(n)!==10)return!1;const ...
function Bd (line 16) | function Bd(t,e){let n=t.pos;const r=t.posMax;if(t.src.charCodeAt(n)!==9...
function Gd (line 16) | function Gd(t,e){let n=t.pos;if(t.src.charCodeAt(n)!==96)return!1;const ...
function Yd (line 16) | function Yd(t,e){const n=t.pos,r=t.src.charCodeAt(n);if(e||r!==126)retur...
function Ki (line 16) | function Ki(t,e){let n;const r=[],a=e.length;for(let i=0;i<a;i++){const ...
function qd (line 16) | function qd(t){const e=t.tokens_meta,n=t.tokens_meta.length;Ki(t,t.delim...
function Hd (line 16) | function Hd(t,e){const n=t.pos,r=t.src.charCodeAt(n);if(e||r!==95&&r!==4...
function Qi (line 16) | function Qi(t,e){const n=e.length;for(let r=n-1;r>=0;r--){const a=e[r];i...
function Vd (line 16) | function Vd(t){const e=t.tokens_meta,n=t.tokens_meta.length;Qi(t,t.delim...
function zd (line 16) | function zd(t,e){let n,r,a,i,s="",o="",c=t.pos,l=!0;if(t.src.charCodeAt(...
function Wd (line 16) | function Wd(t,e){let n,r,a,i,s,o,c,l,u="";const _=t.pos,p=t.posMax;if(t....
function Qd (line 16) | function Qd(t,e){let n=t.pos;if(t.src.charCodeAt(n)!==60)return!1;const ...
function Xd (line 16) | function Xd(t){return/^<a[>\s]/i.test(t)}
function Zd (line 16) | function Zd(t){return/^<\/a\s*>/i.test(t)}
function Jd (line 16) | function Jd(t){const e=t|32;return e>=97&&e<=122}
function jd (line 16) | function jd(t,e){if(!t.md.options.html)return!1;const n=t.posMax,r=t.pos...
function np (line 16) | function np(t,e){const n=t.pos,r=t.posMax;if(t.src.charCodeAt(n)!==38||n...
function Xi (line 16) | function Xi(t){const e={},n=t.length;if(!n)return;let r=0,a=-2;const i=[...
function rp (line 16) | function rp(t){const e=t.tokens_meta,n=t.tokens_meta.length;Xi(t.delimit...
function ap (line 16) | function ap(t){let e,n,r=0;const a=t.tokens,i=t.tokens.length;for(e=n=0;...
function Je (line 16) | function Je(){this.ruler=new le;for(let t=0;t<Tt.length;t++)this.ruler.p...
function ip (line 16) | function ip(t){const e={};t=t||{},e.src_Any=zl.source,e.src_Cc=Wl.source...
function ai (line 16) | function ai(t){return Array.prototype.slice.call(arguments,1).forEach(fu...
function pt (line 16) | function pt(t){return Object.prototype.toString.call(t)}
function op (line 16) | function op(t){return pt(t)==="[object String]"}
function sp (line 16) | function sp(t){return pt(t)==="[object Object]"}
function cp (line 16) | function cp(t){return pt(t)==="[object RegExp]"}
function Zi (line 16) | function Zi(t){return pt(t)==="[object Function]"}
function lp (line 16) | function lp(t){return t.replace(/[.?*+^$[\]\\(){}|-]/g,"\\$&")}
function up (line 16) | function up(t){return Object.keys(t||{}).reduce(function(e,n){return e||...
function mp (line 16) | function mp(t){t.__index__=-1,t.__text_cache__=""}
function Ep (line 16) | function Ep(t){return function(e,n){const r=e.slice(n);return t.test(r)?...
function Ji (line 16) | function Ji(){return function(t,e){e.normalize(t)}}
function ct (line 16) | function ct(t){const e=t.re=ip(t.__opts__),n=t.__tlds__.slice();t.onComp...
function gp (line 16) | function gp(t,e){const n=t.__index__,r=t.__last_index__,a=t.__text_cache...
function ii (line 16) | function ii(t,e){const n=new gp(t,e);return t.__compiled__[n.schema].nor...
function _e (line 16) | function _e(t,e){if(!(this instanceof _e))return new _e(t,e);e||up(t)&&(...
function Re (line 16) | function Re(t){throw new RangeError(Cp[t])}
function Np (line 16) | function Np(t,e){const n=[];let r=t.length;for(;r--;)n[r]=e(t[r]);return n}
function lu (line 16) | function lu(t,e){const n=t.split("@");let r="";n.length>1&&(r=n[0]+"@",t...
function uu (line 16) | function uu(t){const e=[];let n=0;const r=t.length;for(;n<r;){const a=t....
function wp (line 16) | function wp(t){const e=t.trim().toLowerCase();return Mp.test(e)?Lp.test(...
function Pp (line 16) | function Pp(t){const e=ci(t,!0);if(e.hostname&&(!e.protocol||Eu.indexOf(...
function kp (line 16) | function kp(t){const e=ci(t,!0);if(e.hostname&&(!e.protocol||Eu.indexOf(...
function pe (line 16) | function pe(t,e){if(!(this instanceof pe))return new pe(t,e);e||ui(t)||(...
function gu (line 16) | function gu(t){return t instanceof Map?t.clear=t.delete=t.set=function()...
class eo (line 16) | class eo{constructor(e){e.data===void 0&&(e.data={}),this.data=e.data,th...
method constructor (line 16) | constructor(e){e.data===void 0&&(e.data={}),this.data=e.data,this.isMa...
method ignoreMatch (line 16) | ignoreMatch(){this.isMatchIgnored=!0}
function Su (line 16) | function Su(t){return t.replace(/&/g,"&").replace(/</g,"<").repla...
function Oe (line 16) | function Oe(t,...e){const n=Object.create(null);for(const r in t)n[r]=t[...
class Bp (line 16) | class Bp{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,...
method constructor (line 16) | constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(...
method addText (line 16) | addText(e){this.buffer+=Su(e)}
method openNode (line 16) | openNode(e){if(!to(e))return;const n=Up(e.scope,{prefix:this.classPref...
method closeNode (line 16) | closeNode(e){to(e)&&(this.buffer+=Fp)}
method value (line 16) | value(){return this.buffer}
method span (line 16) | span(e){this.buffer+=`<span class="${e}">`}
class Ei (line 16) | class Ei{constructor(){this.rootNode=no(),this.stack=[this.rootNode]}get...
method constructor (line 16) | constructor(){this.rootNode=no(),this.stack=[this.rootNode]}
method top (line 16) | get top(){return this.stack[this.stack.length-1]}
method root (line 16) | get root(){return this.rootNode}
method add (line 16) | add(e){this.top.children.push(e)}
method openNode (line 16) | openNode(e){const n=no({scope:e});this.add(n),this.stack.push(n)}
method closeNode (line 16) | closeNode(){if(this.stack.length>1)return this.stack.pop()}
method closeAllNodes (line 16) | closeAllNodes(){for(;this.closeNode(););}
method toJSON (line 16) | toJSON(){return JSON.stringify(this.rootNode,null,4)}
method walk (line 16) | walk(e){return this.constructor._walk(e,this.rootNode)}
method _walk (line 16) | static _walk(e,n){return typeof n=="string"?e.addText(n):n.children&&(...
method _collapse (line 16) | static _collapse(e){typeof e!="string"&&e.children&&(e.children.every(...
class Gp (line 16) | class Gp extends Ei{constructor(e){super(),this.options=e}addText(e){e!=...
method constructor (line 16) | constructor(e){super(),this.options=e}
method addText (line 16) | addText(e){e!==""&&this.add(e)}
method startScope (line 16) | startScope(e){this.openNode(e)}
method endScope (line 16) | endScope(){this.closeNode()}
method __addSublanguage (line 16) | __addSublanguage(e,n){const r=e.root;n&&(r.scope=`language:${n}`),this...
method toHTML (line 16) | toHTML(){return new Bp(this,this.options).value()}
method finalize (line 16) | finalize(){return this.closeAllNodes(),!0}
function Ke (line 16) | function Ke(t){return t?typeof t=="string"?t:t.source:null}
function bu (line 16) | function bu(t){return ye("(?=",t,")")}
function Yp (line 16) | function Yp(t){return ye("(?:",t,")*")}
function qp (line 16) | function qp(t){return ye("(?:",t,")?")}
function ye (line 16) | function ye(...t){return t.map(n=>Ke(n)).join("")}
function Hp (line 16) | function Hp(t){const e=t[t.length-1];return typeof e=="object"&&e.constr...
function gi (line 16) | function gi(...t){return"("+(Hp(t).capture?"":"?:")+t.map(r=>Ke(r)).join...
function fu (line 16) | function fu(t){return new RegExp(t.toString()+"|").exec("").length-1}
function Vp (line 16) | function Vp(t,e){const n=t&&t.exec(e);return n&&n.index===0}
function Si (line 16) | function Si(t,{joinWith:e}){let n=0;return t.map(r=>{n+=1;const a=n;let ...
function lm (line 16) | function lm(t,e){t.input[t.index-1]==="."&&e.ignoreMatch()}
function um (line 16) | function um(t,e){t.className!==void 0&&(t.scope=t.className,delete t.cla...
function _m (line 16) | function _m(t,e){e&&t.beginKeywords&&(t.begin="\\b("+t.beginKeywords.spl...
function dm (line 16) | function dm(t,e){Array.isArray(t.illegal)&&(t.illegal=gi(...t.illegal))}
function pm (line 16) | function pm(t,e){if(t.match){if(t.begin||t.end)throw new Error("begin & ...
function mm (line 16) | function mm(t,e){t.relevance===void 0&&(t.relevance=1)}
function Ou (line 16) | function Ou(t,e,n=Sm){const r=Object.create(null);return typeof t=="stri...
function bm (line 16) | function bm(t,e){return e?Number(e):fm(t)?0:1}
function fm (line 16) | function fm(t){return gm.includes(t.toLowerCase())}
function hu (line 16) | function hu(t,e,{key:n}){let r=0;const a=t[n],i={},s={};for(let o=1;o<=e...
function Tm (line 16) | function Tm(t){if(Array.isArray(t.begin)){if(t.skip||t.excludeBegin||t.r...
function Rm (line 16) | function Rm(t){if(Array.isArray(t.end)){if(t.skip||t.excludeEnd||t.retur...
function Cm (line 16) | function Cm(t){t.scope&&typeof t.scope=="object"&&t.scope!==null&&(t.beg...
function Nm (line 16) | function Nm(t){Cm(t),typeof t.beginScope=="string"&&(t.beginScope={_wrap...
function Om (line 16) | function Om(t){function e(s,o){return new RegExp(Ke(s),"m"+(t.case_insen...
function Au (line 16) | function Au(t){return t?t.endsWithParent||Au(t.starts):!1}
function hm (line 16) | function hm(t){return t.variants&&!t.cachedVariants&&(t.cachedVariants=t...
class Im (line 16) | class Im extends Error{constructor(e,n){super(e),this.name="HTMLInjectio...
method constructor (line 16) | constructor(e,n){super(e),this.name="HTMLInjectionError",this.html=n}
function c (line 16) | function c(O){return o.noHighlightRe.test(O)}
function l (line 16) | function l(O){let I=O.className+" ";I+=O.parentNode?O.parentNode.classNa...
function u (line 16) | function u(O,I,M){let k="",G="";typeof I=="object"?(k=O,M=I.ignoreIllega...
function _ (line 17) | function _(O,I,M,k){const G=Object.create(null);function Z(A,D){return A...
function p (line 18) | function p(O){const I={value:Ot(O),illegal:!1,relevance:0,_top:s,_emitte...
function m (line 18) | function m(O,I){I=I||o.languages||Object.keys(e);const M=p(O),k=I.filter...
function d (line 18) | function d(O,I,M){const k=I&&n[I]||M;O.classList.add("hljs"),O.classList...
function S (line 18) | function S(O){let I=null;const M=l(O);if(c(M))return;if(K("before:highli...
function g (line 18) | function g(O){o=io(o,O)}
function b (line 18) | function b(){R(),Pe("10.6.0","initHighlightingOnLoad() deprecated. Use ...
function R (line 18) | function R(){function O(){R()}if(document.readyState==="loading"){f||win...
function N (line 18) | function N(O,I){let M=null;try{M=I(t)}catch(k){if(ve("Language definitio...
function C (line 18) | function C(O){delete e[O];for(const I of Object.keys(n))n[I]===O&&delete...
function v (line 18) | function v(){return Object.keys(e)}
function h (line 18) | function h(O){return O=(O||"").toLowerCase(),e[O]||e[n[O]]}
function y (line 18) | function y(O,{languageName:I}){typeof O=="string"&&(O=[O]),O.forEach(M=>...
function w (line 18) | function w(O){const I=h(O);return I&&!I.disableAutodetect}
function x (line 18) | function x(O){O["before:highlightBlock"]&&!O["before:highlightElement"]&...
function U (line 18) | function U(O){x(O),r.push(O)}
function $ (line 18) | function $(O){const I=r.indexOf(O);I!==-1&&r.splice(I,1)}
function K (line 18) | function K(O,I){const M=O;r.forEach(function(k){k[M]&&k[M](I)})}
function F (line 18) | function F(O){return Pe("10.7.0","highlightBlock will be removed entirel...
function Dm (line 18) | function Dm(){if(so)return ht;so=1;function t(e){const n="[A-Za-zА-Яа-яё...
function xm (line 18) | function xm(){if(co)return At;co=1;function t(e){const n=e.regex,r=/^[a-...
function Mm (line 18) | function Mm(){if(lo)return It;lo=1;function t(e){const n=e.regex,r=["GET...
function Lm (line 18) | function Lm(){if(uo)return vt;uo=1;function t(e){const n=e.regex,r=/[a-z...
function wm (line 18) | function wm(){if(_o)return yt;_o=1;function t(e){const n="\\d(_|\\d)*",r...
function Pm (line 18) | function Pm(){if(po)return Dt;po=1;function t(e){const n={className:"bui...
function km (line 18) | function km(){if(mo)return xt;mo=1;function t(e){const n={className:"num...
function Fm (line 18) | function Fm(){if(Eo)return Mt;Eo=1;function t(e){const n=e.regex,r=e.inh...
function Um (line 18) | function Um(){if(go)return Lt;go=1;function t(e){const n=e.regex,r="[A-Z...
function Bm (line 18) | function Bm(){if(So)return wt;So=1;function t(n){const r=n.regex,a=n.COM...
function Gm (line 18) | function Gm(){if(bo)return Pt;bo=1;function t(e){const n={variants:[e.CO...
function Ym (line 18) | function Ym(){if(fo)return kt;fo=1;function t(e){const n=e.regex,r=n.con...
function qm (line 18) | function qm(){if(To)return Ft;To=1;function t(e){const n=e.regex,r={begi...
function Hm (line 18) | function Hm(){if(Ro)return Ut;Ro=1;function t(e){const n=e.regex,r=["fal...
function Vm (line 18) | function Vm(){if(Co)return Bt;Co=1;function t(e){const n={begin:"`[\\s\\...
function zm (line 18) | function zm(){if(No)return Gt;No=1;function t(e){const n="ByRef Case Con...
function Wm (line 18) | function Wm(){if(Oo)return Yt;Oo=1;function t(e){return{name:"AVR Assemb...
function $m (line 18) | function $m(){if(ho)return qt;ho=1;function t(e){const n={className:"var...
function Km (line 18) | function Km(){if(Ao)return Ht;Ao=1;function t(e){const n=e.UNDERSCORE_ID...
function Qm (line 18) | function Qm(){if(Io)return Vt;Io=1;function t(e){const n=e.regex,r={},a=...
function Xm (line 18) | function Xm(){if(vo)return zt;vo=1;function t(e){return{name:"BASIC",cas...
function Zm (line 18) | function Zm(){if(yo)return Wt;yo=1;function t(e){return{name:"Backus–Nau...
function Jm (line 18) | function Jm(){if(Do)return $t;Do=1;function t(e){const n={className:"lit...
function jm (line 18) | function jm(){if(xo)return Kt;xo=1;function t(e){const n=e.regex,r=e.COM...
function eE (line 18) | function eE(){if(Mo)return Qt;Mo=1;function t(e){const n=e.regex,r=["div...
function tE (line 18) | function tE(){if(Lo)return Xt;Lo=1;function t(e){const n=["struct","enum...
function nE (line 18) | function nE(){if(wo)return Zt;wo=1;function t(e){const n=["assembly","mo...
function rE (line 18) | function rE(){if(Po)return Jt;Po=1;function t(e){return{name:"Clean",ali...
function aE (line 18) | function aE(){if(ko)return jt;ko=1;function t(e){const n="a-zA-Z_\\-!.?+...
function iE (line 18) | function iE(){if(Fo)return en;Fo=1;function t(e){return{name:"Clojure RE...
function oE (line 18) | function oE(){if(Uo)return tn;Uo=1;function t(e){return{name:"CMake",ali...
function sE (line 18) | function sE(){if(Bo)return nn;Bo=1;const t=["as","in","of","if","for","w...
function cE (line 18) | function cE(){if(Go)return rn;Go=1;function t(e){return{name:"Coq",keywo...
function lE (line 18) | function lE(){if(Yo)return an;Yo=1;function t(e){return{name:"Caché Obje...
function uE (line 18) | function uE(){if(qo)return on;qo=1;function t(e){const n=e.regex,r=e.COM...
function _E (line 18) | function _E(){if(Ho)return sn;Ho=1;function t(e){const n="primitive rsc_...
function dE (line 18) | function dE(){if(Vo)return cn;Vo=1;function t(e){const n="(_?[ui](8|16|3...
function pE (line 18) | function pE(){if(zo)return ln;zo=1;function t(e){const n=["bool","byte",...
function mE (line 18) | function mE(){if(Wo)return un;Wo=1;function t(e){return{name:"CSP",case_...
function EE (line 18) | function EE(){if($o)return _n;$o=1;const t=l=>({IMPORTANT:{scope:"meta",...
function gE (line 18) | function gE(){if(Ko)return dn;Ko=1;function t(e){const n={$pattern:e.UND...
function SE (line 18) | function SE(){if(Qo)return pn;Qo=1;function t(e){const n=e.regex,r={begi...
function bE (line 18) | function bE(){if(Xo)return mn;Xo=1;function t(e){const n={className:"sub...
function fE (line 18) | function fE(){if(Zo)return En;Zo=1;function t(e){const n=["exports","reg...
function TE (line 18) | function TE(){if(Jo)return gn;Jo=1;function t(e){const n=e.regex;return{...
function RE (line 18) | function RE(){if(jo)return Sn;jo=1;function t(e){const n={begin:/\|[A-Za...
function CE (line 18) | function CE(){if(es)return bn;es=1;function t(e){return{name:"DNS Zone",...
function NE (line 18) | function NE(){if(ts)return fn;ts=1;function t(e){return{name:"Dockerfile...
function OE (line 18) | function OE(){if(ns)return Tn;ns=1;function t(e){const n=e.COMMENT(/^\s*...
function hE (line 18) | function hE(){if(rs)return Rn;rs=1;function t(e){return{keywords:"dsconf...
function AE (line 18) | function AE(){if(as)return Cn;as=1;function t(e){const n={className:"str...
function IE (line 18) | function IE(){if(is)return Nn;is=1;function t(e){return{name:"Dust",alia...
function vE (line 18) | function vE(){if(os)return On;os=1;function t(e){const n=e.COMMENT(/\(\*...
function yE (line 18) | function yE(){if(ss)return hn;ss=1;function t(e){const n=e.regex,r="[a-z...
function DE (line 18) | function DE(){if(cs)return An;cs=1;function t(e){const n={variants:[e.CO...
function xE (line 18) | function xE(){if(ls)return In;ls=1;function t(e){const n=e.regex,r="([a-...
function ME (line 18) | function ME(){if(us)return vn;us=1;function t(e){return{name:"ERB",subLa...
function LE (line 18) | function LE(){if(_s)return yn;_s=1;function t(e){const n=e.regex;return{...
function wE (line 18) | function wE(){if(ds)return Dn;ds=1;function t(e){const n="[a-z'][a-zA-Z0...
function PE (line 18) | function PE(){if(ps)return xn;ps=1;function t(e){return{name:"Excel form...
function kE (line 18) | function kE(){if(ms)return Mn;ms=1;function t(e){return{name:"FIX",conta...
function FE (line 18) | function FE(){if(Es)return Ln;Es=1;function t(e){const n={className:"str...
function UE (line 18) | function UE(){if(gs)return wn;gs=1;function t(e){const n=e.regex,r={clas...
function BE (line 18) | function BE(){if(Ss)return Pn;Ss=1;function t(o){return new RegExp(o.rep...
function GE (line 18) | function GE(){if(bs)return kn;bs=1;function t(e){const n=e.regex,r={keyw...
function YE (line 18) | function YE(){if(fs)return Fn;fs=1;function t(e){const n={keyword:"bool ...
function qE (line 18) | function qE(){if(Ts)return Un;Ts=1;function t(e){const n=e.regex,r={$pat...
function HE (line 18) | function HE(){if(Rs)return Bn;Rs=1;function t(e){return{name:"Gherkin",a...
function VE (line 18) | function VE(){if(Cs)return Gn;Cs=1;function t(e){return{name:"GLSL",keyw...
function zE (line 18) | function zE(){if(Ns)return Yn;Ns=1;function t(e){return{name:"GML",case_...
function WE (line 18) | function WE(){if(Os)return qn;Os=1;function t(e){const s={keyword:["brea...
function $E (line 18) | function $E(){if(hs)return Hn;hs=1;function t(e){return{name:"Golo",keyw...
function KE (line 18) | function KE(){if(As)return Vn;As=1;function t(e){return{name:"Gradle",ca...
function QE (line 18) | function QE(){if(Is)return zn;Is=1;function t(e){const n=e.regex,r=/[_A-...
function XE (line 18) | function XE(){if(vs)return Wn;vs=1;function t(n,r={}){return r.variants=...
function ZE (line 18) | function ZE(){if(ys)return $n;ys=1;function t(e){return{name:"HAML",case...
function JE (line 18) | function JE(){if(Ds)return Kn;Ds=1;function t(e){const n=e.regex,r={$pat...
function jE (line 18) | function jE(){if(xs)return Qn;xs=1;function t(e){const n="([0-9]_*)+",r=...
function eg (line 18) | function eg(){if(Ms)return Xn;Ms=1;function t(e){const n="[a-zA-Z_$][a-z...
function tg (line 18) | function tg(){if(Ls)return Zn;Ls=1;function t(e){return{name:"HSP",case_...
function ng (line 18) | function ng(){if(ws)return Jn;ws=1;function t(e){const n=e.regex,r="HTTP...
function rg (line 18) | function rg(){if(Ps)return jn;Ps=1;function t(e){const n="a-zA-Z_\\-!.?+...
function ag (line 18) | function ag(){if(ks)return er;ks=1;function t(e){const n="\\[",r="\\]";r...
function ig (line 18) | function ig(){if(Fs)return tr;Fs=1;function t(e){const n=e.regex,r={clas...
function og (line 18) | function og(){if(Us)return nr;Us=1;function t(e){const n=e.regex,r={clas...
function sg (line 18) | function sg(){if(Bs)return rr;Bs=1;function t(e){const n="[A-Za-zА-Яа-яё...
function cg (line 18) | function cg(){if(Gs)return ar;Gs=1;var t="[0-9](_*[0-9])*",e=`\\.(${t})`...
function lg (line 18) | function lg(){if(Ys)return ir;Ys=1;const t="[A-Za-z$_][0-9A-Za-z$_]*",e=...
function ug (line 18) | function ug(){if(qs)return or;qs=1;function t(e){const r={className:"par...
function _g (line 18) | function _g(){if(Hs)return sr;Hs=1;function t(e){const n={className:"att...
function dg (line 18) | function dg(){if(Vs)return cr;Vs=1;function t(e){const n="[A-Za-z_\\u00A...
function pg (line 18) | function pg(){if(zs)return lr;zs=1;function t(e){return{name:"Julia REPL...
function mg (line 18) | function mg(){if(Ws)return ur;Ws=1;var t="[0-9](_*[0-9])*",e=`\\.(${t})`...
function Eg (line 19) | function Eg(){if($s)return _r;$s=1;function t(e){const n="[a-zA-Z_][\\w....
function gg (line 19) | function gg(){if(Ks)return dr;Ks=1;function t(e){const r=e.regex.either(...
function Sg (line 19) | function Sg(){if(Qs)return pr;Qs=1;function t(e){return{name:"LDIF",cont...
function bg (line 19) | function bg(){if(Xs)return mr;Xs=1;function t(e){const n=/([A-Za-z_][A-Z...
function fg (line 19) | function fg(){if(Zs)return Er;Zs=1;const t=u=>({IMPORTANT:{scope:"meta",...
function Tg (line 19) | function Tg(){if(Js)return gr;Js=1;function t(e){const n="[a-zA-Z_\\-+\\...
function Rg (line 19) | function Rg(){if(js)return Sr;js=1;function t(e){const n={className:"var...
function Cg (line 19) | function Cg(){if(ec)return br;ec=1;const t=["as","in","of","if","for","w...
function Ng (line 19) | function Ng(){if(tc)return fr;tc=1;function t(e){const n=e.regex,r=/([-a...
function Og (line 19) | function Og(){if(nc)return Tr;nc=1;function t(e){const r={className:"str...
function hg (line 19) | function hg(){if(rc)return Rr;rc=1;function t(e){const n="\\[=*\\[",r="\...
function Ag (line 19) | function Ag(){if(ac)return Cr;ac=1;function t(e){const n={className:"var...
function Ig (line 19) | function Ig(){if(ic)return Nr;ic=1;const t=["AASTriangle","AbelianGroup"...
function vg (line 19) | function vg(){if(oc)return Or;oc=1;function t(e){const n="('|\\.')+",r={...
function yg (line 19) | function yg(){if(sc)return hr;sc=1;function t(e){return{name:"Maxima",ke...
function Dg (line 19) | function Dg(){if(cc)return Ar;cc=1;function t(e){return{name:"MEL",keywo...
function xg (line 19) | function xg(){if(lc)return Ir;lc=1;function t(e){const n={keyword:"modul...
function Mg (line 19) | function Mg(){if(uc)return vr;uc=1;function t(e){return{name:"MIPS Assem...
function Lg (line 19) | function Lg(){if(_c)return yr;_c=1;function t(e){return{name:"Mizar",key...
function wg (line 19) | function wg(){if(dc)return Dr;dc=1;function t(e){const n=e.regex,r=["abs...
function Pg (line 19) | function Pg(){if(pc)return xr;pc=1;function t(e){return{name:"Mojoliciou...
function kg (line 19) | function kg(){if(mc)return Mr;mc=1;function t(e){const n={className:"num...
function Fg (line 19) | function Fg(){if(Ec)return Lr;Ec=1;function t(e){const n={keyword:"if th...
function Ug (line 19) | function Ug(){if(gc)return wr;gc=1;function t(e){return{name:"N1QL",case...
function Bg (line 19) | function Bg(){if(Sc)return Pr;Sc=1;function t(e){const n={match:[/^\s*(?...
function Gg (line 19) | function Gg(){if(bc)return kr;bc=1;function t(e){const n=e.regex,r={clas...
function Yg (line 19) | function Yg(){if(fc)return Fr;fc=1;function t(e){return{name:"Nim",keywo...
function qg (line 19) | function qg(){if(Tc)return Ur;Tc=1;function t(e){const n=e.regex,r={keyw...
function Hg (line 19) | function Hg(){if(Rc)return Br;Rc=1;function t(e){return{name:"Node REPL"...
function Vg (line 19) | function Vg(){if(Cc)return Gr;Cc=1;function t(e){const n=e.regex,r=["ADM...
function zg (line 19) | function zg(){if(Nc)return Yr;Nc=1;function t(e){const n={className:"bui...
function Wg (line 19) | function Wg(){if(Oc)return qr;Oc=1;function t(e){return{name:"OCaml",ali...
function $g (line 19) | function $g(){if(hc)return Hr;hc=1;function t(e){const n={className:"key...
function Kg (line 19) | function Kg(){if(Ac)return Vr;Ac=1;function t(e){const n={$pattern:/\.?\...
function Qg (line 19) | function Qg(){if(Ic)return zr;Ic=1;function t(e){const n=e.COMMENT(/\{/,...
function Xg (line 19) | function Xg(){if(vc)return Wr;vc=1;function t(e){const n={className:"var...
function Zg (line 19) | function Zg(){if(yc)return $r;yc=1;function t(e){const n=e.COMMENT("--",...
function Jg (line 19) | function Jg(){if(Dc)return Kr;Dc=1;function t(e){const n=e.regex,r=/(?![...
function jg (line 20) | function jg(){if(xc)return Qr;xc=1;function t(e){return{name:"PHP templa...
function eS (line 20) | function eS(){if(Mc)return Xr;Mc=1;function t(e){return{name:"Plain text...
function tS (line 20) | function tS(){if(Lc)return Zr;Lc=1;function t(e){const n={keyword:"actor...
function nS (line 20) | function nS(){if(wc)return Jr;wc=1;function t(e){const n=["string","char...
function rS (line 20) | function rS(){if(Pc)return jr;Pc=1;function t(e){const n=e.regex,r=["dis...
function aS (line 20) | function aS(){if(kc)return ea;kc=1;function t(e){return{name:"Python pro...
function iS (line 20) | function iS(){if(Fc)return ta;Fc=1;function t(e){const n={begin:/[a-z][A...
function oS (line 20) | function oS(){if(Uc)return na;Uc=1;function t(e){const n="[ \\t\\f]*",r=...
function sS (line 20) | function sS(){if(Bc)return ra;Bc=1;function t(e){const n=["package","imp...
function cS (line 20) | function cS(){if(Gc)return aa;Gc=1;function t(e){const n={keyword:"and c...
function lS (line 20) | function lS(){if(Yc)return ia;Yc=1;function t(e){const n={className:"str...
function uS (line 20) | function uS(){if(qc)return oa;qc=1;function t(e){const n=e.regex,r=new R...
function _S (line 20) | function _S(){if(Hc)return sa;Hc=1;function t(e){return{aliases:["pycon"...
function dS (line 20) | function dS(){if(Vc)return ca;Vc=1;function t(e){return{name:"Q",aliases...
function pS (line 20) | function pS(){if(zc)return la;zc=1;function t(e){const n=e.regex,r={keyw...
function mS (line 20) | function mS(){if(Wc)return ua;Wc=1;function t(e){const n=e.regex,r=/(?:(...
function ES (line 20) | function ES(){if($c)return _a;$c=1;function t(e){return{name:"ReasonML",...
function gS (line 20) | function gS(){if(Kc)return da;Kc=1;function t(e){return{name:"RenderMan ...
function SS (line 20) | function SS(){if(Qc)return pa;Qc=1;function t(e){const n="[a-zA-Z-_][^\\...
function bS (line 20) | function bS(){if(Xc)return ma;Xc=1;function t(e){const n="foreach do whi...
function fS (line 20) | function fS(){if(Zc)return Ea;Zc=1;function t(e){const n=["abs","acos","...
function TS (line 20) | function TS(){if(Jc)return ga;Jc=1;function t(e){return{name:"Oracle Rul...
function RS (line 20) | function RS(){if(jc)return Sa;jc=1;function t(e){const n=e.regex,r=/(r#)...
function CS (line 20) | function CS(){if(el)return ba;el=1;function t(e){const n=e.regex,r=["do"...
function NS (line 20) | function NS(){if(tl)return fa;tl=1;function t(e){const n=e.regex,r={clas...
function OS (line 20) | function OS(){if(nl)return Ta;nl=1;function t(e){const n="[^\\(\\)\\[\\]...
function hS (line 20) | function hS(){if(rl)return Ra;rl=1;function t(e){const n=[e.C_NUMBER_MOD...
function AS (line 20) | function AS(){if(al)return Ca;al=1;const t=l=>({IMPORTANT:{scope:"meta",...
function IS (line 20) | function IS(){if(il)return Na;il=1;function t(e){return{name:"Shell Sess...
function vS (line 20) | function vS(){if(ol)return Oa;ol=1;function t(e){const n=["add","and","c...
function yS (line 21) | function yS(){if(sl)return ha;sl=1;function t(e){const n="[a-z][a-zA-Z0-...
function DS (line 21) | function DS(){if(cl)return Aa;cl=1;function t(e){return{name:"SML (Stand...
function xS (line 21) | function xS(){if(ll)return Ia;ll=1;function t(e){const n={className:"var...
function MS (line 21) | function MS(){if(ul)return va;ul=1;function t(e){const n=e.regex,r=e.COM...
function LS (line 21) | function LS(){if(_l)return ya;_l=1;function t(e){const n=e.regex,r=["fun...
function wS (line 21) | function wS(){if(dl)return Da;dl=1;function t(e){return{name:"Stata",ali...
function PS (line 23) | function PS(){if(pl)return xa;pl=1;function t(e){return{name:"STEP Part ...
function kS (line 23) | function kS(){if(ml)return Ma;ml=1;const t=l=>({IMPORTANT:{scope:"meta",...
function FS (line 23) | function FS(){if(El)return La;El=1;function t(e){return{name:"SubUnit",c...
function US (line 25) | function US(){if(gl)return wa;gl=1;function t(h){return h?typeof h=="str...
function BS (line 25) | function BS(){if(Sl)return Pa;Sl=1;function t(e){return{name:"Tagger Scr...
function GS (line 25) | function GS(){if(bl)return ka;bl=1;function t(e){const n="true false yes...
function YS (line 25) | function YS(){if(fl)return Fa;fl=1;function t(e){return{name:"Test Anyth...
function qS (line 25) | function qS(){if(Tl)return Ua;Tl=1;function t(e){const n=e.regex,r=/[a-z...
function HS (line 25) | function HS(){if(Rl)return Ba;Rl=1;function t(e){const n=["bool","byte",...
function VS (line 25) | function VS(){if(Cl)return Ga;Cl=1;function t(e){const n={className:"num...
function zS (line 25) | function zS(){if(Nl)return Ya;Nl=1;function t(e){const n=e.regex,r=["abs...
function WS (line 25) | function WS(){if(Ol)return qa;Ol=1;const t="[A-Za-z$_][0-9A-Za-z$_]*",e=...
function $S (line 25) | function $S(){if(hl)return Ha;hl=1;function t(e){return{name:"Vala",keyw...
function KS (line 25) | function KS(){if(Al)return Va;Al=1;function t(e){const n=e.regex,r={clas...
function QS (line 25) | function QS(){if(Il)return za;Il=1;function t(e){const n=e.regex,r=["lca...
function XS (line 25) | function XS(){if(vl)return Wa;vl=1;function t(e){return{name:"VBScript i...
function ZS (line 25) | function ZS(){if(yl)return $a;yl=1;function t(e){const n=e.regex,r={$pat...
function JS (line 25) | function JS(){if(Dl)return Ka;Dl=1;function t(e){const n="\\d(_|\\d)*",r...
function jS (line 25) | function jS(){if(xl)return Qa;xl=1;function t(e){return{name:"Vim Script...
function eb (line 25) | function eb(){if(Ml)return Xa;Ml=1;function t(e){e.regex;const n=e.COMME...
function tb (line 25) | function tb(){if(Ll)return Za;Ll=1;function t(e){const n=e.regex,r=/[a-z...
function nb (line 25) | function nb(){if(wl)return Ja;wl=1;function t(e){return{name:"Intel x86 ...
function rb (line 25) | function rb(){if(Pl)return ja;Pl=1;function t(e){const n=["if","then","e...
function ab (line 25) | function ab(){if(kl)return ei;kl=1;function t(e){return{name:"XQuery",al...
function ib (line 25) | function ib(){if(Fl)return ti;Fl=1;function t(e){const n={className:"str...
function gb (line 25) | function gb(t,e){for(const[n,r]of Object.entries(e))t.registerLanguage(n...
function Sb (line 25) | function Sb(t,e){for(const[n,r]of Object.entries(e))t.registerAliases(r,...
function xu (line 25) | function xu(t,e,n,r,a){try{return e.highlight(r,{language:a!==""?a:"plai...
function bb (line 25) | function bb(t,e,n,r,a){if(a!=="")return xu(t,e,n,r,a);try{return e.highl...
function ni (line 25) | function ni(t){return function(...n){return t(...n).replace(/<code class...
function fb (line 25) | function fb(t){var e,n;for(const r of t.tokens)if(r.type==="inline"&&r.c...
function Tb (line 25) | function Tb(t,e,n,r,a){var i,s;const o=t[e];if(n.highlight==null)throw n...
function fi (line 25) | function fi(t,e){const n=oi(oi({},fi.defaults),e);if(n.hljs==null)throw ...
function Ti (line 25) | function Ti(t,e){return e=Hl(Hl({},Ti.defaults),e),e.hljs==null&&(e.hljs...
method setup (line 25) | setup(t){const e=Xu(),n=Pi({description:"",name:""}),r=Pi(""),a=new pe({...
FILE: install/frontend/dist/assets/index-2Kde3L7C.js
method setup (line 1) | setup(){const e=o({page:1,page_size:20}),s=o(!0),l=o([]),t=o(0);return{l...
method created (line 1) | created(){this.fetchList()}
method fetchList (line 1) | fetchList(){this.loading=!0,b.get("/api/rank/score",{params:this.listQue...
method handleCurrentChange (line 1) | handleCurrentChange(e){this.listQuery.page=e,this.fetchList()}
function Q (line 1) | function Q(e,s,l,t,g,c){const a=r("el-table-column"),u=r("el-table"),h=r...
function L (line 1) | function L(e,s,l,t,g,c){const a=r("score-rank");return i(),d("div",B,[s[...
FILE: install/frontend/dist/assets/index-BNtu7jJ7.js
method created (line 1) | created(){this.fetchList()}
method data (line 1) | data(){const s=g(""),e=g(!1),_=g({id:null,name:"123",active_flag:null,co...
method format (line 1) | format(s){return`倒计时:${s}%`}
method fetchList (line 1) | fetchList(){m.get("/api/challenge",{params:{subject:this.type}}).then(s=...
method handlerClose (line 1) | handlerClose(s){clearTimeout(this.func),s()}
method fetchDetail (line 1) | fetchDetail(s){this.percentage=0;let e=this;clearTimeout(e.func),m.get(`...
method changeType (line 1) | changeType(s){this.type=s,this.fetchList()}
method clickDetail (line 1) | clickDetail(s){this.fetchDetail(s.id)}
method startContainer (line 1) | startContainer(){clearTimeout(this.func),this.percentage=0;let s=this.de...
method delayed (line 1) | delayed(){let s=this.detail.id;m.post(`/api/challenge/${s}/delayed`).the...
method destroy (line 1) | destroy(){let s=this.detail.id;this.destroying=!0,m.post(`/api/challenge...
method submit (line 1) | submit(){let s={flag:this.flag,id:this.detail.id};m.post("/api/challenge...
function m2 (line 1) | function m2(s,e,_,w,t,c){const k=h("el-empty"),b=h("el-tag"),v=h("el-pro...
FILE: install/frontend/dist/assets/index-CUMyn3nz.js
function n (line 2) | function n(o){const a={};return o.integrity&&(a.integrity=o.integrity),o...
function r (line 2) | function r(o){if(o.ep)return;o.ep=!0;const a=n(o);fetch(o.href,a)}
function Dc (line 6) | function Dc(e){const t=Object.create(null);for(const n of e.split(","))t...
function We (line 6) | function We(e){if(de(e)){const t={};for(let n=0;n<e.length;n++){const r=...
function $v (line 6) | function $v(e){const t={};return e.replace(Nv,"").split(zv).forEach(n=>{...
function V (line 6) | function V(e){let t="";if(Le(e))t=e;else if(de(e))for(let n=0;n<e.length...
function Vv (line 6) | function Vv(e){if(!e)return null;let{class:t,style:n}=e;return t&&!Le(t)...
function t4 (line 6) | function t4(e){return!!e||e===""}
function jv (line 6) | function jv(e,t){if(e.length!==t.length)return!1;let n=!0;for(let r=0;n&...
function Wl (line 6) | function Wl(e,t){if(e===t)return!0;let n=S2(e),r=S2(t);if(n||r)return n&...
function n4 (line 6) | function n4(e,t){return e.findIndex(n=>Wl(n,t))}
class a4 (line 10) | class a4{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,th...
method constructor (line 10) | constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effe...
method active (line 10) | get active(){return this._active}
method pause (line 10) | pause(){if(this._active){this._isPaused=!0;let t,n;if(this.scopes)for(...
method resume (line 10) | resume(){if(this._active&&this._isPaused){this._isPaused=!1;let t,n;if...
method run (line 10) | run(t){if(this._active){const n=Zt;try{return Zt=this,t()}finally{Zt=n}}}
method on (line 10) | on(){++this._on===1&&(this.prevScope=Zt,Zt=this)}
method off (line 10) | off(){this._on>0&&--this._on===0&&(Zt=this.prevScope,this.prevScope=vo...
method stop (line 10) | stop(t){if(this._active){this._active=!1;let n,r;for(n=0,r=this.effect...
function Wv (line 10) | function Wv(e){return new a4(e)}
function l4 (line 10) | function l4(){return Zt}
function s4 (line 10) | function s4(e,t=!1){Zt&&Zt.cleanups.push(e)}
class i4 (line 10) | class i4{constructor(t){this.fn=t,this.deps=void 0,this.depsTail=void 0,...
method constructor (line 10) | constructor(t){this.fn=t,this.deps=void 0,this.depsTail=void 0,this.fl...
method pause (line 10) | pause(){this.flags|=64}
method resume (line 10) | resume(){this.flags&64&&(this.flags&=-65,Ji.has(this)&&(Ji.delete(this...
method notify (line 10) | notify(){this.flags&2&&!(this.flags&32)||this.flags&8||c4(this)}
method run (line 10) | run(){if(!(this.flags&1))return this.fn();this.flags|=2,M2(this),d4(th...
method stop (line 10) | stop(){if(this.flags&1){for(let t=this.deps;t;t=t.nextDep)Gc(t);this.d...
method trigger (line 10) | trigger(){this.flags&64?Ji.add(this):this.scheduler?this.scheduler():t...
method runIfDirty (line 10) | runIfDirty(){Iu(this)&&this.run()}
method dirty (line 10) | get dirty(){return Iu(this)}
function c4 (line 10) | function c4(e,t=!1){if(e.flags|=8,t){e.next=nl,nl=e;return}e.next=tl,tl=e}
function Uc (line 10) | function Uc(){u4++}
function Kc (line 10) | function Kc(){if(--u4>0)return;if(nl){let t=nl;for(nl=void 0;t;){const n...
function d4 (line 10) | function d4(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveL...
function f4 (line 10) | function f4(e){let t,n=e.depsTail,r=n;for(;r;){const o=r.prevDep;r.versi...
function Iu (line 10) | function Iu(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.versi...
function p4 (line 10) | function p4(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersio...
function Gc (line 10) | function Gc(e,t=!1){const{dep:n,prevSub:r,nextSub:o}=e;if(r&&(r.nextSub=...
function qv (line 10) | function qv(e){const{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=vo...
function Or (line 10) | function Or(){h4.push(Yn),Yn=!1}
function Lr (line 10) | function Lr(){const e=h4.pop();Yn=e===void 0?!0:e}
function M2 (line 10) | function M2(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const n=gt;gt=v...
class Uv (line 10) | class Uv{constructor(t,n){this.sub=t,this.dep=n,this.version=n.version,t...
method constructor (line 10) | constructor(t,n){this.sub=t,this.dep=n,this.version=n.version,this.nex...
class Yc (line 10) | class Yc{constructor(t){this.computed=t,this.version=0,this.activeLink=v...
method constructor (line 10) | constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,t...
method track (line 10) | track(t){if(!gt||!Yn||gt===this.computed)return;let n=this.activeLink;...
method trigger (line 10) | trigger(t){this.version++,_l++,this.notify(t)}
method notify (line 10) | notify(t){Uc();try{for(let n=this.subs;n;n=n.prevSub)n.sub.notify()&&n...
function v4 (line 10) | function v4(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&...
function Qt (line 10) | function Qt(e,t,n){if(Yn&>){let r=Fs.get(e);r||Fs.set(e,r=new Map);let...
function Er (line 10) | function Er(e,t,n,r,o,a){const l=Fs.get(e);if(!l){_l++;return}const s=i=...
function Kv (line 10) | function Kv(e,t){const n=Fs.get(e);return n&&n.get(t)}
function jo (line 10) | function jo(e){const t=et(e);return t===e?t:(Qt(t,"iterate",bl),Tn(e)?t:...
function hi (line 10) | function hi(e){return Qt(e=et(e),"iterate",bl),e}
function Jr (line 10) | function Jr(e,t){return Rr(e)?ia(Co(e)?tr(t):t):tr(t)}
method [Symbol.iterator] (line 10) | [Symbol.iterator](){return Xi(this,Symbol.iterator,e=>Jr(this,e))}
method concat (line 10) | concat(...e){return jo(this).concat(...e.map(t=>de(t)?jo(t):t))}
method entries (line 10) | entries(){return Xi(this,"entries",e=>(e[1]=Jr(this,e[1]),e))}
method every (line 10) | every(e,t){return wr(this,"every",e,t,void 0,arguments)}
method filter (line 10) | filter(e,t){return wr(this,"filter",e,t,n=>n.map(r=>Jr(this,r)),arguments)}
method find (line 10) | find(e,t){return wr(this,"find",e,t,n=>Jr(this,n),arguments)}
method findIndex (line 10) | findIndex(e,t){return wr(this,"findIndex",e,t,void 0,arguments)}
method findLast (line 10) | findLast(e,t){return wr(this,"findLast",e,t,n=>Jr(this,n),arguments)}
method findLastIndex (line 10) | findLastIndex(e,t){return wr(this,"findLastIndex",e,t,void 0,arguments)}
method forEach (line 10) | forEach(e,t){return wr(this,"forEach",e,t,void 0,arguments)}
method includes (line 10) | includes(...e){return Zi(this,"includes",e)}
method indexOf (line 10) | indexOf(...e){return Zi(this,"indexOf",e)}
method join (line 10) | join(e){return jo(this).join(e)}
method lastIndexOf (line 10) | lastIndexOf(...e){return Zi(this,"lastIndexOf",e)}
method map (line 10) | map(e,t){return wr(this,"map",e,t,void 0,arguments)}
method pop (line 10) | pop(){return Va(this,"pop")}
method push (line 10) | push(...e){return Va(this,"push",e)}
method reduce (line 10) | reduce(e,...t){return T2(this,"reduce",e,t)}
method reduceRight (line 10) | reduceRight(e,...t){return T2(this,"reduceRight",e,t)}
method shift (line 10) | shift(){return Va(this,"shift")}
method some (line 10) | some(e,t){return wr(this,"some",e,t,void 0,arguments)}
method splice (line 10) | splice(...e){return Va(this,"splice",e)}
method toReversed (line 10) | toReversed(){return jo(this).toReversed()}
method toSorted (line 10) | toSorted(e){return jo(this).toSorted(e)}
method toSpliced (line 10) | toSpliced(...e){return jo(this).toSpliced(...e)}
method unshift (line 10) | unshift(...e){return Va(this,"unshift",e)}
method values (line 10) | values(){return Xi(this,"values",e=>Jr(this,e))}
function Xi (line 10) | function Xi(e,t,n){const r=hi(e),o=r[t]();return r!==e&&!Tn(e)&&(o._next...
function wr (line 10) | function wr(e,t,n,r,o,a){const l=hi(e),s=l!==e&&!Tn(e),i=l[t];if(i!==Yv[...
function T2 (line 10) | function T2(e,t,n,r){const o=hi(e);let a=n;return o!==e&&(Tn(e)?n.length...
function Zi (line 10) | function Zi(e,t,n){const r=et(e);Qt(r,"iterate",bl);const o=r[t](...n);r...
function Va (line 10) | function Va(e,t,n=[]){Or(),Uc();const r=et(e)[t].apply(e,n);return Kc(),...
function Xv (line 10) | function Xv(e){er(e)||(e=String(e));const t=et(this);return Qt(t,"has",e...
class g4 (line 10) | class g4{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get...
method constructor (line 10) | constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}
method get (line 10) | get(t,n,r){if(n==="__v_skip")return t.__v_skip;const o=this._isReadonl...
class _4 (line 10) | class _4 extends g4{constructor(t=!1){super(!1,t)}set(t,n,r,o){let a=t[n...
method constructor (line 10) | constructor(t=!1){super(!1,t)}
method set (line 10) | set(t,n,r,o){let a=t[n];const l=de(t)&&di(n);if(!this._isShallow){cons...
method deleteProperty (line 10) | deleteProperty(t,n){const r=Ge(t,n);t[n];const o=Reflect.deletePropert...
method has (line 10) | has(t,n){const r=Reflect.has(t,n);return(!er(n)||!m4.has(n))&&Qt(t,"ha...
method ownKeys (line 10) | ownKeys(t){return Qt(t,"iterate",de(t)?"length":yo),Reflect.ownKeys(t)}
class Zv (line 10) | class Zv extends g4{constructor(t=!1){super(!0,t)}set(t,n){return!0}dele...
method constructor (line 10) | constructor(t=!1){super(!0,t)}
method set (line 10) | set(t,n){return!0}
method deleteProperty (line 10) | deleteProperty(t,n){return!0}
function n8 (line 10) | function n8(e,t,n){return function(...r){const o=this.__v_raw,a=et(o),l=...
function cs (line 10) | function cs(e){return function(...t){return e==="delete"?!1:e==="clear"?...
function r8 (line 10) | function r8(e,t){const n={get(o){const a=this.__v_raw,l=et(a),s=et(o);e|...
function Jc (line 10) | function Jc(e,t){const n=r8(e,t);return(r,o,a)=>o==="__v_isReactive"?!e:...
function i8 (line 10) | function i8(e){switch(e){case"Object":case"Array":return 1;case"Map":cas...
function u8 (line 10) | function u8(e){return e.__v_skip||!Object.isExtensible(e)?0:i8(Rv(e))}
function Ct (line 10) | function Ct(e){return Rr(e)?e:Xc(e,!1,Qv,o8,b4)}
function vi (line 10) | function vi(e){return Xc(e,!1,t8,a8,w4)}
function Mo (line 10) | function Mo(e){return Xc(e,!0,e8,l8,y4)}
function Xc (line 10) | function Xc(e,t,n,r,o){if(!Ie(e)||e.__v_raw&&!(t&&e.__v_isReactive))retu...
function Co (line 10) | function Co(e){return Rr(e)?Co(e.__v_raw):!!(e&&e.__v_isReactive)}
function Rr (line 10) | function Rr(e){return!!(e&&e.__v_isReadonly)}
function Tn (line 10) | function Tn(e){return!!(e&&e.__v_isShallow)}
function mi (line 10) | function mi(e){return e?!!e.__v_raw:!1}
function et (line 10) | function et(e){const t=e&&e.__v_raw;return t?et(t):e}
function wl (line 10) | function wl(e){return!Ge(e,"__v_skip")&&Object.isExtensible(e)&&e4(e,"__...
function yt (line 10) | function yt(e){return e?e.__v_isRef===!0:!1}
function I (line 10) | function I(e){return C4(e,!1)}
function Jn (line 10) | function Jn(e){return C4(e,!0)}
function C4 (line 10) | function C4(e,t){return yt(e)?e:new c8(e,t)}
class c8 (line 10) | class c8{constructor(t,n){this.dep=new Yc,this.__v_isRef=!0,this.__v_isS...
method constructor (line 10) | constructor(t,n){this.dep=new Yc,this.__v_isRef=!0,this.__v_isShallow=...
method value (line 10) | get value(){return this.dep.track(),this._value}
method value (line 10) | set value(t){const n=this._rawValue,r=this.__v_isShallow||Tn(t)||Rr(t)...
function Ms (line 10) | function Ms(e){e.dep&&e.dep.trigger()}
function w (line 10) | function w(e){return yt(e)?e.value:e}
function x4 (line 10) | function x4(e){return Co(e)?e:new Proxy(e,d8)}
function Br (line 10) | function Br(e){const t=de(e)?new Array(e.length):{};for(const n in e)t[n...
class f8 (line 10) | class f8{constructor(t,n,r){this._object=t,this._key=n,this._defaultValu...
method constructor (line 10) | constructor(t,n,r){this._object=t,this._key=n,this._defaultValue=r,thi...
method value (line 10) | get value(){let t=this._object[this._key];return this._shallow&&(t=w(t...
method value (line 10) | set value(t){if(this._shallow&&yt(this._raw[this._key])){const n=this....
method dep (line 10) | get dep(){return Kv(this._raw,this._key)}
class p8 (line 10) | class p8{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isRead...
method constructor (line 10) | constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0...
method value (line 10) | get value(){return this._value=this._getter()}
function Nt (line 10) | function Nt(e,t,n){return yt(e)?e:Ce(e)?new p8(e):Ie(e)&&arguments.lengt...
function S4 (line 10) | function S4(e,t,n){return new f8(e,t,n)}
class h8 (line 10) | class h8{constructor(t,n,r){this.fn=t,this.setter=n,this._value=void 0,t...
method constructor (line 10) | constructor(t,n,r){this.fn=t,this.setter=n,this._value=void 0,this.dep...
method notify (line 10) | notify(){if(this.flags|=16,!(this.flags&8)&>!==this)return c4(this,!...
method value (line 10) | get value(){const t=this.dep.track();return p4(this),t&&(t.version=thi...
method value (line 10) | set value(t){this.setter&&this.setter(t)}
function v8 (line 10) | function v8(e,t,n=!1){let r,o;return Ce(e)?r=e:(r=e.get,o=e.set),new h8(...
function m8 (line 10) | function m8(e,t=!1,n=po){if(n){let r=Ds.get(n);r||Ds.set(n,r=[]),r.push(...
function g8 (line 10) | function g8(e,t,n=ht){const{immediate:r,deep:o,once:a,scheduler:l,augmen...
function Mr (line 10) | function Mr(e,t=1/0,n){if(t<=0||!Ie(e)||e.__v_skip||(n=n||new Map,(n.get...
function ql (line 14) | function ql(e,t,n,r){try{return r?e(...r):e()}catch(o){gi(o,t,n)}}
function nr (line 14) | function nr(e,t,n,r){if(Ce(e)){const o=ql(e,t,n,r);return o&&Z1(o)&&o.ca...
function gi (line 14) | function gi(e,t,n,r=!0){const o=t?t.vnode:null,{errorHandler:a,throwUnha...
function _8 (line 14) | function _8(e,t,n,r=!0,o=!1){if(o)throw e;console.error(e)}
function De (line 14) | function De(e){const t=js||E4;return e?t.then(this?e.bind(this):e):t}
function b8 (line 14) | function b8(e){let t=ur+1,n=un.length;for(;t<n;){const r=t+n>>>1,o=un[r]...
function Zc (line 14) | function Zc(e){if(!(e.flags&1)){const t=yl(e),n=un[un.length-1];!n||!(e....
function M4 (line 14) | function M4(){js||(js=E4.then(A4))}
function w8 (line 14) | function w8(e){de(e)?ta.push(...e):Xr&&e.id===-1?Xr.splice(Yo+1,0,e):e.f...
function A2 (line 14) | function A2(e,t,n=ur+1){for(;n<un.length;n++){const r=un[n];if(r&&r.flag...
function T4 (line 14) | function T4(e){if(ta.length){const t=[...new Set(ta)].sort((n,r)=>yl(n)-...
function A4 (line 14) | function A4(e){try{for(ur=0;ur<un.length;ur++){const t=un[ur];t&&!(t.fla...
function Ws (line 14) | function Ws(e){const t=Wt;return Wt=e,O4=e&&e.type.__scopeId||null,t}
function te (line 14) | function te(e,t=Wt,n){if(!t||e._n)return e;const r=(...o)=>{r._d&&Ks(-1)...
function Tt (line 14) | function Tt(e,t){if(Wt===null)return e;const n=Ci(Wt),r=e.dirs||(e.dirs=...
function io (line 14) | function io(e,t,n,r){const o=e.dirs,a=t&&t.dirs;for(let l=0;l<o.length;l...
function at (line 14) | function at(e,t){if(en){let n=en.provides;const r=en.parent&&en.parent.p...
function ye (line 14) | function ye(e,t,n=!1){const r=qe();if(r||ra){let o=ra?ra._context.provid...
function To (line 14) | function To(e,t){return Qc(e,null,t)}
function ge (line 14) | function ge(e,t,n){return Qc(e,t,n)}
function Qc (line 14) | function Qc(e,t,n=ht){const{immediate:r,deep:o,flush:a,once:l}=n,s=Ht({}...
function x8 (line 14) | function x8(e,t,n){const r=this.proxy,o=Le(e)?e.includes(".")?L4(r,e):()...
function L4 (line 14) | function L4(e,t){const n=t.split(".");return()=>{let r=e;for(let o=0;o<n...
method process (line 14) | process(e,t,n,r,o,a,l,s,i,u){const{mc:c,pc:d,pbc:f,o:{insert:v,querySele...
method remove (line 14) | remove(e,t,n,{um:r,o:{remove:o}},a){const{shapeFlag:l,children:s,anchor:...
function fs (line 14) | function fs(e,t,n,{o:{insert:r},m:o},a=2){a===0&&r(e.targetAnchor,t,n);c...
function S8 (line 14) | function S8(e,t,n,r,o,a,{o:{nextSibling:l,parentNode:s,querySelector:i,i...
function Ts (line 14) | function Ts(e,t){const n=e.ctx;if(n&&n.ut){let r,o;for(t?(r=e.el,o=e.anc...
function ku (line 14) | function ku(e,t,n,r,o=null){const a=t.targetStart=n(""),l=t.targetAnchor...
function B4 (line 14) | function B4(){const e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leaving...
method setup (line 14) | setup(e,{slots:t}){const n=qe(),r=B4();return()=>{const o=t.default&&e0(...
function k4 (line 14) | function k4(e){let t=e[0];if(e.length>1){for(const n of e)if(n.type!==Ot...
function N4 (line 14) | function N4(e,t){const{leavingVNodes:n}=e;let r=n.get(t.type);return r||...
function Cl (line 14) | function Cl(e,t,n,r,o){const{appear:a,mode:l,persisted:s=!1,onBeforeEnte...
function Qi (line 14) | function Qi(e){if(_i(e))return e=vr(e),e.children=null,e}
function P2 (line 14) | function P2(e){if(!_i(e))return P4(e.type)&&e.children?k4(e.children):e;...
function Ao (line 14) | function Ao(e,t){e.shapeFlag&6&&e.component?(e.transition=t,Ao(e.compone...
function e0 (line 14) | function e0(e,t=!1,n){let r=[],o=0;for(let a=0;a<e.length;a++){let l=e[a...
function T (line 14) | function T(e,t){return Ce(e)?Ht({name:e.name},t,{setup:e}):e}
function $4 (line 14) | function $4(e){e.ids=[e.ids[0]+e.ids[2]+++"-",0,0]}
function I2 (line 14) | function I2(e,t){let n;return!!((n=Object.getOwnPropertyDescriptor(e,t))...
function ol (line 14) | function ol(e,t,n,r,o=!1){if(de(e)){e.forEach((p,_)=>ol(p,t&&(de(t)?t[_]...
function B2 (line 14) | function B2(e){const t=qs.get(e);t&&(t.flags|=8,qs.delete(e))}
function V4 (line 14) | function V4(e,t){D4(e,"a",t)}
function F4 (line 14) | function F4(e,t){D4(e,"da",t)}
function D4 (line 14) | function D4(e,t,n=en){const r=e.__wdc||(e.__wdc=()=>{let o=n;for(;o;){if...
function A8 (line 14) | function A8(e,t,n,r){const o=bi(t,e,r,!0);Sa(()=>{Wc(r[t],o)},n)}
function bi (line 14) | function bi(e,t,n=en,r=!1){if(n){const o=n[e]||(n[e]=[]),a=t.__weh||(t._...
function P8 (line 14) | function P8(e,t=en){bi("ec",e,t)}
function je (line 14) | function je(e,t){return n0(t0,e,!0,t)||e}
function lt (line 14) | function lt(e){return Le(e)?n0(t0,e,!1)||e:e||W4}
function q4 (line 14) | function q4(e){return n0(I8,e)}
function n0 (line 14) | function n0(e,t,n=!0,r=!1){const o=Wt||en;if(o){const a=o.type;if(e===t0...
function H2 (line 14) | function H2(e,t){return e&&(e[t]||e[On(t)]||e[jl(On(t))])}
function Tr (line 14) | function Tr(e,t,n,r){let o;const a=n,l=de(e);if(l||Le(e)){const s=l&&Co(...
function r0 (line 14) | function r0(e,t){for(let n=0;n<t.length;n++){const r=t[n];if(de(r))for(l...
function se (line 14) | function se(e,t,n={},r,o){if(Wt.ce||Wt.parent&&na(Wt.parent)&&Wt.parent....
function U4 (line 14) | function U4(e){return e.some(t=>Ft(t)?!(t.type===Ot||t.type===Je&&!U4(t....
function B8 (line 14) | function B8(e,t){const n={};for(const r in e)n[el(r)]=e[r];return n}
method get (line 14) | get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:r,data:o,...
method set (line 14) | set({_:e},t,n){const{data:r,setupState:o,ctx:a}=e;return eu(o,t)?(o[t]=n...
method has (line 14) | has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:o,props:a,typ...
method defineProperty (line 14) | defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:Ge(n,"valu...
function zr (line 14) | function zr(){return K4().slots}
function z8 (line 14) | function z8(){return K4().attrs}
function K4 (line 14) | function K4(e){const t=qe();return t.setupContext||(t.setupContext=vp(t))}
function z2 (line 14) | function z2(e){return de(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}
function k8 (line 14) | function k8(e){const t=Y4(e),n=e.proxy,r=e.ctx;$u=!1,t.beforeCreate&&k2(...
function N8 (line 14) | function N8(e,t,n=Lt){de(e)&&(e=Vu(e));for(const r in e){const o=e[r];le...
function k2 (line 14) | function k2(e,t,n){nr(de(e)?e.map(r=>r.bind(t.proxy)):e.bind(t.proxy),t,n)}
function G4 (line 14) | function G4(e,t,n,r){let o=r.includes(".")?L4(n,r):()=>n[r];if(Le(e)){co...
function Y4 (line 14) | function Y4(e){const t=e.type,{mixins:n,extends:r}=t,{mixins:o,optionsCa...
function Us (line 14) | function Us(e,t,n,r=!1){const{mixins:o,extends:a}=t;a&&Us(e,a,n,!0),o&&o...
function N2 (line 14) | function N2(e,t){return t?e?function(){return Ht(Ce(e)?e.call(this,this)...
function V8 (line 14) | function V8(e,t){return Ga(Vu(e),Vu(t))}
function Vu (line 14) | function Vu(e){if(de(e)){const t={};for(let n=0;n<e.length;n++)t[e[n]]=e...
function an (line 14) | function an(e,t){return e?[...new Set([].concat(e,t))]:t}
function Ga (line 14) | function Ga(e,t){return e?Ht(Object.create(null),e,t):t}
function $2 (line 14) | function $2(e,t){return e?de(e)&&de(t)?[...new Set([...e,...t])]:Ht(Obje...
function F8 (line 14) | function F8(e,t){if(!e)return t;if(!t)return e;const n=Ht(Object.create(...
function J4 (line 14) | function J4(){return{app:null,config:{isNativeTag:X1,performance:!1,glob...
function j8 (line 14) | function j8(e,t){return function(r,o=null){Ce(r)||(r=Ht({},r)),o!=null&&...
function q8 (line 14) | function q8(e,t,...n){if(e.isUnmounted)return;const r=e.vnode.props||ht;...
function X4 (line 14) | function X4(e,t,n=!1){const r=n?U8:t.emitsCache,o=r.get(e);if(o!==void 0...
function yi (line 14) | function yi(e,t){return!e||!ui(t)?!1:(t=t.slice(2).replace(/Once$/,""),G...
function V2 (line 14) | function V2(e){const{type:t,vnode:n,proxy:r,withProxy:o,propsOptions:[a]...
function Y8 (line 14) | function Y8(e,t,n){const{props:r,children:o,component:a}=e,{props:l,chil...
function F2 (line 14) | function F2(e,t,n){const r=Object.keys(t);if(r.length!==Object.keys(e).l...
function Z4 (line 14) | function Z4(e,t,n){const r=e[n],o=t[n];return n==="style"&&Ie(r)&&Ie(o)?...
function J8 (line 14) | function J8({vnode:e,parent:t},n){for(;t;){const r=t.subTree;if(r.suspen...
function X8 (line 14) | function X8(e,t,n,r=!1){const o={},a=ep();e.propsDefaults=Object.create(...
function Z8 (line 14) | function Z8(e,t,n,r){const{props:o,attrs:a,vnode:{patchFlag:l}}=e,s=et(o...
function np (line 14) | function np(e,t,n,r){const[o,a]=e.propsOptions;let l=!1,s;if(t)for(let i...
function Fu (line 14) | function Fu(e,t,n,r,o,a){const l=e[n];if(l!=null){const s=Ge(l,"default"...
function rp (line 14) | function rp(e,t,n=!1){const r=n?Q8:t.propsCache,o=r.get(e);if(o)return o...
function D2 (line 14) | function D2(e){return e[0]!=="$"&&!Qa(e)}
function rm (line 14) | function rm(e){return om(e)}
function om (line 14) | function om(e,t){const n=pi();n.__VUE__=!0;const{insert:r,remove:o,patch...
function tu (line 14) | function tu({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n...
function uo (line 14) | function uo({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33...
function am (line 14) | function am(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}
function l0 (line 14) | function l0(e,t,n=!1){const r=e.children,o=t.children;if(de(r)&&de(o))fo...
function lm (line 14) | function lm(e){const t=e.slice(),n=[0];let r,o,a,l,s;const i=e.length;fo...
function sp (line 14) | function sp(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.as...
function j2 (line 14) | function j2(e){if(e)for(let t=0;t<e.length;t++)e[t].flags|=8}
function ip (line 14) | function ip(e){if(e.placeholder)return e.placeholder;const t=e.component...
function sm (line 14) | function sm(e,t){t&&t.pendingBranch?de(e)?t.effects.push(...e):t.effects...
function g (line 14) | function g(e=!1){ll.push(En=e?null:[])}
function im (line 14) | function im(){ll.pop(),En=ll[ll.length-1]||null}
function Ks (line 14) | function Ks(e,t=!1){xl+=e,e<0&&En&&t&&(En.hasOnce=!0)}
function cp (line 14) | function cp(e){return e.dynamicChildren=xl>0?En||Qo:null,im(),xl>0&&En&&...
function x (line 14) | function x(e,t,n,r,o,a){return cp(m(e,t,n,r,o,a,!0))}
function ue (line 14) | function ue(e,t,n,r,o){return cp(re(e,t,n,r,o,!0))}
function Ft (line 14) | function Ft(e){return e?e.__v_isVNode===!0:!1}
function vo (line 14) | function vo(e,t){return e.type===t.type&&e.key===t.key}
function m (line 14) | function m(e,t=null,n=null,r=0,o=null,a=e===Je?0:1,l=!1,s=!1){const i={_...
function um (line 14) | function um(e,t=null,n=null,r=0,o=null,a=!1){if((!e||e===W4)&&(e=Ot),Ft(...
function fp (line 14) | function fp(e){return e?mi(e)||tp(e)?Ht({},e):e:null}
function vr (line 14) | function vr(e,t,n=!1,r=!1){const{props:o,ref:a,patchFlag:l,children:s,tr...
function _t (line 14) | function _t(e=" ",t=0){return re(Ea,null,e,t)}
function pe (line 14) | function pe(e="",t=!1){return t?(g(),ue(Ot,null,e)):re(Ot,null,e)}
function dr (line 14) | function dr(e){return e==null||typeof e=="boolean"?re(Ot):de(e)?re(Je,nu...
function Sr (line 14) | function Sr(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:vr(e)}
function s0 (line 14) | function s0(e,t){let n=0;const{shapeFlag:r}=e;if(t==null)t=null;else if(...
function At (line 14) | function At(...e){const t={};for(let n=0;n<e.length;n++){const r=e[n];fo...
function sr (line 14) | function sr(e,t,n,r=null){nr(e,t,7,[n,r])}
function fm (line 14) | function fm(e,t,n){const r=e.type,o=(t?t.appContext:e.appContext)||cm,a=...
function pp (line 14) | function pp(e){return e.vnode.shapeFlag&4}
function pm (line 14) | function pm(e,t=!1,n=!1){t&&Du(t);const{props:r,children:o}=e.vnode,a=pp...
function hm (line 14) | function hm(e,t){const n=e.type;e.accessCache=Object.create(null),e.prox...
function q2 (line 14) | function q2(e,t,n){Ce(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render...
function hp (line 14) | function hp(e,t,n){const r=e.type;e.render||(e.render=r.render||Lt);{con...
method get (line 14) | get(e,t){return Qt(e,"get",""),e[t]}
function vp (line 14) | function vp(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.att...
function Ci (line 14) | function Ci(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(...
function mm (line 14) | function mm(e,t=!0){return Ce(e)?e.displayName||e.name:e.name||t&&e.__name}
function gm (line 14) | function gm(e){return Ce(e)&&"__vccOpts"in e}
function Ae (line 14) | function Ae(e,t,n){try{Ks(-1);const r=arguments.length;return r===2?Ie(t...
method setScopeId (line 18) | setScopeId(e,t){e.setAttribute(t,"")}
method insertStaticContent (line 18) | insertStaticContent(e,t,n,r,o,a){const l=n?n.previousSibling:t.lastChild...
function bp (line 18) | function bp(e){const t={};for(const z in e)z in gp||(t[z]=e[z]);if(e.css...
function Sm (line 18) | function Sm(e){if(e==null)return null;if(Ie(e))return[ru(e.enter),ru(e.l...
function ru (line 18) | function ru(e){return Hv(e)}
function ir (line 18) | function ir(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[ua]...
function Ur (line 18) | function Ur(e,t){t.split(/\s+/).forEach(r=>r&&e.classList.remove(r));con...
function Y2 (line 18) | function Y2(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}
function J2 (line 18) | function J2(e,t,n,r){const o=e._endId=++Em,a=()=>{o===e._endId&&r()};if(...
function wp (line 18) | function wp(e,t){const n=window.getComputedStyle(e),r=h=>(n[h]||"").spli...
function X2 (line 18) | function X2(e,t){for(;e.length<t.length;)e=e.concat(e);return Math.max(....
function Z2 (line 18) | function Z2(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",",".")...
function Wu (line 18) | function Wu(e){return(e?e.ownerDocument:document).body.offsetHeight}
function Mm (line 18) | function Mm(e,t,n){const r=e[ua];r&&(t=(t?[t,...r]:[...r]).join(" ")),t=...
method beforeMount (line 18) | beforeMount(e,{value:t},{transition:n}){e[Ys]=e.style.display==="none"?"...
method mounted (line 18) | mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)}
method updated (line 18) | updated(e,{value:t,oldValue:n},{transition:r}){!t!=!n&&(r?t?(r.beforeEnt...
method beforeUnmount (line 18) | beforeUnmount(e,{value:t}){ja(e,t)}
function ja (line 18) | function ja(e,t){e.style.display=t?e[Ys]:"none",e[yp]=!t}
function Om (line 18) | function Om(e,t,n){const r=e.style,o=Le(n);let a=!1;if(n&&!o){if(t)if(Le...
function Os (line 18) | function Os(e,t,n){if(de(n))n.forEach(r=>Os(e,t,r));else if(n==null&&(n=...
function Lm (line 18) | function Lm(e,t){const n=ou[t];if(n)return n;let r=On(t);if(r!=="filter"...
function nd (line 18) | function nd(e,t,n,r,o,a=Dv(t)){r&&t.startsWith("xlink:")?n==null?e.remov...
function rd (line 18) | function rd(e,t,n,r,o){if(t==="innerHTML"||t==="textContent"){n!=null&&(...
function Cp (line 18) | function Cp(e,t,n,r){e.addEventListener(t,n,r)}
function Rm (line 18) | function Rm(e,t,n,r){e.removeEventListener(t,n,r)}
function Pm (line 18) | function Pm(e,t,n,r,o=null){const a=e[od]||(e[od]={}),l=a[t];if(r&&l)l.v...
function Im (line 18) | function Im(e){let t;if(ad.test(e)){t={};let r;for(;r=e.match(ad);)e=e.s...
function zm (line 18) | function zm(e,t){const n=r=>{if(!r._vts)r._vts=Date.now();else if(r._vts...
function km (line 18) | function km(e,t){if(de(t)){const n=e.stopImmediatePropagation;return e.s...
function $m (line 18) | function $m(e,t,n,r){if(r)return!!(t==="innerHTML"||t==="textContent"||t...
method setup (line 18) | setup(e,{slots:t}){const n=qe(),r=B4();let o,a;return xa(()=>{if(!o.leng...
function jm (line 18) | function jm(e){const t=e.el;t[Js]&&t[Js](),t[sd]&&t[sd]()}
function Wm (line 18) | function Wm(e){Sp.set(e,Ep(e.el))}
function qm (line 18) | function qm(e){const t=xp.get(e),n=Sp.get(e),r=t.left-n.left,o=t.top-n.t...
function Ep (line 18) | function Ep(e){const t=e.getBoundingClientRect();return{left:t.left,top:...
function Um (line 18) | function Um(e,t,n){const r=e.cloneNode(),o=e[ua];o&&o.forEach(s=>{s.spli...
method created (line 18) | created(e,t,n){e[lu]=id(n),Cp(e,"change",()=>{const r=e._modelValue,o=Km...
method beforeUpdate (line 18) | beforeUpdate(e,t,n){e[lu]=id(n),ud(e,t,n)}
function ud (line 18) | function ud(e,{value:t,oldValue:n},r){e._modelValue=t;let o;if(de(t))o=n...
function Km (line 18) | function Km(e){return"_value"in e?e._value:e.value}
function Tp (line 18) | function Tp(e,t){const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}
function Ap (line 18) | function Ap(){return cd||(cd=rm(Xm))}
function Zm (line 18) | function Zm(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLEl...
function Qm (line 18) | function Qm(e){return Le(e)?document.querySelector(e):e}
function eg (line 18) | function eg(){return Lp().__VUE_DEVTOOLS_GLOBAL_HOOK__}
function Lp (line 18) | function Lp(){return typeof navigator<"u"&&typeof window<"u"?window:type...
function og (line 18) | function og(){var e;return Wo!==void 0||(typeof window<"u"&&window.perfo...
function ag (line 18) | function ag(){return og()?qu.now():Date.now()}
class lg (line 18) | class lg{constructor(t,n){this.target=null,this.targetQueue=[],this.onQu...
method constructor (line 18) | constructor(t,n){this.target=null,this.targetQueue=[],this.onQueue=[],...
method setRealTarget (line 18) | async setRealTarget(t){this.target=t;for(const n of this.onQueue)this....
function sg (line 18) | function sg(e,t){const n=e,r=Lp(),o=eg(),a=tg&&n.enableEarlyProxy;if(o&&...
function Rp (line 22) | function Rp(e){return typeof e=="object"||"displayName"in e||"props"in e...
function ig (line 22) | function ig(e){return e.__esModule||e[Symbol.toStringTag]==="Module"||e....
function su (line 22) | function su(e,t){const n={};for(const r in t){const o=t[r];n[r]=rr(o)?o....
function dd (line 22) | function dd(e,t){const n={};for(const r in e)n[r]=r in t?t[r]:e[r];retur...
function i0 (line 22) | function i0(e){return e==null?"":encodeURI(""+e).replace(mg,"|").replace...
function _g (line 22) | function _g(e){return i0(e).replace(Hp,"{").replace(zp,"}").replace(Bp,"...
function Uu (line 22) | function Uu(e){return i0(e).replace(Ip,"%2B").replace(gg,"+").replace(Pp...
function bg (line 22) | function bg(e){return Uu(e).replace(dg,"%3D")}
function wg (line 22) | function wg(e){return i0(e).replace(Pp,"%23").replace(fg,"%3F")}
function yg (line 22) | function yg(e){return wg(e).replace(cg,"%2F")}
function El (line 22) | function El(e){if(e==null)return null;try{return decodeURIComponent(""+e...
function iu (line 22) | function iu(e,t,n="/"){let r,o={},a="",l="";const s=t.indexOf("#");let i...
function Sg (line 22) | function Sg(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+...
function fd (line 22) | function fd(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?...
function Eg (line 22) | function Eg(e,t,n){const r=t.matched.length-1,o=n.matched.length-1;retur...
function da (line 22) | function da(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}
function kp (line 22) | function kp(e,t){if(Object.keys(e).length!==Object.keys(t).length)return...
function Mg (line 22) | function Mg(e,t){return rr(e)?pd(e,t):rr(t)?pd(t,e):(e==null?void 0:e.va...
function pd (line 22) | function pd(e,t){return rr(t)?e.length===t.length&&e.every((n,r)=>n===t[...
function Tg (line 22) | function Tg(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t....
function Ag (line 22) | function Ag(e){if(!e)if(Jo){const t=document.querySelector("base");e=t&&...
function Lg (line 22) | function Lg(e,t){return e.replace(Og,"#")+t}
function Rg (line 22) | function Rg(e,t){const n=document.documentElement.getBoundingClientRect(...
function Pg (line 22) | function Pg(e){let t;if("el"in e){const n=e.el,r=typeof n=="string"&&n.s...
function hd (line 22) | function hd(e,t){return(history.state?history.state.position-t:-1)+e}
function Ig (line 22) | function Ig(e,t){Gu.set(e,t)}
function Bg (line 22) | function Bg(e){const t=Gu.get(e);return Gu.delete(e),t}
function Hg (line 22) | function Hg(e){return typeof e=="string"||e&&typeof e=="object"}
function Np (line 22) | function Np(e){return typeof e=="string"||typeof e=="symbol"}
function fa (line 22) | function fa(e,t){return ut(new Error,{type:e,[$p]:!0},t)}
function yr (line 22) | function yr(e,t){return e instanceof Error&&$p in e&&(t==null||!!(e.type...
function kg (line 22) | function kg(e){if(typeof e=="string")return e;if(e.path!=null)return e.p...
function Ng (line 22) | function Ng(e){const t={};if(e===""||e==="?")return t;const n=(e[0]==="?...
function vd (line 22) | function vd(e){let t="";for(let n in e){const r=e[n];if(n=bg(n),r==null)...
function $g (line 22) | function $g(e){const t={};for(const n in e){const r=e[n];r!==void 0&&(t[...
function Wa (line 22) | function Wa(){let e=[];function t(r){return e.push(r),()=>{const o=e.ind...
function Zr (line 22) | function Zr(e,t,n,r,o,a=l=>l()){const l=r&&(r.enterCallbacks[o]=r.enterC...
function cu (line 22) | function cu(e,t,n,r,o=a=>a()){const a=[];for(const l of e)for(const s in...
function Fg (line 22) | function Fg(e,t){const n=[],r=[],o=[],a=Math.max(t.matched.length,e.matc...
function Vp (line 26) | function Vp(e,t){const{pathname:n,search:r,hash:o}=t,a=e.indexOf("#");if...
function jg (line 26) | function jg(e,t,n,r){let o=[],a=[],l=null;const s=({state:f})=>{const v=...
function gd (line 26) | function gd(e,t,n,r=!1,o=!1){return{back:e,current:t,forward:n,replaced:...
function Wg (line 26) | function Wg(e){const{history:t,location:n}=window,r={value:Vp(e,n)},o={v...
function qg (line 26) | function qg(e){e=Ag(e);const t=Wg(e),n=jg(e,t.state,t.location,t.replace...
function Gg (line 26) | function Gg(e){if(!e)return[[]];if(e==="/")return[[Ug]];if(!e.startsWith...
function Xg (line 26) | function Xg(e,t){const n=ut({},Yg,t),r=[];let o=n.start?"^":"";const a=[...
function Zg (line 26) | function Zg(e,t){let n=0;for(;n<e.length&&n<t.length;){const r=t[n]-e[n]...
function Fp (line 26) | function Fp(e,t){let n=0;const r=e.score,o=t.score;for(;n<r.length&&n<o....
function bd (line 26) | function bd(e){const t=e[e.length-1];return e.length>0&&t[t.length-1]<0}
function e5 (line 26) | function e5(e,t,n){const r=Xg(Gg(e.path),n),o=ut(r,{record:e,parent:t,ch...
function t5 (line 26) | function t5(e,t){const n=[],r=new Map;t=dd(Qg,t);function o(d){return r....
function wd (line 26) | function wd(e,t){const n={};for(const r of t)r in e&&(n[r]=e[r]);return n}
function yd (line 26) | function yd(e){const t={path:e.path,redirect:e.redirect,name:e.name,meta...
function n5 (line 26) | function n5(e){const t={},n=e.props||!1;if("component"in e)t.default=n;e...
function Cd (line 26) | function Cd(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}
function r5 (line 26) | function r5(e){return e.reduce((t,n)=>ut(t,n.meta),{})}
function o5 (line 26) | function o5(e,t){let n=0,r=t.length;for(;n!==r;){const a=n+r>>1;Fp(e,t[a...
function a5 (line 26) | function a5(e){let t=e;for(;t=t.parent;)if(Dp(t)&&Fp(e,t)===0)return t}
function Dp (line 26) | function Dp({record:e}){return!!(e.name||e.components&&Object.keys(e.com...
function xd (line 26) | function xd(e){const t=ye(Si),n=ye(u0),r=A(()=>{const i=w(e.to);return t...
function l5 (line 26) | function l5(e){return e.length===1?e[0]:e}
method setup (line 26) | setup(e,{slots:t}){const n=Ct(xd(e)),{options:r}=ye(Si),o=A(()=>({[Ed(e....
function u5 (line 26) | function u5(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defa...
function c5 (line 26) | function c5(e,t){for(const n in t){const r=t[n],o=e[n];if(typeof r=="str...
function Sd (line 26) | function Sd(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}
method setup (line 26) | setup(e,{attrs:t,slots:n}){const r=ye(Yu),o=A(()=>e.route||r.value),a=ye...
function Md (line 26) | function Md(e,t){if(!e)return null;const n=e(t);return n.length===1?n[0]:n}
function p5 (line 26) | function p5(e){const t=t5(e.routes,e),n=e.parseQuery||Ng,r=e.stringifyQu...
function h5 (line 26) | function h5(){return ye(Si)}
function jp (line 26) | function jp(e){return ye(u0)}
function _5 (line 26) | function _5(e){var t=m5.call(e,qa),n=e[qa];try{e[qa]=void 0;var r=!0}cat...
function y5 (line 26) | function y5(e){return w5.call(e)}
function Bo (line 26) | function Bo(e){return e==null?e===void 0?x5:C5:Td&&Td in Object(e)?_5(e)...
function mr (line 26) | function mr(e){return e!=null&&typeof e=="object"}
function Ei (line 26) | function Ei(e){return typeof e=="symbol"||mr(e)&&Bo(e)==S5}
function c0 (line 26) | function c0(e,t){for(var n=-1,r=e==null?0:e.length,o=Array(r);++n<r;)o[n...
function Kp (line 26) | function Kp(e){if(typeof e=="string")return e;if(pn(e))return c0(e,Kp)+"...
function M5 (line 26) | function M5(e){for(var t=e.length;t--&&E5.test(e.charAt(t)););return t}
function A5 (line 26) | function A5(e){return e&&e.slice(0,M5(e)+1).replace(T5,"")}
function wn (line 26) | function wn(e){var t=typeof e;return e!=null&&(t=="object"||t=="function")}
function il (line 26) | function il(e){if(typeof e=="number")return e;if(Ei(e))return Ld;if(wn(e...
function d0 (line 26) | function d0(e){return e}
function f0 (line 26) | function f0(e){if(!wn(e))return!1;var t=Bo(e);return t==B5||t==H5||t==I5...
function k5 (line 26) | function k5(e){return!!Rd&&Rd in e}
function Ho (line 26) | function Ho(e){if(e!=null){try{return $5.call(e)}catch{}try{return e+""}...
function K5 (line 26) | function K5(e){if(!wn(e)||k5(e))return!1;var t=f0(e)?U5:F5;return t.test...
function G5 (line 26) | function G5(e,t){return e==null?void 0:e[t]}
function zo (line 26) | function zo(e,t){var n=G5(e,t);return K5(n)?n:void 0}
function e (line 26) | function e(){}
function J5 (line 26) | function J5(e,t,n){switch(n.length){case 0:return e.call(t);case 1:retur...
function Gp (line 26) | function Gp(e,t){var n=-1,r=e.length;for(t||(t=Array(r));++n<r;)t[n]=e[n...
function e9 (line 26) | function e9(e){var t=0,n=0;return function(){var r=Q5(),o=Z5-(r-n);if(n=...
function t9 (line 26) | function t9(e){return function(){return e}}
function r9 (line 26) | function r9(e,t){for(var n=-1,r=e==null?0:e.length;++n<r&&t(e[n],n,e)!==...
function o9 (line 26) | function o9(e,t,n,r){e.length;for(var o=n+1;o--;)if(t(e[o],o,e))return o...
function Mi (line 26) | function Mi(e,t){var n=typeof e;return t=t??a9,!!t&&(n=="number"||n!="sy...
function p0 (line 26) | function p0(e,t,n){t=="__proto__"&&Xs?Xs(e,t,{configurable:!0,enumerable...
function Kl (line 26) | function Kl(e,t){return e===t||e!==e&&t!==t}
function h0 (line 26) | function h0(e,t,n){var r=e[t];(!(i9.call(e,t)&&Kl(r,n))||n===void 0&&!(t...
function Ma (line 26) | function Ma(e,t,n,r){var o=!n;n||(n={});for(var a=-1,l=t.length;++a<l;){...
function Jp (line 26) | function Jp(e,t,n){return t=Id(t===void 0?e.length-1:t,0),function(){for...
function u9 (line 26) | function u9(e,t){return Yp(Jp(e,t,d0),e+"")}
function v0 (line 26) | function v0(e){return typeof e=="number"&&e>-1&&e%1==0&&e<=c9}
function Ta (line 26) | function Ta(e){return e!=null&&v0(e.length)&&!f0(e)}
function d9 (line 26) | function d9(e,t,n){if(!wn(n))return!1;var r=typeof t;return(r=="number"?...
function f9 (line 26) | function f9(e){return u9(function(t,n){var r=-1,o=n.length,a=o>1?n[o-1]:...
function m0 (line 26) | function m0(e){var t=e&&e.constructor,n=typeof t=="function"&&t.prototyp...
function h9 (line 26) | function h9(e,t){for(var n=-1,r=Array(e);++n<e;)r[n]=t(n);return r}
function Bd (line 26) | function Bd(e){return mr(e)&&Bo(e)==v9}
function _9 (line 26) | function _9(){return!1}
function q9 (line 26) | function q9(e){return mr(e)&&v0(e.length)&&!!wt[Bo(e)]}
function g0 (line 26) | function g0(e){return function(t){return e(t)}}
function e3 (line 26) | function e3(e,t){var n=pn(e),r=!n&&Ml(e),o=!n&&!r&&Tl(e),a=!n&&!r&&!o&&_...
function t3 (line 26) | function t3(e,t){return function(n){return e(t(n))}}
function Z9 (line 26) | function Z9(e){if(!m0(e))return Y9(e);var t=[];for(var n in Object(e))X9...
function Gl (line 26) | function Gl(e){return Ta(e)?e3(e):Z9(e)}
function Q9 (line 26) | function Q9(e){var t=[];if(e!=null)for(var n in Object(e))t.push(n);retu...
function n_ (line 26) | function n_(e){if(!wn(e))return Q9(e);var t=m0(e),n=[];for(var r in e)r=...
function Yl (line 26) | function Yl(e){return Ta(e)?e3(e,!0):n_(e)}
function b0 (line 26) | function b0(e,t){if(pn(e))return!1;var n=typeof e;return n=="number"||n=...
function a_ (line 26) | function a_(){this.__data__=Al?Al(null):{},this.size=0}
function l_ (line 26) | function l_(e){var t=this.has(e)&&delete this.__data__[e];return this.si...
function c_ (line 26) | function c_(e){var t=this.__data__;if(Al){var n=t[e];return n===s_?void ...
function p_ (line 26) | function p_(e){var t=this.__data__;return Al?t[e]!==void 0:f_.call(t,e)}
function v_ (line 26) | function v_(e,t){var n=this.__data__;return this.size+=this.has(e)?0:1,n...
function Oo (line 26) | function Oo(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t<n;){va...
function m_ (line 26) | function m_(){this.__data__=[],this.size=0}
function Ti (line 26) | function Ti(e,t){for(var n=e.length;n--;)if(Kl(e[n][0],t))return n;retur...
function b_ (line 26) | function b_(e){var t=this.__data__,n=Ti(t,e);if(n<0)return!1;var r=t.len...
function w_ (line 26) | function w_(e){var t=this.__data__,n=Ti(t,e);return n<0?void 0:t[n][1]}
function y_ (line 26) | function y_(e){return Ti(this.__data__,e)>-1}
function C_ (line 26) | function C_(e,t){var n=this.__data__,r=Ti(n,e);return r<0?(++this.size,n...
function kr (line 26) | function kr(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t<n;){va...
function x_ (line 26) | function x_(){this.size=0,this.__data__={hash:new Oo,map:new(Ol||kr),str...
function S_ (line 26) | function S_(e){var t=typeof e;return t=="string"||t=="number"||t=="symbo...
function Ai (line 26) | function Ai(e,t){var n=e.__data__;return S_(t)?n[typeof t=="string"?"str...
function E_ (line 26) | function E_(e){var t=Ai(this,e).delete(e);return this.size-=t?1:0,t}
function M_ (line 26) | function M_(e){return Ai(this,e).get(e)}
function T_ (line 26) | function T_(e){return Ai(this,e).has(e)}
function A_ (line 26) | function A_(e,t){var n=Ai(this,e),r=n.size;return n.set(e,t),this.size+=...
function Nr (line 26) | function Nr(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t<n;){va...
function w0 (line 26) | function w0(e,t){if(typeof e!="function"||t!=null&&typeof t!="function")...
function R_ (line 26) | function R_(e){var t=w0(e,function(r){return n.size===L_&&n.clear(),r}),...
function H_ (line 26) | function H_(e){return e==null?"":Kp(e)}
function Aa (line 26) | function Aa(e,t){return pn(e)?e:b0(e,t)?[e]:B_(H_(e))}
function Oa (line 26) | function Oa(e){if(typeof e=="string"||Ei(e))return e;var t=e+"";return t...
function Oi (line 26) | function Oi(e,t){t=Aa(t,e);for(var n=0,r=t.length;e!=null&&n<r;)e=e[Oa(t...
function dn (line 26) | function dn(e,t,n){var r=e==null?void 0:Oi(e,t);return r===void 0?n:r}
function y0 (line 26) | function y0(e,t){for(var n=-1,r=t.length,o=e.length;++n<r;)e[o+n]=t[n];r...
function z_ (line 26) | function z_(e){return pn(e)||Ml(e)||!!(Nd&&e&&e[Nd])}
function n3 (line 26) | function n3(e,t,n,r,o){var a=-1,l=e.length;for(n||(n=z_),o||(o=[]);++a<l...
function k_ (line 26) | function k_(e){var t=e==null?0:e.length;return t?n3(e):[]}
function r3 (line 26) | function r3(e){return Yp(Jp(e,void 0,k_),e+"")}
function x0 (line 26) | function x0(e){if(!mr(e)||Bo(e)!=N_)return!1;var t=C0(e);if(t===null)ret...
function j_ (line 26) | function j_(e,t,n){var r=-1,o=e.length;t<0&&(t=-t>o?0:o+t),n=n>o?o:n,n<0...
function cn (line 26) | function cn(){if(!arguments.length)return[];var e=arguments[0];return pn...
function W_ (line 26) | function W_(e,t,n){return e===e&&(n!==void 0&&(e=e<=n?e:n),t!==void 0&&(...
function a3 (line 26) | function a3(e,t,n){return n===void 0&&(n=t,t=void 0),n!==void 0&&(n=il(n...
function q_ (line 26) | function q_(){this.__data__=new kr,this.size=0}
function U_ (line 26) | function U_(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n}
function K_ (line 26) | function K_(e){return this.__data__.get(e)}
function G_ (line 26) | function G_(e){return this.__data__.has(e)}
function J_ (line 26) | function J_(e,t){var n=this.__data__;if(n instanceof kr){var r=n.__data_...
function Xn (line 26) | function Xn(e){var t=this.__data__=new kr(e);this.size=t.size}
function X_ (line 26) | function X_(e,t){return e&&Ma(t,Gl(t),e)}
function Z_ (line 26) | function Z_(e,t){return e&&Ma(t,Yl(t),e)}
function s3 (line 26) | function s3(e,t){if(t)return e.slice();var n=e.length,r=Fd?Fd(n):new e.c...
function eb (line 26) | function eb(e,t){for(var n=-1,r=e==null?0:e.length,o=0,a=[];++n<r;){var ...
function i3 (line 26) | function i3(){return[]}
function rb (line 26) | function rb(e,t){return Ma(e,S0(e),t)}
function ab (line 26) | function ab(e,t){return Ma(e,u3(e),t)}
function c3 (line 26) | function c3(e,t,n){var r=t(e);return pn(e)?r:y0(r,n(e))}
function Xu (line 26) | function Xu(e){return c3(e,Gl,S0)}
function d3 (line 26) | function d3(e){return c3(e,Yl,u3)}
function hb (line 26) | function hb(e){var t=e.length,n=new e.constructor(t);return t&&typeof e[...
function E0 (line 26) | function E0(e){var t=new e.constructor(e.byteLength);return new Zs(t).se...
function vb (line 26) | function vb(e,t){var n=t?E0(e.buffer):e.buffer;return new e.constructor(...
function gb (line 26) | function gb(e){var t=new e.constructor(e.source,mb.exec(e));return t.las...
function _b (line 26) | function _b(e){return Yd?Object(Yd.call(e)):{}}
function f3 (line 26) | function f3(e,t){var n=t?E0(e.buffer):e.buffer;return new e.constructor(...
function Nb (line 26) | function Nb(e,t,n){var r=e.constructor;switch(t){case Tb:return E0(e);ca...
function p3 (line 26) | function p3(e){return typeof e.constructor=="function"&&!m0(e)?Y5(C0(e))...
function Vb (line 26) | function Vb(e){return mr(e)&&qn(e)==$b}
function jb (line 26) | function jb(e){return mr(e)&&qn(e)==Db}
function cl (line 26) | function cl(e,t,n,r,o,a){var l,s=t&qb,i=t&Ub,u=t&Kb;if(n&&(l=o?n(e,r,o,a...
function dl (line 26) | function dl(e){return cl(e,gw|_w)}
function ww (line 26) | function ww(e){return this.__data__.set(e,bw),this}
function yw (line 26) | function yw(e){return this.__data__.has(e)}
function Qs (line 26) | function Qs(e){var t=-1,n=e==null?0:e.length;for(this.__data__=new Nr;++...
function Cw (line 26) | function Cw(e,t){for(var n=-1,r=e==null?0:e.length;++n<r;)if(t(e[n],n,e)...
function xw (line 26) | function xw(e,t){return e.has(t)}
function g3 (line 26) | function g3(e,t,n,r,o,a){var l=n&Sw,s=e.length,i=t.length;if(s!=i&&!(l&&...
function Mw (line 26) | function Mw(e){var t=-1,n=Array(e.size);return e.forEach(function(r,o){n...
function Tw (line 26) | function Tw(e){var t=-1,n=Array(e.size);return e.forEach(function(r){n[+...
function Fw (line 26) | function Fw(e,t,n,r,o,a,l){switch(n){case Vw:if(e.byteLength!=t.byteLeng...
function qw (line 26) | function qw(e,t,n,r,o,a){var l=n&Dw,s=Xu(e),i=s.length,u=Xu(t),c=u.lengt...
function Gw (line 26) | function Gw(e,t,n,r,o,a){var l=pn(e),s=pn(t),i=l?ef:qn(e),u=s?ef:qn(t);i...
function Li (line 26) | function Li(e,t,n,r,o){return e===t?!0:e==null||t==null||!mr(e)&&!mr(t)?...
function Xw (line 26) | function Xw(e,t,n,r){var o=n.length,a=o;if(e==null)return!a;for(e=Object...
function _3 (line 26) | function _3(e){return e===e&&!wn(e)}
function Zw (line 26) | function Zw(e){for(var t=Gl(e),n=t.length;n--;){var r=t[n],o=e[r];t[n]=[...
function b3 (line 26) | function b3(e,t){return function(n){return n==null?!1:n[e]===t&&(t!==voi...
function Qw (line 26) | function Qw(e){var t=Zw(e);return t.length==1&&t[0][2]?b3(t[0][0],t[0][1...
function ey (line 26) | function ey(e,t){return e!=null&&t in Object(e)}
function ty (line 26) | function ty(e,t,n){t=Aa(t,e);for(var r=-1,o=t.length,a=!1;++r<o;){var l=...
function w3 (line 26) | function w3(e,t){return e!=null&&ty(e,t,ey)}
function oy (line 26) | function oy(e,t){return b0(e)&&_3(t)?b3(Oa(e),t):function(n){var r=dn(n,...
function ay (line 26) | function ay(e){return function(t){return t==null?void 0:t[e]}}
function ly (line 26) | function ly(e){return function(t){return Oi(t,e)}}
function sy (line 26) | function sy(e){return b0(e)?ay(Oa(e)):ly(e)}
function y3 (line 26) | function y3(e){return typeof e=="function"?e:e==null?d0:typeof e=="objec...
function iy (line 26) | function iy(e){return function(t,n,r){for(var o=-1,a=Object(t),l=r(t),s=...
function uy (line 26) | function uy(e,t){return e&&C3(e,t,Gl)}
function cy (line 26) | function cy(e,t){return function(n,r){if(n==null)return n;if(!Ta(n))retu...
function ei (line 26) | function ei(e,t,n){var r,o,a,l,s,i,u=0,c=!1,d=!1,f=!0;if(typeof e!="func...
function tc (line 26) | function tc(e,t,n){(n!==void 0&&!Kl(e[t],n)||n===void 0&&!(t in e))&&p0(...
function vy (line 26) | function vy(e){return mr(e)&&Ta(e)}
function nc (line 26) | function nc(e,t){if(!(t==="constructor"&&typeof e[t]=="function")&&t!="_...
function my (line 26) | function my(e){return Ma(e,Yl(e))}
function gy (line 26) | function gy(e,t,n,r,o,a,l){var s=nc(e,n),i=nc(t,n),u=l.get(i);if(u){tc(e...
function x3 (line 26) | function x3(e,t,n,r,o){e!==t&&C3(t,function(a,l){if(o||(o=new Xn),wn(a))...
function _y (line 26) | function _y(e){var t=e==null?0:e.length;return t?e[t-1]:void 0}
function by (line 26) | function by(e,t,n){var r=e==null?0:e.length;if(!r)return-1;var o=r-1;ret...
function wy (line 26) | function wy(e,t){var n=-1,r=Ta(e)?Array(e.length):[];return dy(e,functio...
function yy (line 26) | function yy(e,t){var n=pn(e)?c0:wy;return n(e,y3(t))}
function S3 (line 26) | function S3(e,t){return n3(yy(e,t))}
function Ll (line 26) | function Ll(e){for(var t=-1,n=e==null?0:e.length,r={};++t<n;){var o=e[t]...
function Cy (line 26) | function Cy(e,t){return t.length<2?e:Oi(e,j_(t,0,-1))}
function Un (line 26) | function Un(e,t){return Li(e,t)}
function ro (line 26) | function ro(e){return e==null}
function Ri (line 26) | function Ri(e){return e===null}
function xy (line 26) | function xy(e){return e===void 0}
function My (line 26) | function My(e,t){t=Aa(t,e);var n=-1,r=t.length;if(!r)return!0;for(var o=...
function Ty (line 26) | function Ty(e){return x0(e)?void 0:e}
function T3 (line 26) | function T3(e,t,n,r){if(!wn(e))return e;t=Aa(t,e);for(var o=-1,a=t.lengt...
function Ry (line 26) | function Ry(e,t,n){for(var r=-1,o=t.length,a={};++r<o;){var l=t[r],s=Oi(...
function Py (line 26) | function Py(e,t){return Ry(e,t,function(n,r){return w3(e,r)})}
function Iy (line 26) | function Iy(e,t,n){return e==null?e:T3(e,t,n)}
function R3 (line 26) | function R3(e,t){var n;const r=Jn();return To(()=>{r.value=e()},{...t,fl...
function Jl (line 26) | function Jl(e){return l4()?(s4(e),!0):!1}
function ha (line 26) | function ha(e){return typeof e=="function"?e():w(e)}
function Wy (line 26) | function Wy(){var e,t;return st&&((e=window==null?void 0:window.navigato...
function qy (line 26) | function qy(e,t){function n(...r){return new Promise((o,a)=>{Promise.res...
function Uy (line 26) | function Uy(e,t={}){let n,r,o=oa;const a=s=>{clearTimeout(s),o(),o=oa};r...
function Ky (line 26) | function Ky(e){return qe()}
function P3 (line 26) | function P3(e,t=200,n={}){return qy(Uy(t,n),e)}
function Gy (line 26) | function Gy(e,t=200,n={}){const r=I(e.value),o=P3(()=>{r.value=e.value},...
function Yy (line 26) | function Yy(e,t=!0,n){Ky()?rt(e,n):t?e():De(e)}
function Rl (line 26) | function Rl(e,t,n={}){const{immediate:r=!0}=n,o=I(!1);let a=null;functio...
function fn (line 26) | function fn(e){var t;const n=ha(e);return(t=n==null?void 0:n.$el)!=null?...
function nn (line 26) | function nn(...e){let t,n,r,o;if(typeof e[0]=="string"||Array.isArray(e[...
function Xy (line 26) | function Xy(e,t,n={}){const{window:r=La,ignore:o=[],capture:a=!0,detectI...
function Zy (line 26) | function Zy(){const e=I(!1),t=qe();return t&&rt(()=>{e.value=!0},t),e}
function I3 (line 26) | function I3(e){const t=Zy();return A(()=>(t.value,!!e()))}
function Qy (line 26) | function Qy(e,t,n={}){const{window:r=La,...o}=n;let a;const l=I3(()=>r&&...
function e7 (line 26) | function e7(e={}){const{document:t=Jy}=e;if(!t)return I("visible");const...
function kt (line 26) | function kt(e,t,n={}){const{window:r=La,...o}=n;let a;const l=I3(()=>r&&...
function rf (line 26) | function rf(e,t={width:0,height:0},n={}){const{window:r=La,box:o="conten...
function t7 (line 26) | function t7(e={}){const{window:t=La}=e;if(!t)return I(!1);const n=I(t.do...
class n7 (line 26) | class n7 extends Error{constructor(t){super(t),this.name="ElementPlusErr...
method constructor (line 26) | constructor(t){super(t),this.name="ElementPlusError"}
function Nn (line 26) | function Nn(e,t){throw new n7(`[${e}] ${t}`)}
method value (line 26) | get value(){return dn(e,t,n)}
method value (line 26) | set value(r){Iy(e,t,r)}
function Vr (line 26) | function Vr(e,t=void 0){const n=qe()?ye(Wp,ti):ti;return e?A(()=>{var r,...
function A0 (line 26) | function A0(e,t){const n=Vr(),r=Te(e,A(()=>{var s;return((s=n.value)==nu...
method setup (line 26) | setup(e){return(t,n)=>t.disabled?se(t.$slots,"default",{key:0}):(g(),ue(...
function Ln (line 26) | function Ln(e,t="px"){if(!e&&e!==0)return"";if(Ye(e)||Vy(e))return`${e}$...
function g7 (line 26) | function g7(e,t){if(!st)return;if(!t){e.scrollTop=0;return}const n=[];le...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg","...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg","...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg","...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg","...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg","...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg","...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg","...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg","...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg","...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg","...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){return(t,n)=>(g(),x("svg",{xmlns:"http://www.w3.org/2000/svg",v...
method setup (line 26) | setup(e){const t=e,n=Te("icon"),r=A(()=>{const{size:o,color:a}=t,l=Ln(o)...
function XR (line 26) | function XR(e){return Ft(e)&&e.type===Ot}
method setup (line 26) | setup(e,{emit:t}){const{Close:n}=H0,r=e,o=t,a=zr(),l=Te("alert"),s=I(!0)...
function gr (line 26) | function gr(e){return e?(e.nodeName||"").toLowerCase():null}
function Rn (line 26) | function Rn(e){if(e==null)return window;if(e.toString()!=="[object Windo...
function Ro (line 26) | function Ro(e){var t=Rn(e).Element;return e instanceof t||e instanceof E...
function Bn (line 26) | function Bn(e){var t=Rn(e).HTMLElement;return e instanceof t||e instance...
function k0 (line 26) | function k0(e){if(typeof ShadowRoot>"u")return!1;var t=Rn(e).ShadowRoot;...
function dP (line 26) | function dP(e){var t=e.state;Object.keys(t.elements).forEach(function(n)...
function fP (line 26) | function fP(e){var t=e.state,n={popper:{position:t.options.strategy,left...
function hr (line 26) | function hr(e){return e.split("-")[0]}
function lc (line 26) | function lc(){var e=navigator.userAgentData;return e!=null&&e.brands&&Ar...
function e6 (line 26) | function e6(){return!/^((?!chrome|android).)*safari/i.test(lc())}
function ba (line 26) | function ba(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!1);var r=e.getBoun...
function N0 (line 26) | function N0(e){var t=ba(e),n=e.offsetWidth,r=e.offsetHeight;return Math....
function t6 (line 26) | function t6(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))r...
function Pr (line 26) | function Pr(e){return Rn(e).getComputedStyle(e)}
function pP (line 26) | function pP(e){return["table","td","th"].indexOf(gr(e))>=0}
function oo (line 26) | function oo(e){return((Ro(e)?e.ownerDocument:e.document)||window.documen...
function Hi (line 26) | function Hi(e){return gr(e)==="html"?e:e.assignedSlot||e.parentNode||(k0...
function ff (line 26) | function ff(e){return!Bn(e)||Pr(e).position==="fixed"?null:e.offsetParent}
function hP (line 26) | function hP(e){var t=/firefox/i.test(lc()),n=/Trident/i.test(lc());if(n&...
function Ql (line 26) | function Ql(e){for(var t=Rn(e),n=ff(e);n&&pP(n)&&Pr(n).position==="stati...
function $0 (line 26) | function $0(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}
function pl (line 26) | function pl(e,t,n){return xo(e,ri(t,n))}
function vP (line 26) | function vP(e,t,n){var r=pl(e,t,n);return r>n?n:r}
function n6 (line 26) | function n6(){return{top:0,right:0,bottom:0,left:0}}
function r6 (line 26) | function r6(e){return Object.assign({},n6(),e)}
function o6 (line 26) | function o6(e,t){return t.reduce(function(n,r){return n[r]=e,n},{})}
function gP (line 26) | function gP(e){var t,n=e.state,r=e.name,o=e.options,a=n.elements.arrow,l...
function _P (line 26) | function _P(e){var t=e.state,n=e.options,r=n.element,o=r===void 0?"[data...
function wa (line 26) | function wa(e){return e.split("-")[1]}
function yP (line 26) | function yP(e,t){var n=e.x,r=e.y,o=t.devicePixelRatio||1;return{x:_a(n*o...
function pf (line 26) | function pf(e){var t,n=e.popper,r=e.popperRect,o=e.placement,a=e.variati...
function CP (line 26) | function CP(e){var t=e.state,n=e.options,r=n.gpuAcceleration,o=r===void ...
function xP (line 26) | function xP(e){var t=e.state,n=e.instance,r=e.options,o=r.scroll,a=o===v...
function Ps (line 26) | function Ps(e){return e.replace(/left|right|bottom|top/g,function(t){ret...
function hf (line 26) | function hf(e){return e.replace(/start|end/g,function(t){return EP[t]})}
function V0 (line 26) | function V0(e){var t=Rn(e),n=t.pageXOffset,r=t.pageYOffset;return{scroll...
function F0 (line 26) | function F0(e){return ba(oo(e)).left+V0(e).scrollLeft}
function MP (line 26) | function MP(e,t){var n=Rn(e),r=oo(e),o=n.visualViewport,a=r.clientWidth,...
function TP (line 26) | function TP(e){var t,n=oo(e),r=V0(e),o=(t=e.ownerDocument)==null?void 0:...
function D0 (line 26) | function D0(e){var t=Pr(e),n=t.overflow,r=t.overflowX,o=t.overflowY;retu...
function s6 (line 26) | function s6(e){return["html","body","#document"].indexOf(gr(e))>=0?e.own...
function hl (line 26) | function hl(e,t){var n;t===void 0&&(t=[]);var r=s6(e),o=r===((n=e.ownerD...
function sc (line 26) | function sc(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.w...
function AP (line 26) | function AP(e,t){var n=ba(e,!1,t==="fixed");return n.top=n.top+e.clientT...
function vf (line 26) | function vf(e,t,n){return t===Z3?sc(MP(e,n)):Ro(t)?AP(t,n):sc(TP(oo(e)))}
function OP (line 26) | function OP(e){var t=hl(Hi(e)),n=["absolute","fixed"].indexOf(Pr(e).posi...
function LP (line 26) | function LP(e,t,n,r){var o=t==="clippingParents"?OP(e):[].concat(t),a=[]...
function i6 (line 26) | function i6(e){var t=e.reference,n=e.element,r=e.placement,o=r?hr(r):nul...
function Bl (line 26) | function Bl(e,t){t===void 0&&(t={});var n=t,r=n.placement,o=r===void 0?e...
function RP (line 26) | function RP(e,t){t===void 0&&(t={});var n=t,r=n.placement,o=n.boundary,a...
function PP (line 26) | function PP(e){if(hr(e)===z0)return[];var t=Ps(e);return[hf(e),t,hf(t)]}
function IP (line 26) | function IP(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]...
function mf (line 26) | function mf(e,t,n){return n===void 0&&(n={x:0,y:0}),{top:e.top-t.height-...
function gf (line 26) | function gf(e){return[_n,Vn,$n,bn].some(function(t){return e[t]>=0})}
function HP (line 26) | function HP(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popp...
function kP (line 26) | function kP(e,t,n){var r=hr(e),o=[bn,_n].indexOf(r)>=0?-1:1,a=typeof n==...
function NP (line 26) | function NP(e){var t=e.state,n=e.options,r=e.name,o=n.offset,a=o===void ...
function VP (line 26) | function VP(e){var t=e.state,n=e.name;t.modifiersData[n]=i6({reference:t...
function FP (line 26) | function FP(e){return e==="x"?"y":"x"}
function DP (line 26) | function DP(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,a=o===voi...
function WP (line 26) | function WP(e){return{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}}
function qP (line 26) | function qP(e){return e===Rn(e)||!Bn(e)?V0(e):WP(e)}
function UP (line 26) | function UP(e){var t=e.getBoundingClientRect(),n=_a(t.width)/e.offsetWid...
function KP (line 26) | function KP(e,t,n){n===void 0&&(n=!1);var r=Bn(t),o=Bn(t)&&UP(t),a=oo(t)...
function GP (line 26) | function GP(e){var t=new Map,n=new Set,r=[];e.forEach(function(a){t.set(...
function YP (line 26) | function YP(e){var t=GP(e);return cP.reduce(function(n,r){return n.conca...
function JP (line 26) | function JP(e){var t;return function(){return t||(t=new Promise(function...
function XP (line 26) | function XP(e){var t=e.reduce(function(n,r){var o=n[r.name];return n[r.n...
function bf (line 26) | function bf(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]...
function j0 (line 26) | function j0(e){e===void 0&&(e={});var t=e,n=t.defaultModifiers,r=n===voi...
function wf (line 26) | function wf(){let e;const t=(r,o)=>{n(),e=window.setTimeout(r,o)},n=()=>...
function fI (line 26) | function fI(e){const t=window.getComputedStyle(e),n=t.getPropertyValue("...
function Cf (line 26) | function Cf(e,t=1,n){var r;if(!mn){mn=document.createElement("textarea")...
function p6 (line 26) | function p6(e,{disabled:t,beforeFocus:n,afterFocus:r,beforeBlur:o,afterB...
function v6 (line 26) | function v6({afterComposition:e,emit:t}){const n=I(!1),r=s=>{t==null||t(...
function _I (line 26) | function _I(e){let t;function n(){if(e.value==null)return;const{selectio...
method setup (line 26) | setup(e,{expose:t,emit:n}){const r=e,o=n,a=z8(),l=vI(),s=zr(),i=A(()=>[r...
method setup (line 26) | setup(e){const t=e,n=ye(q0),r=Te("scrollbar");n||Nn(AI,"can not inject s...
method setup (line 26) | setup(e,{expose:t}){const n=e,r=ye(q0),o=I(0),a=I(0),l=I(""),s=I(""),i=I...
method setup (line 26) | setup(e,{expose:t,emit:n}){const r=e,o=n,a=Te("scrollbar");let l,s,i,u=0...
method setup (line 26) | setup(e,{expose:t}){const n=e,r=I(),o=I(),a=I(),l=I(),s=A(()=>n.role),i=...
method setup (line 26) | setup(e,{expose:t}){const n=Te("popper"),{arrowRef:r,arrowStyle:o}=ye(w6...
method mounted (line 26) | mounted(t){e(t)}
method updated (line 26) | updated(t){e(t)}
method unmounted (line 26) | unmounted(){e(null)}
method setup (line 26) | setup(e,{slots:t,attrs:n}){var r;const o=ye(y6),a=WI((r=o==null?void 0:o...
function x6 (line 26) | function x6(e){if(!e)return[null,0];const t=e,n=t.filter(r=>r.type!==Ot)...
function Ef (line 26) | function Ef(e){const t=Te("only-child");return re("span",{class:t.e("con...
method setup (line 26) | setup(e,{expose:t}){const n=e,{role:r,triggerRef:o}=ye(U0,void 0);jI(o);...
function Lf (line 26) | function Lf(e,t){const n=[...e],r=e.indexOf(t);return r!==-1&&n.splice(r...
method setup (line 26) | setup(e,{emit:t}){const n=I();let r,o;const{focusReason:a}=tB();rB(h=>{e...
function aB (line 26) | function aB(e,t,n,r,o,a){return se(e.$slots,"default",{handleKeydown:e.o...
function uB (line 26) | function uB(e){const{offset:t,gpuAcceleration:n,fallbackPlacements:r}=e;...
function cB (line 26) | function cB(e,t){t&&(e.modifiers=[...e.modifiers,...t??[]])}
function fB (line 26) | function fB(e){const t=Object.keys(e.elements),n=Ll(t.map(o=>[o,e.styles...
method setup (line 26) | setup(e,{expose:t,emit:n}){const r=n,o=e,{focusStartRef:a,trapped:l,onFo...
method setup (line 26) | setup(e,{expose:t}){const n=e,r=Te("tooltip"),{controlled:o,id:a,open:l,...
method setup (line 26) | setup(e,{expose:t}){const n=e,{selector:r}=M6(),o=Te("tooltip"),a=I(),l=...
method setup (line 26) | setup(e,{expose:t,emit:n}){const r=e,o=n;wB();const a=Te("tooltip"),l=Fn...
method setup (line 26) | setup(e,{emit:t}){const n=e,r=t,o=ye(T6,void 0),a=Te("avatar"),l=I(!1),s...
method setup (line 26) | setup(e,{slots:t}){const n=Te("avatar-group");return at(T6,Ct({size:Nt(e...
method setup (line 26) | setup(e,{expose:t}){const n=e,r=Te("badge"),o=A(()=>n.isDot?"":Ye(n.valu...
function qt (line 26) | function qt(e,t){kB(e)&&(e="100%");var n=NB(e);return e=t===360?e:Math.m...
function bs (line 26) | function bs(e){return Math.min(1,Math.max(0,e))}
function kB (line 26) | function kB(e){return typeof e=="string"&&e.indexOf(".")!==-1&&parseFloa...
function NB (line 26) | function NB(e){return typeof e=="string"&&e.indexOf("%")!==-1}
function R6 (line 26) | function R6(e){return e=parseFloat(e),(isNaN(e)||e<0||e>1)&&(e=1),e}
function ws (line 26) | function ws(e){return e<=1?"".concat(Number(e)*100,"%"):e}
function _o (line 26) | function _o(e){return e.length===1?"0"+e:String(e)}
function $B (line 26) | function $B(e,t,n){return{r:qt(e,255)*255,g:qt(t,255)*255,b:qt(n,255)*255}}
function zf (line 26) | function zf(e,t,n){e=qt(e,255),t=qt(t,255),n=qt(n,255);var r=Math.max(e,...
function _u (line 26) | function _u(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+(t-e)*(6*n):n<...
function VB (line 26) | function VB(e,t,n){var r,o,a;if(e=qt(e,360),t=qt(t,100),n=qt(n,100),t===...
function kf (line 26) | function kf(e,t,n){e=qt(e,255),t=qt(t,255),n=qt(n,255);var r=Math.max(e,...
function FB (line 26) | function FB(e,t,n){e=qt(e,360)*6,t=qt(t,100),n=qt(n,100);var r=Math.floo...
function Nf (line 26) | function Nf(e,t,n,r){var o=[_o(Math.round(e).toString(16)),_o(Math.round...
function DB (line 26) | function DB(e,t,n,r,o){var a=[_o(Math.round(e).toString(16)),_o(Math.rou...
function jB (line 26) | function jB(e){return Math.round(parseFloat(e)*255).toString(16)}
function $f (line 26) | function $f(e){return xn(e)/255}
function xn (line 26) | function xn(e){return parseInt(e,16)}
function WB (line 26) | function WB(e){return{r:e>>16,g:(e&65280)>>8,b:e&255}}
function qB (line 26) | function qB(e){var t={r:0,g:0,b:0},n=1,r=null,o=null,a=null,l=!1,s=!1;re...
function GB (line 26) | function GB(e){if(e=e.trim().toLowerCase(),e.length===0)return!1;var t=!...
function Cr (line 26) | function Cr(e){return!!Wn.CSS_UNIT.exec(String(e))}
function e (line 26) | function e(t,n){t===void 0&&(t=""),n===void 0&&(n={});var r;if(t instanc...
function jr (line 26) | function jr(e,t=20){return e.mix("#141414",t).toString()}
function YB (line 26) | function YB(e){const t=Ba(),n=Te("button");return A(()=>{let r={},o=e.co...
method setup (line 26) | setup(e,{expose:t,emit:n}){const r=e,o=n,a=YB(r),l=Te("button"),{_ref:s,...
method setup (line 26) | setup(e){const t=e;at(L6,Ct({size:Nt(t,"size"),type:Nt(t,"type")}));cons...
function QB (line 26) | function QB(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.c...
method setup (line 26) | setup(e,{emit:t}){const n=e,r=t,o=_r(),{t:a}=Kt(),l=Te("tag"),s=A(()=>{c...
function oH (line 26) | function oH(e){const t=I({...pc,...e.props});let n={...e.props};return g...
function sH (line 26) | function sH(e,t){const n=ye($i);n||Nn(hc,"usage: <el-select><el-option /...
method setup (line 26) | setup(e){const t=Te("select"),n=Fn(),r=A(()=>[t.be("dropdown","item"),t....
function cH (line 26) | function cH(e,t,n,r,o,a){return Tt((g(),x("li",{id:e.id,class:V(e.contai...
method setup (line 26) | setup(){const e=ye($i),t=Te("select"),n=A(()=>e.props.popperClass),r=A((...
function pH (line 26) | function pH(e,t,n,r,o,a){return g(),x("div",{class:V([e.ns.b("dropdown")...
method afterFocus (line 26) | afterFocus(){e.automaticDropdown&&!y.value&&(y.value=!0,s.menuVisibleOnF...
method beforeBlur (line 26) | beforeBlur(U){var _e,Ne;return((_e=c.value)==null?void 0:_e.isFocusInsid...
method afterBlur (line 26) | afterBlur(){var U;y.value=!1,s.menuVisibleOnFocus=!1,e.validateEvent&&((...
method get (line 26) | get(){return y.value&&(e.loading||!q.value||e.remote&&!!r.empty)&&(!E.va...
method set (line 26) | set(U){y.value=U}
method isDisabled (line 26) | get isDisabled(){return Cn.isDisabled}
method setup (line 26) | setup(e,{slots:t}){const n=ye($i);let r=[];return()=>{var o,a;const l=(o...
method setup (line 26) | setup(e){const t=Te("select"),n=I(),r=qe(),o=I([]);at(B6,Ct({...Br(e)}))...
function bH (line 26) | function bH(e,t,n,r,o,a){return Tt((g(),x("ul",{ref:"groupRef",class:V(e...
function Vf (line 26) | function Vf(e,t){let n=[];return de(t.arg)?n=t.arg:Kn(t.arg)&&n.push(t.a...
method beforeMount (line 26) | beforeMount(e,t){Gr.has(e)||Gr.set(e,[]),Gr.get(e).push({documentHandler...
method updated (line 26) | updated(e,t){Gr.has(e)||Gr.set(e,[]);const n=Gr.get(e),r=n.findIndex(a=>...
method unmounted (line 26) | unmounted(e){Gr.delete(e)}
function wH (line 26) | function wH(){const e=Jn(),t=I(0),n=A(()=>({minWidth:`${Math.max(t.value...
method setup (line 26) | setup(e,{emit:t,slots:n}){const r=qe(),o=CH(r.appContext);o.count+=1,r.a...
function TH (line 26) | function TH(e,t,n,r,o,a){const l=je("el-tag"),s=je("el-tooltip"),i=je("e...
method setup (line 26) | setup(e){const t=Vr("card"),n=Te("card");return(r,o)=>{var a;return g(),...
method setup (line 26) | setup(c,{slots:d}){return()=>(s(),d.default?Ae(i,{render:d.default}):null)}
function u (line 26) | function u(h){var p,_,C,S;return[!0,e.trueValue,e.trueLabel].includes(h)...
function c (line 26) | function c(h,p){i(Zn,u(h),p)}
function d (line 26) | function d(h){if(n.value)return;const p=h.target;i(Zn,u(p.checked),h)}
function f (line 26) | async function f(h){n.value||!r.value&&!o.value&&a.value&&(h.composedPat...
method get (line 26) | get(){var s,i;return o.value?(s=r==null?void 0:r.modelValue)==null?void ...
method set (line 26) | set(s){var i,u;o.value&&de(s)?(a.value=((i=r==null?void 0:r.max)==null?v...
function S (line 26) | function S(){var y,b;de(r.value)&&!r.value.includes(d.value)?r.value.pus...
method setup (line 26) | setup(e){const t=e,n=zr(),{inputId:r,isLabeledByFormItem:o,isChecked:a,i...
method setup (line 26) | setup(e){const t=e,n=zr(),{isFocused:r,isChecked:o,isDisabled:a,checkbox...
method setup (line 26) | setup(e,{emit:t}){const n=e,r=t,o=Te("checkbox"),a=Ba(),{formItem:l}=Ha(...
method setup (line 26) | setup(e){const t=e,{gutter:n}=ye(j6,{gutter:A(()=>0)}),r=Te("col"),o=A((...
method setup (line 26) | setup(e){const t=Te("collapse-transition"),n=o=>{o.style.maxHeight="",o....
method setup (line 26) | setup(e,{slots:t,emit:n}){const r=Te(JH),o=i=>{n("click",i)},{onClick:a,...
method setup (line 26) | setup(e,{expose:t}){const{t:n}=Kt(),{Close:r}=qR,o=e,{dialogRef:a,header...
function P (line 26) | function P(){o("opened")}
function H (line 26) | function H(){o("closed"),o(Vt,!1),e.destroyOnClose&&(d.value=!1),v.value...
function D (line 26) | function D(){v.value=!0,o("close")}
function z (line 26) | function z(){p==null||p(),h==null||h(),e.openDelay&&e.openDelay>0?{stop:...
function k (line 26) | function k(){h==null||h(),p==null||p(),e.closeDelay&&e.closeDelay>0?{sto...
function Z (line 26) | function Z(){function j(oe){oe||(c.value=!0,u.value=!1)}e.beforeClose?e....
function ve (line 26) | function ve(){e.closeOnClickModal&&Z()}
function ie (line 26) | function ie(){st&&(u.value=!0)}
function N (line 26) | function N(){u.value=!1}
function $ (line 26) | function $(){o("openAutoFocus")}
function G (line 26) | function G(){o("closeAutoFocus")}
function K (line 26) | function K(j){var oe;((oe=j.detail)==null?void 0:oe.focusReason)==="poin...
function ee (line 26) | function ee(){e.closeOnPressEscape&&Z()}
method setup (line 26) | setup(e,{expose:t}){const n=e,r=zr();So({scope:"el-dialog",from:"the tit...
function lz (line 26) | function lz(e,t,n,r,o,a){return se(e.$slots,"default")}
function uz (line 26) | function uz(e,t,n,r,o,a){return se(e.$slots,"default")}
method setup (line 26) | setup(){const s=I(),i=new Map;at(r,{itemMap:i,getItems:()=>{const c=w(s)...
method setup (line 26) | setup(s,{attrs:i}){const u=I(),c=ye(r,void 0);at(o,{collectionItemRef:u}...
method setup (line 26) | setup(e,{emit:t}){var n;const r=I((n=e.currentTabId||e.defaultCurrentTab...
function Cz (line 26) | function Cz(e,t,n,r,o,a){return se(e.$slots,"default")}
function Ez (line 26) | function Ez(e,t,n,r,o,a){const l=je("el-roving-focus-group-impl"),s=je("...
method setup (line 26) | setup(e,{emit:t}){const n=qe(),r=Te("dropdown"),{t:o}=Kt(),a=I(),l=I(),s...
function Rz (line 26) | function Rz(e,t,n,r,o,a){var l;const s=je("el-roving-focus-group"),i=je(...
method setup (line 26) | setup(e,{emit:t}){const{currentTabbedId:n,onItemFocus:r,onItemShiftTab:o...
function Bz (line 26) | function Bz(e,t,n,r,o,a){const l=je("el-roving-focus-collection-item");r...
method setup (line 26) | setup(e,{emit:t}){const n=Te("dropdown"),{role:r}=ye(Vi,void 0),{collect...
function Nz (line 26) | function Nz(e,t,n,r,o,a){const l=je("el-icon");return g(),x(Je,null,[e.d...
method setup (line 26) | setup(e,{emit:t,attrs:n}){const{elDropdown:r}=eh(),o=qe(),{onItemEnter:a...
function Fz (line 26) | function Fz(e,t,n,r,o,a){const l=je("el-dropdown-item-impl"),s=je("el-ro...
method setup (line 26) | setup(e){const t=Te("dropdown"),{_elDropdownSize:n}=eh(),r=n.value,{cont...
function Wz (line 26) | function Wz(e,t,n,r,o,a){return g(),x("ul",{ref:e.dropdownListWrapperRef...
method setup (line 26) | setup(e){const t=Te("empty"),n=Fn();return(r,o)=>(g(),x("svg",Uz,[m("def...
method setup (line 26) | setup(e){const t=e,{t:n}=Kt(),r=Te("empty"),o=A(()=>t.description||n("el...
function Ck (line 26) | function Ck(){const e=I([]),t=A(()=>{if(!e.value.length)return"0";const ...
method setup (line 26) | setup(e,{expose:t,emit:n}){const r=e,o=n,a=I(),l=Ct([]),s=new Map,i=_r()...
function bo (line 26) | function bo(){return bo=Object.assign?Object.assign.bind():function(e){f...
function Tk (line 26) | function Tk(e,t){e.prototype=Object.create(t.prototype),e.prototype.cons...
function _c (line 26) | function _c(e){return _c=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function Nl (line 26) | function Nl(e,t){return Nl=Object.setPrototypeOf?Object.setPrototypeOf.b...
function Ak (line 26) | function Ak(){if(typeof Reflect>"u"||!Reflect.construct||Reflect.constru...
function Is (line 26) | function Is(e,t,n){return Ak()?Is=Reflect.construct.bind():Is=function(o...
function Ok (line 26) | function Ok(e){return Function.toString.call(e).indexOf("[native code]")...
function bc (line 26) | function bc(e){var t=typeof Map=="function"?new Map:void 0;return bc=fun...
function wc (line 26) | function wc(e){if(!e||!e.length)return null;var t={};return e.forEach(fu...
function Mn (line 26) | function Mn(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r<t...
function Pk (line 26) | function Pk(e){return e==="string"||e==="url"||e==="hex"||e==="email"||e...
function $t (line 26) | function $t(e,t){return!!(e==null||t==="array"&&Array.isArray(e)&&!e.len...
function Ik (line 26) | function Ik(e,t,n){var r=[],o=0,a=e.length;function l(s){r.push.apply(r,...
function Qf (line 26) | function Qf(e,t,n){var r=0,o=e.length;function a(l){if(l&&l.length){n(l)...
function Bk (line 26) | function Bk(e){var t=[];return Object.keys(e).forEach(function(n){t.push...
function t (line 26) | function t(n,r){var o;return o=e.call(this,"Async Validation Error")||th...
function Hk (line 26) | function Hk(e,t,n,r,o){if(t.first){var a=new Promise(function(f,v){var h...
function zk (line 26) | function zk(e){return!!(e&&e.message!==void 0)}
function kk (line 26) | function kk(e,t){for(var n=e,r=0;r<t.length;r++){if(n==null)return n;n=n...
function t1 (line 26) | function t1(e,t){return function(n){var r;return e.fullFields?r=kk(t,e.f...
function n1 (line 26) | function n1(e,t){if(t){for(var n in t)if(t.hasOwnProperty(n)){var r=t[n]...
function yc (line 37) | function yc(){return{default:"Validation error on field %s",required:"%s...
function e (line 37) | function e(n){this.rules=null,this._messages=Cc,this.define(n)}
function c (line 37) | function c(p){var _=[],C={};function S(b){if(Array.isArray(b)){var E;_=(...
function y (line 37) | function y(O,M){return bo({},M,{fullField:C.fullField+"."+O,fullFields:C...
function b (line 37) | function b(O){O===void 0&&(O=[]);var M=Array.isArray(O)?O:[O];!i.suppres...
method setup (line 37) | setup(e,{slots:t}){const n=ye($o,void 0),r=ye(Po);r||Nn(o1,"usage: <el-f...
method setup (line 37) | setup(e,{expose:t}){const n=e,r=zr(),o=ye($o,void 0),a=ye(Po,void 0),l=_...
method setup (line 37) | setup(e,{emit:t}){const n=e,r=t,o=Vr("link");So({scope:"el-link",from:"T...
method constructor (line 37) | constructor(t,n){this.parent=t,this.domNode=n,i1(this,"subMenuItems"),i1...
method init (line 37) | init(){this.subMenuItems=this.domNode.querySelectorAll("li"),this.addLis...
method gotoSubIndex (line 37) | gotoSubIndex(t){t===this.subMenuItems.length?t=0:t<0&&(t=this.subMenuIte...
method addListeners (line 37) | addListeners(){const t=this.parent.domNode;Array.prototype.forEach.call(...
class gN (line 37) | class gN{constructor(t,n){this.domNode=t,mN(this,"submenu",null),this.su...
method constructor (line 37) | constructor(t,n){this.domNode=t,mN(this,"submenu",null),this.submenu=n...
method init (line 37) | init(t){this.domNode.setAttribute("tabindex","0");const n=this.domNode...
method addListeners (line 37) | addListeners(){this.domNode.addEventListener("keydown",t=>{const n=Ut(...
method constructor (line 37) | constructor(t,n){this.domNode=t,this.init(n)}
method init (line 37) | init(t){const n=this.domNode.childNodes;Array.from(n).forEach(r=>{r.node...
method setup (line 37) | setup(e){const t=Te("menu"),n={onBeforeEnter:r=>r.style.opacity="0.2",on...
function ah (line 37) | function ah(e,t){const n=A(()=>{let o=e.parent;const a=[t.value];for(;o....
function wN (line 37) | function wN(e){return A(()=>{const n=e.backgroundColor;return n?new P6(n...
method setup (line 37) | setup(e,{slots:t,expose:n}){const r=qe(),{indexPath:o,parentMenu:a}=ah(r...
method setup (line 37) | setup(e,{emit:t,slots:n,expose:r}){const o=qe(),a=o.appContext.config.gl...
method setup (line 37) | setup(e,{expose:t,emit:n}){const r=e,o=n;Ar(r.index)&&void 0;const a=qe(...
method setup (line 37) | setup(e){const t=Te("menu-item-group");return(n,r)=>(g(),x("li",{class:V...
method setup (line 37) | setup(e){const t=e,{t:n}=Kt(),r=A(()=>t.disabled||t.currentPage<=1);retu...
method setup (line 37) | setup(e){const t=e,{t:n}=Kt(),r=A(()=>t.disabled||t.currentPage===t.page...
method setup (line 37) | setup(e,{emit:t}){const n=e,r=t,{t:o}=Kt(),a=Te("pagination"),l=a2(),s=I...
method setup (line 37) | setup(e){const{t}=Kt(),n=Te("pagination"),{pageCount:r,disabled:o,curren...
method setup (line 37) | setup(e){const{t}=Kt(),n=Te("pagination"),{disabled:r}=a2();return(o,a)=...
method setup (line 37) | setup(e,{emit:t}){const n=e,r=t,o=Te("pager"),a=Te("icon"),{t:l}=Kt(),s=...
method setup (line 37) | setup(e,{emit:t,slots:n}){const{t:r}=Kt(),o=Te("pagination"),a=qe().vnod...
method setup (line 37) | setup(e){const t={success:"#13ce66",exception:"#ff4949",warning:"#e6a23c...
method setup (line 42) | setup(e){const t=e,n=Te("row"),r=A(()=>t.gutter);at(j6,{gutter:r});const...
function g$ (line 42) | function g$(e,t){const n={};let r;for(r in e)n[r]=e[r];for(r in t)if(Ge(...
function l2 (line 42) | function l2(e){return e===""||vt(e)||(e=Number.parseInt(e,10),Number.isN...
function dh (line 42) | function dh(e){return e===""||vt(e)||(e=l2(e),Number.isNaN(e)&&(e=80)),e}
function _$ (line 42) | function _$(e){return Ye(e)?e:Le(e)?/^\d+(?:px)?$/.test(e)?Number.parseI...
function b$ (line 42) | function b$(...e){return e.length===0?t=>t:e.length===1?e[0]:e.reduce((t...
function li (line 42) | function li(e,t,n,r,o,a,l){let s=a??0,i=!1;const c=(()=>{if(!l)return e....
function w$ (line 42) | function w$(e,t,n="children",r="hasChildren",o=!1){const a=s=>!(de(s)&&s...
function C$ (line 42) | function C$(e,t,n,r,o,a){var l;const s=y$(e,t,n,r),i={...s,slotContent:v...
function fh (line 42) | function fh(e){return e.children?S3(e.children,fh):[e]}
function g1 (line 42) | function g1(e,t){return e+t.colSpan}
function _1 (line 42) | function _1(e,t){return e+(Ri(t.realWidth)||Number.isNaN(t.realWidth)?Nu...
function hh (line 42) | function hh(e){return e.some(t=>Ft(t)?!(t.type===Ot||t.type===Je&&!hh(t....
function x$ (line 42) | function x$(e){const t=qe(),n=I(!1),r=I([]),o=(u,c)=>{var d;const f=t.st...
function S$ (line 42) | function S$(e){const t=qe(),n=I(null),r=I(null),o=u=>{t.store.assertRowK...
function E$ (line 42) | function E$(e){const t=I([]),n=I({}),r=I(16),o=I(!1),a=I({}),l=I("hasChi...
function T$ (line 42) | function T$(){var e;const t=qe(),{size:n}=Br((e=t.proxy)==null?void 0:e....
function xc (line 42) | function xc(e,t){return e.map(n=>{var r;return n.id===t.id?t:((r=n.child...
function Sc (line 42) | function Sc(e){e.forEach(t=>{var n,r;t.no=(n=t.getColumnIndex)==null?voi...
function A$ (line 42) | function A$(){const e=qe(),t=T$(),n=Te("table"),{t:r}=Kt();return{ns:n,t...
function O$ (line 42) | function O$(e,t){if(!e)throw new Error("Table is required.");const n=A$(...
function L$ (line 42) | function L$(e,t){Object.keys(u2).forEach(n=>{ge(()=>mh(t,n),r=>{vh(r,n,e...
function vh (line 42) | function vh(e,t,n){let r=e,o=u2[t];Ie(o)&&(r=r||o.default,o=o.key),n.sta...
function mh (line 42) | function mh(e,t){if(t.includes(".")){const n=t.split(".");let r=e;return...
class I$ (line 42) | class I${constructor(t){Dt(this,"observers"),Dt(this,"table"),Dt(this,"s...
method constructor (line 42) | constructor(t){Dt(this,"observers"),Dt(this,"table"),Dt(this,"store"),...
method updateScrollY (line 42) | updateScrollY(){const t=this.height.value;if(Ri(t))return!1;const n=th...
method setHeight (line 42) | setHeight(t,n="height"){if(!st)return;const r=this.table.vnode.el;if(t...
method setMaxHeight (line 42) | setMaxHeight(t){this.setHeight(t,"max-height")}
method getFlattenColumns (line 42) | getFlattenColumns(){const t=[];return this.table.store.states.columns....
method updateElsHeight (line 42) | updateElsHeight(){this.updateScrollY(),this.notifyObservers("scrollabl...
method headerDisplayNone (line 42) | headerDisplayNone(t){if(!t)return!0;let n=t;for(;n.tagName!=="DIV";){i...
method updateColumnsWidth (line 42) | updateColumnsWidth(){var t;if(!st)return;const n=this.fit,r=(t=this.ta...
method addObserver (line 42) | addObserver(t){this.observers.push(t)}
method removeObserver (line 42) | removeObserver(t){const n=this.observers.indexOf(t);n!==-1&&this.obser...
method notifyObservers (line 42) | notifyObservers(t){this.observers.forEach(r=>{var o,a;switch(t){case"c...
method setup (line 42) | setup(e){const t=qe(),{t:n}=Kt(),r=Te("table-filter"),o=t==null?void 0:t...
function $$ (line 42) | function $$(e,t,n,r,o,a){const l=je("el-checkbox"),s=je("el-checkbox-gro...
function c2 (line 42) | function c2(e){const t=qe();wi(()=>{n.value.addObserver(t)}),rt(()=>{r(n...
function F$ (line 42) | function F$(e,t){const n=qe(),r=ye(ar),o=p=>{p.stopPropagation()},a=(p,_...
function D$ (line 42) | function D$(e){const t=ye(ar),n=Te("table");return{getHeaderRowStyle:s=>...
function j$ (line 42) | function j$(e){const t=ye(ar),n=A(()=>_h(e.store.states.originColumns.va...
method setup (line 42) | setup(e,{emit:t}){const n=qe(),r=ye(ar),o=Te("table"),a=I({}),{onColumns...
method render (line 42) | render(){const{ns:e,t,isGroup:n,columnRows:r,getHeaderCellStyle:o,getHea...
function Mu (line 42) | function Mu(e,t,n=.03){return e-t>n}
function q$ (line 42) | function q$(e){const t=ye(ar),n=I(""),r=I(Ae("div")),o=(h,p,_)=>{var C,S...
function U$ (line 42) | function U$(e){const t=ye(ar),n=Te("table");return{getRowStyle:(u,c)=>{c...
method setup (line 42) | setup(e){return(t,n)=>(g(),x("td",{colspan:e.colspan,rowspan:e.rowspan},...
function Y$ (line 42) | function Y$(e){const t=ye(ar),n=Te("table"),{handleDoubleClick:r,handleC...
method setup (line 42) | setup(e){var t;const n=qe(),r=ye(ar),o=Te("table"),{wrappedRowRender:a,t...
method render (line 42) | render(){const{wrappedRowRender:e,store:t}=this,n=(t==null?void 0:t.stat...
function Z$ (line 42) | function Z$(){const e=ye(ar),t=e==null?void 0:e.store,n=A(()=>{var s;ret...
function Q$ (line 42) | function Q$(e){const{columns:t}=Z$(),n=Te("table");return{getCellClasses...
method setup (line 42) | setup(e){const t=ye(ar),n=Te("table"),{getCellClasses:r,getCellStyles:o,...
method render (line 42) | render(){const{columns:e,getCellStyles:t,getCellClasses:n,summaryMethod:...
function tV (line 42) | function tV(e){return{setCurrentRow:d=>{e.commit("setCurrentRow",d)},get...
function nV (line 42) | function nV(e,t,n,r){const o=I(!1),a=I(null),l=I(!1),s=N=>{l.value=N},i=...
function rV (line 42) | function rV(e){let t;const n=()=>{const o=e.vnode.el.querySelector(".hid...
function bh (line 42) | function bh(e){const t=e.tableLayout==="auto";let n=e.columns||[];t&&n.e...
function gn (line 42) | function gn(){if(!b1){b1=!0;var e=navigator.userAgent,t=/(?:MSIE.(\d+\.\...
function uV (line 42) | function uV(e,t){if(!Sh.canUseDOM||t&&!("addEventListener"in document))r...
function Mh (line 42) | function Mh(e){var t=0,n=0,r=0,o=0;return"detail"in e&&(n=e.detail),"whe...
method beforeMount (line 55) | beforeMount(e,t){x1(e,t.value)}
method unmounted (line 55) | unmounted(e){Th(e)}
method updated (line 55) | updated(e,t){t.value!==t.oldValue&&x1(e,t.value)}
method setup (line 55) | setup(e){const{t}=Kt(),n=Te("table"),r=Vr("table"),o=qe();at(ar,o);const...
function gV (line 55) | function gV(e,t,n,r,o,a){const l=je("hColgroup"),s=je("table-header"),i=...
method renderHeader (line 55) | renderHeader({store:e}){var t;function n(){return e.states.data.value&&e...
method renderCell (line 55) | renderCell({row:e,column:t,store:n,$index:r}){return Ae(kl,{disabled:t.s...
method renderHeader (line 55) | renderHeader({column:e}){return e.label||"#"}
method renderCell (line 55) | renderCell({column:e,$index:t}){let n=t+1;const r=e.index;return Ye(r)?n...
method renderHeader (line 55) | renderHeader({column:e}){return e.label||""}
method renderCell (line 55) | renderCell({column:e,row:t,store:n,expanded:r,$index:o}){var a,l,s;const...
function xV (line 55) | function xV({row:e,column:t,$index:n}){var r;const o=t.property,a=o&&no(...
function SV (line 55) | function SV({row:e,treeNode:t,store:n},r=!1){const{ns:o}=n;if(!t)return ...
function S1 (line 55) | function S1(e,t){return e.reduce((n,r)=>(n[r]=r,n),t)}
function EV (line 55) | function EV(e,t){const n=qe();return{registerComplexWatchers:()=>{const ...
function MV (line 55) | function MV(e,t,n){const r=qe(),o=I(""),a=I(!1),l=I(),s=I(),i=Te("table"...
method setup (line 55) | setup(e,{slots:t}){const n=qe(),r=Vr("table"),o=I({}),a=A(()=>{let b=n.p...
method render (line 55) | render(){var e,t,n;try{const r=(t=(e=this.$slots).default)==null?void 0:...
method setup (line 55) | setup(e,{expose:t}){const n=e,r=ye(Fi);r||Nn(T1,"<el-tabs><el-tab-bar />...
method setup (line 55) | setup(e,{expose:t,emit:n}){const r=ye(Fi);r||Nn(A1,"<el-tabs><tab-nav />...
method setup (line 55) | setup(e,{emit:t,slots:n,expose:r}){var o;const a=Te("tabs"),l=A(()=>["le...
method setup (line 55) | setup(e){const t=e,n=qe(),r=zr(),o=ye(Fi);o||Nn(O1,"usage: <el-tabs><el-...
method setup (line 55) | setup(e,{slots:t}){const n=Te("timeline");at(Lh,{props:e,slots:t});const...
method setup (line 55) | setup(e){const t=e,{props:n}=ye(Lh),r=Te("timeline-item"),o=A(()=>[r.e("...
function DV (line 55) | function DV(e,t){let n;const r=I(!1),o=Ct({...e,originalPosition:"",orig...
method mounted (line 55) | mounted(e,t){t.value&&H1(e,t)}
method updated (line 55) | updated(e,t){const n=e[Xa];if(!t.value){n==null||n.instance.close(),e[Xa...
method unmounted (line 55) | unmounted(e){var t;(t=e[Xa])==null||t.instance.close(),e[Xa]=null}
method install (line 55) | install(e){sa._context=e._context,gl._context=e._context,e.directive("lo...
method setup (line 55) | setup(e,{expose:t,emit:n}){const{Close:r}=H0,o=e,a=n,l=I(!1),{ns:s,zInde...
function sF (line 55) | function sF(e){for(const t in Qn)if(Ge(Qn,t)){const n=[...Qn[t]];for(con...
function iF (line 55) | function iF(e){if(!Qn[e])return;[...Qn[e]].forEach(n=>n.handler.close())}
method beforeMount (line 55) | beforeMount(e){e[Ic]=uf(e),wo.push(e),wo.length<=1&&document.addEventLis...
method updated (line 55) | updated(e){De(()=>{e[Ic]=uf(e)})}
method unmounted (line 55) | unmounted(){wo.shift(),wo.length===0&&document.removeEventListener("keyd...
method setup (line 55) | setup(e,{emit:t}){const{locale:n,zIndex:r,ns:o,size:a}=A0("message-box",...
function hF (line 55) | function hF(e,t,n,r,o,a){const l=je("el-icon"),s=je("el-input"),i=je("el...
function ka (line 55) | function ka(e,t=null){if(!st)return Promise.reject();let n;return Le(e)|...
function CF (line 55) | function CF(e){return(t,n,r,o)=>{let a="";return Ie(n)?(r=n,a=""):vt(n)?...
function Ss (line 55) | function Ss(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];fo...
function Bc (line 55) | function Bc(e,t){function n(o,a,l){if(!(typeof document>"u")){l=Ss({},t,...
function p2 (line 55) | function p2(){return d2.get(f2)}
function Pj (line 55) | function Pj(e){return d2.set(f2,e)}
function Hh (line 55) | function Hh(){return d2.remove(f2)}
method setup (line 55) | setup(){const e=h5(),t=jp(),n=I(p2());return{token:n,username:I("test"),...
function HF (line 55) | function HF(e,t,n,r,o,a){const l=je("router-link"),s=je("ArrowDown"),i=j...
function kh (line 55) | function kh(e,t){return function(){return e.apply(t,arguments)}}
function NF (line 55) | function NF(e){return e!==null&&!Fl(e)&&e.constructor!==null&&!Fl(e.cons...
function $F (line 55) | function $F(e){let t;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?t...
function ts (line 55) | function ts(e,t,{allOwnKeys:n=!1}={}){if(e===null||typeof e>"u")return;l...
function Vh (line 55) | function Vh(e,t){t=t.toLowerCase();const n=Object.keys(e);let r=n.length...
function Hc (line 55) | function Hc(){const{caseless:e}=Dh(this)&&this||{},t={},n=(r,o)=>{const ...
function pD (line 55) | function pD(e){return!!(e&&Hn(e.append)&&e[Symbol.toStringTag]==="FormDa...
function ot (line 55) | function ot(e,t,n,r,o){Error.call(this),Error.captureStackTrace?Error.ca...
function zc (line 55) | function zc(e){return ne.isPlainObject(e)||ne.isArray(e)}
function Kh (line 55) | function Kh(e){return ne.endsWith(e,"[]")?e.slice(0,-2):e}
function $1 (line 55) | function $1(e,t,n){return e?e.concat(t).map(function(o,a){return o=Kh(o)...
function _D (line 55) | function _D(e){return ne.isArray(e)&&!e.some(zc)}
function qi (line 55) | function qi(e,t,n){if(!ne.isObject(e))throw new TypeError("target must b...
function V1 (line 55) | function V1(e){const t={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E...
function v2 (line 55) | function v2(e,t){this._pairs=[],e&&qi(e,this,t)}
function wD (line 55) | function wD(e){return encodeURIComponent(e).replace(/%3A/gi,":").replace...
function Yh (line 55) | function Yh(e,t,n){if(!t)return e;const r=n&&n.encode||wD,o=n&&n.seriali...
class F1 (line 55) | class F1{constructor(){this.handlers=[]}use(t,n,r){return this.handlers....
method constructor (line 55) | constructor(){this.handlers=[]}
method use (line 55) | use(t,n,r){return this.handlers.push({fulfilled:t,rejected:n,synchrono...
method eject (line 55) | eject(t){this.handlers[t]&&(this.handlers[t]=null)}
method clear (line 55) | clear(){this.handlers&&(this.handlers=[])}
method forEach (line 55) | forEach(t){ne.forEach(this.handlers,function(r){r!==null&&t(r)})}
function AD (line 55) | function AD(e,t){return qi(e,new pr.classes.URLSearchParams,Object.assig...
function OD (line 55) | function OD(e){return ne.matchAll(/\w+|\[(\w*)]/g,e).map(t=>t[0]==="[]"?...
function LD (line 55) | function LD(e){const t={},n=Object.keys(e);let r;const o=n.length;let a;...
function Zh (line 55) | function Zh(e){function t(n,r,o,a){let l=n[a++];if(l==="__proto__")retur...
function RD (line 55) | function RD(e,t,n){if(ne.isString(e))try{return(t||JSON.parse)(e),ne.tri...
function Ka (line 56) | function Ka(e){return e&&String(e).trim().toLowerCase()}
function Vs (line 56) | function Vs(e){return e===!1||e==null?e:ne.isArray(e)?e.map(Vs):String(e)}
function BD (line 56) | function BD(e){const t=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;...
function Ou (line 56) | function Ou(e,t,n,r,o){if(ne.isFunction(r))return r.call(this,t,n);if(o&...
function zD (line 56) | function zD(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(...
function kD (line 56) | function kD(e,t){const n=ne.toCamelCase(" "+t);["get","set","has"].forEa...
method constructor (line 56) | constructor(t){t&&this.set(t)}
method set (line 56) | set(t,n,r){const o=this;function a(s,i,u){const c=Ka(i);if(!c)throw new ...
method get (line 56) | get(t,n){if(t=Ka(t),t){const r=ne.findKey(this,t);if(r){const o=this[r];...
method has (line 56) | has(t,n){if(t=Ka(t),t){const r=ne.findKey(this,t);return!!(r&&this[r]!==...
method delete (line 56) | delete(t,n){const r=this;let o=!1;function a(l){if(l=Ka(l),l){const s=ne...
method clear (line 56) | clear(t){const n=Object.keys(this);let r=n.length,o=!1;for(;r--;){const ...
method normalize (line 56) | normalize(t){const n=this,r={};return ne.forEach(this,(o,a)=>{const l=ne...
method concat (line 56) | concat(...t){return this.constructor.concat(this,...t)}
method toJSON (line 56) | toJSON(t){const n=Object.create(null);return ne.forEach(this,(r,o)=>{r!=...
method [Symbol.iterator] (line 56) | [Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator...
method toString (line 56) | toString(){return Object.entries(this.toJSON()).map(([t,n])=>t+": "+n).j...
method [Symbol.toStringTag] (line 57) | get[Symbol.toStringTag](){return"AxiosHeaders"}
method from (line 57) | static from(t){return t instanceof this?t:new this(t)}
method concat (line 57) | static concat(t,...n){const r=new this(t);return n.forEach(o=>r.set(o)),r}
method accessor (line 57) | static accessor(t){const r=(this[D1]=this[D1]={accessors:{}}).accessors,...
method set (line 57) | set(r){this[n]=r}
function Lu (line 57) | function Lu(e,t){const n=this||ns,r=t||n,o=zn.from(r.headers);let a=r.da...
function Qh (line 57) | function Qh(e){return!!(e&&e.__CANCEL__)}
function rs (line 57) | function rs(e,t,n){ot.call(this,e??"canceled",ot.ERR_CANCELED,t,n),this....
function ND (line 57) | function ND(e,t,n){const r=n.config.validateStatus;!n.status||!r||r(n.st...
method write (line 57) | write(e,t,n,r,o,a){const l=[e+"="+encodeURIComponent(t)];ne.isNumber(n)&...
method read (line 57) | read(e){const t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]...
method remove (line 57) | remove(e){this.write(e,"",Date.now()-864e5)}
method write (line 57) | write(){}
method read (line 57) | read(){return null}
method remove (line 57) | remove(){}
function VD (line 57) | function VD(e){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(e)}
function FD (line 57) | function FD(e,t){return t?e.replace(/\/?\/$/,"")+"/"+t.replace(/^\/+/,""...
function ev (line 57) | function ev(e,t){return e&&!VD(t)?FD(e,t):t}
function o (line 57) | function o(a){let l=a;return t&&(n.setAttribute("href",l),l=n.href),n.se...
function jD (line 57) | function jD(e){const t=/^([-+\w]{1,25})(:?\/\/|:)/.exec(e);return t&&t[1...
function WD (line 57) | function WD(e,t){e=e||10;const n=new Array(e),r=new Array(e);let o=0,a=0...
function j1 (line 57) | function j1(e,t){let n=0;const r=WD(50,250);return o=>{const a=o.loaded,...
function u (line 57) | function u(){e.cancelToken&&e.cancelToken.unsubscribe(i),e.signal&&e.sig...
function v (line 57) | function v(){if(!d)return;const p=zn.from("getAllResponseHeaders"in d&&d...
function Ru (line 59) | function Ru(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.sign...
function q1 (line 59) | function q1(e){return Ru(e),e.headers=zn.from(e.headers),e.data=Lu.call(...
function Ca (line 59) | function Ca(e,t){t=t||{};const n={};function r(u,c,d){return ne.isPlainO...
function o (line 59) | function o(a,l){return"[Axios v"+nv+"] Transitional option '"+a+"'"+l+(r...
function GD (line 59) | function GD(e,t,n){if(typeof e!="object")throw new ot("options must be a...
method constructor (line 59) | constructor(t){this.defaults=t,this.interceptors={request:new F1,respons...
method request (line 59) | async request(t,n){try{return await this._request(t,n)}catch(r){if(r ins...
method _request (line 60) | _request(t,n){typeof t=="string"?(n=n||{},n.url=t):n=t||{},n=Ca(this.def...
method getUri (line 60) | getUri(t){t=Ca(this.defaults,t);const n=ev(t.baseURL,t.url);return Yh(n,...
function n (line 60) | function n(r){return function(a,l,s){return this.request(Ca(s||{},{metho...
method constructor (line 60) | constructor(t){if(typeof t!="function")throw new TypeError("executor mus...
method throwIfRequested (line 60) | throwIfRequested(){if(this.reason)throw this.reason}
method subscribe (line 60) | subscribe(t){if(this.reason){t(this.reason);return}this._listeners?this....
method unsubscribe (line 60) | unsubscribe(t){if(!this._listeners)return;const n=this._listeners.indexO...
method source (line 60) | static source(){let t;return{token:new rv(function(o){t=o}),cancel:t}}
function JD (line 60) | function JD(e){return function(n){return e.apply(null,n)}}
function XD (line 60) | function XD(e){return ne.isObject(e)&&e.isAxiosError===!0}
function ov (line 60) | function ov(e){const t=new Eo(e),n=kh(Eo.prototype.request,t);return ne....
function a (line 60) | function a(l){const s=new Event("vite:preloadError",{cancelable:!0});if(...
method setup (line 60) | setup(){const e=Ct({isShowNav:!1,isShowSlider:!1}),t=Ct({visible:!1,cont...
function sj (line 60) | function sj(e,t,n,r,o,a){const l=je("el-button"),s=je("HeaderNav"),i=je(...
function Xj (line 64) | function Xj(e){return e===void 0&&(e=null),ye(e!==null?e:av)}
function $a (line 64) | function $a(e,t){Object.keys(e).forEach(function(n){return t(e[n],n)})}
function lv (line 64) | function lv(e){return e!==null&&typeof e=="object"}
function uj (line 64) | function uj(e){return e&&typeof e.then=="function"}
function cj (line 64) | function cj(e,t){return function(){return e(t)}}
function sv (line 64) | function sv(e,t,n){return t.indexOf(e)<0&&(n&&n.prepend?t.unshift(e):t.p...
function iv (line 64) | function iv(e,t){e._actions=Object.create(null),e._mutations=Object.crea...
function g2 (line 64) | function g2(e,t,n){var r=e._state,o=e._scope;e.getters={},e._makeLocalGe...
function Gi (line 64) | function Gi(e,t,n,r,o){var a=!n.length,l=e._modules.getNamespace(n);if(r...
function dj (line 64) | function dj(e,t,n){var r=t==="",o={dispatch:r?e.dispatch:function(a,l,s)...
function uv (line 64) | function uv(e,t){if(!e._makeLocalGettersCache[t]){var n={},r=t.length;Ob...
function fj (line 64) | function fj(e,t,n,r){var o=e._mutations[t]||(e._mutations[t]=[]);o.push(...
function pj (line 64) | function pj(e,t,n,r){var o=e._actions[t]||(e._actions[t]=[]);o.push(func...
function hj (line 64) | function hj(e,t,n,r){e._wrappedGetters[t]||(e._wrappedGetters[t]=functio...
function vj (line 64) | function vj(e){ge(function(){return e._state.data},function(){},{deep:!0...
function _2 (line 64) | function _2(e,t){return t.reduce(function(n,r){return n[r]},e)}
function ii (line 64) | function ii(e,t,n){return lv(e)&&e.type&&(n=t,t=e,e=e.type),{type:e,payl...
function _j (line 64) | function _j(e,t){sg({id:"org.vuejs.vuex",app:e,label:"Vuex",homepage:"ht...
function dv (line 64) | function dv(e){return e&&e!=="root"?e.split("/").slice(-2,-1)[0]:"Root"}
function fv (line 64) | function fv(e,t){return{id:t||"root",label:dv(t),tags:e.namespaced?[cv]:...
function pv (line 64) | function pv(e,t,n,r){r.includes(n)&&e.push({id:r||"root",label:r.endsWit...
function yj (line 64) | function yj(e,t,n){t=n==="root"?t:t[n];var r=Object.keys(t),o={state:Obj...
function Cj (line 64) | function Cj(e){var t={};return Object.keys(e).forEach(function(n){var r=...
function xj (line 64) | function xj(e,t){var n=t.split("/").filter(function(r){return r});return...
function Vc (line 64) | function Vc(e){try{return e()}catch(t){return t}}
function vv (line 64) | function vv(e,t,n){if(t.update(n),n.modules)for(var r in n.modules){if(!...
function Sj (line 64) | function Sj(e){return new yn(e)}
function Ej (line 64) | function Ej(e){return Mj(e)?Array.isArray(e)?e.map(function(t){return{ke...
function Mj (line 64) | function Mj(e){return Array.isArray(e)||lv(e)}
function Tj (line 64) | function Tj(e){return function(t,n){return typeof t!="string"?(n=t,t="")...
function Aj (line 64) | function Aj(e,t,n){var r=e._modulesNamespaceMap[n];return r}
method delTagsItem (line 64) | delTagsItem(e,t){e.tagsList.splice(t.index,1)}
method setTagsItem (line 64) | setTagsItem(e,t){e.tagsList.push(t)}
method clearTags (line 64) | clearTags(e){e.tagsList=[]}
method closeTagsOther (line 64) | closeTagsOther(e,t){e.tagsList=t}
method closeCurrentTag (line 64) | closeCurrentTag(e,t){for(let n=0,r=e.tagsList.length;n<r;n++)if(e.tagsLi...
method handleCollapse (line 64) | handleCollapse(e,t){e.collapse=t}
method set_user (line 64) | set_user(e,t){e.user.username=t.username,e.user.id=t.id}
method getInfo (line 64) | getInfo({commit:e,state:t}){return new Promise((n,r)=>{Ki.get("/api/info...
function o (line 65) | function o(h,p,_){return h<p?p:h>_?_:h}
function a (line 65) | function a(h){return(-1+h)*100}
function l (line 65) | function l(h,p,_){var C;return r.positionUsing==="translate3d"?C={transf...
function p (line 65) | function p(){var _=h.shift();_&&_(p)}
function _ (line 65) | function _(b){return b.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,fun...
function C (line 65) | function C(b){var E=document.body.style;if(b in E)return b;for(var O=h.l...
function S (line 65) | function S(b){return b=_(b),p[b]||(p[b]=C(b))}
function y (line 65) | function y(b,E,O){E=S(E),b.style[E]=O}
function u (line 65) | function u(h,p){var _=typeof h=="string"?h:f(h);return _.indexOf(" "+p+"...
function c (line 65) | function c(h,p){var _=f(h),C=_+p;u(_,p)||(h.className=C.substring(1))}
function d (line 65) | function d(h,p){var _=f(h),C;u(h,p)&&(C=_.replace(" "+p+" "," "),h.class...
function f (line 65) | function f(h){return(" "+(h.className||"")+" ").replace(/\s+/gi," ")}
function v (line 65) | function v(h){h&&h.parentNode&&h.parentNode.removeChild(h)}
FILE: install/frontend/dist/assets/index-DAXaKKX5.js
method created (line 1) | created(){this.fetchList()}
method setup (line 1) | setup(){return{list:_([])}}
method fetchList (line 1) | fetchList(){m.get("/api/notice").then(t=>{this.list=t.data})}
function y (line 1) | function y(t,B,k,o,C,D){const n=a("el-card"),r=a("el-timeline-item"),p=a...
FILE: install/frontend/dist/assets/index-DTf6UOgP.js
method setup (line 1) | setup(){const e=U().getters.user;return{form:v({username:e.username}),ru...
method submit (line 1) | submit(){this.$refs.form_account.validate(t=>{if(t){let e={username:this...
function I (line 1) | function I(t,e,m,r,i,u){const d=a("el-input"),l=a("el-form-item"),c=a("e...
method setup (line 1) | setup(){U().getters.user;const e=v({old_password:null,confirm_password:n...
method submit (line 1) | submit(){this.$refs.form_rest.validate(t=>{if(t){let e={password:this.fo...
function $ (line 1) | function $(t,e,m,r,i,u){const d=a("el-input"),l=a("el-form-item"),c=a("e...
method setup (line 1) | setup(){return{activeTab:v("account")}}
method created (line 1) | created(){this.getUser()}
method getUser (line 1) | getUser(){}
function j (line 1) | function j(t,e,m,r,i,u){const d=a("el-avatar"),l=a("el-card"),c=a("el-co...
FILE: install/frontend/dist/assets/index-D_63FOFH.js
function lt (line 1) | function lt(w){return w instanceof Uint8Array?w:w instanceof ArrayBuffer...
function Ut (line 1) | function Ut(w,a){if(_t&&w.data instanceof Blob)return w.data.arrayBuffer...
function Vt (line 1) | function Vt(){return new TransformStream({transform(w,a){Ut(w,f=>{const ...
function Oe (line 1) | function Oe(w){return w.reduce((a,f)=>a+f.length,0)}
function Me (line 1) | function Me(w,a){if(w[0].length===a)return w.shift();const f=new Uint8Ar...
function Gt (line 1) | function Gt(w,a){Ve||(Ve=new TextDecoder);const f=[];let b=0,T=-1,H=!1;r...
function re (line 1) | function re(w){if(w)return Xt(w)}
function Xt (line 1) | function Xt(w){for(var a in re.prototype)w[a]=re.prototype[a];return w}
function f (line 1) | function f(){this.off(w,f),a.apply(this,arguments)}
function Ct (line 1) | function Ct(w,...a){return a.reduce((f,b)=>(w.hasOwnProperty(b)&&(f[b]=w...
function Ne (line 1) | function Ne(w,a){a.useNativeTimers?(w.setTimeoutFn=Yt.bind(ce),w.clearTi...
function ei (line 1) | function ei(w){return typeof w=="string"?ti(w):Math.ceil((w.byteLength||...
function ti (line 1) | function ti(w){let a=0,f=0;for(let b=0,T=w.length;b<T;b++)a=w.charCodeAt...
function bt (line 1) | function bt(){return Date.now().toString(36).substring(3)+Math.random()....
function ii (line 1) | function ii(w){let a="";for(let f in w)w.hasOwnProperty(f)&&(a.length&&(...
function si (line 1) | function si(w){let a={},f=w.split("&");for(let b=0,T=f.length;b<T;b++){l...
class ri (line 1) | class ri extends Error{constructor(a,f,b){super(a),this.description=f,th...
method constructor (line 1) | constructor(a,f,b){super(a),this.description=f,this.context=b,this.typ...
class st (line 1) | class st extends re{constructor(a){super(),this.writable=!1,Ne(this,a),t...
method constructor (line 1) | constructor(a){super(),this.writable=!1,Ne(this,a),this.opts=a,this.qu...
method onError (line 1) | onError(a,f,b){return super.emitReserved("error",new ri(a,f,b)),this}
method open (line 1) | open(){return this.readyState="opening",this.doOpen(),this}
method close (line 1) | close(){return(this.readyState==="opening"||this.readyState==="open")&...
method send (line 1) | send(a){this.readyState==="open"&&this.write(a)}
method onOpen (line 1) | onOpen(){this.readyState="open",this.writable=!0,super.emitReserved("o...
method onData (line 1) | onData(a){const f=it(a,this.socket.binaryType);this.onPacket(f)}
method onPacket (line 1) | onPacket(a){super.emitReserved("packet",a)}
method onClose (line 1) | onClose(a){this.readyState="closed",super.emitReserved("close",a)}
method pause (line 1) | pause(a){}
method createUri (line 1) | createUri(a,f={}){return a+"://"+this._hostname()+this._port()+this.op...
method _hostname (line 1) | _hostname(){const a=this.opts.hostname;return a.indexOf(":")===-1?a:"[...
method _port (line 1) | _port(){return this.opts.port&&(this.opts.secure&&Number(this.opts.por...
method _query (line 1) | _query(a){const f=ii(a);return f.length?"?"+f:""}
class ni (line 1) | class ni extends st{constructor(){super(...arguments),this._polling=!1}g...
method constructor (line 1) | constructor(){super(...arguments),this._polling=!1}
method name (line 1) | get name(){return"polling"}
method doOpen (line 1) | doOpen(){this._poll()}
method pause (line 1) | pause(a){this.readyState="pausing";const f=()=>{this.readyState="pause...
method _poll (line 1) | _poll(){this._polling=!0,this.doPoll(),this.emitReserved("poll")}
method onData (line 1) | onData(a){const f=b=>{if(this.readyState==="opening"&&b.type==="open"&...
method doClose (line 1) | doClose(){const a=()=>{this.write([{type:"close"}])};this.readyState==...
method write (line 1) | write(a){this.writable=!1,jt(a,f=>{this.doWrite(f,()=>{this.writable=!...
method uri (line 1) | uri(){const a=this.opts.secure?"https":"http",f=this.query||{};return ...
function ai (line 1) | function ai(){}
class hi (line 1) | class hi extends ni{constructor(a){if(super(a),typeof location<"u"){cons...
method constructor (line 1) | constructor(a){if(super(a),typeof location<"u"){const f=location.proto...
method doWrite (line 1) | doWrite(a,f){const b=this.request({method:"POST",data:a});b.on("succes...
method doPoll (line 1) | doPoll(){const a=this.request();a.on("data",this.onData.bind(this)),a....
class fe (line 1) | class fe extends re{constructor(a,f,b){super(),this.createRequest=a,Ne(t...
method constructor (line 1) | constructor(a,f,b){super(),this.createRequest=a,Ne(this,b),this._opts=...
method _create (line 1) | _create(){var a;const f=Ct(this._opts,"agent","pfx","key","passphrase"...
method _onError (line 1) | _onError(a){this.emitReserved("error",a,this._xhr),this._cleanup(!0)}
method _cleanup (line 1) | _cleanup(a){if(!(typeof this._xhr>"u"||this._xhr===null)){if(this._xhr...
method _onLoad (line 1) | _onLoad(){const a=this._xhr.responseText;a!==null&&(this.emitReserved(...
method abort (line 1) | abort(){this._cleanup()}
function dt (line 1) | function dt(){for(let w in fe.requests)fe.requests.hasOwnProperty(w)&&fe...
class li (line 1) | class li extends hi{constructor(a){super(a);const f=a&&a.forceBase64;thi...
method constructor (line 1) | constructor(a){super(a);const f=a&&a.forceBase64;this.supportsBinary=c...
method request (line 1) | request(a={}){return Object.assign(a,{xd:this.xd},this.opts),new fe
Condensed preview — 189 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (6,973K chars).
[
{
"path": ".env_example",
"chars": 472,
"preview": "HTTP_PORT=80\n\n# 数据库配置\nDB_HOST=db\nDB_PORT=3306\nDB_USER=root\nDB_PASSWORD=123456\nDB_NAME=ocean\n\n# Redis配置\nREDIS_HOST=redis\n"
},
{
"path": ".flake8",
"chars": 237,
"preview": "[flake8]\nmax-line-length = 100\nexclude = .git,__pycache__,build,dist,.env,venv,env\nignore = E203, W503, E501\n# E203: whi"
},
{
"path": ".github/workflows/main.yml",
"chars": 1167,
"preview": "name: Ocean CTF CI/CD\n\non:\n push:\n branches: [main, test-cicd]\n pull_request:\n branches: [main, test-cicd]\n\njobs"
},
{
"path": ".gitignore",
"chars": 430,
"preview": "# IDE\n/.idea/\n.vscode\n\n# Python\n__pycache__/\n*.py[cod]\n*$py.class\n*.so\n.Python\nbuild/\ndevelop-eggs/\ndownloads/\neggs/\n.eg"
},
{
"path": ".pylintrc",
"chars": 1515,
"preview": "[MASTER]\n# Python version\npy-version = 3.9\n\n# Ignore specific directories\nignore=.git,__pycache__,venv,env\n\n# Add files "
},
{
"path": "LICENSE",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2022 tongcb\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "Makefile",
"chars": 3737,
"preview": ".PHONY: help lint lint-flake8 format format-black format-isort install-dev install-prod install-test clean test docker-d"
},
{
"path": "README.md",
"chars": 3712,
"preview": "# 🌊 Ocean CTF\n\n一个开箱即用的 **动态 Flag / 练习靶场 / 比赛平台**,支持容器化题目、题库管理、排行榜、公告通知,以及漏洞复现(Vulhub/自维护库)等能力,适合学校社团、战队训练、内部演练与小型赛事。\n\n##"
},
{
"path": "app/__init__.py",
"chars": 3711,
"preview": "import logging\nfrom urllib.parse import urljoin, urlparse\nimport sqlalchemy.exc\nfrom flask import Flask, g, request, url"
},
{
"path": "app/api/__init__.py",
"chars": 13,
"preview": "# API 包初始化文件\n"
},
{
"path": "app/api/admin/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "app/api/admin/ctf.py",
"chars": 13849,
"preview": "import logging\nimport os\nimport uuid\n\nimport docker\nfrom docker import errors as docker_error\nfrom flask import Blueprin"
},
{
"path": "app/api/admin/docker.py",
"chars": 10915,
"preview": "import json\nimport logging\nimport os\n\nimport docker\nimport requests\nimport yaml\nfrom docker import APIClient\nfrom docker"
},
{
"path": "app/api/admin/schemas/__init__.py",
"chars": 48,
"preview": "from app.api.admin.schemas.ctf import * # noqa\n"
},
{
"path": "app/api/admin/schemas/ctf.py",
"chars": 317,
"preview": "from typing import Optional\n\nfrom pydantic import BaseModel\n\n\nclass QuestionForm(BaseModel):\n name: str\n active: O"
},
{
"path": "app/api/admin/schemas/docker.py",
"chars": 460,
"preview": "from typing import Optional\n\nfrom pydantic import BaseModel\n\n\nclass PageForm(BaseModel):\n page: int = 1\n page_size"
},
{
"path": "app/api/admin/system.py",
"chars": 17133,
"preview": "import logging\nimport os\nimport uuid\nfrom datetime import datetime\nfrom operator import or_\n\nfrom flask import Blueprint"
},
{
"path": "app/api/admin/vulnerability.py",
"chars": 7239,
"preview": "import logging\n\nimport docker\nimport yaml\nfrom docker.errors import NotFound\nfrom flask import Blueprint, g, request\nfro"
},
{
"path": "app/api/health.py",
"chars": 3378,
"preview": "import os\nimport time\n\nfrom flask import Blueprint, jsonify\n\nfrom app.extensions import cache, db\n\nhealth_bp = Blueprint"
},
{
"path": "app/api/player/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "app/api/player/views.py",
"chars": 14119,
"preview": "import logging\nimport random\nimport string\nfrom datetime import datetime, timedelta\n\nimport docker\nfrom docker.errors im"
},
{
"path": "app/api/player/vulnerability.py",
"chars": 3860,
"preview": "import logging\n\nimport docker\nfrom docker.errors import NotFound\nfrom flask import Blueprint, g, request\nfrom sqlalchemy"
},
{
"path": "app/api/player/ws.py",
"chars": 7235,
"preview": "import logging\nimport threading\nimport typing\n\nimport docker\nfrom docker.models.containers import Container\nfrom flask i"
},
{
"path": "app/api/route.py",
"chars": 1024,
"preview": "from app.extensions import db\n\n\ndef register_blueprints(flask_app):\n \"\"\"\n 这里如果在开头引用回出现循环引用的问题\n app_factory "
},
{
"path": "app/core/api.py",
"chars": 1224,
"preview": "import typing\n\nfrom flask import jsonify\n\n\ndef api_success(\n data: typing.Union[typing.Dict, None] = None,\n msg=\"\""
},
{
"path": "app/core/command.py",
"chars": 1326,
"preview": "import click\nfrom flask.cli import with_appcontext\n\nfrom app.extensions import db\nfrom app.utils.security import hash_pa"
},
{
"path": "app/core/const.py",
"chars": 135,
"preview": "class ConstCacheKey:\n IP_DAY_SET = \"ip-%s\"\n REQ_DAY_COUNT = \"req-%s\"\n # tar包编译日志存储 1 day\n TASK_BUILD_LOG = \""
},
{
"path": "app/core/decorators.py",
"chars": 968,
"preview": "import logging\nfrom functools import wraps\n\nfrom flask import g, request\n\nfrom app.core.api import api_fail\nfrom app.ext"
},
{
"path": "app/core/error_handlers.py",
"chars": 1436,
"preview": "import logging\n\nimport redis\nfrom flask import Flask, jsonify, make_response, request\nfrom flask_pydantic import Validat"
},
{
"path": "app/core/exceptions.py",
"chars": 336,
"preview": "class RestExceptions(Exception):\n default_msg = \"error message\"\n default_code = 400\n default_status = 400\n\n "
},
{
"path": "app/core/flask_celery.py",
"chars": 787,
"preview": "from celery import Task as TaskBase\nfrom flask import current_app\n\nfrom app.extensions import db\n\n\nclass ContextTask(Tas"
},
{
"path": "app/core/middlewares.py",
"chars": 1343,
"preview": "\"\"\"\n中间件\n\"\"\"\n\nimport logging\nfrom datetime import datetime\n\nfrom flask import g, request\n\nfrom app.core.api import api_fa"
},
{
"path": "app/core/tools.py",
"chars": 486,
"preview": "import datetime\n\nfrom flask import request\n\n\ndef get_ip():\n if request.access_route:\n ip = request.access_rout"
},
{
"path": "app/extensions.py",
"chars": 326,
"preview": "from celery import Celery\nfrom flask_redis import FlaskRedis\nfrom flask_socketio import SocketIO\n\n# noinspection PyUnres"
},
{
"path": "app/models/__init__.py",
"chars": 2847,
"preview": "# -*- coding: utf-8 -*-\nimport logging\nfrom datetime import datetime\nfrom typing import Optional, Type, TypeVar\n\nfrom fl"
},
{
"path": "app/models/admin.py",
"chars": 5927,
"preview": "import datetime\n\nfrom sqlalchemy import ForeignKey\nfrom sqlalchemy.orm import Mapped, mapped_column, relationship\n\nfrom "
},
{
"path": "app/models/ctf.py",
"chars": 4374,
"preview": "\"\"\"\n用户相关模型\n\"\"\"\n\nfrom enum import Enum\n\nfrom sqlalchemy.orm import Mapped, mapped_column, relationship\n\nfrom app.extensio"
},
{
"path": "app/models/docker.py",
"chars": 5165,
"preview": "from datetime import datetime\n\nfrom sqlalchemy import Column, UniqueConstraint\nfrom sqlalchemy.orm import Mapped, mapped"
},
{
"path": "app/models/user.py",
"chars": 473,
"preview": "\"\"\"\n用户相关模型\n\"\"\"\n\nfrom sqlalchemy.orm import mapped_column\n\nfrom app.extensions import db\nfrom app.models import Model\n\n\nc"
},
{
"path": "app/services/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "app/services/docker.py",
"chars": 3380,
"preview": "import logging\nimport random\n\nimport docker\nfrom docker.errors import ImageNotFound, NotFound\n\nfrom app.extensions impor"
},
{
"path": "app/services/player.py",
"chars": 5019,
"preview": "import logging\n\nfrom sqlalchemy import desc, func\n\nfrom app.extensions import db\nfrom app.models.ctf import Answer, CtfR"
},
{
"path": "app/services/system.py",
"chars": 1197,
"preview": "from flask import g, request\n\nfrom app.extensions import db\nfrom app.models.admin import AdminMessage, Config, MessageLe"
},
{
"path": "app/tasks/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "app/tasks/ctf.py",
"chars": 5397,
"preview": "import logging\nimport os\nfrom datetime import datetime\nfrom urllib.parse import urlparse\n\nimport docker\nimport git\nimpor"
},
{
"path": "app/tasks/docker.py",
"chars": 5799,
"preview": "import json\nimport logging\nimport os\nfrom datetime import datetime\nfrom io import BytesIO\n\nimport docker.errors\nfrom doc"
},
{
"path": "app/tasks/player.py",
"chars": 1276,
"preview": "import logging\nfrom datetime import datetime\n\nimport docker\nfrom docker.errors import DockerException\n\nfrom app.core.fla"
},
{
"path": "app/tasks/system.py",
"chars": 786,
"preview": "from __future__ import absolute_import\n\nimport logging\nfrom datetime import datetime, timedelta\n\nfrom app.core.const imp"
},
{
"path": "app/tasks/vulnerability.py",
"chars": 7212,
"preview": "import logging\nimport os\n\nimport docker\nimport git\nimport yaml\nfrom docker.errors import ImageNotFound, NotFound\n\nfrom a"
},
{
"path": "app/utils/security.py",
"chars": 1225,
"preview": "import base64\nimport hashlib\nimport os\nfrom random import Random\n\n\n# Helper function to hash a password\ndef hash_passwor"
},
{
"path": "app/utils/tools.py",
"chars": 507,
"preview": "import os\nimport random\nimport string\n\n\ndef generate_flag():\n \"\"\"\n 生成flag\n return generate flag\n \"\"\"\n rd_"
},
{
"path": "app/utils/validator.py",
"chars": 247,
"preview": "import re\n\n\ndef check_image_name(name: str) -> bool:\n if re.search(\n r\"^[a-z0-9]+(?:[._-][a-z0-9]+)*(?:\\/[a-z0"
},
{
"path": "app/worker.py",
"chars": 266,
"preview": "from app import create_app\nfrom app.extensions import celery\n\napp = create_app()\n\ncelery.autodiscover_tasks([\"app.ctf\", "
},
{
"path": "config/config.py",
"chars": 2822,
"preview": "import os\nimport logging.config\nfrom dotenv import load_dotenv\nfrom celery.schedules import crontab\n\nBASE_DIR = os.path."
},
{
"path": "docker-compose.yml",
"chars": 4279,
"preview": "services:\n db:\n image: mysql:8.0\n command: --default-authentication-plugin=mysql_native_password\n container_na"
},
{
"path": "install/config/supervisord/ocean.ini",
"chars": 2489,
"preview": "[group:partition1]\nprograms=api,ocean_worker,ocean_beat\n\n[program:api]\ndirectory = /opt/ocean_ctf\ncommand = /opt/ocean_c"
},
{
"path": "install/db_init/ocean.sql",
"chars": 345,
"preview": "/*\n Navicat Premium Data Transfer\n\n Source Server Type : MariaDB\n Source Server Version : 50568\n Source Schema "
},
{
"path": "install/docker/ocean_web.Dockerfile",
"chars": 661,
"preview": "FROM python:3.13-alpine\n\n# 安装必要的构建工具\nRUN apk add --no-cache gcc musl-dev linux-headers make git\n\nWORKDIR /opt/ocean_ctf\n"
},
{
"path": "install/docker/wait-for-it.sh",
"chars": 5226,
"preview": "#!/usr/bin/env bash\n# Use this script to test if a given TCP host/port are available\n\nWAITFORIT_cmdname=${0##*/}\n\nechoer"
},
{
"path": "install/frontend/dist/assets/detail-Beu8V9iA.js",
"chars": 1036670,
"preview": "import{K as Vl,_ as zu,L as Wu,c as $u,a as nt,t as Ku,s as Qu,M as Xu,h as Pi,o as Zu}from\"./index-CUMyn3nz.js\";const k"
},
{
"path": "install/frontend/dist/assets/detail-CwA0i7lp.css",
"chars": 2217,
"preview": "pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!\n Theme: GitHub Dark\n Description"
},
{
"path": "install/frontend/dist/assets/index-2Kde3L7C.js",
"chars": 1639,
"preview": "import{_ as p,s as b,c as d,v as f,p as v,w as x,a as _,d as n,F as C,h as o,x as k,r,o as i}from\"./index-CUMyn3nz.js\";c"
},
{
"path": "install/frontend/dist/assets/index-BNtu7jJ7.js",
"chars": 17463,
"preview": "import{_ as G,m as S,c as a,a as l,n as f,p as u,f as r,w as d,T as I,d as y,s as m,E,r as h,h as g,o,F as D,q as T,t as"
},
{
"path": "install/frontend/dist/assets/index-CUMyn3nz.js",
"chars": 718513,
"preview": "const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=[\"assets/index-PyJu_6A4.js\",\"assets/index-qmyYS7Pw.css\",\"assets/"
},
{
"path": "install/frontend/dist/assets/index-Cn092HV2.css",
"chars": 378259,
"preview": "@charset \"UTF-8\";#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smo"
},
{
"path": "install/frontend/dist/assets/index-CocFE3Kg.css",
"chars": 14937,
"preview": ".xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10"
},
{
"path": "install/frontend/dist/assets/index-DAXaKKX5.js",
"chars": 3995,
"preview": "import{_ as d,c as l,d as i,w as c,s as m,r as a,h as _,o as e,F as h,q as f,p as w,f as g,a as v,t as x}from\"./index-CU"
},
{
"path": "install/frontend/dist/assets/index-DSpYhb0F.css",
"chars": 889,
"preview": ".container[data-v-edb04676]{padding-top:0}.notice-card[data-v-edb04676]{color:#fffffff0;background:linear-gradient(180de"
},
{
"path": "install/frontend/dist/assets/index-DTf6UOgP.js",
"chars": 4418,
"preview": "import{_ as g,s as y,E as k,N as q,O as b,h as v,p as w,w as o,P as U,r as a,o as p,d as s,i as V,c as B,a as f,t as h}f"
},
{
"path": "install/frontend/dist/assets/index-D_63FOFH.js",
"chars": 342273,
"preview": "import{_ as Bt,y as Tt,z as Ot,A as Mt,B as Pt,C as It,D as Ht,G as Ft,H as Nt,c as de,a as se,d as J,w as Q,F as me,q a"
},
{
"path": "install/frontend/dist/assets/index-Dst5s0sm.css",
"chars": 581,
"preview": "[data-v-9cbaa1e0] .btn-prev,[data-v-9cbaa1e0] .btn-next{background:none!important}[data-v-9cbaa1e0] .el-pager>.number{ba"
},
{
"path": "install/frontend/dist/assets/index-IBfy7H4-.css",
"chars": 2783,
"preview": ".form[data-v-add4e763]{color:#ffffffeb}.form[data-v-add4e763] .el-form-item__label{color:#ffffffb8;font-weight:600}.form"
},
{
"path": "install/frontend/dist/assets/index-M3X3VVYJ.css",
"chars": 5645,
"preview": ".container[data-v-1823ad21]{max-width:1200px;margin:0 auto}.types-container[data-v-1823ad21]{margin:0 0 2rem;padding:10p"
},
{
"path": "install/frontend/dist/assets/index-PyJu_6A4.js",
"chars": 3213,
"preview": "import{_ as b,l as k,u as x,c as _,a as e,b as C,d as l,w as t,e as P,t as w,f as U,F as T,s as N,g as z,r as n,h as i,o"
},
{
"path": "install/frontend/dist/assets/index-qmyYS7Pw.css",
"chars": 3329,
"preview": ".container[data-v-7dd8ec26]{height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden}.containe"
},
{
"path": "install/frontend/dist/assets/register-C1gwrr0Z.css",
"chars": 527,
"preview": ".login-box[data-v-ed39f80d]{padding-top:10%;margin:auto;width:500px}.title-wrap[data-v-ed39f80d]{display:flex;margin:20p"
},
{
"path": "install/frontend/dist/assets/register-CwwKsjmL.js",
"chars": 1898,
"preview": "import{_ as V,j as x,c as k,a,b as y,d as e,w as o,s as v,E as h,k as C,r as s,h as b,o as U,i as B}from\"./index-CUMyn3n"
},
{
"path": "install/frontend/dist/index.html",
"chars": 518,
"preview": "<!DOCTYPE html>\r\n<html lang=\"zh-CN\" class=\"dark\">\r\n <head>\r\n <meta charset=\"utf-8\">\r\n <meta http-equiv=\"X-UA-Comp"
},
{
"path": "install/manager/dist/index.html",
"chars": 1566,
"preview": "<!DOCTYPE html>\r\n<html lang=\"en\">\r\n <head>\r\n <meta charset=\"UTF-8\" />\r\n <meta http-equiv=\"X-UA-Compatible\" conten"
},
{
"path": "install/manager/dist/serverConfig.json",
"chars": 505,
"preview": "{\n \"Version\": \"4.5.0\",\n \"Title\": \"Ocean\",\n \"FixedHeader\": true,\n \"HiddenSideBar\": false,\n \"MultiTagsCache\": false,\n"
},
{
"path": "install/manager/dist/static/css/LineChart-CAiRPB6S.css",
"chars": 197,
"preview": ".chart[data-v-7d9bce62]{height:350px;width:100%}@media screen and (max-width:768px){.chart[data-v-7d9bce62]{height:300px"
},
{
"path": "install/manager/dist/static/css/PanelGroup-TcT6nE-z.css",
"chars": 2539,
"preview": ".panel-group[data-v-412b5fb3]{margin-top:18px}.panel-group .card-panel-col[data-v-412b5fb3]{margin-bottom:32px}.panel-gr"
},
{
"path": "install/manager/dist/static/css/UserCard-D6QtR3QQ.css",
"chars": 847,
"preview": ".box-center[data-v-c9c63467]{display:table;margin:0 auto}.text-muted[data-v-c9c63467]{color:#777}.user-profile .user-nam"
},
{
"path": "install/manager/dist/static/css/addImage-qmRBmbjD.css",
"chars": 423,
"preview": ".small[data-v-9d6be9f8]{font-size:80%}.btn-group svg[data-v-9d6be9f8]{height:14px;margin-right:4px;width:14px}.wd-title["
},
{
"path": "install/manager/dist/static/css/answer-C7eDAiIE.css",
"chars": 157,
"preview": ".fnt-12{font-size:12px}.search-form :deep(.el-form-item){margin-bottom:12px}.page-header{align-items:center;display:flex"
},
{
"path": "install/manager/dist/static/css/config-CZAQQi9l.css",
"chars": 107,
"preview": ".config-form :deep(.el-form-item__label){font-weight:500}.config-form :deep(.el-input-number){width:200px}\n"
},
{
"path": "install/manager/dist/static/css/config-W0rb9i92.css",
"chars": 34,
"preview": ".el-form-item{margin-bottom:22px}\n"
},
{
"path": "install/manager/dist/static/css/container-C6zUPM_j.css",
"chars": 77,
"preview": ".fnt-12{font-size:12px}.search-form :deep(.el-form-item){margin-bottom:12px}\n"
},
{
"path": "install/manager/dist/static/css/container-DtRsVCeN.css",
"chars": 1950,
"preview": ".container[data-v-753174a7]{padding:15px}.widget[data-v-753174a7]{background:#fff;border:1px solid #e9e9e9;border-radius"
},
{
"path": "install/manager/dist/static/css/editResources-Df9j_O02.css",
"chars": 1571,
"preview": ".section-title{border-bottom:1px solid #ebeef5;color:#303133;font-size:16px;padding-bottom:10px}.form-section{padding:16"
},
{
"path": "install/manager/dist/static/css/frame-C56j9Uki.css",
"chars": 215,
"preview": ".frame[data-v-fe57e7fd]{inset:0;position:absolute}.frame .frame-iframe[data-v-fe57e7fd]{border:0;box-sizing:border-box;h"
},
{
"path": "install/manager/dist/static/css/host-C9ss6-xf.css",
"chars": 1103,
"preview": ".sys-info span[data-v-46e3c0fe]{font-size:80%}.sys-info span+span[data-v-46e3c0fe]{margin-left:5px}.host-container[data-"
},
{
"path": "install/manager/dist/static/css/hostDetail-DJ2SmXrv.css",
"chars": 1749,
"preview": ".endpoint-info[data-v-93658b71]{border-collapse:collapse;border-spacing:0;margin-bottom:20px;max-width:100%;width:100%}."
},
{
"path": "install/manager/dist/static/css/imageDetail-LDv3EwHR.css",
"chars": 626,
"preview": ".container[data-v-11ba9705]{padding:15px}.widget[data-v-11ba9705]{background:#fff;border:1px solid #e9e9e9;border-radius"
},
{
"path": "install/manager/dist/static/css/images-Tpvwj_u0.css",
"chars": 228,
"preview": ".small[data-v-3993604c]{font-size:80%}.txt-hid[data-v-3993604c]{color:#337ab7;cursor:pointer;font-weight:600;overflow:hi"
},
{
"path": "install/manager/dist/static/css/index-B2cknHNH.css",
"chars": 2262,
"preview": ".auto-textarea-wrapper[data-v-33c11741]{height:100%}.markdown-body strong{font-weight:bolder}.markdown-body .hljs-center"
},
{
"path": "install/manager/dist/static/css/index-B5rb6rib.css",
"chars": 1198,
"preview": ".wave[data-v-5c7f7f37]{bottom:0;height:100%;left:0;position:fixed;width:80%;z-index:-1}.login-container[data-v-5c7f7f37]"
},
{
"path": "install/manager/dist/static/css/index-BOEMDA_E.css",
"chars": 88936,
"preview": "/*!\n * animate.css - https://animate.style/\n * Version - 4.1.1\n * Licensed under the MIT license - http://opensource.org"
},
{
"path": "install/manager/dist/static/css/index-BcTahPvV.css",
"chars": 49,
"preview": ".dialog-footer{margin-top:16px;text-align:right}\n"
},
{
"path": "install/manager/dist/static/css/index-C4pPArRS.css",
"chars": 408661,
"preview": "@charset \"UTF-8\";#nprogress{pointer-events:none}#nprogress .bar{background:#29d;height:2px;left:0;position:fixed;top:0;w"
},
{
"path": "install/manager/dist/static/css/index-CQHzbCBB.css",
"chars": 6528,
"preview": "@charset \"UTF-8\";.dialog-header[data-v-7b59c84d]{align-items:center;display:flex;justify-content:space-between;padding:4"
},
{
"path": "install/manager/dist/static/css/index-ChP2PXZC.css",
"chars": 606,
"preview": ".dashboard-container[data-v-b999e1a6]{box-sizing:border-box;padding:16px;width:100%}.dashboard-container .chart-containe"
},
{
"path": "install/manager/dist/static/css/index-DNZyiNmE.css",
"chars": 5793,
"preview": "@charset \"UTF-8\";.todoapp{background:#fff;color:#4d4d4d;font:14px Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:"
},
{
"path": "install/manager/dist/static/css/index-m7pPGTca.css",
"chars": 48,
"preview": ".box-center[data-v-e61bc00c]{text-align:center}\n"
},
{
"path": "install/manager/dist/static/css/operator-C9HCk2lz.css",
"chars": 101,
"preview": ".search-form .el-form-item{margin-bottom:10px}.page-r{margin-top:20px;padding:10px;text-align:right}\n"
},
{
"path": "install/manager/dist/static/css/question-qN-CSJNX.css",
"chars": 278,
"preview": ".page-header[data-v-afb43b72]{align-items:center;display:flex}.page-header .select[data-v-afb43b72]{margin:0 10px}.el-fo"
},
{
"path": "install/manager/dist/static/css/questionItem-4bFCFt94.css",
"chars": 686,
"preview": ".form[data-v-24c18daf]{margin:auto}.block-input[data-v-24c18daf]{width:100%!important}.block-input input[data-v-24c18daf"
},
{
"path": "install/manager/dist/static/css/resource-CM1VvOIh.css",
"chars": 180,
"preview": ".fnt-12{font-size:12px}.search-form :deep(.el-form-item){margin-bottom:12px}.page-r{margin-top:20px;padding:10px;text-al"
},
{
"path": "install/manager/dist/static/css/resourceForm-p8bnP91q.css",
"chars": 245,
"preview": ".dialog-footer[data-v-6ae4d3a7]{display:flex;gap:10px;justify-content:flex-end}[data-v-6ae4d3a7] .el-form-item__label{fo"
},
{
"path": "install/manager/dist/static/css/resourceItem-DzDzzvBa.css",
"chars": 1629,
"preview": ".form[data-v-5a28cb1b]{margin:auto}.card-header[data-v-5a28cb1b]{align-items:center;display:flex;justify-content:space-b"
},
{
"path": "install/manager/dist/static/css/resources-BH1s_vA0.css",
"chars": 1275,
"preview": ".search-form .el-form-item{margin-bottom:10px}.log-dialog :deep(.el-dialog){background-color:#1e1e1e;border:none;box-sha"
},
{
"path": "install/manager/dist/static/css/resources-Ct3K675F.css",
"chars": 131,
"preview": ".fnt-12{font-size:12px}.search-form :deep(.el-form-item){margin-bottom:12px}.page-r{margin-top:20px;padding:10px;text-al"
},
{
"path": "install/manager/dist/static/css/sysInfo-BohfULNO.css",
"chars": 90,
"preview": ".config-form{margin:0 auto;max-width:800px}.config-form .el-form-item{margin-bottom:22px}\n"
},
{
"path": "install/manager/dist/static/css/user-DerJW7Bd.css",
"chars": 117,
"preview": "[data-v-2d567a8b] .el-dropdown-menu__item i{margin:0}.search-form[data-v-2d567a8b] .el-form-item{margin-bottom:12px}\n"
},
{
"path": "install/manager/dist/static/js/403-DKEdQNMH.js",
"chars": 11654,
"preview": "import{m as p,n as a,p as d,q as m,H as F,G as r,k as c,F as h,R as e,S as n,Q as f,u as M,D as B,a9 as k}from\"./index-D"
},
{
"path": "install/manager/dist/static/js/404-lPQWHYlH.js",
"chars": 12749,
"preview": "import{m as h,n as a,p as o,q as m,H as r,G as F,k as e,F as l,R as c,S as n,Q as f,u as M,D as v,a9 as B}from\"./index-D"
},
{
"path": "install/manager/dist/static/js/500-Bi6ZUpQv.js",
"chars": 14738,
"preview": "import{m as h,n as t,p as i,q as o,H as r,G as F,k as e,F as l,R as c,S as n,Q as M,u as f,D as k,a9 as v}from\"./index-D"
},
{
"path": "install/manager/dist/static/js/Account-Bb8H_MKm.js",
"chars": 956,
"preview": "import{_ as i,D as n,S as _,n as c,Q as o,H as t,R as f}from\"./index-D4veOIBM.js\";const p={props:{user:Object},methods:{"
},
{
"path": "install/manager/dist/static/js/LineChart-CyBl2sMc.js",
"chars": 1040447,
"preview": "import{_ as GI,aW as FI,m as HI,n as WI,J as UI,N as YI}from\"./index-D4veOIBM.js\";/*! **********************************"
},
{
"path": "install/manager/dist/static/js/PanelGroup-QRQhV6r1.js",
"chars": 13750,
"preview": "import{_ as g,m as C,n as V,P as F,D as p,S as D,Q as u,H as o,G as s}from\"./index-D4veOIBM.js\";import{b as k}from\"./bac"
},
{
"path": "install/manager/dist/static/js/RestPass-mWtPbTNp.js",
"chars": 1412,
"preview": "import{_ as i,a7 as _,D as r,S as f,n as c,Q as o,H as e,R as h}from\"./index-D4veOIBM.js\";const V={props:{user:{type:Obj"
},
{
"path": "install/manager/dist/static/js/Timeline-CGNLPXAJ.js",
"chars": 985,
"preview": "import{_ as d,D as t,m as i,n,H as l,Q as a,K as u,L as h,S as f,G as m,P as o}from\"./index-D4veOIBM.js\";const C={data()"
},
{
"path": "install/manager/dist/static/js/Todo-B8eVum4J.js",
"chars": 1437,
"preview": "import{_ as c,a9 as r,m as u,n as g,G as s,F as f,P as v,I as m,aF as l,N as T}from\"./index-D4veOIBM.js\";const h={name:\""
},
{
"path": "install/manager/dist/static/js/UserCard-D9xOWVMi.js",
"chars": 1963,
"preview": "import{_ as p,D as i,S as _,n as m,Q as l,G as s,H as t,R as v,P as r}from\"./index-D4veOIBM.js\";const g={components:{},p"
},
{
"path": "install/manager/dist/static/js/addHost-BVYUJp7N.js",
"chars": 2330,
"preview": "import{_ as H,a7 as u,D as d,S as V,n as b,Q as l,G as f,H as t,R as h}from\"./index-D4veOIBM.js\";const v={name:\"addHost\""
},
{
"path": "install/manager/dist/static/js/addImage-Cex8BYxg.js",
"chars": 3749,
"preview": "import{_ as D,az as V,ay as N,D as s,m,n as c,G as a,H as t,Q as l,R as u,K as E,L as H,P as T,a7 as F}from\"./index-D4ve"
},
{
"path": "install/manager/dist/static/js/addUser-C2q5OAyK.js",
"chars": 1058,
"preview": "import{_ as o,a7 as a}from\"./index-D4veOIBM.js\";const u={name:\"addUser\",props:{data:{type:Object,default(){return{}}}},d"
},
{
"path": "install/manager/dist/static/js/admins-BKHsnYfX.js",
"chars": 6630,
"preview": "var U=Object.defineProperty;var V=Object.getOwnPropertySymbols;var T=Object.prototype.hasOwnProperty,N=Object.prototype."
},
{
"path": "install/manager/dist/static/js/answer-PdY72vFF.js",
"chars": 4239,
"preview": "import{_ as D,D as o,a9 as k,m,n as r,H as e,G as p,Q as n,K as y,L as b,S as g,R as w,F as S,P as T,a7 as d}from\"./inde"
},
{
"path": "install/manager/dist/static/js/audit-DbCqE0TT.js",
"chars": 3997,
"preview": "import{_ as V,D as o,a9 as L,m as y,n as c,H as e,G as u,Q as n,K as T,L as k,S as b,R as g,F as S,P as f,a7 as B,av as "
},
{
"path": "install/manager/dist/static/js/back_top-B8RoSTAY.js",
"chars": 700,
"preview": "import{m as t,n,G as e}from\"./index-D4veOIBM.js\";const o={xmlns:\"http://www.w3.org/2000/svg\",width:\"24\",height:\"24\"};fun"
},
{
"path": "install/manager/dist/static/js/config-BerqnP1C.js",
"chars": 2481,
"preview": "var d=(t,e,n)=>new Promise((b,o)=>{var _=r=>{try{l(n.next(r))}catch(a){o(a)}},p=r=>{try{l(n.throw(r))}catch(a){o(a)}},l="
},
{
"path": "install/manager/dist/static/js/config-DU8nLchD.js",
"chars": 1609,
"preview": "import{_ as d,D as s,m as g,n as b,G as i,H as e,Q as n,R as x,a7 as m}from\"./index-D4veOIBM.js\";const h={name:\"ctf\",dat"
},
{
"path": "install/manager/dist/static/js/container--f66ltxq.js",
"chars": 3449,
"preview": "import{_ as w,D as o,a9 as v,m as x,n as _,H as e,G as c,Q as l,R as p,F as k,S as z,P as Q,a7 as g}from\"./index-D4veOIB"
},
{
"path": "install/manager/dist/static/js/container-62xqkrS2.js",
"chars": 3708,
"preview": "import{_ as S,D as p,m as c,n as d,G as s,H as l,Q as h,P as m,N as I,K as _,L as g,a7 as u}from\"./index-D4veOIBM.js\";co"
},
{
"path": "install/manager/dist/static/js/dark-Dv7Dbtg0.js",
"chars": 13538,
"preview": "var j=(t,e,a)=>new Promise((o,i)=>{var p=n=>{try{c(a.next(n))}catch(d){i(d)}},T=n=>{try{c(a.throw(n))}catch(d){i(d)}},c="
},
{
"path": "install/manager/dist/static/js/docker_resource_sync-Dxn6rGPc.js",
"chars": 1242,
"preview": "import{_ as b,a7 as g,D as o,S as h,n as y,Q as s,H as t,G as k,R as a,P as V}from\"./index-D4veOIBM.js\";const v={name:\"d"
},
{
"path": "install/manager/dist/static/js/editAdmin-xd3VaCzj.js",
"chars": 2529,
"preview": "import{_ as V,a7 as m,D as r,S as f,n as d,Q as s,H as l,G as w,m as g,K as y,L as v,R as u,P as x}from\"./index-D4veOIBM"
},
{
"path": "install/manager/dist/static/js/editResources-jJ-omP3I.js",
"chars": 6262,
"preview": "var b=(i,e,u)=>new Promise((V,t)=>{var m=n=>{try{d(u.next(n))}catch(c){t(c)}},f=n=>{try{d(u.throw(n))}catch(c){t(c)}},d="
},
{
"path": "install/manager/dist/static/js/editRole-Dqlx3Hxu.js",
"chars": 1579,
"preview": "import{_,D as s,S as b,n as g,Q as l,H as i,G as D,R as r,P as x,a7 as m}from\"./index-D4veOIBM.js\";const y={name:\"edtRol"
},
{
"path": "install/manager/dist/static/js/frame-CRmNRX0K.js",
"chars": 1060,
"preview": "import{q as p,r as i,i as g,k as o,z as k,o as x,a9 as I,F as R,m as S,n as B,G as E,U as L,_ as P}from\"./index-D4veOIBM"
},
{
"path": "install/manager/dist/static/js/hooks-CzcwFjli.js",
"chars": 911,
"preview": "var I=Object.defineProperty;var c=Object.getOwnPropertySymbols;var d=Object.prototype.hasOwnProperty,p=Object.prototype."
},
{
"path": "install/manager/dist/static/js/host-g1EgSM4r.js",
"chars": 2622,
"preview": "import{_ as v,D as l,m as d,n as i,G as s,S as f,E as h,H as c,K as k,L as y,Q as x,P as o,a7 as _}from\"./index-D4veOIBM"
},
{
"path": "install/manager/dist/static/js/hostDetail-DeVpx4OE.js",
"chars": 2898,
"preview": "import{_ as p,D as a,m as u,n as c,G as t,P as e,H as l,Q as i,a7 as f}from\"./index-D4veOIBM.js\";const g={name:\"hostDeta"
},
{
"path": "install/manager/dist/static/js/imageDetail-quKI1yqP.js",
"chars": 3883,
"preview": "import{_ as b,D as c,m as n,n as l,G as t,E as d,K as p,L as _,P as a,H as r,Q as g,a7 as m,S as k,R as C}from\"./index-D"
},
{
"path": "install/manager/dist/static/js/images-BUkzOY-1.js",
"chars": 2003,
"preview": "import{_ as x,ax as C,ay as $,D as n,m as b,n as u,G as p,H as t,Q as a,R as c,K as L,L as B,S as P,P as f,a7 as k}from\""
},
{
"path": "install/manager/dist/static/js/index-BLCelF29.js",
"chars": 18543,
"preview": "var b=(o,e,t)=>new Promise((n,y)=>{var h=a=>{try{d(t.next(a))}catch(f){y(f)}},v=a=>{try{d(t.throw(a))}catch(f){y(f)}},d="
},
{
"path": "install/manager/dist/static/js/index-B_eHX4aD.js",
"chars": 1264,
"preview": "import{q as h,c as w,r as C,aG as c,D as a,S,n as d,Q as t,H as o,J as i,R as V,m as b,K as x,L as B,G as m,P,W as H,w a"
},
{
"path": "install/manager/dist/static/js/index-BqIwwt-c.js",
"chars": 6343,
"preview": "var f=(i,e,s)=>new Promise((V,l)=>{var d=n=>{try{r(s.next(n))}catch(c){l(c)}},b=n=>{try{r(s.throw(n))}catch(c){l(c)}},r="
},
{
"path": "install/manager/dist/static/js/index-BzD9KVPC.js",
"chars": 331389,
"preview": "var Ro=Object.defineProperty,No=Object.defineProperties;var Vo=Object.getOwnPropertyDescriptors;var _t=Object.getOwnProp"
},
{
"path": "install/manager/dist/static/js/index-Cj3Ji7Ce.js",
"chars": 1044,
"preview": "import{_ as l,D as e,m as _,n as o,H as r,Q as h,S as p,E as m,a7 as u}from\"./index-D4veOIBM.js\";import C from\"./PanelGr"
},
{
"path": "install/manager/dist/static/js/index-Cuc6Bha6.js",
"chars": 2427,
"preview": "import{q as k,c as w,D as o,a9 as x,S as s,n as l,Q as t,H as d,J as u,R as a,P as C,k as _,bk as A,G as y,E as f,bl as "
},
{
"path": "install/manager/dist/static/js/index-D4veOIBM.js",
"chars": 1264076,
"preview": "const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=[\"static/js/index-BzD9KVPC.js\",\"static/js/dark-Dv7Dbtg0.js\",\"sta"
},
{
"path": "install/manager/dist/static/js/index-DGss3hGv.js",
"chars": 6456,
"preview": "var H=(c,t,r)=>new Promise((s,a)=>{var b=i=>{try{o(r.next(i))}catch(h){a(h)}},y=i=>{try{o(r.throw(i))}catch(h){a(h)}},o="
},
{
"path": "install/manager/dist/static/js/index-DqeA2Szn.js",
"chars": 2662,
"preview": "import p from\"./Todo-B8eVum4J.js\";import{_ as T,D as _,m as a,n,G as s,F as c,aF as v,I as h,K as u,L as g,S as x,R as S"
},
{
"path": "install/manager/dist/static/js/index-Lj5hgGY3.js",
"chars": 1728,
"preview": "import v from\"./Timeline-CGNLPXAJ.js\";import x from\"./Account-Bb8H_MKm.js\";import g from\"./RestPass-mWtPbTNp.js\";import "
},
{
"path": "install/manager/dist/static/js/index-xEKAsWxN.js",
"chars": 409800,
"preview": "import{aX as NOOP,aY as extend,aZ as isString,a_ as NO,a$ as isSymbol,b0 as isBuiltInDirective,b1 as capitalize,b2 as ca"
},
{
"path": "install/manager/dist/static/js/notifications-D8ciMJpU.js",
"chars": 3987,
"preview": "var h=(t,e,g)=>new Promise((C,s)=>{var i=l=>{try{d(g.next(l))}catch(p){s(p)}},y=l=>{try{d(g.throw(l))}catch(p){s(p)}},d="
},
{
"path": "install/manager/dist/static/js/operator-BiRoUc-y.js",
"chars": 3494,
"preview": "import{_ as Q,D as n,a9 as z,m as x,n as g,H as e,G as i,Q as s,R as _,F as V,S as k,P as L,a7 as S,av as T}from\"./index"
},
{
"path": "install/manager/dist/static/js/question-DPBmlVI1.js",
"chars": 4664,
"preview": "import{_ as A,D as i,a9 as D,m as w,n as d,H as e,G as p,Q as a,K as L,L as S,S as m,R as u,F as j,a7 as h}from\"./index-"
},
{
"path": "install/manager/dist/static/js/questionItem-BnT1RndV.js",
"chars": 6391,
"preview": "import{_ as q,a7 as d,D as n,S as f,n as u,Q as i,H as a,m as h,K as _,L as b,G as p,R as v,P as D}from\"./index-D4veOIBM"
},
{
"path": "install/manager/dist/static/js/questionTypeManage-C4aIrDS_.js",
"chars": 444,
"preview": "import{_ as a,D as n,S as s,E as r,n as i,Q as l,G as c}from\"./index-D4veOIBM.js\";const d={name:\"questionTypeManage\",pro"
},
{
"path": "install/manager/dist/static/js/redirect-BNWD9FK6.js",
"chars": 304,
"preview": "import{q as s,u as c,k as p,m as u,n as m}from\"./index-D4veOIBM.js\";const l=s({name:\"Redirect\",__name:\"redirect\",setup(_"
},
{
"path": "install/manager/dist/static/js/resource-B26noqeQ.js",
"chars": 7846,
"preview": "var N=Object.defineProperty;var D=Object.getOwnPropertySymbols;var j=Object.prototype.hasOwnProperty,G=Object.prototype."
},
{
"path": "install/manager/dist/static/js/resourceForm-UYjwLmkY.js",
"chars": 3947,
"preview": "var y=(_,o,r)=>new Promise((e,p)=>{var l=d=>{try{n(r.next(d))}catch(u){p(u)}},m=d=>{try{n(r.throw(d))}catch(u){p(u)}},n="
},
{
"path": "install/manager/dist/static/js/resourceItem-BIlM6slQ.js",
"chars": 5559,
"preview": "import{_ as B,a7 as p,av as u,D as s,S as b,E as C,n as d,Q as t,H as o,G as a,m as k,K as w,L as E,R as f,P as S}from\"."
},
{
"path": "install/manager/dist/static/js/resources-D66Xg1B4.js",
"chars": 5862,
"preview": "var q=Object.defineProperty;var R=Object.getOwnPropertySymbols;var U=Object.prototype.hasOwnProperty,G=Object.prototype."
},
{
"path": "install/manager/dist/static/js/resources-eAiTMxgh.js",
"chars": 7186,
"preview": "var m=(e,t,c)=>new Promise((z,n)=>{var i=p=>{try{w(c.next(p))}catch(o){n(o)}},v=p=>{try{w(c.throw(p))}catch(o){n(o)}},w="
},
{
"path": "install/manager/dist/static/js/role-Cj_rRiT0.js",
"chars": 4610,
"preview": "var V=Object.defineProperty;var C=Object.getOwnPropertySymbols;var B=Object.prototype.hasOwnProperty,Q=Object.prototype."
},
{
"path": "install/manager/dist/static/js/runner-C4c3DT0z.js",
"chars": 5034,
"preview": "var w=(e,t,p)=>new Promise((x,n)=>{var r=i=>{try{h(p.next(i))}catch(c){n(c)}},k=i=>{try{h(p.throw(i))}catch(c){n(c)}},h="
},
{
"path": "install/manager/dist/static/js/sysInfo-BJzi9t0G.js",
"chars": 3904,
"preview": "var c=(l,e,s)=>new Promise((g,n)=>{var _=m=>{try{t(s.next(m))}catch(d){n(d)}},u=m=>{try{t(s.throw(m))}catch(d){n(d)}},t="
},
{
"path": "install/manager/dist/static/js/user-BBi65gzK.js",
"chars": 5849,
"preview": "import{_ as z,D as i,a9 as L,m as w,n as f,H as t,G as o,Q as r,R as u,F as D,S as b,P as c,a7 as _,av as y}from\"./index"
},
{
"path": "install/manager/dist/version.json",
"chars": 38,
"preview": "{ \"version\": \"4.5.0\", \"external\": \"\" }"
},
{
"path": "install/nginx.conf",
"chars": 1487,
"preview": "# ctf.conf nginx\nuser root;\nworker_processes auto;\nevents {\n\tworker_connections 768;\n\t# multi_accept on;\n}\n\nhttp {\n s"
},
{
"path": "main.py",
"chars": 258,
"preview": "from app import create_app\nfrom app.extensions import socketio\n\napp = create_app()\n\n\ndef main():\n for uri in app.url_"
},
{
"path": "pyproject.toml",
"chars": 1189,
"preview": "# Ocean CTF Platform - 工具配置文件\n# 依赖管理已迁移到 requirements/ 目录\n\n[tool.black]\nline-length = 100\ntarget-version = ['py39']\nincl"
},
{
"path": "requirements/base.txt",
"chars": 318,
"preview": "Flask==3.1.0\nSQLAlchemy==2.0.36\nFlask-SQLAlchemy==3.1.1\nFlask-Cors==5.0.0\nFlask-Pydantic==0.12.0\nFlask-SocketIO==5.5.1\np"
},
{
"path": "requirements/dev.txt",
"chars": 96,
"preview": "-r base.txt\n\n# Development dependencies\nflake8==7.0.0\npylint==3.1.0\nblack==24.3.0\nisort==5.13.2\n"
},
{
"path": "requirements/prod.txt",
"chars": 65,
"preview": "-r base.txt\n\n# Production dependencies\nGunicorn\neventlet>=0.33.0\n"
},
{
"path": "requirements/test.txt",
"chars": 65,
"preview": "-r base.txt\n\n# Test dependencies\npytest==8.0.0\npytest-cov==4.1.0\n"
},
{
"path": "requirements.txt",
"chars": 41,
"preview": "# 生产环境依赖 - 用于部署\n-r requirements/prod.txt\n"
},
{
"path": "run.sh",
"chars": 8,
"preview": "gunicorn"
},
{
"path": "tests/conftest.py",
"chars": 788,
"preview": "import docker\nimport pytest\nfrom docker import APIClient\n\nfrom app import create_app\n\n\n@pytest.fixture(scope=\"session\")\n"
},
{
"path": "tests/test_docker.py",
"chars": 1074,
"preview": "from unittest import mock\n\nimport pytest\n\n\n@pytest.mark.skip(reason=\"需要Docker环境,CI中跳过\")\ndef test_docker_image(docker_api"
},
{
"path": "tests/test_health_api.py",
"chars": 799,
"preview": "import json\n\n\ndef test_ping_endpoint(client):\n \"\"\"测试 ping 接口是否正常工作\"\"\"\n response = client.get(\"/api/health/ping\")\n "
},
{
"path": "tests/test_tasks.py",
"chars": 408,
"preview": "from app.tasks.ctf import sync_ctf_question_repo\nfrom app.tasks.vulnerability import sync_remote_vulnerability_repo\n\n\nde"
},
{
"path": "tests/test_validator.py",
"chars": 385,
"preview": "from app.utils.validator import check_image_name\n\n\ndef test_check_image_name():\n \"\"\"测试镜像名称验证函数\"\"\"\n assert check_im"
},
{
"path": "upload/.gitignore",
"chars": 32,
"preview": "# 忽略所有文件\n*\n# 除了这个文件\n!.gitignore\n"
},
{
"path": "wsgi.py",
"chars": 111,
"preview": "import eventlet\neventlet.monkey_patch()\n\n# 导入应用\nfrom main import app\n\nif __name__ == \"__main__\":\n app.run()\n"
}
]
About this extraction
This page contains the full source code of the tongchengbin/ocean_ctf GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 189 files (6.2 MB), approximately 1.6M tokens, and a symbol index with 8203 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.