mirror of
https://github.com/whyour/qinglong.git
synced 2025-07-28 23:46:06 +08:00
Merge branch 'develop' into develop
This commit is contained in:
commit
66d6c2f13c
59
.github/workflows/build_base.yml
vendored
59
.github/workflows/build_base.yml
vendored
|
@ -1,59 +0,0 @@
|
||||||
name: Build QL BASE
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '00 18 * * *' # GMT 14:00 => 北京时间 2:00
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: '14'
|
|
||||||
|
|
||||||
- name: Set time zone
|
|
||||||
uses: szenius/set-timezone@v1.0
|
|
||||||
with:
|
|
||||||
timezoneLinux: "Asia/Shanghai"
|
|
||||||
timezoneMacos: "Asia/Shanghai"
|
|
||||||
timezoneWindows: "China Standard Time"
|
|
||||||
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Login to GHCR
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
id: docker_build
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
build-args: |
|
|
||||||
MAINTAINER=${{ github.repository_owner }}
|
|
||||||
QL_BRANCH=${{ github.ref_name }}
|
|
||||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
|
|
||||||
context: .
|
|
||||||
file: docker/base/Dockerfile
|
|
||||||
push: true
|
|
||||||
tags: whyour/ql:base
|
|
|
@ -16,6 +16,10 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
favicon: '/images/g5.ico',
|
favicon: '/images/g5.ico',
|
||||||
proxy: {
|
proxy: {
|
||||||
|
'/api/public': {
|
||||||
|
target: 'http://127.0.0.1:5400/',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://127.0.0.1:5600/',
|
target: 'http://127.0.0.1:5600/',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
|
21
README.md
21
README.md
|
@ -44,6 +44,27 @@
|
||||||
# 待完善
|
# 待完善
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### podman 部署
|
||||||
|
|
||||||
|
1. podman 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
https://podman.io/getting-started/installation
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 启动容器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
podman run -dit \
|
||||||
|
--network bridge \
|
||||||
|
-v $PWD/ql:/ql/data \
|
||||||
|
-p 5700:5700 \
|
||||||
|
--name qinglong \
|
||||||
|
--hostname qinglong \
|
||||||
|
--restart unless-stopped \
|
||||||
|
docker.io/whyour/qinglong:latest
|
||||||
|
```
|
||||||
|
|
||||||
### docker 部署
|
### docker 部署
|
||||||
|
|
||||||
1. docker 安装
|
1. docker 安装
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
FROM whyour/ql:base
|
FROM node:alpine
|
||||||
|
|
||||||
ARG QL_MAINTAINER="whyour"
|
ARG QL_MAINTAINER="whyour"
|
||||||
LABEL maintainer="${QL_MAINTAINER}"
|
LABEL maintainer="${QL_MAINTAINER}"
|
||||||
ARG QL_URL=https://github.com/${QL_MAINTAINER}/qinglong.git
|
ARG QL_URL=https://github.com/${QL_MAINTAINER}/qinglong.git
|
||||||
|
@ -13,17 +14,45 @@ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
|
||||||
|
|
||||||
WORKDIR ${QL_DIR}
|
WORKDIR ${QL_DIR}
|
||||||
|
|
||||||
RUN git clone -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
RUN set -x \
|
||||||
|
&& sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||||
|
&& apk update -f \
|
||||||
|
&& apk upgrade \
|
||||||
|
&& apk --no-cache add -f bash \
|
||||||
|
coreutils \
|
||||||
|
moreutils \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
tzdata \
|
||||||
|
perl \
|
||||||
|
openssl \
|
||||||
|
nginx \
|
||||||
|
python3 \
|
||||||
|
jq \
|
||||||
|
openssh \
|
||||||
|
py3-pip \
|
||||||
|
&& rm -rf /var/cache/apk/* \
|
||||||
|
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||||
|
&& echo "Asia/Shanghai" > /etc/timezone \
|
||||||
|
&& touch ~/.bashrc \
|
||||||
|
&& git config --global user.email "qinglong@@users.noreply.github.com" \
|
||||||
|
&& git config --global user.name "qinglong" \
|
||||||
|
&& npm install -g pnpm \
|
||||||
|
&& pnpm install -g pm2 \
|
||||||
|
&& pnpm install -g ts-node typescript tslib \
|
||||||
|
&& git clone -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
||||||
&& cd ${QL_DIR} \
|
&& cd ${QL_DIR} \
|
||||||
&& cp -f .env.example .env \
|
&& cp -f .env.example .env \
|
||||||
&& chmod 777 ${QL_DIR}/shell/*.sh \
|
&& chmod 777 ${QL_DIR}/shell/*.sh \
|
||||||
&& chmod 777 ${QL_DIR}/docker/*.sh \
|
&& chmod 777 ${QL_DIR}/docker/*.sh \
|
||||||
&& cp -rf /node_modules ./ \
|
|
||||||
&& rm -rf /node_modules \
|
|
||||||
&& pnpm install --prod \
|
&& pnpm install --prod \
|
||||||
&& rm -rf /root/.pnpm-store \
|
&& rm -rf /root/.pnpm-store \
|
||||||
&& rm -rf /root/.cache \
|
&& rm -rf /root/.cache \
|
||||||
|
&& rm -rf /root/.npm \
|
||||||
&& git clone -b ${QL_BRANCH} https://github.com/${QL_MAINTAINER}/qinglong-static.git /static \
|
&& git clone -b ${QL_BRANCH} https://github.com/${QL_MAINTAINER}/qinglong-static.git /static \
|
||||||
&& cp -rf /static/* ${QL_DIR} \
|
&& mkdir -p ${QL_DIR}/static \
|
||||||
|
&& cp -rf /static/* ${QL_DIR}/static \
|
||||||
&& rm -rf /static
|
&& rm -rf /static
|
||||||
|
|
||||||
ENTRYPOINT ["./docker/docker-entrypoint.sh"]
|
ENTRYPOINT ["./docker/docker-entrypoint.sh"]
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
FROM node:alpine
|
|
||||||
ARG QL_MAINTAINER="whyour"
|
|
||||||
LABEL maintainer="${QL_MAINTAINER}"
|
|
||||||
|
|
||||||
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
|
|
||||||
LANG=zh_CN.UTF-8 \
|
|
||||||
SHELL=/bin/bash \
|
|
||||||
PS1="\u@\h:\w \$ "
|
|
||||||
|
|
||||||
COPY package.json /
|
|
||||||
|
|
||||||
RUN set -x \
|
|
||||||
&& sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
|
||||||
&& apk update -f \
|
|
||||||
&& apk upgrade \
|
|
||||||
&& apk --no-cache add -f bash \
|
|
||||||
coreutils \
|
|
||||||
moreutils \
|
|
||||||
git \
|
|
||||||
curl \
|
|
||||||
wget \
|
|
||||||
tzdata \
|
|
||||||
perl \
|
|
||||||
openssl \
|
|
||||||
nginx \
|
|
||||||
python3 \
|
|
||||||
jq \
|
|
||||||
openssh \
|
|
||||||
py3-pip \
|
|
||||||
python2 \
|
|
||||||
g++ \
|
|
||||||
make \
|
|
||||||
&& rm -rf /var/cache/apk/* \
|
|
||||||
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
|
||||||
&& echo "Asia/Shanghai" > /etc/timezone \
|
|
||||||
&& touch ~/.bashrc \
|
|
||||||
&& git config --global user.email "qinglong@@users.noreply.github.com" \
|
|
||||||
&& git config --global user.name "qinglong" \
|
|
||||||
&& npm install -g pnpm \
|
|
||||||
&& pnpm install -g pm2 \
|
|
||||||
&& pnpm install -g ts-node typescript tslib \
|
|
||||||
&& cd / && pnpm install --prod \
|
|
||||||
&& apk --purge del python2 g++ make \
|
|
||||||
&& rm -rf /root/.npm \
|
|
||||||
&& rm -rf /root/.pnpm-store \
|
|
||||||
&& rm -rf /root/.cache \
|
|
||||||
&& rm -f /package.json
|
|
||||||
|
|
||||||
CMD [ "node" ]
|
|
|
@ -4,10 +4,12 @@
|
||||||
"start": "concurrently -n w: npm:start:*",
|
"start": "concurrently -n w: npm:start:*",
|
||||||
"start:front": "umi dev",
|
"start:front": "umi dev",
|
||||||
"start:back": "nodemon",
|
"start:back": "nodemon",
|
||||||
|
"start:public": "ts-node back/public.ts",
|
||||||
"build:front": "umi build",
|
"build:front": "umi build",
|
||||||
"build:back": "tsc -p tsconfig.back.json",
|
"build:back": "tsc -p tsconfig.back.json",
|
||||||
"panel": "npm run build:back && node static/build/app.js",
|
"panel": "npm run build:back && node static/build/app.js",
|
||||||
"schedule": "npm run build:back && node static/build/schedule.js",
|
"schedule": "npm run build:back && node static/build/schedule.js",
|
||||||
|
"public": "npm run build:back && node static/build/public.js",
|
||||||
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
||||||
"prepare": "umi generate tmp",
|
"prepare": "umi generate tmp",
|
||||||
"test": "umi-test",
|
"test": "umi-test",
|
||||||
|
|
12
shell/bot.sh
12
shell/bot.sh
|
@ -22,9 +22,9 @@ if [[ ! -d ${repo_path}/.git ]]; then
|
||||||
git_clone_scripts ${url} ${repo_path} "main"
|
git_clone_scripts ${url} ${repo_path} "main"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cp -rf "$repo_path/jbot" $dir_root
|
cp -rf "$repo_path/jbot" $dir_data
|
||||||
if [[ ! -f "$dir_root/config/bot.json" ]]; then
|
if [[ ! -f "$dir_config/bot.json" ]]; then
|
||||||
cp -f "$repo_path/config/bot.json" "$dir_root/config"
|
cp -f "$repo_path/config/bot.json" "$dir_config"
|
||||||
fi
|
fi
|
||||||
echo -e "\nbot文件下载成功...\n"
|
echo -e "\nbot文件下载成功...\n"
|
||||||
|
|
||||||
|
@ -32,9 +32,9 @@ echo -e "3、安装python3依赖...\n"
|
||||||
if [[ $PipMirror ]]; then
|
if [[ $PipMirror ]]; then
|
||||||
pip3 config set global.index-url $PipMirror
|
pip3 config set global.index-url $PipMirror
|
||||||
fi
|
fi
|
||||||
cp -f "$repo_path/jbot/requirements.txt" "$dir_root"
|
cp -f "$repo_path/jbot/requirements.txt" "$dir_data"
|
||||||
|
|
||||||
cd $dir_root
|
cd $dir_data
|
||||||
cat requirements.txt | while read LREAD
|
cat requirements.txt | while read LREAD
|
||||||
do
|
do
|
||||||
if [[ ! $(pip3 show "${LREAD%%=*}" 2>/dev/null) ]]; then
|
if [[ ! $(pip3 show "${LREAD%%=*}" 2>/dev/null) ]]; then
|
||||||
|
@ -46,7 +46,7 @@ echo -e "\npython3依赖安装成功...\n"
|
||||||
|
|
||||||
echo -e "4、启动bot程序...\n"
|
echo -e "4、启动bot程序...\n"
|
||||||
make_dir $dir_log/bot
|
make_dir $dir_log/bot
|
||||||
cd $dir_root
|
cd $dir_data
|
||||||
ps -ef | grep "python3 -m jbot" | grep -v grep | awk '{print $1}' | xargs kill -9 2>/dev/null
|
ps -ef | grep "python3 -m jbot" | grep -v grep | awk '{print $1}' | xargs kill -9 2>/dev/null
|
||||||
nohup python3 -m jbot >$dir_log/bot/nohup.log 2>&1 &
|
nohup python3 -m jbot >$dir_log/bot/nohup.log 2>&1 &
|
||||||
echo -e "bot启动成功...\n"
|
echo -e "bot启动成功...\n"
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
.monaco-editor:not(.rename-box) {
|
.monaco-editor:not(.rename-box) {
|
||||||
height: calc(100vh - 128px) !important;
|
height: calc(100vh - 128px) !important;
|
||||||
height: calc(100vh - var(--vh-offset, 0px) - 128px) !important;
|
height: calc(100vh - var(--vh-offset, 0px) - 128px) !important;
|
||||||
|
|
||||||
.view-overlays .current-line {
|
.view-overlays .current-line {
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
}
|
}
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
|
|
||||||
.rename-box {
|
.rename-box {
|
||||||
height: 0;
|
height: 0;
|
||||||
|
|
||||||
.rename-input {
|
.rename-input {
|
||||||
height: 0;
|
height: 0;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
@ -43,6 +45,7 @@
|
||||||
.ant-pro-grid-content.wide {
|
.ant-pro-grid-content.wide {
|
||||||
max-width: unset !important;
|
max-width: unset !important;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
.ant-pro-page-container-children-content {
|
.ant-pro-page-container-children-content {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -57,6 +60,7 @@
|
||||||
|
|
||||||
.ant-tooltip {
|
.ant-tooltip {
|
||||||
max-width: 500px !important;
|
max-width: 500px !important;
|
||||||
|
|
||||||
.ant-tooltip-inner {
|
.ant-tooltip-inner {
|
||||||
word-break: break-all !important;
|
word-break: break-all !important;
|
||||||
}
|
}
|
||||||
|
@ -72,6 +76,7 @@
|
||||||
.log-select {
|
.log-select {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-page-header-heading-left {
|
.ant-page-header-heading-left {
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
|
@ -81,6 +86,7 @@
|
||||||
.config-select {
|
.config-select {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-page-header-heading-left {
|
.ant-page-header-heading-left {
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
|
@ -119,10 +125,13 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: calc(100vh - 48px);
|
height: calc(100vh - 48px);
|
||||||
height: calc(100vh - var(--vh-offset, 0px) - 48px);
|
height: calc(100vh - var(--vh-offset, 0px) - 48px);
|
||||||
|
|
||||||
.ant-pro-grid-content.wide {
|
.ant-pro-grid-content.wide {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
.ant-pro-grid-content-children {
|
.ant-pro-grid-content-children {
|
||||||
height: calc(100% - 36px);
|
height: calc(100% - 36px);
|
||||||
|
|
||||||
> div,
|
> div,
|
||||||
.log-container,
|
.log-container,
|
||||||
.react-codemirror2,
|
.react-codemirror2,
|
||||||
|
@ -197,6 +206,7 @@
|
||||||
.Resizer.disabled {
|
.Resizer.disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Resizer.disabled:hover {
|
.Resizer.disabled:hover {
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
@ -209,6 +219,7 @@
|
||||||
|
|
||||||
.inline-countdown.ant-statistic {
|
.inline-countdown.ant-statistic {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
.ant-statistic-content {
|
.ant-statistic-content {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 0 3px;
|
padding: 0 3px;
|
||||||
|
@ -253,6 +264,7 @@
|
||||||
|
|
||||||
.ant-pro-basicLayout-content {
|
.ant-pro-basicLayout-content {
|
||||||
margin: 18px;
|
margin: 18px;
|
||||||
|
|
||||||
.ant-pro-page-container {
|
.ant-pro-page-container {
|
||||||
margin: -18px -18px -18px;
|
margin: -18px -18px -18px;
|
||||||
}
|
}
|
||||||
|
@ -280,25 +292,27 @@
|
||||||
|
|
||||||
.side-menu-user-drop-menu {
|
.side-menu-user-drop-menu {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0;
|
|
||||||
padding: 4px 0;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
background-clip: padding-box;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: #fff;
|
padding: 4px 0;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 0 6px 16px -8px rgb(0 0 0 / 8%), 0 9px 28px 0 rgb(0 0 0 / 5%),
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||||
0 12px 48px 16px rgb(0 0 0 / 3%);
|
overflow: auto;
|
||||||
.ant-dropdown-menu-item:hover {
|
background-color: #fff;
|
||||||
color: #1890ff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-dark='true'] .side-menu-user-drop-menu {
|
||||||
|
background-color: #373739;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-pro-sider-logo {
|
.ant-pro-sider-logo {
|
||||||
padding: 16px 8px !important;
|
padding: 16px 8px !important;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin-left: 5px !important;
|
margin-left: 5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 32px !important;
|
width: 32px !important;
|
||||||
border-radius: 52% !important;
|
border-radius: 52% !important;
|
||||||
|
|
|
@ -272,7 +272,7 @@ export default function (props: any) {
|
||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
rightContentRender={() =>
|
rightContentRender={() =>
|
||||||
ctx.isPhone && (
|
ctx.isPhone && (
|
||||||
<Dropdown overlay={menu} trigger={['click']}>
|
<Dropdown overlay={menu} placement="bottomRight" trigger={['click']}>
|
||||||
<span className="side-menu-user-wrapper">
|
<span className="side-menu-user-wrapper">
|
||||||
<Avatar shape="square" size="small" icon={<UserOutlined />} />
|
<Avatar shape="square" size="small" icon={<UserOutlined />} />
|
||||||
<span style={{ marginLeft: 5 }}>admin</span>
|
<span style={{ marginLeft: 5 }}>admin</span>
|
||||||
|
@ -289,7 +289,7 @@ export default function (props: any) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!collapsed && !ctx.isPhone && (
|
{!collapsed && !ctx.isPhone && (
|
||||||
<Dropdown overlay={menu} trigger={['hover']}>
|
<Dropdown overlay={menu} placement="topLeft" trigger={['hover']}>
|
||||||
<span className="side-menu-user-wrapper">
|
<span className="side-menu-user-wrapper">
|
||||||
<Avatar shape="square" size="small" icon={<UserOutlined />} />
|
<Avatar shape="square" size="small" icon={<UserOutlined />} />
|
||||||
<span style={{ marginLeft: 5 }}>admin</span>
|
<span style={{ marginLeft: 5 }}>admin</span>
|
||||||
|
|
|
@ -50,11 +50,13 @@ const CronDetailModal = ({
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
visible,
|
||||||
theme,
|
theme,
|
||||||
|
isPhone,
|
||||||
}: {
|
}: {
|
||||||
cron?: any;
|
cron?: any;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
handleCancel: (needUpdate?: boolean) => void;
|
handleCancel: (needUpdate?: boolean) => void;
|
||||||
theme: string;
|
theme: string;
|
||||||
|
isPhone: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const [activeTabKey, setActiveTabKey] = useState('log');
|
const [activeTabKey, setActiveTabKey] = useState('log');
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
@ -64,6 +66,8 @@ const CronDetailModal = ({
|
||||||
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
|
||||||
const editorRef = useRef<any>(null);
|
const editorRef = useRef<any>(null);
|
||||||
const [scriptInfo, setScriptInfo] = useState<any>({});
|
const [scriptInfo, setScriptInfo] = useState<any>({});
|
||||||
|
const [logUrl, setLogUrl] = useState('');
|
||||||
|
const [validTabs, setValidTabs] = useState(tabList);
|
||||||
|
|
||||||
const contentList: any = {
|
const contentList: any = {
|
||||||
log: (
|
log: (
|
||||||
|
@ -99,6 +103,8 @@ const CronDetailModal = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickItem = (item: LogItem) => {
|
const onClickItem = (item: LogItem) => {
|
||||||
|
localStorage.setItem('logCron', cron.id);
|
||||||
|
setLogUrl(`${config.apiPrefix}logs/${item.directory}/${item.filename}`);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}logs/${item.directory}/${item.filename}`)
|
.get(`${config.apiPrefix}logs/${item.directory}/${item.filename}`)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
|
@ -126,6 +132,7 @@ const CronDetailModal = ({
|
||||||
const getScript = () => {
|
const getScript = () => {
|
||||||
const cmd = cron.command.split(' ') as string[];
|
const cmd = cron.command.split(' ') as string[];
|
||||||
if (cmd[0] === 'task') {
|
if (cmd[0] === 'task') {
|
||||||
|
setValidTabs(validTabs);
|
||||||
if (cmd[1].startsWith('/ql/data/scripts')) {
|
if (cmd[1].startsWith('/ql/data/scripts')) {
|
||||||
cmd[1] = cmd[1].replace('/ql/data/scripts/', '');
|
cmd[1] = cmd[1].replace('/ql/data/scripts/', '');
|
||||||
}
|
}
|
||||||
|
@ -141,6 +148,8 @@ const CronDetailModal = ({
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setValue(data.data);
|
setValue(data.data);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
setValidTabs([validTabs[0]]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -171,6 +180,7 @@ const CronDetailModal = ({
|
||||||
})
|
})
|
||||||
.then((_data: any) => {
|
.then((_data: any) => {
|
||||||
if (_data.code === 200) {
|
if (_data.code === 200) {
|
||||||
|
setValue(content);
|
||||||
message.success(`保存成功`);
|
message.success(`保存成功`);
|
||||||
} else {
|
} else {
|
||||||
message.error(_data);
|
message.error(_data);
|
||||||
|
@ -198,7 +208,9 @@ const CronDetailModal = ({
|
||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
<span>{cron.name}</span>
|
<span>{cron.name}</span>
|
||||||
|
{cron.labels?.length > 0 && cron.labels[0] !== '' && (
|
||||||
<Divider type="vertical"></Divider>
|
<Divider type="vertical"></Divider>
|
||||||
|
)}
|
||||||
{cron.labels?.length > 0 &&
|
{cron.labels?.length > 0 &&
|
||||||
cron.labels[0] !== '' &&
|
cron.labels[0] !== '' &&
|
||||||
cron.labels?.map((label: string, i: number) => (
|
cron.labels?.map((label: string, i: number) => (
|
||||||
|
@ -213,11 +225,17 @@ const CronDetailModal = ({
|
||||||
forceRender
|
forceRender
|
||||||
footer={false}
|
footer={false}
|
||||||
onCancel={() => handleCancel()}
|
onCancel={() => handleCancel()}
|
||||||
width={'80vw'}
|
wrapClassName="crontab-detail"
|
||||||
bodyStyle={{ background: '#eee', padding: 12 }}
|
width={!isPhone ? '80vw' : ''}
|
||||||
>
|
>
|
||||||
<div style={{ height: '80vh' }}>
|
<div className="card-wrapper">
|
||||||
<Card bodyStyle={{ display: 'flex', justifyContent: 'space-between' }}>
|
<Card>
|
||||||
|
<div className="cron-detail-info-item">
|
||||||
|
<div className="cron-detail-info-title">任务</div>
|
||||||
|
<div className="cron-detail-info-value">{cron.command}</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card style={{ marginTop: 10 }}>
|
||||||
<div className="cron-detail-info-item">
|
<div className="cron-detail-info-item">
|
||||||
<div className="cron-detail-info-title">状态</div>
|
<div className="cron-detail-info-title">状态</div>
|
||||||
<div className="cron-detail-info-value">
|
<div className="cron-detail-info-value">
|
||||||
|
@ -250,10 +268,6 @@ const CronDetailModal = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="cron-detail-info-item">
|
|
||||||
<div className="cron-detail-info-title">任务</div>
|
|
||||||
<div className="cron-detail-info-value">{cron.command}</div>
|
|
||||||
</div>
|
|
||||||
<div className="cron-detail-info-item">
|
<div className="cron-detail-info-item">
|
||||||
<div className="cron-detail-info-title">定时</div>
|
<div className="cron-detail-info-title">定时</div>
|
||||||
<div className="cron-detail-info-value">{cron.schedule}</div>
|
<div className="cron-detail-info-value">{cron.schedule}</div>
|
||||||
|
@ -289,8 +303,8 @@ const CronDetailModal = ({
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card
|
<Card
|
||||||
style={{ marginTop: 16 }}
|
style={{ marginTop: 10 }}
|
||||||
tabList={tabList}
|
tabList={validTabs}
|
||||||
activeTabKey={activeTabKey}
|
activeTabKey={activeTabKey}
|
||||||
onTabChange={(key) => {
|
onTabChange={(key) => {
|
||||||
onTabChange(key);
|
onTabChange(key);
|
||||||
|
@ -306,7 +320,6 @@ const CronDetailModal = ({
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
bodyStyle={{ height: 'calc(80vh - 188px)', overflowY: 'auto' }}
|
|
||||||
>
|
>
|
||||||
{contentList[activeTabKey]}
|
{contentList[activeTabKey]}
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -318,6 +331,7 @@ const CronDetailModal = ({
|
||||||
}}
|
}}
|
||||||
cron={cron}
|
cron={cron}
|
||||||
data={log}
|
data={log}
|
||||||
|
logUrl={logUrl}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,6 +8,64 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.crontab-detail {
|
||||||
|
.card-wrapper {
|
||||||
|
height: 80vh;
|
||||||
|
height: calc(80vh - var(--vh-offset, 0px));
|
||||||
|
|
||||||
|
.ant-card:last-child {
|
||||||
|
.ant-card-body {
|
||||||
|
height: calc(80vh - 238px);
|
||||||
|
height: calc(80vh - var(--vh-offset, 0px) - 238px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-body {
|
||||||
|
background: #eee;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-body {
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
.ant-card-head {
|
||||||
|
padding: 0 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card:first-child {
|
||||||
|
max-height: 66px;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.ant-card-body {
|
||||||
|
min-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cron-detail-info-item {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.cron-detail-info-title {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cron-detail-info-value {
|
||||||
|
flex: 1;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card:nth-child(2) {
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
.ant-card-body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-width: 600px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.cron-detail-info-item {
|
.cron-detail-info-item {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
|
|
||||||
|
@ -16,7 +74,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.cron-detail-info-value {
|
.cron-detail-info-value {
|
||||||
margin-top: 18px;
|
margin-top: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ import { getTableScroll } from '@/utils/index';
|
||||||
import { history } from 'umi';
|
import { history } from 'umi';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
|
|
||||||
export enum CrontabStatus {
|
export enum CrontabStatus {
|
||||||
|
@ -85,6 +85,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||||
goToScriptManager(record);
|
goToScriptManager(record);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{record.labels?.length > 0 && record.labels[0] !== '' ? (
|
||||||
<Popover
|
<Popover
|
||||||
placement="right"
|
placement="right"
|
||||||
trigger={isPhone ? 'click' : 'hover'}
|
trigger={isPhone ? 'click' : 'hover'}
|
||||||
|
@ -96,7 +97,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||||
style={{ cursor: 'point' }}
|
style={{ cursor: 'point' }}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setSearchText(`label:${label}`);
|
setSearchValue(`label:${label}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a>{label}</a>
|
<a>{label}</a>
|
||||||
|
@ -107,6 +108,9 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||||
>
|
>
|
||||||
{record.name || '-'}
|
{record.name || '-'}
|
||||||
</Popover>
|
</Popover>
|
||||||
|
) : (
|
||||||
|
record.name || '-'
|
||||||
|
)}
|
||||||
{record.isPinned ? (
|
{record.isPinned ? (
|
||||||
<span>
|
<span>
|
||||||
<PushpinOutlined />
|
<PushpinOutlined />
|
||||||
|
@ -130,16 +134,16 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
render: (text: string, record: any) => {
|
render: (text: string, record: any) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'left',
|
|
||||||
width: '100%',
|
|
||||||
display: 'inline-block',
|
|
||||||
wordBreak: 'break-all',
|
wordBreak: 'break-all',
|
||||||
|
marginBottom: 0,
|
||||||
|
textAlign: 'left',
|
||||||
}}
|
}}
|
||||||
|
ellipsis={{ tooltip: text, rows: 2 }}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</span>
|
</Paragraph>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
sorter: {
|
sorter: {
|
||||||
|
@ -364,6 +368,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||||
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
|
||||||
const [isDetailModalVisible, setIsDetailModalVisible] = useState(false);
|
const [isDetailModalVisible, setIsDetailModalVisible] = useState(false);
|
||||||
const [detailCron, setDetailCron] = useState<any>();
|
const [detailCron, setDetailCron] = useState<any>();
|
||||||
|
const [searchValue, setSearchValue] = useState('');
|
||||||
|
|
||||||
const goToScriptManager = (record: any) => {
|
const goToScriptManager = (record: any) => {
|
||||||
const cmd = record.command.split(' ') as string[];
|
const cmd = record.command.split(' ') as string[];
|
||||||
|
@ -627,7 +632,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||||
index: number;
|
index: number;
|
||||||
}> = ({ record, index }) => (
|
}> = ({ record, index }) => (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
arrow
|
arrow={{ pointAtCenter: true }}
|
||||||
|
placement="bottomRight"
|
||||||
trigger={['click']}
|
trigger={['click']}
|
||||||
overlay={
|
overlay={
|
||||||
<Menu
|
<Menu
|
||||||
|
@ -842,7 +848,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||||
enterButton
|
enterButton
|
||||||
allowClear
|
allowClear
|
||||||
loading={loading}
|
loading={loading}
|
||||||
value={searchText}
|
value={searchValue}
|
||||||
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
onSearch={onSearch}
|
onSearch={onSearch}
|
||||||
/>,
|
/>,
|
||||||
<Button key="2" type="primary" onClick={() => addCron()}>
|
<Button key="2" type="primary" onClick={() => addCron()}>
|
||||||
|
@ -968,6 +975,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
|
||||||
}}
|
}}
|
||||||
cron={detailCron}
|
cron={detailCron}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
isPhone={isPhone}
|
||||||
/>
|
/>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -21,11 +21,13 @@ const CronLogModal = ({
|
||||||
handleCancel,
|
handleCancel,
|
||||||
visible,
|
visible,
|
||||||
data,
|
data,
|
||||||
|
logUrl,
|
||||||
}: {
|
}: {
|
||||||
cron?: any;
|
cron?: any;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
handleCancel: () => void;
|
handleCancel: () => void;
|
||||||
data?: string;
|
data?: string;
|
||||||
|
logUrl?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = useState<string>('启动中...');
|
const [value, setValue] = useState<string>('启动中...');
|
||||||
const [loading, setLoading] = useState<any>(true);
|
const [loading, setLoading] = useState<any>(true);
|
||||||
|
@ -38,7 +40,7 @@ const CronLogModal = ({
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
}
|
}
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}crons/${cron.id}/log`)
|
.get(logUrl ? logUrl : `${config.apiPrefix}crons/${cron.id}/log`)
|
||||||
.then((data: any) => {
|
.then((data: any) => {
|
||||||
if (localStorage.getItem('logCron') === String(cron.id)) {
|
if (localStorage.getItem('logCron') === String(cron.id)) {
|
||||||
const log = data.data as string;
|
const log = data.data as string;
|
||||||
|
@ -99,10 +101,10 @@ const CronLogModal = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cron && cron.id) {
|
if (cron && cron.id && visible) {
|
||||||
getCronLog(true);
|
getCronLog(true);
|
||||||
}
|
}
|
||||||
}, [cron]);
|
}, [cron, visible]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
|
|
20
src/pages/env/index.tsx
vendored
20
src/pages/env/index.tsx
vendored
|
@ -32,7 +32,7 @@ import './index.less';
|
||||||
import { getTableScroll } from '@/utils/index';
|
import { getTableScroll } from '@/utils/index';
|
||||||
import { doc } from 'prettier';
|
import { doc } from 'prettier';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const { Search, TextArea } = Input;
|
const { Search, TextArea } = Input;
|
||||||
|
|
||||||
enum Status {
|
enum Status {
|
||||||
|
@ -124,18 +124,18 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||||
key: 'value',
|
key: 'value',
|
||||||
align: 'center' as const,
|
align: 'center' as const,
|
||||||
width: '35%',
|
width: '35%',
|
||||||
ellipsis: {
|
|
||||||
showTitle: false,
|
|
||||||
},
|
|
||||||
render: (text: string, record: any) => {
|
render: (text: string, record: any) => {
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Paragraph
|
||||||
placement="topLeft"
|
style={{
|
||||||
title={text}
|
wordBreak: 'break-all',
|
||||||
trigger={['hover', 'click']}
|
marginBottom: 0,
|
||||||
|
textAlign: 'left',
|
||||||
|
}}
|
||||||
|
ellipsis={{ tooltip: text, rows: 2 }}
|
||||||
>
|
>
|
||||||
<span>{text}</span>
|
{text}
|
||||||
</Tooltip>
|
</Paragraph>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -177,7 +177,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
||||||
秒后自动刷新
|
秒后自动刷新
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
duration: 10,
|
duration: 30,
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
|
|
|
@ -120,7 +120,7 @@ const Setting = ({
|
||||||
const [theme, setTheme] = useState(defaultDarken);
|
const [theme, setTheme] = useState(defaultDarken);
|
||||||
const [dataSource, setDataSource] = useState<any[]>([]);
|
const [dataSource, setDataSource] = useState<any[]>([]);
|
||||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
const [editedApp, setEditedApp] = useState();
|
const [editedApp, setEditedApp] = useState<any>();
|
||||||
const [tabActiveKey, setTabActiveKey] = useState('security');
|
const [tabActiveKey, setTabActiveKey] = useState('security');
|
||||||
const [loginLogData, setLoginLogData] = useState<any[]>([]);
|
const [loginLogData, setLoginLogData] = useState<any[]>([]);
|
||||||
const [notificationInfo, setNotificationInfo] = useState<any>();
|
const [notificationInfo, setNotificationInfo] = useState<any>();
|
||||||
|
@ -143,6 +143,7 @@ const Setting = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const addApp = () => {
|
const addApp = () => {
|
||||||
|
setEditedApp(null);
|
||||||
setIsModalVisible(true);
|
setIsModalVisible(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user