dev_1.0.0_xuxt 完成web和alert模块开发,以及模块e2e测试 #21
2
src/web/.gitignore
vendored
2
src/web/.gitignore
vendored
@ -41,3 +41,5 @@ Thumbs.db
|
|||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
.vite/
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
# ---- 1. 构建阶段 ----
|
|
||||||
FROM node:20-alpine AS build
|
|
||||||
|
|
||||||
# 设置工作目录
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# 复制依赖清单
|
|
||||||
COPY package*.json ./
|
|
||||||
|
|
||||||
# 安装依赖
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
# 复制全部源码
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# 构建生产环境代码
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
|
|
||||||
# ---- 2. 部署阶段 ----
|
|
||||||
FROM nginx:alpine
|
|
||||||
|
|
||||||
# 删除默认配置
|
|
||||||
RUN rm /etc/nginx/conf.d/default.conf
|
|
||||||
|
|
||||||
# 复制你自己的 nginx 配置
|
|
||||||
COPY build_tools/front_end/nginx.conf /etc/nginx/conf.d/default.conf
|
|
||||||
|
|
||||||
# 将打包好的 dist 文件放到 nginx 的静态目录
|
|
||||||
COPY --from=build /app/dist /usr/share/nginx/html
|
|
||||||
|
|
||||||
# 暴露 80 端口
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
# 启动 Nginx
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
@ -1,12 +1,12 @@
|
|||||||
# React + Vite
|
# Argus-web
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
架构:React + Vite + Mantine
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
## 打包部署
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
根目录下运行
|
||||||
|
```bash
|
||||||
|
bash src/web/buld_tools/frontend/build.sh
|
||||||
|
```
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
|
||||||
|
|
||||||
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
docker pull node:20-alpine
|
|
||||||
docker pull nginx:alpine
|
|
||||||
cd ../..
|
|
||||||
docker build -t portal-frontend .
|
|
||||||
rm -f portal-frontend.tar.gz && sudo docker image save portal-frontend:latest | gzip > portal-frontend.tar.gz
|
|
@ -1,13 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name web.argus.com;
|
|
||||||
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
# React 前端路由兼容
|
|
||||||
location / {
|
|
||||||
try_files $uri /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
89
src/web/build_tools/frontend/Dockerfile
Normal file
89
src/web/build_tools/frontend/Dockerfile
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# ========== 构建阶段 ==========
|
||||||
|
FROM node:20 AS builder
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app/src/web
|
||||||
|
|
||||||
|
# 复制依赖文件并安装
|
||||||
|
COPY src/web/package*.json ./
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# 复制源码并打包
|
||||||
|
COPY src/web ./
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ========== 运行阶段 ==========
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
# 安装 nginx 和 supervisor
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y nginx supervisor curl vim net-tools inetutils-ping ca-certificates passwd && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ENV FRONTEND_BASE_PATH=/private/argus/web/frontend
|
||||||
|
|
||||||
|
RUN mkdir -p ${FRONTEND_BASE_PATH} && \
|
||||||
|
mkdir -p /private/argus/etc
|
||||||
|
|
||||||
|
# 创建 web 用户(可自定义 UID/GID)
|
||||||
|
# 创建 web 用户组
|
||||||
|
RUN groupadd -g 2015 web
|
||||||
|
|
||||||
|
# 创建 web 用户并指定组
|
||||||
|
RUN useradd -M -s /usr/sbin/nologin -u 2133 -g 2015 web
|
||||||
|
|
||||||
|
RUN chown -R web:web ${FRONTEND_BASE_PATH} && \
|
||||||
|
chown -R web:web /private/argus/etc && \
|
||||||
|
chown -R web:web /usr/local/bin
|
||||||
|
|
||||||
|
# 配置内网 apt 源 (如果指定了内网选项)
|
||||||
|
RUN if [ "$USE_INTRANET" = "true" ]; then \
|
||||||
|
echo "Configuring intranet apt sources..." && \
|
||||||
|
cp /etc/apt/sources.list /etc/apt/sources.list.bak && \
|
||||||
|
echo "deb [trusted=yes] http://10.68.64.1/ubuntu2204/ jammy main" > /etc/apt/sources.list && \
|
||||||
|
echo 'Acquire::https::Verify-Peer "false";' > /etc/apt/apt.conf.d/99disable-ssl-check && \
|
||||||
|
echo 'Acquire::https::Verify-Host "false";' >> /etc/apt/apt.conf.d/99disable-ssl-check; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# 配置部署时使用的 apt 源
|
||||||
|
RUN if [ "$USE_INTRANET" = "true" ]; then \
|
||||||
|
echo "deb [trusted=yes] https://10.92.132.52/mirrors/ubuntu2204/ jammy main" > /etc/apt/sources.list; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 前端编译产物放到 nginx 目录
|
||||||
|
COPY --from=builder /app/src/web/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# 复制 nginx 配置(保证 React 前端路由兼容)
|
||||||
|
COPY src/web/build_tools/frontend/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
# COPY src/web/build_tools/frontend/conf.d/ /etc/nginx/conf.d/
|
||||||
|
|
||||||
|
# 复制 supervisor 配置
|
||||||
|
COPY src/web/build_tools/frontend/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
|
||||||
|
# 创建 supervisor 日志目录
|
||||||
|
RUN mkdir -p /var/log/supervisor
|
||||||
|
|
||||||
|
# 复制启动脚本
|
||||||
|
COPY src/web/build_tools/frontend/start-web-supervised.sh /usr/local/bin/start-web-supervised.sh
|
||||||
|
RUN chmod +x /usr/local/bin/start-web-supervised.sh
|
||||||
|
|
||||||
|
# 复制 DNS 监控脚本
|
||||||
|
COPY src/web/build_tools/frontend/dns-monitor.sh /usr/local/bin/dns-monitor.sh
|
||||||
|
RUN chmod +x /usr/local/bin/dns-monitor.sh
|
||||||
|
|
||||||
|
# 复制健康检查脚本
|
||||||
|
COPY src/web/build_tools/frontend/health-check.sh /usr/local/bin/health-check.sh
|
||||||
|
RUN chmod +x /usr/local/bin/health-check.sh
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# 保持 root 用户,由 supervisor 控制 user 切换
|
||||||
|
USER root
|
||||||
|
|
||||||
|
# 以 supervisor 为入口
|
||||||
|
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
4
src/web/build_tools/frontend/build.sh
Normal file
4
src/web/build_tools/frontend/build.sh
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
docker pull node:20
|
||||||
|
docker pull ubuntu:24.04
|
||||||
|
docker build -f src/web/build_tools/frontend/Dockerfile -t argus-web:0.1.1 .
|
||||||
|
rm -f argus-web-0.1.1.tar && sudo docker image save argus-web:0.1.1 > argus-web-0.1.1.tar
|
68
src/web/build_tools/frontend/dns-monitor.sh
Normal file
68
src/web/build_tools/frontend/dns-monitor.sh
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# DNS监控脚本 - 每10秒检查dns.conf是否有变化
|
||||||
|
# 如果有变化则执行update-dns.sh脚本
|
||||||
|
|
||||||
|
DNS_CONF="/private/argus/etc/dns.conf"
|
||||||
|
DNS_BACKUP="/tmp/dns.conf.backup"
|
||||||
|
UPDATE_SCRIPT="/private/argus/etc/update-dns.sh"
|
||||||
|
LOG_FILE="/var/log/supervisor/dns-monitor.log"
|
||||||
|
|
||||||
|
# 确保日志文件存在
|
||||||
|
touch "$LOG_FILE"
|
||||||
|
|
||||||
|
log_message() {
|
||||||
|
echo "$(date '+%Y-%m-%d %H:%M:%S') [DNS-Monitor] $1" >> "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_message "DNS监控脚本启动"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if [ -f "$DNS_CONF" ]; then
|
||||||
|
if [ -f "$DNS_BACKUP" ]; then
|
||||||
|
# 比较文件内容
|
||||||
|
if ! cmp -s "$DNS_CONF" "$DNS_BACKUP"; then
|
||||||
|
log_message "检测到DNS配置变化"
|
||||||
|
|
||||||
|
# 更新备份文件
|
||||||
|
cp "$DNS_CONF" "$DNS_BACKUP"
|
||||||
|
|
||||||
|
# 执行更新脚本
|
||||||
|
if [ -x "$UPDATE_SCRIPT" ]; then
|
||||||
|
log_message "执行DNS更新脚本: $UPDATE_SCRIPT"
|
||||||
|
"$UPDATE_SCRIPT" >> "$LOG_FILE" 2>&1
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
log_message "DNS更新脚本执行成功"
|
||||||
|
else
|
||||||
|
log_message "DNS更新脚本执行失败"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_message "警告: 更新脚本不存在或不可执行: $UPDATE_SCRIPT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
|
||||||
|
# 第一次检测到配置文件,执行更新脚本
|
||||||
|
if [ -x "$UPDATE_SCRIPT" ]; then
|
||||||
|
log_message "执行DNS更新脚本: $UPDATE_SCRIPT"
|
||||||
|
"$UPDATE_SCRIPT" >> "$LOG_FILE" 2>&1
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
log_message "DNS更新脚本执行成功"
|
||||||
|
|
||||||
|
# 第一次运行,创建备份并执行更新
|
||||||
|
cp "$DNS_CONF" "$DNS_BACKUP"
|
||||||
|
log_message "创建DNS配置备份文件"
|
||||||
|
|
||||||
|
else
|
||||||
|
log_message "DNS更新脚本执行失败"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_message "警告: 更新脚本不存在或不可执行: $UPDATE_SCRIPT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_message "警告: DNS配置文件不存在: $DNS_CONF"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
done
|
16
src/web/build_tools/frontend/health-check.sh
Normal file
16
src/web/build_tools/frontend/health-check.sh
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
URL="http://127.0.0.1:80"
|
||||||
|
|
||||||
|
echo "[INFO] Starting frontend health check loop for $URL..."
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if curl -s --max-time 5 "$URL" > /dev/null; then
|
||||||
|
echo "[OK] $(date '+%Y-%m-%d %H:%M:%S') Frontend is healthy"
|
||||||
|
else
|
||||||
|
echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') Frontend health check failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 10
|
||||||
|
done
|
56
src/web/build_tools/frontend/nginx.conf
Normal file
56
src/web/build_tools/frontend/nginx.conf
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
user web;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
sendfile on;
|
||||||
|
|
||||||
|
# React 前端服务
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name web.argus.com;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# React 前端路由兼容
|
||||||
|
location / {
|
||||||
|
try_files $uri /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Master 服务,需要增加 CORS 支持
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name master.argus.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://master.argus.com;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# CORS 支持
|
||||||
|
add_header 'Access-Control-Allow-Origin' 'http://web.argus.com' always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization' always;
|
||||||
|
|
||||||
|
if ($request_method = OPTIONS) {
|
||||||
|
add_header 'Access-Control-Allow-Origin' 'http://web.argus.com' always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization' always;
|
||||||
|
add_header 'Content-Length' 0;
|
||||||
|
add_header 'Content-Type' 'text/plain';
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
39
src/web/build_tools/frontend/start-web-supervised.sh
Normal file
39
src/web/build_tools/frontend/start-web-supervised.sh
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "[INFO] Starting React frontend under supervisor..."
|
||||||
|
|
||||||
|
DNS_DIR="/private/argus/etc"
|
||||||
|
DNS_SCRIPT="${DNS_DIR}/update-dns.sh"
|
||||||
|
DOMAIN=web.argus.com
|
||||||
|
WEB_DOMAIN_FILE="${DNS_DIR}/${DOMAIN}"
|
||||||
|
RUNTIME_USER="${ARGUS_RUNTIME_USER:-argus}"
|
||||||
|
RUNTIME_UID="${ARGUS_BUILD_UID:-2133}"
|
||||||
|
RUNTIME_GID="${ARGUS_BUILD_GID:-2015}"
|
||||||
|
|
||||||
|
mkdir -p "$DNS_DIR"
|
||||||
|
chown -R "$RUNTIME_UID:$RUNTIME_GID" "$DNS_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
|
if [[ -x "$DNS_SCRIPT" ]]; then
|
||||||
|
echo "[INFO] Running update-dns.sh before master starts"
|
||||||
|
# 若脚本存在则执行,保证容器使用 bind 作为 DNS
|
||||||
|
"$DNS_SCRIPT" || echo "[WARN] update-dns.sh execution failed"
|
||||||
|
else
|
||||||
|
echo "[WARN] DNS update script not found or not executable: $DNS_SCRIPT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# 记录容器 IP
|
||||||
|
IP=$(ifconfig | grep -A 1 eth0 | grep inet | awk '{print $2}' || true)
|
||||||
|
if [[ -n "${IP}" ]]; then
|
||||||
|
echo "current IP: ${IP}"
|
||||||
|
echo "${IP}" > "$WEB_DOMAIN_FILE"
|
||||||
|
chown "$RUNTIME_UID:$RUNTIME_GID" "$WEB_DOMAIN_FILE" 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo "[WARN] Failed to detect web IP via ifconfig"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[INFO] Launching nginx..."
|
||||||
|
|
||||||
|
# 启动 nginx 前台模式
|
||||||
|
exec /usr/sbin/nginx -g "daemon off;"
|
39
src/web/build_tools/frontend/supervisord.conf
Normal file
39
src/web/build_tools/frontend/supervisord.conf
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
logfile=/var/log/supervisor/supervisord.log
|
||||||
|
pidfile=/var/run/supervisord.pid
|
||||||
|
user=root
|
||||||
|
|
||||||
|
[program:web]
|
||||||
|
command=/usr/local/bin/start-web-supervised.sh
|
||||||
|
user=root
|
||||||
|
stdout_logfile=/var/log/supervisor/web.log
|
||||||
|
stderr_logfile=/var/log/supervisor/web_error.log
|
||||||
|
autorestart=true
|
||||||
|
startretries=3
|
||||||
|
startsecs=5
|
||||||
|
stopwaitsecs=10
|
||||||
|
killasgroup=true
|
||||||
|
stopasgroup=true
|
||||||
|
|
||||||
|
[program:web-health]
|
||||||
|
command=/usr/local/bin/health-check.sh
|
||||||
|
user=web
|
||||||
|
stdout_logfile=/var/log/supervisor/web-health.log
|
||||||
|
stderr_logfile=/var/log/supervisor/web-health_error.log
|
||||||
|
autorestart=true
|
||||||
|
startretries=3
|
||||||
|
startsecs=5
|
||||||
|
stopwaitsecs=10
|
||||||
|
killasgroup=true
|
||||||
|
stopasgroup=true
|
||||||
|
|
||||||
|
[unix_http_server]
|
||||||
|
file=/var/run/supervisor.sock
|
||||||
|
chmod=0700
|
||||||
|
|
||||||
|
[supervisorctl]
|
||||||
|
serverurl=unix:///var/run/supervisor.sock
|
||||||
|
|
||||||
|
[rpcinterface:supervisor]
|
||||||
|
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
@ -1,17 +1,67 @@
|
|||||||
# 使用轻量级 Nginx 基础镜像
|
FROM ubuntu:24.04
|
||||||
FROM nginx:1.25-alpine
|
|
||||||
|
|
||||||
# 删除默认配置
|
USER root
|
||||||
RUN rm -rf /etc/nginx/conf.d/*
|
|
||||||
|
|
||||||
# 复制自定义 Proxy 配置
|
# 安装 nginx 和 supervisor
|
||||||
# (可以在构建时直接COPY进去,也可以运行时挂载)
|
RUN apt-get update && \
|
||||||
COPY conf.d/ /etc/nginx/conf.d/
|
apt-get install -y nginx supervisor curl vim net-tools inetutils-ping ca-certificates passwd && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# 日志目录(可选)
|
ENV FRONTEND_BASE_PATH=/private/argus/web/proxy
|
||||||
VOLUME ["/var/log/nginx"]
|
|
||||||
|
RUN mkdir -p ${FRONTEND_BASE_PATH} && \
|
||||||
|
mkdir -p /private/argus/etc
|
||||||
|
|
||||||
|
# 创建 proxy 用户(可自定义 UID/GID)
|
||||||
|
# 创建 proxy 用户组
|
||||||
|
RUN groupadd -g 2015 web_proxy
|
||||||
|
|
||||||
|
# 创建 proxy 用户并指定组
|
||||||
|
RUN useradd -M -s /usr/sbin/nologin -u 2133 -g 2015 web_proxy
|
||||||
|
|
||||||
|
RUN chown -R web_proxy:web_proxy ${FRONTEND_BASE_PATH} && \
|
||||||
|
chown -R web_proxy:web_proxy /private/argus/etc && \
|
||||||
|
chown -R web_proxy:web_proxy /usr/local/bin
|
||||||
|
|
||||||
|
# 配置内网 apt 源 (如果指定了内网选项)
|
||||||
|
RUN if [ "$USE_INTRANET" = "true" ]; then \
|
||||||
|
echo "Configuring intranet apt sources..." && \
|
||||||
|
cp /etc/apt/sources.list /etc/apt/sources.list.bak && \
|
||||||
|
echo "deb [trusted=yes] http://10.68.64.1/ubuntu2204/ jammy main" > /etc/apt/sources.list && \
|
||||||
|
echo 'Acquire::https::Verify-Peer "false";' > /etc/apt/apt.conf.d/99disable-ssl-check && \
|
||||||
|
echo 'Acquire::https::Verify-Host "false";' >> /etc/apt/apt.conf.d/99disable-ssl-check; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# 配置部署时使用的 apt 源
|
||||||
|
RUN if [ "$USE_INTRANET" = "true" ]; then \
|
||||||
|
echo "deb [trusted=yes] https://10.92.132.52/mirrors/ubuntu2204/ jammy main" > /etc/apt/sources.list; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# 复制 nginx 配置(保证 React 前端路由兼容)
|
||||||
|
COPY src/web/build_tools/proxy/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY src/web/build_tools/proxy/conf.d/ /etc/nginx/conf.d/
|
||||||
|
|
||||||
|
# 复制 supervisor 配置
|
||||||
|
COPY src/web/build_tools/proxy/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
|
||||||
|
# 创建 supervisor 日志目录
|
||||||
|
RUN mkdir -p /var/log/supervisor
|
||||||
|
|
||||||
|
# 复制启动脚本
|
||||||
|
COPY src/web/build_tools/proxy/start-proxy-supervised.sh /usr/local/bin/start-proxy-supervised.sh
|
||||||
|
RUN chmod +x /usr/local/bin/start-proxy-supervised.sh
|
||||||
|
|
||||||
|
# 复制 DNS 监控脚本
|
||||||
|
COPY src/web/build_tools/proxy/dns-monitor.sh /usr/local/bin/dns-monitor.sh
|
||||||
|
RUN chmod +x /usr/local/bin/dns-monitor.sh
|
||||||
|
|
||||||
# 暴露端口
|
# 暴露端口
|
||||||
EXPOSE 80 443
|
EXPOSE 80
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
# 保持 root 用户,由 supervisor 控制 user 切换
|
||||||
|
USER root
|
||||||
|
|
||||||
|
# 以 supervisor 为入口
|
||||||
|
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||||
|
Binary file not shown.
@ -1,71 +0,0 @@
|
|||||||
# 门户前端 (React 静态资源通过内网 Nginx 或 Node.js 服务暴露)
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name web.argus.com;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://web.argus.com; # 门户前端内部服务
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Grafana
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name grafana.metric.argus.com;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://grafana.metric.argus.com;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Prometheus
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name prometheus.metric.argus.com;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://prometheus.metric.argus.com;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Elasticsearch
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name es.log.argus.com;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://es.log.argus.com;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Kibana
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name kibana.log.argus.com;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://kibana.log.argus.com;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Alertmanager
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name alertmanager.alert.argus.com;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://alertmanager.alert.argus.com;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
docker build -t argus-proxy:latest .
|
|
||||||
rm -f argus-proxy.tar.gz && sudo docker image save argus-proxy:latest | gzip > argus-proxy.tar.gz
|
|
8
src/web/build_tools/proxy/conf.d/alert.conf
Normal file
8
src/web/build_tools/proxy/conf.d/alert.conf
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name alertmanager.alert.argus.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://alertmanager.alert.argus.com;
|
||||||
|
}
|
||||||
|
}
|
@ -1,78 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name web.argus.com;
|
|
||||||
|
|
||||||
# 门户网站(React 前端),通过 proxy 转发到内部服务
|
|
||||||
location / {
|
|
||||||
proxy_pass http://portal-frontend:80;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name grafana.metric.argus.com;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://grafana.metric.argus.com;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name prometheus.metric.argus.com;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://prometheus.metric.argus.com;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name es.log.argus.com;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://es.log.argus.com;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name kibana.log.argus.com;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://kibana.log.argus.com;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name alertmanager.alert.argus.com;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://alertmanager.alert.argus.com;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
}
|
|
19
src/web/build_tools/proxy/conf.d/log.conf
Normal file
19
src/web/build_tools/proxy/conf.d/log.conf
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Elasticsearch
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name es.log.argus.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://es.log.argus.com;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Kibana
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name kibana.log.argus.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://kibana.log.argus.com;
|
||||||
|
}
|
||||||
|
}
|
26
src/web/build_tools/proxy/conf.d/master.conf
Normal file
26
src/web/build_tools/proxy/conf.d/master.conf
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name master.argus.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://master.argus.com;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# CORS 支持
|
||||||
|
add_header 'Access-Control-Allow-Origin' 'http://web.argus.com' always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization' always;
|
||||||
|
|
||||||
|
if ($request_method = OPTIONS) {
|
||||||
|
add_header 'Access-Control-Allow-Origin' 'http://web.argus.com' always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization' always;
|
||||||
|
add_header 'Content-Length' 0;
|
||||||
|
add_header 'Content-Type' 'text/plain';
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
src/web/build_tools/proxy/conf.d/metric.conf
Normal file
19
src/web/build_tools/proxy/conf.d/metric.conf
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Prometheus
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name prometheus.metric.argus.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://prometheus.metric.argus.com;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grafana
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name grafana.metric.argus.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://grafana.metric.argus.com;
|
||||||
|
}
|
||||||
|
}
|
8
src/web/build_tools/proxy/conf.d/web.conf
Normal file
8
src/web/build_tools/proxy/conf.d/web.conf
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name web.argus.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://web.argus.com;
|
||||||
|
}
|
||||||
|
}
|
68
src/web/build_tools/proxy/dns-monitor.sh
Normal file
68
src/web/build_tools/proxy/dns-monitor.sh
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# DNS监控脚本 - 每10秒检查dns.conf是否有变化
|
||||||
|
# 如果有变化则执行update-dns.sh脚本
|
||||||
|
|
||||||
|
DNS_CONF="/private/argus/etc/dns.conf"
|
||||||
|
DNS_BACKUP="/tmp/dns.conf.backup"
|
||||||
|
UPDATE_SCRIPT="/private/argus/etc/update-dns.sh"
|
||||||
|
LOG_FILE="/var/log/supervisor/dns-monitor.log"
|
||||||
|
|
||||||
|
# 确保日志文件存在
|
||||||
|
touch "$LOG_FILE"
|
||||||
|
|
||||||
|
log_message() {
|
||||||
|
echo "$(date '+%Y-%m-%d %H:%M:%S') [DNS-Monitor] $1" >> "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_message "DNS监控脚本启动"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if [ -f "$DNS_CONF" ]; then
|
||||||
|
if [ -f "$DNS_BACKUP" ]; then
|
||||||
|
# 比较文件内容
|
||||||
|
if ! cmp -s "$DNS_CONF" "$DNS_BACKUP"; then
|
||||||
|
log_message "检测到DNS配置变化"
|
||||||
|
|
||||||
|
# 更新备份文件
|
||||||
|
cp "$DNS_CONF" "$DNS_BACKUP"
|
||||||
|
|
||||||
|
# 执行更新脚本
|
||||||
|
if [ -x "$UPDATE_SCRIPT" ]; then
|
||||||
|
log_message "执行DNS更新脚本: $UPDATE_SCRIPT"
|
||||||
|
"$UPDATE_SCRIPT" >> "$LOG_FILE" 2>&1
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
log_message "DNS更新脚本执行成功"
|
||||||
|
else
|
||||||
|
log_message "DNS更新脚本执行失败"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_message "警告: 更新脚本不存在或不可执行: $UPDATE_SCRIPT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
|
||||||
|
# 第一次检测到配置文件,执行更新脚本
|
||||||
|
if [ -x "$UPDATE_SCRIPT" ]; then
|
||||||
|
log_message "执行DNS更新脚本: $UPDATE_SCRIPT"
|
||||||
|
"$UPDATE_SCRIPT" >> "$LOG_FILE" 2>&1
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
log_message "DNS更新脚本执行成功"
|
||||||
|
|
||||||
|
# 第一次运行,创建备份并执行更新
|
||||||
|
cp "$DNS_CONF" "$DNS_BACKUP"
|
||||||
|
log_message "创建DNS配置备份文件"
|
||||||
|
|
||||||
|
else
|
||||||
|
log_message "DNS更新脚本执行失败"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_message "警告: 更新脚本不存在或不可执行: $UPDATE_SCRIPT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_message "警告: DNS配置文件不存在: $DNS_CONF"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
done
|
27
src/web/build_tools/proxy/nginx.conf
Normal file
27
src/web/build_tools/proxy/nginx.conf
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
user web_proxy;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
sendfile on;
|
||||||
|
|
||||||
|
# 使用系统 resolv.conf(由 update-dns.sh 动态更新)
|
||||||
|
resolver $(awk '/^nameserver/ {print $2}' /etc/resolv.conf | tr '\n' ' ') valid=30s ipv6=off;
|
||||||
|
|
||||||
|
# 启用访问日志
|
||||||
|
access_log /var/log/nginx/access.log;
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
|
||||||
|
# 反向代理默认头部
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
}
|
27
src/web/build_tools/proxy/start-proxy-supervised.sh
Normal file
27
src/web/build_tools/proxy/start-proxy-supervised.sh
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "[INFO] Starting proxy under supervisor..."
|
||||||
|
|
||||||
|
DNS_DIR="/private/argus/etc"
|
||||||
|
DNS_SCRIPT="${DNS_DIR}/update-dns.sh"
|
||||||
|
RUNTIME_USER="${ARGUS_RUNTIME_USER:-argus}"
|
||||||
|
RUNTIME_UID="${ARGUS_BUILD_UID:-2133}"
|
||||||
|
RUNTIME_GID="${ARGUS_BUILD_GID:-2015}"
|
||||||
|
|
||||||
|
mkdir -p "$DNS_DIR"
|
||||||
|
chown -R "$RUNTIME_UID:$RUNTIME_GID" "$DNS_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
|
if [[ -x "$DNS_SCRIPT" ]]; then
|
||||||
|
echo "[INFO] Running update-dns.sh before master starts"
|
||||||
|
# 若脚本存在则执行,保证容器使用 bind 作为 DNS
|
||||||
|
"$DNS_SCRIPT" || echo "[WARN] update-dns.sh execution failed"
|
||||||
|
else
|
||||||
|
echo "[WARN] DNS update script not found or not executable: $DNS_SCRIPT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
echo "[INFO] Launching nginx..."
|
||||||
|
|
||||||
|
# 启动 nginx 前台模式
|
||||||
|
exec /usr/sbin/nginx -g "daemon off;"
|
39
src/web/build_tools/proxy/supervisord.conf
Normal file
39
src/web/build_tools/proxy/supervisord.conf
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
logfile=/var/log/supervisor/supervisord.log
|
||||||
|
pidfile=/var/run/supervisord.pid
|
||||||
|
user=root
|
||||||
|
|
||||||
|
[program:proxy]
|
||||||
|
command=/usr/local/bin/start-proxy-supervised.sh
|
||||||
|
user=root
|
||||||
|
stdout_logfile=/var/log/supervisor/web-proxy.log
|
||||||
|
stderr_logfile=/var/log/supervisor/web-proxy_error.log
|
||||||
|
autorestart=true
|
||||||
|
startretries=3
|
||||||
|
startsecs=5
|
||||||
|
stopwaitsecs=10
|
||||||
|
killasgroup=true
|
||||||
|
stopasgroup=true
|
||||||
|
|
||||||
|
[program:dns-monitor]
|
||||||
|
command=/usr/local/bin/dns-monitor.sh
|
||||||
|
user=root
|
||||||
|
stdout_logfile=/var/log/supervisor/dns-monitor.log
|
||||||
|
stderr_logfile=/var/log/supervisor/dns-monitor_error.log
|
||||||
|
autorestart=true
|
||||||
|
startretries=3
|
||||||
|
startsecs=5
|
||||||
|
stopwaitsecs=10
|
||||||
|
killasgroup=true
|
||||||
|
stopasgroup=true
|
||||||
|
|
||||||
|
[unix_http_server]
|
||||||
|
file=/var/run/supervisor.sock
|
||||||
|
chmod=0700
|
||||||
|
|
||||||
|
[supervisorctl]
|
||||||
|
serverurl=unix:///var/run/supervisor.sock
|
||||||
|
|
||||||
|
[rpcinterface:supervisor]
|
||||||
|
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
363
src/web/package-lock.json
generated
363
src/web/package-lock.json
generated
@ -8,6 +8,7 @@
|
|||||||
"name": "argus-web",
|
"name": "argus-web",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.14.0",
|
||||||
"@mantine/core": "^8.3.1",
|
"@mantine/core": "^8.3.1",
|
||||||
"@mantine/hooks": "^8.3.1",
|
"@mantine/hooks": "^8.3.1",
|
||||||
"@mantine/notifications": "^8.3.1",
|
"@mantine/notifications": "^8.3.1",
|
||||||
@ -33,7 +34,6 @@
|
|||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-validator-identifier": "^7.27.1",
|
"@babel/helper-validator-identifier": "^7.27.1",
|
||||||
@ -89,7 +89,6 @@
|
|||||||
"version": "7.28.3",
|
"version": "7.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
|
||||||
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
|
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.28.3",
|
"@babel/parser": "^7.28.3",
|
||||||
@ -123,7 +122,6 @@
|
|||||||
"version": "7.28.0",
|
"version": "7.28.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
||||||
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -133,7 +131,6 @@
|
|||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
|
||||||
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
|
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/traverse": "^7.27.1",
|
"@babel/traverse": "^7.27.1",
|
||||||
@ -175,7 +172,6 @@
|
|||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -185,7 +181,6 @@
|
|||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
||||||
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -219,7 +214,6 @@
|
|||||||
"version": "7.28.4",
|
"version": "7.28.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
|
||||||
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
|
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.28.4"
|
"@babel/types": "^7.28.4"
|
||||||
@ -276,7 +270,6 @@
|
|||||||
"version": "7.27.2",
|
"version": "7.27.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||||
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
@ -291,7 +284,6 @@
|
|||||||
"version": "7.28.4",
|
"version": "7.28.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
|
||||||
"integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
|
"integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
@ -310,7 +302,6 @@
|
|||||||
"version": "7.28.4",
|
"version": "7.28.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
|
||||||
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
|
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.27.1",
|
"@babel/helper-string-parser": "^7.27.1",
|
||||||
@ -320,6 +311,126 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@emotion/babel-plugin": {
|
||||||
|
"version": "11.13.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
||||||
|
"integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-module-imports": "^7.16.7",
|
||||||
|
"@babel/runtime": "^7.18.3",
|
||||||
|
"@emotion/hash": "^0.9.2",
|
||||||
|
"@emotion/memoize": "^0.9.0",
|
||||||
|
"@emotion/serialize": "^1.3.3",
|
||||||
|
"babel-plugin-macros": "^3.1.0",
|
||||||
|
"convert-source-map": "^1.5.0",
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
|
"find-root": "^1.1.0",
|
||||||
|
"source-map": "^0.5.7",
|
||||||
|
"stylis": "4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/babel-plugin/node_modules/convert-source-map": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/cache": {
|
||||||
|
"version": "11.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
|
||||||
|
"integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/memoize": "^0.9.0",
|
||||||
|
"@emotion/sheet": "^1.4.0",
|
||||||
|
"@emotion/utils": "^1.4.2",
|
||||||
|
"@emotion/weak-memoize": "^0.4.0",
|
||||||
|
"stylis": "4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/hash": {
|
||||||
|
"version": "0.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
|
||||||
|
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/memoize": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/react": {
|
||||||
|
"version": "11.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
||||||
|
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.18.3",
|
||||||
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
|
"@emotion/cache": "^11.14.0",
|
||||||
|
"@emotion/serialize": "^1.3.3",
|
||||||
|
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
|
||||||
|
"@emotion/utils": "^1.4.2",
|
||||||
|
"@emotion/weak-memoize": "^0.4.0",
|
||||||
|
"hoist-non-react-statics": "^3.3.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/serialize": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/hash": "^0.9.2",
|
||||||
|
"@emotion/memoize": "^0.9.0",
|
||||||
|
"@emotion/unitless": "^0.10.0",
|
||||||
|
"@emotion/utils": "^1.4.2",
|
||||||
|
"csstype": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/sheet": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/unitless": {
|
||||||
|
"version": "0.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
|
||||||
|
"integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/use-insertion-effect-with-fallbacks": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/utils": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@emotion/weak-memoize": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.25.9",
|
"version": "0.25.9",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz",
|
||||||
@ -1025,7 +1136,6 @@
|
|||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
@ -1047,7 +1157,6 @@
|
|||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
@ -1057,14 +1166,12 @@
|
|||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.31",
|
"version": "0.3.31",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
@ -1510,6 +1617,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/parse-json": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.1.12",
|
"version": "19.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
|
||||||
@ -1614,6 +1727,21 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/babel-plugin-macros": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"cosmiconfig": "^7.0.0",
|
||||||
|
"resolve": "^1.19.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10",
|
||||||
|
"npm": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@ -1669,7 +1797,6 @@
|
|||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@ -1765,6 +1892,31 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cosmiconfig": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/parse-json": "^4.0.0",
|
||||||
|
"import-fresh": "^3.2.1",
|
||||||
|
"parse-json": "^5.0.0",
|
||||||
|
"path-type": "^4.0.0",
|
||||||
|
"yaml": "^1.10.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cosmiconfig/node_modules/yaml": {
|
||||||
|
"version": "1.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||||
|
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@ -1790,7 +1942,6 @@
|
|||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@ -1834,6 +1985,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/error-ex": {
|
||||||
|
"version": "1.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
|
||||||
|
"integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-arrayish": "^0.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.9",
|
"version": "0.25.9",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
|
||||||
@ -1890,7 +2050,6 @@
|
|||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
@ -2129,6 +2288,12 @@
|
|||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/find-root": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/find-up": {
|
"node_modules/find-up": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||||
@ -2182,6 +2347,15 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gensync": {
|
"node_modules/gensync": {
|
||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.2",
|
||||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||||
@ -2237,6 +2411,27 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
@ -2251,7 +2446,6 @@
|
|||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||||
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"parent-module": "^1.0.0",
|
"parent-module": "^1.0.0",
|
||||||
@ -2274,6 +2468,27 @@
|
|||||||
"node": ">=0.8.19"
|
"node": ">=0.8.19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-arrayish": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/is-core-module": {
|
||||||
|
"version": "2.16.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
|
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-extglob": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
@ -2327,7 +2542,6 @@
|
|||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"jsesc": "bin/jsesc"
|
"jsesc": "bin/jsesc"
|
||||||
@ -2343,6 +2557,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/json-parse-even-better-errors": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/json-schema-traverse": {
|
"node_modules/json-schema-traverse": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
@ -2394,6 +2614,12 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lines-and-columns": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||||
@ -2456,7 +2682,6 @@
|
|||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
@ -2555,7 +2780,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"callsites": "^3.0.0"
|
"callsites": "^3.0.0"
|
||||||
@ -2564,6 +2788,24 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parse-json": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": "^7.0.0",
|
||||||
|
"error-ex": "^1.3.1",
|
||||||
|
"json-parse-even-better-errors": "^2.3.0",
|
||||||
|
"lines-and-columns": "^1.1.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-exists": {
|
"node_modules/path-exists": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
@ -2584,11 +2826,25 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/path-parse": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/path-type": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
@ -2851,11 +3107,30 @@
|
|||||||
"react-dom": ">=16.6.0"
|
"react-dom": ">=16.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/resolve": {
|
||||||
|
"version": "1.22.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
|
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-core-module": "^2.16.0",
|
||||||
|
"path-parse": "^1.0.7",
|
||||||
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"resolve": "bin/resolve"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/resolve-from": {
|
"node_modules/resolve-from": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
@ -2947,6 +3222,15 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/source-map": {
|
||||||
|
"version": "0.5.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
|
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@ -2970,6 +3254,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stylis": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/supports-color": {
|
"node_modules/supports-color": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
@ -2983,6 +3273,18 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/supports-preserve-symlinks-flag": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tabbable": {
|
"node_modules/tabbable": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
|
||||||
@ -3283,6 +3585,21 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/yaml": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.14.0",
|
||||||
"@mantine/core": "^8.3.1",
|
"@mantine/core": "^8.3.1",
|
||||||
"@mantine/hooks": "^8.3.1",
|
"@mantine/hooks": "^8.3.1",
|
||||||
"@mantine/notifications": "^8.3.1",
|
"@mantine/notifications": "^8.3.1",
|
||||||
|
@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
|
|||||||
import { Card, Text, Group, TextInput, Stack, ActionIcon } from "@mantine/core";
|
import { Card, Text, Group, TextInput, Stack, ActionIcon } from "@mantine/core";
|
||||||
import { IconEdit, IconX, IconCheck, IconPlus, IconTrash } from "@tabler/icons-react";
|
import { IconEdit, IconX, IconCheck, IconPlus, IconTrash } from "@tabler/icons-react";
|
||||||
import { apiRequest } from "../config/request";
|
import { apiRequest } from "../config/request";
|
||||||
import { EXTERNAL_API } from "../config/api";
|
import { MASTER_API } from "../config/api";
|
||||||
|
|
||||||
export default function NodeConfigCard({ nodeId, config = {}, onSaved }) {
|
export default function NodeConfigCard({ nodeId, config = {}, onSaved }) {
|
||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
@ -37,12 +37,23 @@ export default function NodeConfigCard({ nodeId, config = {}, onSaved }) {
|
|||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
const configObj = Object.fromEntries(configList);
|
let finalList = [...configList];
|
||||||
await apiRequest(`${EXTERNAL_API.MASTER_NODES}/${nodeId}`, {
|
// 如果有未点击“+”的新配置,补充进去
|
||||||
|
if (newKey && !finalList.find(([k]) => k === newKey)) {
|
||||||
|
finalList = [...finalList, [newKey, newValue]];
|
||||||
|
setNewKey("");
|
||||||
|
setNewValue("");
|
||||||
|
}
|
||||||
|
|
||||||
|
const configObj = Object.fromEntries(finalList);
|
||||||
|
|
||||||
|
await apiRequest(MASTER_API.CONFIG(nodeId), {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ config: configObj }),
|
body: JSON.stringify({ config: configObj }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setConfigList(finalList); // 更新 state,保持 UI 同步
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
onSaved && onSaved();
|
onSaved && onSaved();
|
||||||
} finally {
|
} finally {
|
||||||
@ -50,6 +61,7 @@ export default function NodeConfigCard({ nodeId, config = {}, onSaved }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card shadow="sm" radius="md" withBorder>
|
<Card shadow="sm" radius="md" withBorder>
|
||||||
<Group position="apart" mb="sm">
|
<Group position="apart" mb="sm">
|
||||||
@ -106,8 +118,14 @@ export default function NodeConfigCard({ nodeId, config = {}, onSaved }) {
|
|||||||
placeholder="新增 Value"
|
placeholder="新增 Value"
|
||||||
value={newValue}
|
value={newValue}
|
||||||
onChange={(e) => setNewValue(e.target.value)}
|
onChange={(e) => setNewValue(e.target.value)}
|
||||||
onKeyDown={(e) => e.key === "Enter" && addConfig()}
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
addConfig();
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ActionIcon color="blue" onClick={addConfig}>
|
<ActionIcon color="blue" onClick={addConfig}>
|
||||||
<IconPlus size={16} />
|
<IconPlus size={16} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
Drawer,
|
Drawer,
|
||||||
Text,
|
Text,
|
||||||
@ -9,10 +9,12 @@ import {
|
|||||||
Divider,
|
Divider,
|
||||||
ThemeIcon,
|
ThemeIcon,
|
||||||
Stack,
|
Stack,
|
||||||
|
ActionIcon,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import { IconRefresh } from "@tabler/icons-react";
|
||||||
import { healthStatus } from "../config/status";
|
import { healthStatus } from "../config/status";
|
||||||
import { apiRequest } from "../config/request";
|
import { apiRequest } from "../config/request";
|
||||||
import { EXTERNAL_API } from "../config/api";
|
import { MASTER_API } from "../config/api";
|
||||||
|
|
||||||
import NodeConfigCard from "./NodeConfigCard";
|
import NodeConfigCard from "./NodeConfigCard";
|
||||||
import NodeLabelCard from "./NodeLabelCard";
|
import NodeLabelCard from "./NodeLabelCard";
|
||||||
@ -23,11 +25,11 @@ export default function NodeDetailDrawer({ opened, nodeId, onClose }) {
|
|||||||
const [node, setNode] = useState(null);
|
const [node, setNode] = useState(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const fetchNodeDetail = async () => {
|
const fetchNodeDetail = async (id) => {
|
||||||
if (!nodeId) return;
|
if (!id) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await apiRequest(`${EXTERNAL_API.MASTER_NODES}/${nodeId}`);
|
const res = await apiRequest(MASTER_API.DETAIL(id));
|
||||||
setNode(res);
|
setNode(res);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -35,7 +37,7 @@ export default function NodeDetailDrawer({ opened, nodeId, onClose }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (opened && nodeId) fetchNodeDetail();
|
if (opened && nodeId) fetchNodeDetail(nodeId);
|
||||||
}, [opened, nodeId]);
|
}, [opened, nodeId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -48,7 +50,7 @@ export default function NodeDetailDrawer({ opened, nodeId, onClose }) {
|
|||||||
padding="lg"
|
padding="lg"
|
||||||
overlayProps={{ backgroundOpacity: 0.4, blur: 4 }}
|
overlayProps={{ backgroundOpacity: 0.4, blur: 4 }}
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading && !node ? (
|
||||||
<Center h={200}>
|
<Center h={200}>
|
||||||
<Loader size="sm" />
|
<Loader size="sm" />
|
||||||
</Center>
|
</Center>
|
||||||
@ -64,25 +66,36 @@ export default function NodeDetailDrawer({ opened, nodeId, onClose }) {
|
|||||||
paddingBottom: 8,
|
paddingBottom: 8,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Group spacing="sm" align="center">
|
<Group spacing="sm" align="center" position="apart">
|
||||||
<ThemeIcon
|
<Group spacing="sm" align="center">
|
||||||
size="lg"
|
<ThemeIcon
|
||||||
radius="xl"
|
size="lg"
|
||||||
color={healthStatus(node.status).color}
|
radius="xl"
|
||||||
variant="light"
|
color={healthStatus(node.status).color}
|
||||||
>
|
variant="light"
|
||||||
{healthStatus(node.status).icon}
|
>
|
||||||
</ThemeIcon>
|
{healthStatus(node.status).icon}
|
||||||
|
</ThemeIcon>
|
||||||
|
|
||||||
<Text fw={700} size="xl">
|
<Text fw={700} size="xl">{node.name}</Text>
|
||||||
{node.name}
|
<Text c="dimmed">{node.type}</Text>
|
||||||
</Text>
|
<Text c={healthStatus(node.status).color}>{node.status}</Text>
|
||||||
<Text c="dimmed">{node.type}</Text>
|
<Text c="dimmed" size="sm">
|
||||||
<Text c={healthStatus(node.status).color}>{node.status}</Text>
|
最近上报时间: {new Date(node.last_report).toLocaleString()}
|
||||||
<Text c="dimmed" size="sm">
|
</Text>
|
||||||
最后更新时间: {new Date(node.last_updated).toLocaleString()}
|
</Group>
|
||||||
</Text>
|
|
||||||
|
{/* 刷新按钮固定在右侧 */}
|
||||||
|
<ActionIcon
|
||||||
|
color="blue"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => fetchNodeDetail(node.id)}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
<IconRefresh size={18} />
|
||||||
|
</ActionIcon>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Divider my="sm" />
|
<Divider my="sm" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -90,10 +103,10 @@ export default function NodeDetailDrawer({ opened, nodeId, onClose }) {
|
|||||||
<ScrollArea style={{ flex: 1 }}>
|
<ScrollArea style={{ flex: 1 }}>
|
||||||
<Stack spacing="md">
|
<Stack spacing="md">
|
||||||
{/* 配置信息 */}
|
{/* 配置信息 */}
|
||||||
<NodeConfigCard nodeId={node.id} config={node.config || {}} onSaved={fetchNodeDetail} />
|
<NodeConfigCard nodeId={node.id} config={node.config || {}} onSaved={() => fetchNodeDetail(node.id)} />
|
||||||
|
|
||||||
{/* 标签信息 */}
|
{/* 标签信息 */}
|
||||||
<NodeLabelCard nodeId={node.id} labels={Array.isArray(node.label) ? node.label : []} onSaved={fetchNodeDetail} />
|
<NodeLabelCard nodeId={node.id} labels={Array.isArray(node.label) ? node.label : []} onSaved={() => fetchNodeDetail(node.id)} />
|
||||||
|
|
||||||
{/* 元数据 */}
|
{/* 元数据 */}
|
||||||
<NodeMetaCard node={node} />
|
<NodeMetaCard node={node} />
|
||||||
@ -105,6 +118,7 @@ export default function NodeDetailDrawer({ opened, nodeId, onClose }) {
|
|||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<Text fw={500}>注册时间: <Text span c="dimmed">{new Date(node.register_time).toLocaleString()}</Text></Text>
|
<Text fw={500}>注册时间: <Text span c="dimmed">{new Date(node.register_time).toLocaleString()}</Text></Text>
|
||||||
<Text fw={500}>最近上报时间: <Text span c="dimmed">{new Date(node.last_report).toLocaleString()}</Text></Text>
|
<Text fw={500}>最近上报时间: <Text span c="dimmed">{new Date(node.last_report).toLocaleString()}</Text></Text>
|
||||||
|
<Text fw={500}>最后更新时间: <Text span c="dimmed">{new Date(node.last_updated).toLocaleString()}</Text></Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
@ -1,14 +1,55 @@
|
|||||||
import { Card, Text, Stack } from "@mantine/core";
|
import { useState } from "react";
|
||||||
|
import { Card, Text, Stack, Group, ActionIcon, Badge, Popover } from "@mantine/core";
|
||||||
|
import { IconInfoCircle } from "@tabler/icons-react";
|
||||||
|
|
||||||
export default function NodeHealthCard({ node }) {
|
export default function NodeHealthCard({ node }) {
|
||||||
const health = node.health || {};
|
const health = node.health || {};
|
||||||
|
|
||||||
|
const renderHealthItem = (moduleName, data) => {
|
||||||
|
const status = data?.status || "unknown";
|
||||||
|
const color = status === "healthy" ? "green" : status === "unhealthy" ? "red" : "gray";
|
||||||
|
const [opened, setOpened] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group key={moduleName} spacing="xs" align="center">
|
||||||
|
<Text size="sm" fw={500}>{moduleName}</Text>
|
||||||
|
<Badge color={color} variant="light">{status}</Badge>
|
||||||
|
{(data?.error || data?.timestamp) && (
|
||||||
|
<Popover
|
||||||
|
opened={opened}
|
||||||
|
onClose={() => setOpened(false)}
|
||||||
|
position="bottom"
|
||||||
|
withArrow
|
||||||
|
shadow="sm"
|
||||||
|
>
|
||||||
|
<Popover.Target>
|
||||||
|
<ActionIcon size="xs" color="blue" variant="light" onClick={() => setOpened((o) => !o)}>
|
||||||
|
<IconInfoCircle size={14} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Popover.Target>
|
||||||
|
<Popover.Dropdown>
|
||||||
|
<Stack spacing={4}>
|
||||||
|
{data.error && <Text size="xs" c="red">Error: {data.error}</Text>}
|
||||||
|
{data.timestamp && (
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
Updated: {new Date(data.timestamp).toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Popover.Dropdown>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card shadow="xs" radius="md" withBorder>
|
<Card shadow="xs" radius="md" withBorder>
|
||||||
<Text fw={600} mb="sm">健康信息</Text>
|
<Text fw={600} mb="sm">健康信息</Text>
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<Text size="sm">日志: <Text span c="dimmed">{health.log || "无"}</Text></Text>
|
{Object.entries(health).map(([moduleName, data]) =>
|
||||||
<Text size="sm">指标: <Text span c="dimmed">{health.metric || "无"}</Text></Text>
|
renderHealthItem(moduleName, data)
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
|
|||||||
import { Card, Text, Group, TextInput, Stack, ActionIcon, Badge } from "@mantine/core";
|
import { Card, Text, Group, TextInput, Stack, ActionIcon, Badge } from "@mantine/core";
|
||||||
import { IconEdit, IconX, IconCheck, IconPlus, IconTrash } from "@tabler/icons-react";
|
import { IconEdit, IconX, IconCheck, IconPlus, IconTrash } from "@tabler/icons-react";
|
||||||
import { apiRequest } from "../config/request";
|
import { apiRequest } from "../config/request";
|
||||||
import { EXTERNAL_API } from "../config/api";
|
import { MASTER_API } from "../config/api";
|
||||||
|
|
||||||
export default function NodeLabelCard({ nodeId, labels = [], onSaved }) {
|
export default function NodeLabelCard({ nodeId, labels = [], onSaved }) {
|
||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
@ -12,7 +12,7 @@ export default function NodeLabelCard({ nodeId, labels = [], onSaved }) {
|
|||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
const randomColor = () => {
|
const randomColor = () => {
|
||||||
const colors = ["red","pink","grape","violet","indigo","blue","cyan","teal","green","lime","yellow","orange","gray"];
|
const colors = ["red", "pink", "grape", "violet", "indigo", "blue", "cyan", "teal", "green", "lime", "yellow", "orange", "gray"];
|
||||||
return colors[Math.floor(Math.random() * colors.length)];
|
return colors[Math.floor(Math.random() * colors.length)];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -42,11 +42,19 @@ export default function NodeLabelCard({ nodeId, labels = [], onSaved }) {
|
|||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
await apiRequest(`${EXTERNAL_API.MASTER_NODES}/${nodeId}`, {
|
let finalTags = [...tagList];
|
||||||
|
if (newTag && !finalTags.includes(newTag)) {
|
||||||
|
finalTags = [...finalTags, newTag];
|
||||||
|
setNewTag(""); // 清空输入框
|
||||||
|
}
|
||||||
|
|
||||||
|
await apiRequest(MASTER_API.CONFIG(nodeId), {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ label: tagList }),
|
body: JSON.stringify({ label: finalTags }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setTagList(finalTags);
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
onSaved && onSaved();
|
onSaved && onSaved();
|
||||||
} finally {
|
} finally {
|
||||||
@ -54,6 +62,7 @@ export default function NodeLabelCard({ nodeId, labels = [], onSaved }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card shadow="sm" radius="md" withBorder>
|
<Card shadow="sm" radius="md" withBorder>
|
||||||
<Group position="apart" mb="sm">
|
<Group position="apart" mb="sm">
|
||||||
@ -79,7 +88,18 @@ export default function NodeLabelCard({ nodeId, labels = [], onSaved }) {
|
|||||||
</Group>
|
</Group>
|
||||||
))}
|
))}
|
||||||
<Group spacing="xs">
|
<Group spacing="xs">
|
||||||
<TextInput placeholder="新增标签" value={newTag} onChange={(e) => setNewTag(e.target.value)} onKeyDown={(e) => e.key === "Enter" && addTag()} />
|
<TextInput
|
||||||
|
placeholder="新增标签"
|
||||||
|
value={newTag}
|
||||||
|
onChange={(e) => setNewTag(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault(); // 阻止默认提交行为
|
||||||
|
addTag();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<ActionIcon color="blue" onClick={addTag}><IconPlus size={16} /></ActionIcon>
|
<ActionIcon color="blue" onClick={addTag}><IconPlus size={16} /></ActionIcon>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Card, Table, Button, Loader, Center, TextInput, Select, Group, Anchor, Text } from "@mantine/core";
|
import { Card, Table, Button, Loader, Center, Group, Anchor, Text } from "@mantine/core";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import NodeStatus from "./NodeStatus";
|
import NodeStatus from "./NodeStatus";
|
||||||
import PaginationControl from "./PaginationControl";
|
import PaginationControl from "./PaginationControl";
|
||||||
import { apiRequest } from "../config/request";
|
import { apiRequest } from "../config/request";
|
||||||
import { EXTERNAL_API } from "../config/api";
|
import { MASTER_API } from "../config/api";
|
||||||
import { statusOptions } from "../config/status";
|
|
||||||
|
|
||||||
export function NodeTable({
|
export function NodeTable({
|
||||||
withSearch = false,
|
withSearch = false,
|
||||||
@ -17,15 +16,10 @@ export function NodeTable({
|
|||||||
viewMoreLink,
|
viewMoreLink,
|
||||||
}) {
|
}) {
|
||||||
const [nodes, setNodes] = useState([]);
|
const [nodes, setNodes] = useState([]);
|
||||||
const [totalCount, setTotalCount] = useState(0);
|
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [pageSize, setPageSize] = useState(5);
|
const [pageSize, setPageSize] = useState(5);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
// 搜索条件
|
|
||||||
const [searchName, setSearchName] = useState("");
|
|
||||||
const [searchStatus, setSearchStatus] = useState("");
|
|
||||||
|
|
||||||
// 拉取节点数据(仅 NodePage 使用)
|
// 拉取节点数据(仅 NodePage 使用)
|
||||||
const fetchNodes = async (params = {}) => {
|
const fetchNodes = async (params = {}) => {
|
||||||
if (!withPagination && !withSearch) return; // Dashboard 只用 clusterData
|
if (!withPagination && !withSearch) return; // Dashboard 只用 clusterData
|
||||||
@ -33,14 +27,11 @@ export function NodeTable({
|
|||||||
try {
|
try {
|
||||||
const query = new URLSearchParams({
|
const query = new URLSearchParams({
|
||||||
page: params.page || page,
|
page: params.page || page,
|
||||||
pageSize: params.pageSize || pageSize,
|
limit: params.pageSize || pageSize,
|
||||||
name: params.name !== undefined ? params.name : searchName,
|
|
||||||
status: params.status !== undefined ? params.status : searchStatus,
|
|
||||||
}).toString();
|
}).toString();
|
||||||
|
|
||||||
const result = await apiRequest(`${EXTERNAL_API.MASTER_NODES}?${query}`);
|
const result = await apiRequest(`${MASTER_API.LIST}?${query}`);
|
||||||
setNodes(result.data);
|
setNodes(result);
|
||||||
setTotalCount(result.total || 0);
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -51,8 +42,7 @@ export function NodeTable({
|
|||||||
if (withPagination || withSearch) {
|
if (withPagination || withSearch) {
|
||||||
fetchNodes();
|
fetchNodes();
|
||||||
} else if (clusterData) {
|
} else if (clusterData) {
|
||||||
setNodes(clusterData.nodes || []);
|
setNodes(clusterData || []);
|
||||||
setTotalCount(clusterData.total_nodes || 0);
|
|
||||||
}
|
}
|
||||||
}, [clusterData]);
|
}, [clusterData]);
|
||||||
|
|
||||||
@ -96,21 +86,6 @@ export function NodeTable({
|
|||||||
{/* 搜索区域 */}
|
{/* 搜索区域 */}
|
||||||
{withSearch && (
|
{withSearch && (
|
||||||
<div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
|
<div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
|
||||||
<TextInput
|
|
||||||
placeholder="搜索节点名称"
|
|
||||||
value={searchName}
|
|
||||||
onChange={(e) => setSearchName(e.target.value)}
|
|
||||||
onKeyDown={(e) => e.key === "Enter" && fetchNodes({ page: 1, name: searchName, status: searchStatus })}
|
|
||||||
style={{ width: 200 }}
|
|
||||||
/>
|
|
||||||
<Select
|
|
||||||
data={statusOptions}
|
|
||||||
value={searchStatus}
|
|
||||||
onChange={setSearchStatus}
|
|
||||||
placeholder="状态"
|
|
||||||
style={{ width: 120 }}
|
|
||||||
/>
|
|
||||||
<Button onClick={() => fetchNodes({ page: 1, name: searchName, status: searchStatus })}>搜索</Button>
|
|
||||||
<Button onClick={() => fetchNodes()} variant="outline">刷新列表</Button>
|
<Button onClick={() => fetchNodes()} variant="outline">刷新列表</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -135,9 +110,10 @@ export function NodeTable({
|
|||||||
|
|
||||||
{withPagination && (
|
{withPagination && (
|
||||||
<PaginationControl
|
<PaginationControl
|
||||||
totalItems={totalCount}
|
|
||||||
page={page}
|
page={page}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
|
hasPrevPage={page > 1}
|
||||||
|
hasNextPage={nodes.length === pageSize}
|
||||||
onPageChange={(p) => {
|
onPageChange={(p) => {
|
||||||
setPage(p);
|
setPage(p);
|
||||||
fetchNodes({ page: p });
|
fetchNodes({ page: p });
|
||||||
|
@ -1,17 +1,44 @@
|
|||||||
import { Select, Pagination } from "@mantine/core";
|
import { Button, Group, Select, Text } from "@mantine/core";
|
||||||
|
|
||||||
export default function PaginationControl({ totalItems, page, pageSize, onPageChange, onPageSizeChange }) {
|
export default function PaginationControl({
|
||||||
const totalPages = Math.ceil(totalItems / pageSize);
|
page,
|
||||||
|
pageSize,
|
||||||
|
onPageChange,
|
||||||
|
onPageSizeChange,
|
||||||
|
hasNextPage,
|
||||||
|
hasPrevPage,
|
||||||
|
}) {
|
||||||
|
const pageSizeValue = pageSize ? String(pageSize) : "10"; // 兜底,避免 undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: "flex", justifyContent: "space-between", marginTop: 16 }}>
|
<div style={{ display: "flex", justifyContent: "space-between", marginTop: 16 }}>
|
||||||
<Select
|
<Select
|
||||||
data={["5", "10", "20", "50"]}
|
data={["5", "10", "20", "50"]}
|
||||||
value={pageSize.toString()}
|
value={pageSizeValue}
|
||||||
onChange={(val) => onPageSizeChange(Number(val))}
|
onChange={(val) => {
|
||||||
|
if (val) onPageSizeChange(Number(val));
|
||||||
|
}}
|
||||||
style={{ width: 100 }}
|
style={{ width: 100 }}
|
||||||
/>
|
/>
|
||||||
<Pagination total={totalPages} page={page} onChange={onPageChange} />
|
<Group>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
disabled={!hasPrevPage}
|
||||||
|
onClick={() => onPageChange(page - 1)}
|
||||||
|
>
|
||||||
|
上一页
|
||||||
|
</Button>
|
||||||
|
<Text size="sm" style={{ minWidth: 60, textAlign: "center" }}>
|
||||||
|
第 {page} 页
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
disabled={!hasNextPage}
|
||||||
|
onClick={() => onPageChange(page + 1)}
|
||||||
|
>
|
||||||
|
下一页
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,30 @@
|
|||||||
export const EXTERNAL_API = {
|
// config/api.js
|
||||||
MASTER_NODES: "http://master.argus.com/api/v1/master/nodes",
|
|
||||||
MASTER_NODES_STATISTICS: "http://master.argus.com/api/v1/master/nodes/statistics",
|
|
||||||
ALERTS_INFOS: "http://localhost:9093/api/v2/alerts",
|
|
||||||
}
|
|
||||||
|
|
||||||
// proxy location定位到具体位置。
|
// Master 节点相关 API
|
||||||
// proxy需要单独的机器,nginx配置。提供对外的endpoint,通过算力平台映射。
|
export const MASTER_API = {
|
||||||
|
// 节点列表
|
||||||
|
LIST: "http://master.argus.com/api/v1/master/nodes",
|
||||||
|
|
||||||
|
// 节点详情(需要 nodeId)
|
||||||
|
DETAIL: (nodeId) => `http://master.argus.com/api/v1/master/nodes/${nodeId}`,
|
||||||
|
|
||||||
|
// 节点配置(需要 nodeId)
|
||||||
|
CONFIG: (nodeId) => `http://master.argus.com/api/v1/master/nodes/${nodeId}/config`,
|
||||||
|
|
||||||
|
// 节点统计信息
|
||||||
|
STATISTICS: "http://master.argus.com/api/v1/master/nodes/statistics",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 其他外部 API
|
||||||
|
export const EXTERNAL_API = {
|
||||||
|
ALERTS_INFOS: "http://localhost:9093/api/v2/alerts",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 外部服务 Host
|
||||||
export const EXTERNAL_HOST = {
|
export const EXTERNAL_HOST = {
|
||||||
ALERTS: "http://localhost:9093",
|
ALERTS: "http://localhost:9093",
|
||||||
GRAFANA: "http://grafana.metric.argus.com",
|
GRAFANA: "http://grafana.metric.argus.com",
|
||||||
PROMETHEUS: "http://prometheus.metric.argus.com",
|
PROMETHEUS: "http://prometheus.metric.argus.com",
|
||||||
ES: "http://es.log.argus.com",
|
ES: "http://es.log.argus.com",
|
||||||
KIBANA: "http://kibana.log.argus.com",
|
KIBANA: "http://kibana.log.argus.com",
|
||||||
}
|
};
|
||||||
|
@ -35,76 +35,13 @@ export async function apiRequest(url, options = {}, successMsg) {
|
|||||||
return data;
|
return data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("API 请求错误:", err);
|
console.log("API 请求错误:", err);
|
||||||
// notifications.show({
|
notifications.show({
|
||||||
// title: "操作失败",
|
title: "操作失败",
|
||||||
// message: err.message || "接口调用失败",
|
message: err.message || "接口调用失败",
|
||||||
// color: "red",
|
color: "red",
|
||||||
// });
|
});
|
||||||
// throw err; // 继续抛出错误,方便上层处理
|
throw err; // 继续抛出错误,方便上层处理
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回 mock 数据
|
|
||||||
if (url.includes("/api/v1/master/nodes")) {
|
}
|
||||||
if (url.includes("/statistics")) {
|
|
||||||
return {
|
|
||||||
"total": 30,
|
|
||||||
"status_statistics": [
|
|
||||||
{ "status": "online", "count": 20 },
|
|
||||||
{ "status": "offline", "count": 10 },
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (/\/api\/v1\/master\/nodes\/[^\/]+$/.test(url)) {
|
|
||||||
return {
|
|
||||||
"id": "A1", // master分配的唯一ID, A代表Agent,数字1开始按顺序编号c
|
|
||||||
"name": "Node 1", // agent上报时提供的hostname
|
|
||||||
"status": "online",
|
|
||||||
"config": {
|
|
||||||
// !!! 预留字段,KV形式非固定key, web配置下发给agent用的
|
|
||||||
"setting1": "value1",
|
|
||||||
"setting2": "value2",
|
|
||||||
"setting3": "value3",
|
|
||||||
"setting4": "value4"
|
|
||||||
},
|
|
||||||
"meta_data": {
|
|
||||||
// 元数据: 容器生命周期内不变
|
|
||||||
"hostname": "dev-yyrshare-nbnyx10-cp2f-pod-0",
|
|
||||||
"ip": "177.177.74.223",
|
|
||||||
"env": "dev", // 环境名, 从hostname中提取第一段
|
|
||||||
"user": "yyrshare", // 账户名,从hostname中提取第二段
|
|
||||||
"instance": "nbnyx10", // 容器示例名,从hostname中提取第三段
|
|
||||||
"cpu_number": 16,
|
|
||||||
"memory_in_bytes": 2015040000,
|
|
||||||
"gpu_number": 8
|
|
||||||
},
|
|
||||||
"label": [
|
|
||||||
// 用户或运维人员绑定到节点上的标签,业务属性, tag
|
|
||||||
"gpu", "exp001"
|
|
||||||
],
|
|
||||||
"health": { // agent收集到各端上模块的health描述文件
|
|
||||||
"log": "", //字符串,转义,防止换行、引号
|
|
||||||
"metric": ""
|
|
||||||
},
|
|
||||||
"register_time": "2023-10-03T12:00:00Z", // 节点注册时间
|
|
||||||
"last_report": "2023-10-03T12:00:00Z", // 最近一次上报时间
|
|
||||||
"last_updated": "2023-10-05T12:00:00Z", // 更新NodeObject落库时间戳
|
|
||||||
"type": "agent" // 缺省为agent,未来可能有新的节点类型
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
"total": 30,
|
|
||||||
"data": [
|
|
||||||
{ id: "node1", name: "节点A", status: "online", type: "agent", version: "1.0.0" },
|
|
||||||
{ id: "node2", name: "节点B", status: "online", type: "agent", version: "1.0.0" },
|
|
||||||
{ id: "node3", name: "节点C", status: "offline", type: "agent", version: "1.0.0" },
|
|
||||||
{ id: "node4", name: "节点D", status: "online", type: "agent", version: "1.0.0" },
|
|
||||||
{ id: "node5", name: "节点E", status: "online", type: "agent", version: "1.0.0" },
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
|
|
||||||
}
|
|
@ -5,6 +5,7 @@ import { HealthCard } from "../components/HealthCard";
|
|||||||
import { AlertStats } from "../components/AlertStats";
|
import { AlertStats } from "../components/AlertStats";
|
||||||
import { apiRequest } from "../config/request";
|
import { apiRequest } from "../config/request";
|
||||||
import { EXTERNAL_API } from "../config/api";
|
import { EXTERNAL_API } from "../config/api";
|
||||||
|
import { MASTER_API } from "../config/api";
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const [cluster, setCluster] = useState(null);
|
const [cluster, setCluster] = useState(null);
|
||||||
@ -28,15 +29,12 @@ export default function Dashboard() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const [clusterRes, healthRes, alertsRes] = await Promise.all([
|
const [clusterRes, healthRes, alertsRes] = await Promise.all([
|
||||||
apiRequest(EXTERNAL_API.MASTER_NODES),
|
apiRequest(MASTER_API.LIST),
|
||||||
apiRequest(EXTERNAL_API.MASTER_NODES_STATISTICS),
|
apiRequest(MASTER_API.STATISTICS),
|
||||||
apiRequest(EXTERNAL_API.ALERTS_INFOS),
|
apiRequest(EXTERNAL_API.ALERTS_INFOS),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setCluster({
|
setCluster(clusterRes || []);
|
||||||
total_nodes: clusterRes?.total || 0,
|
|
||||||
nodes: clusterRes?.data || [],
|
|
||||||
});
|
|
||||||
|
|
||||||
setHealth({
|
setHealth({
|
||||||
total: healthRes?.total || 0,
|
total: healthRes?.total || 0,
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Grid } from "@mantine/core";
|
import { Grid } from "@mantine/core";
|
||||||
import { apiRequest } from "../config/request";
|
import { apiRequest } from "../config/request";
|
||||||
import { EXTERNAL_API } from "../config/api";
|
import { MASTER_API } from "../config/api";
|
||||||
import { NodeTable } from "../components/NodeTable";
|
import { NodeTable } from "../components/NodeTable";
|
||||||
import NodeDetailDrawer from "../components/NodeDetailDrawer";
|
import NodeDetailDrawer from "../components/NodeDetailDrawer";
|
||||||
|
|
||||||
export default function NodePage() {
|
export default function NodePage() {
|
||||||
const [selectedNode, setSelectedNode] = useState(null);
|
const [selectedNodeId, setSelectedNodeId] = useState(null);
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
const [detailLoading, setDetailLoading] = useState(false);
|
const [detailLoading, setDetailLoading] = useState(false);
|
||||||
|
|
||||||
@ -15,8 +15,8 @@ export default function NodePage() {
|
|||||||
setDetailLoading(true);
|
setDetailLoading(true);
|
||||||
setDrawerOpen(true);
|
setDrawerOpen(true);
|
||||||
try {
|
try {
|
||||||
const result = await apiRequest(`${EXTERNAL_API.MASTER_NODES}/${id}`);
|
const result = await apiRequest(MASTER_API.DETAIL(id));
|
||||||
setSelectedNode(result);
|
setSelectedNodeId(result.id);
|
||||||
} finally {
|
} finally {
|
||||||
setDetailLoading(false);
|
setDetailLoading(false);
|
||||||
}
|
}
|
||||||
@ -38,7 +38,7 @@ export default function NodePage() {
|
|||||||
<NodeDetailDrawer
|
<NodeDetailDrawer
|
||||||
opened={drawerOpen}
|
opened={drawerOpen}
|
||||||
onClose={() => setDrawerOpen(false)}
|
onClose={() => setDrawerOpen(false)}
|
||||||
nodeId={selectedNode}
|
nodeId={selectedNodeId}
|
||||||
loading={detailLoading}
|
loading={detailLoading}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user