Merge branch 'develop' into develop

This commit is contained in:
langren1353 2022-03-20 03:50:13 +08:00 committed by GitHub
commit 66d6c2f13c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 239 additions and 193 deletions

View File

@ -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

View File

@ -16,6 +16,10 @@ export default defineConfig({
},
favicon: '/images/g5.ico',
proxy: {
'/api/public': {
target: 'http://127.0.0.1:5400/',
changeOrigin: true,
},
'/api': {
target: 'http://127.0.0.1:5600/',
changeOrigin: true,

View File

@ -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 部署
1. docker 安装

View File

@ -1,4 +1,5 @@
FROM whyour/ql:base
FROM node:alpine
ARG QL_MAINTAINER="whyour"
LABEL maintainer="${QL_MAINTAINER}"
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}
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} \
&& cp -f .env.example .env \
&& chmod 777 ${QL_DIR}/shell/*.sh \
&& chmod 777 ${QL_DIR}/docker/*.sh \
&& cp -rf /node_modules ./ \
&& rm -rf /node_modules \
&& pnpm install --prod \
&& rm -rf /root/.pnpm-store \
&& rm -rf /root/.cache \
&& rm -rf /root/.npm \
&& 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
ENTRYPOINT ["./docker/docker-entrypoint.sh"]

View File

@ -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" ]

View File

@ -4,10 +4,12 @@
"start": "concurrently -n w: npm:start:*",
"start:front": "umi dev",
"start:back": "nodemon",
"start:public": "ts-node back/public.ts",
"build:front": "umi build",
"build:back": "tsc -p tsconfig.back.json",
"panel": "npm run build:back && node static/build/app.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}'",
"prepare": "umi generate tmp",
"test": "umi-test",

View File

@ -22,9 +22,9 @@ if [[ ! -d ${repo_path}/.git ]]; then
git_clone_scripts ${url} ${repo_path} "main"
fi
cp -rf "$repo_path/jbot" $dir_root
if [[ ! -f "$dir_root/config/bot.json" ]]; then
cp -f "$repo_path/config/bot.json" "$dir_root/config"
cp -rf "$repo_path/jbot" $dir_data
if [[ ! -f "$dir_config/bot.json" ]]; then
cp -f "$repo_path/config/bot.json" "$dir_config"
fi
echo -e "\nbot文件下载成功...\n"
@ -32,9 +32,9 @@ echo -e "3、安装python3依赖...\n"
if [[ $PipMirror ]]; then
pip3 config set global.index-url $PipMirror
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
do
if [[ ! $(pip3 show "${LREAD%%=*}" 2>/dev/null) ]]; then
@ -46,7 +46,7 @@ echo -e "\npython3依赖安装成功...\n"
echo -e "4、启动bot程序...\n"
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
nohup python3 -m jbot >$dir_log/bot/nohup.log 2>&1 &
echo -e "bot启动成功...\n"

View File

@ -26,6 +26,7 @@
.monaco-editor:not(.rename-box) {
height: calc(100vh - 128px) !important;
height: calc(100vh - var(--vh-offset, 0px) - 128px) !important;
.view-overlays .current-line {
border-width: 0;
}
@ -33,6 +34,7 @@
.rename-box {
height: 0;
.rename-input {
height: 0;
padding: 0 !important;
@ -43,6 +45,7 @@
.ant-pro-grid-content.wide {
max-width: unset !important;
overflow: auto;
.ant-pro-page-container-children-content {
overflow: auto;
height: 100%;
@ -57,6 +60,7 @@
.ant-tooltip {
max-width: 500px !important;
.ant-tooltip-inner {
word-break: break-all !important;
}
@ -72,6 +76,7 @@
.log-select {
width: 250px;
}
.ant-page-header-heading-left {
min-width: 100px;
}
@ -81,6 +86,7 @@
.config-select {
width: 250px;
}
.ant-page-header-heading-left {
min-width: 100px;
}
@ -119,10 +125,13 @@
flex-direction: column;
height: calc(100vh - 48px);
height: calc(100vh - var(--vh-offset, 0px) - 48px);
.ant-pro-grid-content.wide {
flex: 1;
.ant-pro-grid-content-children {
height: calc(100% - 36px);
> div,
.log-container,
.react-codemirror2,
@ -197,6 +206,7 @@
.Resizer.disabled {
cursor: not-allowed;
}
.Resizer.disabled:hover {
border-color: transparent;
}
@ -209,6 +219,7 @@
.inline-countdown.ant-statistic {
display: inline-block;
.ant-statistic-content {
font-size: 14px;
padding: 0 3px;
@ -253,6 +264,7 @@
.ant-pro-basicLayout-content {
margin: 18px;
.ant-pro-page-container {
margin: -18px -18px -18px;
}
@ -280,25 +292,27 @@
.side-menu-user-drop-menu {
position: relative;
margin: 0;
padding: 4px 0;
text-align: left;
background-clip: padding-box;
outline: none;
background-color: #fff;
padding: 4px 0;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 4px;
box-shadow: 0 6px 16px -8px rgb(0 0 0 / 8%), 0 9px 28px 0 rgb(0 0 0 / 5%),
0 12px 48px 16px rgb(0 0 0 / 3%);
.ant-dropdown-menu-item:hover {
color: #1890ff;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
overflow: auto;
background-color: #fff;
}
[data-dark='true'] .side-menu-user-drop-menu {
background-color: #373739;
}
.ant-pro-sider-logo {
padding: 16px 8px !important;
h1 {
margin-left: 5px !important;
}
img {
width: 32px !important;
border-radius: 52% !important;

View File

@ -272,7 +272,7 @@ export default function (props: any) {
collapsed={collapsed}
rightContentRender={() =>
ctx.isPhone && (
<Dropdown overlay={menu} trigger={['click']}>
<Dropdown overlay={menu} placement="bottomRight" trigger={['click']}>
<span className="side-menu-user-wrapper">
<Avatar shape="square" size="small" icon={<UserOutlined />} />
<span style={{ marginLeft: 5 }}>admin</span>
@ -289,7 +289,7 @@ export default function (props: any) {
}}
>
{!collapsed && !ctx.isPhone && (
<Dropdown overlay={menu} trigger={['hover']}>
<Dropdown overlay={menu} placement="topLeft" trigger={['hover']}>
<span className="side-menu-user-wrapper">
<Avatar shape="square" size="small" icon={<UserOutlined />} />
<span style={{ marginLeft: 5 }}>admin</span>

View File

@ -50,11 +50,13 @@ const CronDetailModal = ({
handleCancel,
visible,
theme,
isPhone,
}: {
cron?: any;
visible: boolean;
handleCancel: (needUpdate?: boolean) => void;
theme: string;
isPhone: boolean;
}) => {
const [activeTabKey, setActiveTabKey] = useState('log');
const [loading, setLoading] = useState(true);
@ -64,6 +66,8 @@ const CronDetailModal = ({
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
const editorRef = useRef<any>(null);
const [scriptInfo, setScriptInfo] = useState<any>({});
const [logUrl, setLogUrl] = useState('');
const [validTabs, setValidTabs] = useState(tabList);
const contentList: any = {
log: (
@ -99,6 +103,8 @@ const CronDetailModal = ({
};
const onClickItem = (item: LogItem) => {
localStorage.setItem('logCron', cron.id);
setLogUrl(`${config.apiPrefix}logs/${item.directory}/${item.filename}`);
request
.get(`${config.apiPrefix}logs/${item.directory}/${item.filename}`)
.then((data) => {
@ -126,6 +132,7 @@ const CronDetailModal = ({
const getScript = () => {
const cmd = cron.command.split(' ') as string[];
if (cmd[0] === 'task') {
setValidTabs(validTabs);
if (cmd[1].startsWith('/ql/data/scripts')) {
cmd[1] = cmd[1].replace('/ql/data/scripts/', '');
}
@ -141,6 +148,8 @@ const CronDetailModal = ({
.then((data) => {
setValue(data.data);
});
} else {
setValidTabs([validTabs[0]]);
}
};
@ -171,6 +180,7 @@ const CronDetailModal = ({
})
.then((_data: any) => {
if (_data.code === 200) {
setValue(content);
message.success(`保存成功`);
} else {
message.error(_data);
@ -198,7 +208,9 @@ const CronDetailModal = ({
title={
<>
<span>{cron.name}</span>
{cron.labels?.length > 0 && cron.labels[0] !== '' && (
<Divider type="vertical"></Divider>
)}
{cron.labels?.length > 0 &&
cron.labels[0] !== '' &&
cron.labels?.map((label: string, i: number) => (
@ -213,11 +225,17 @@ const CronDetailModal = ({
forceRender
footer={false}
onCancel={() => handleCancel()}
width={'80vw'}
bodyStyle={{ background: '#eee', padding: 12 }}
wrapClassName="crontab-detail"
width={!isPhone ? '80vw' : ''}
>
<div style={{ height: '80vh' }}>
<Card bodyStyle={{ display: 'flex', justifyContent: 'space-between' }}>
<div className="card-wrapper">
<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-title"></div>
<div className="cron-detail-info-value">
@ -250,10 +268,6 @@ const CronDetailModal = ({
)}
</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-title"></div>
<div className="cron-detail-info-value">{cron.schedule}</div>
@ -289,8 +303,8 @@ const CronDetailModal = ({
</div>
</Card>
<Card
style={{ marginTop: 16 }}
tabList={tabList}
style={{ marginTop: 10 }}
tabList={validTabs}
activeTabKey={activeTabKey}
onTabChange={(key) => {
onTabChange(key);
@ -306,7 +320,6 @@ const CronDetailModal = ({
</Button>
)
}
bodyStyle={{ height: 'calc(80vh - 188px)', overflowY: 'auto' }}
>
{contentList[activeTabKey]}
</Card>
@ -318,6 +331,7 @@ const CronDetailModal = ({
}}
cron={cron}
data={log}
logUrl={logUrl}
/>
</Modal>
);

View File

@ -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 {
flex: auto;
@ -16,7 +74,8 @@
}
.cron-detail-info-value {
margin-top: 18px;
margin-top: 12px;
}
}
}

View File

@ -40,7 +40,7 @@ import { getTableScroll } from '@/utils/index';
import { history } from 'umi';
import './index.less';
const { Text } = Typography;
const { Text, Paragraph } = Typography;
const { Search } = Input;
export enum CrontabStatus {
@ -85,6 +85,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
goToScriptManager(record);
}}
>
{record.labels?.length > 0 && record.labels[0] !== '' ? (
<Popover
placement="right"
trigger={isPhone ? 'click' : 'hover'}
@ -96,7 +97,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
style={{ cursor: 'point' }}
onClick={(e) => {
e.stopPropagation();
setSearchText(`label:${label}`);
setSearchValue(`label:${label}`);
}}
>
<a>{label}</a>
@ -107,6 +108,9 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
>
{record.name || '-'}
</Popover>
) : (
record.name || '-'
)}
{record.isPinned ? (
<span>
<PushpinOutlined />
@ -130,16 +134,16 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
align: 'center' as const,
render: (text: string, record: any) => {
return (
<span
<Paragraph
style={{
textAlign: 'left',
width: '100%',
display: 'inline-block',
wordBreak: 'break-all',
marginBottom: 0,
textAlign: 'left',
}}
ellipsis={{ tooltip: text, rows: 2 }}
>
{text}
</span>
</Paragraph>
);
},
sorter: {
@ -364,6 +368,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
const [isDetailModalVisible, setIsDetailModalVisible] = useState(false);
const [detailCron, setDetailCron] = useState<any>();
const [searchValue, setSearchValue] = useState('');
const goToScriptManager = (record: any) => {
const cmd = record.command.split(' ') as string[];
@ -627,7 +632,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
index: number;
}> = ({ record, index }) => (
<Dropdown
arrow
arrow={{ pointAtCenter: true }}
placement="bottomRight"
trigger={['click']}
overlay={
<Menu
@ -842,7 +848,8 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
enterButton
allowClear
loading={loading}
value={searchText}
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
onSearch={onSearch}
/>,
<Button key="2" type="primary" onClick={() => addCron()}>
@ -968,6 +975,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
}}
cron={detailCron}
theme={theme}
isPhone={isPhone}
/>
</PageContainer>
);

View File

@ -21,11 +21,13 @@ const CronLogModal = ({
handleCancel,
visible,
data,
logUrl,
}: {
cron?: any;
visible: boolean;
handleCancel: () => void;
data?: string;
logUrl?: string;
}) => {
const [value, setValue] = useState<string>('启动中...');
const [loading, setLoading] = useState<any>(true);
@ -38,7 +40,7 @@ const CronLogModal = ({
setLoading(true);
}
request
.get(`${config.apiPrefix}crons/${cron.id}/log`)
.get(logUrl ? logUrl : `${config.apiPrefix}crons/${cron.id}/log`)
.then((data: any) => {
if (localStorage.getItem('logCron') === String(cron.id)) {
const log = data.data as string;
@ -99,10 +101,10 @@ const CronLogModal = ({
};
useEffect(() => {
if (cron && cron.id) {
if (cron && cron.id && visible) {
getCronLog(true);
}
}, [cron]);
}, [cron, visible]);
useEffect(() => {
if (data) {

View File

@ -32,7 +32,7 @@ import './index.less';
import { getTableScroll } from '@/utils/index';
import { doc } from 'prettier';
const { Text } = Typography;
const { Text, Paragraph } = Typography;
const { Search, TextArea } = Input;
enum Status {
@ -124,18 +124,18 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
key: 'value',
align: 'center' as const,
width: '35%',
ellipsis: {
showTitle: false,
},
render: (text: string, record: any) => {
return (
<Tooltip
placement="topLeft"
title={text}
trigger={['hover', 'click']}
<Paragraph
style={{
wordBreak: 'break-all',
marginBottom: 0,
textAlign: 'left',
}}
ellipsis={{ tooltip: text, rows: 2 }}
>
<span>{text}</span>
</Tooltip>
{text}
</Paragraph>
);
},
},

View File

@ -177,7 +177,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
</span>
),
duration: 10,
duration: 30,
});
setTimeout(() => {
window.location.reload();

View File

@ -120,7 +120,7 @@ const Setting = ({
const [theme, setTheme] = useState(defaultDarken);
const [dataSource, setDataSource] = useState<any[]>([]);
const [isModalVisible, setIsModalVisible] = useState(false);
const [editedApp, setEditedApp] = useState();
const [editedApp, setEditedApp] = useState<any>();
const [tabActiveKey, setTabActiveKey] = useState('security');
const [loginLogData, setLoginLogData] = useState<any[]>([]);
const [notificationInfo, setNotificationInfo] = useState<any>();
@ -143,6 +143,7 @@ const Setting = ({
};
const addApp = () => {
setEditedApp(null);
setIsModalVisible(true);
};