Merge remote-tracking branch 'origin/master' into dev

This commit is contained in:
whyour 2021-04-22 17:54:09 +08:00
commit e5e8c45e8b
20 changed files with 168 additions and 60 deletions

View File

@ -30,9 +30,11 @@ jobs:
echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin echo "${DOCKER_PASSWORD}" | docker login --username "${DOCKER_USERNAME}" --password-stdin
- name: Docker buildx image and push on master branch - name: Docker buildx image and push on master branch
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
run: | run: |
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag whyour/qinglong:latest docker/ docker buildx build --build-arg SSH_PRIVATE_KEY="${SSH_PRIVATE_KEY}" --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag whyour/qinglong:latest docker/
- name: Replace tag without `v` - name: Replace tag without `v`
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
@ -44,7 +46,9 @@ jobs:
result-encoding: string result-encoding: string
- name: Docker buildx image and push on release - name: Docker buildx image and push on release
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
run: | run: |
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag whyour/qinglong:${{steps.version.outputs.result}} docker/ docker buildx build --build-arg SSH_PRIVATE_KEY="${SSH_PRIVATE_KEY}" --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag whyour/qinglong:${{steps.version.outputs.result}} docker/
docker buildx build --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag whyour/qinglong:latest docker/ docker buildx build --build-arg SSH_PRIVATE_KEY="${SSH_PRIVATE_KEY}" --output "type=image,push=true" --platform=linux/amd64,linux/arm/v7,linux/arm64 --tag whyour/qinglong:latest docker/

View File

@ -8,7 +8,7 @@ export default defineConfig({
type: 'none', type: 'none',
}, },
fastRefresh: {}, fastRefresh: {},
favicon: 'https://image.whyour.cn/others/g5.ico', favicon: 'https://qinglong.whyour.cn/g5.ico',
proxy: { proxy: {
'/api': { '/api': {
target: 'http://127.0.0.1:5678/', target: 'http://127.0.0.1:5678/',

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
MIT LICENSE
Copyright (c) 2021-present whyour
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,16 +1,51 @@
## 青龙(WIP) <p align="center">
<a href="https://github.com/whyour/qinglong">
<img width="150" src="https://qinglong.whyour.cn/qinglong.png">
</a>
</p>
<h1 align="center">青龙(WIP)</h1>
<div align="center">
python和javaScript的定时任务管理面板
[![donate][donate-image]][donate-url] [![build status][build-status-image]][build-status-url] [![docker pulls][docker-pulls-image]][docker-pulls-url] [![docker version][docker-version-image]][docker-version-url] [![docker stars][docker-stars-image]][docker-stars-url] [![docker image size][docker-image-size-image]][docker-image-size-url]
[donate-image]: https://img.shields.io/badge/donate-wechat-green?style=for-the-badge
[donate-url]: https://qinglong.whyour.cn/nice.png
[build-status-image]: https://img.shields.io/docker/cloud/build/whyour/qinglong?style=for-the-badge
[build-status-url]: https://img.shields.io/docker/cloud/build/whyour/qinglong
[docker-pulls-image]: https://img.shields.io/docker/pulls/whyour/qinglong?style=for-the-badge
[docker-pulls-url]: https://hub.docker.com/r/whyour/qinglong
[docker-version-image]: https://img.shields.io/docker/v/whyour/qinglong?style=for-the-badge
[docker-version-url]: https://hub.docker.com/r/whyour/qinglong/tags?page=1&ordering=last_updated
[docker-stars-image]: https://img.shields.io/docker/stars/whyour/qinglong?style=for-the-badge
[docker-stars-url]: https://hub.docker.com/r/whyour/qinglong
[docker-image-size-image]: https://img.shields.io/docker/image-size/whyour/qinglong?style=for-the-badge
[docker-image-size-url]: https://hub.docker.com/r/whyour/qinglong
</div>
青龙,又名苍龙,在中国传统文化中是四象之一、[天之四灵](https://zh.wikipedia.org/wiki/%E5%A4%A9%E4%B9%8B%E5%9B%9B%E7%81%B5)之一,根据五行学说,它是代表东方的灵兽,为青色的龙,五行属木,代表的季节是春季,八卦主震。苍龙与应龙一样,都是身具羽翼。《张果星宗》称“又有辅翼,方为真龙”。 青龙,又名苍龙,在中国传统文化中是四象之一、[天之四灵](https://zh.wikipedia.org/wiki/%E5%A4%A9%E4%B9%8B%E5%9B%9B%E7%81%B5)之一,根据五行学说,它是代表东方的灵兽,为青色的龙,五行属木,代表的季节是春季,八卦主震。苍龙与应龙一样,都是身具羽翼。《张果星宗》称“又有辅翼,方为真龙”。
《后汉书·律历志下》记载:日周于天,一寒一暑,四时备成,万物毕改,摄提迁次,青龙移辰,谓之岁。 《后汉书·律历志下》记载:日周于天,一寒一暑,四时备成,万物毕改,摄提迁次,青龙移辰,谓之岁。
在中国[二十八宿](https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%8D%81%E5%85%AB%E5%AE%BF)中,青龙是东方七宿(角、亢、氐、房、心、尾、箕)的总称。 在早期星宿信仰中,祂是最尊贵的天神[1]。 但被道教信仰吸纳入其神系后,神格大跌,道教将其称为“孟章”,在不同的道经中有“帝君”、“圣将”、“神将”和“捕鬼将”等称呼[2],与白虎监兵神君一起,是道教的护卫天神。 在中国[二十八宿](https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%8D%81%E5%85%AB%E5%AE%BF)中,青龙是东方七宿(角、亢、氐、房、心、尾、箕)的总称。 在早期星宿信仰中,祂是最尊贵的天神。 但被道教信仰吸纳入其神系后,神格大跌,道教将其称为“孟章”,在不同的道经中有“帝君”、“圣将”、“神将”和“捕鬼将”等称呼,与白虎监兵神君一起,是道教的护卫天神。
## 多谢 ## 多谢
[https://github.com/nevinee/jd_shell](https://github.com/nevinee/jd_shell) * [https://github.com/nevinee/jd_shell](https://github.com/nevinee/jd_shell)
[https://github.com/alseambusher/crontab-ui](https://github.com/alseambusher/crontab-ui) * [https://github.com/alseambusher/crontab-ui](https://github.com/alseambusher/crontab-ui)
* [Ant Design](https://ant.design)
* [Ant Design Pro](https://pro.ant.design/)
* [Umijs3.0](https://umijs.org)
* [darkreader](https://github.com/darkreader/darkreader)
## 免责声明 ## 免责声明
@ -27,8 +62,3 @@
6. 如果任何单位或个人认为此仓储脚本可能涉嫌侵犯其权利,应及时通知并提供身份证明,所有权证明,我们将在收到认证文件确认后删除此仓储脚本。 6. 如果任何单位或个人认为此仓储脚本可能涉嫌侵犯其权利,应及时通知并提供身份证明,所有权证明,我们将在收到认证文件确认后删除此仓储脚本。
7. 所有直接或间接使用、查看此仓储脚本的人均应该仔细阅读此声明。本人保留随时更改或补充此声明的权利。一旦您使用或复制了此仓储脚本,即视为您已接受此免责声明。 7. 所有直接或间接使用、查看此仓储脚本的人均应该仔细阅读此声明。本人保留随时更改或补充此声明的权利。一旦您使用或复制了此仓储脚本,即视为您已接受此免责声明。
### one more thing
![one-more-thing](https://image.whyour.cn/others/nice.png)

View File

@ -16,23 +16,17 @@ const initData = [
status: CrontabStatus.idle, status: CrontabStatus.idle,
}, },
{ {
name: '自定义仓库', name: 'build面板',
command: `sleep ${randomSchedule( command: 'rebuild >> ${QL_DIR}/log/rebuild.log 2>&1',
60, schedule: '30 7 */7 * *',
1, status: CrontabStatus.disabled,
)} && diy whyour hundun "quanx/jx|quanx/jd" tokens >> $QL_DIR/log/diy_pull.log 2>&1`,
schedule: `${randomSchedule(60, 1)} ${randomSchedule(
24,
6,
).toString()} * * *`,
status: CrontabStatus.idle,
}, },
{ {
name: '自定义仓库', name: '自定义仓库',
command: `sleep ${randomSchedule( command: `sleep ${randomSchedule(
60, 60,
1, 1,
)} && diy monk-coder dust "i-chenzhe|normal" >> $QL_DIR/log/diy_pull.log 2>&1`, )} && diy https://ghproxy.com/https://github.com/whyour/hundun.git "quanx/jx|quanx/jd" tokens >> $QL_DIR/log/diy_pull.log 2>&1`,
schedule: `${randomSchedule(60, 1)} ${randomSchedule( schedule: `${randomSchedule(60, 1)} ${randomSchedule(
24, 24,
6, 6,
@ -45,12 +39,6 @@ const initData = [
schedule: '48 5 * * *', schedule: '48 5 * * *',
status: CrontabStatus.idle, status: CrontabStatus.idle,
}, },
{
name: 'build面板',
command: 'rebuild >> ${QL_DIR}/log/rebuild.log 2>&1',
schedule: '30 7 */7 * *',
status: CrontabStatus.disabled,
},
{ {
name: '删除日志', name: '删除日志',
command: 'rm_log >/dev/null 2>&1', command: 'rm_log >/dev/null 2>&1',

View File

@ -81,10 +81,11 @@ export default class CookieService {
public async refreshCookie(_id: string) { public async refreshCookie(_id: string) {
const current = await this.get(_id); const current = await this.get(_id);
const { status } = await this.getJdInfo(current.value); const { status, nickname } = await this.getJdInfo(current.value);
return { return {
...current, ...current,
status, status,
nickname,
}; };
} }
@ -121,14 +122,15 @@ export default class CookieService {
} }
public async create(payload: string[]): Promise<Cookie[]> { public async create(payload: string[]): Promise<Cookie[]> {
const cookies = await this.cookies('', { postion: 1 }); const cookies = await this.cookies('');
let position = initCookiePosition; let position = initCookiePosition;
if (cookies && cookies.length > 0) { if (cookies && cookies.length > 0) {
position = cookies[0].position / 2; position = cookies[cookies.length - 1].position;
} }
const tabs = payload.map((x) => { const tabs = payload.map((x) => {
const cookie = new Cookie({ value: x, position }); const cookie = new Cookie({ value: x, position });
position = position / 2; position = position / 2;
cookie.position = position;
return cookie; return cookie;
}); });
const docs = await this.insert(tabs); const docs = await this.insert(tabs);

View File

@ -48,7 +48,7 @@ export default class CronService {
const doc = await this.get(_id); const doc = await this.get(_id);
const tab = new Crontab({ ...doc, ...other }); const tab = new Crontab({ ...doc, ...other });
tab.saved = false; tab.saved = false;
const newDoc = await this.update(tab); const newDoc = await this.updateDb(tab);
await this.set_crontab(); await this.set_crontab();
return newDoc; return newDoc;
} }

View File

@ -15,9 +15,7 @@ RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
coreutils \ coreutils \
moreutils \ moreutils \
git \ git \
wget \
curl \ curl \
nano \
tzdata \ tzdata \
perl \ perl \
openssl \ openssl \

View File

@ -7,6 +7,9 @@ echo
echo -e "======================2. 检测配置文件========================\n" echo -e "======================2. 检测配置文件========================\n"
[ ! -d ${QL_DIR}/config ] && mkdir -p ${QL_DIR}/config [ ! -d ${QL_DIR}/config ] && mkdir -p ${QL_DIR}/config
[ ! -d ${QL_DIR}/log ] && mkdir -p ${QL_DIR}/log
[ ! -d ${QL_DIR}/db ] && mkdir -p ${QL_DIR}/db
[ ! -d ${QL_DIR}/manual_log ] && mkdir -p ${QL_DIR}/manual_log
if [ ! -s ${QL_DIR}/config/crontab.list ] if [ ! -s ${QL_DIR}/config/crontab.list ]
then then
@ -52,6 +55,9 @@ pm2 start ${QL_DIR}/build/app.js -n panel
echo -e "控制面板启动成功...\n" echo -e "控制面板启动成功...\n"
echo -e "\n容器启动成功...\n" echo -e "\n容器启动成功...\n"
echo -e "\n请先访问5700端口登录面板成功之后先手动执行一次git_pull命令...\n"
echo -e "\n如果需要启动挂机程序手动执行docker exec -it qinglong js hangup...\n"
echo -e "\n或者去cron管理搜索hangup手动执行挂机任务...\n"
crond -f crond -f

View File

@ -13,6 +13,7 @@ echo -e "重新build...\n"
yarn install --network-timeout 1000000000 || yarn install --registry=https://registry.npm.taobao.org --network-timeout 1000000000 yarn install --network-timeout 1000000000 || yarn install --registry=https://registry.npm.taobao.org --network-timeout 1000000000
yarn build yarn build
yarn build-back yarn build-back
yarn cache clean
echo -e "重新build完成...\n" echo -e "重新build完成...\n"
echo -e "重启服务...\n" echo -e "重启服务...\n"

View File

@ -3,9 +3,6 @@ import { request } from '@/utils/http';
import config from '@/utils/config'; import config from '@/utils/config';
export function render(oldRender: any) { export function render(oldRender: any) {
if (history.location.pathname === '/login') {
oldRender();
}
request request
.get(`${config.apiPrefix}user`) .get(`${config.apiPrefix}user`)
.then((data) => { .then((data) => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -8,7 +8,6 @@ import {
RadiusSettingOutlined, RadiusSettingOutlined,
ControlOutlined, ControlOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import logo from '@/assets/logo.png';
export default { export default {
route: { route: {
@ -70,5 +69,5 @@ export default {
fixSiderbar: true, fixSiderbar: true,
contentWidth: 'Fixed', contentWidth: 'Fixed',
splitMenus: false, splitMenus: false,
logo: logo, logo: 'https://qinglong.whyour.cn/qinglong.png',
} as any; } as any;

View File

@ -23,6 +23,12 @@ body {
} }
} }
.cookie-wrapper {
th {
white-space: nowrap;
}
}
@media (max-width: 768px) { @media (max-width: 768px) {
.ant-pro-grid-content.wide { .ant-pro-grid-content.wide {
.ant-pro-page-container-children-content { .ant-pro-page-container-children-content {

View File

@ -113,8 +113,18 @@ const Config = () => {
render: (text: string, record: any) => { render: (text: string, record: any) => {
const match = record.value.match(/pt_pin=([^; ]+)(?=;?)/); const match = record.value.match(/pt_pin=([^; ]+)(?=;?)/);
const val = (match && match[1]) || '未匹配用户名'; const val = (match && match[1]) || '未匹配用户名';
return <span style={{ cursor: 'text' }}>{decodeUrl(val)}</span>;
},
},
{
title: '昵称',
dataIndex: 'nickname',
key: 'nickname',
align: 'center' as const,
width: '15%',
render: (text: string, record: any, index: number) => {
return ( return (
<span style={{ cursor: 'text' }}>{decodeURIComponent(val)}</span> <span style={{ cursor: 'text' }}>{record.nickname || '-'} </span>
); );
}, },
}, },
@ -212,6 +222,36 @@ const Config = () => {
.finally(() => setLoading(false)); .finally(() => setLoading(false));
}; };
const decodeUrl = (val: string) => {
try {
const newUrl = decodeURIComponent(val);
return newUrl;
} catch (error) {
return val;
}
};
useEffect(() => {
if (value && loading) {
asyncUpdateStatus();
}
}, [value]);
const asyncUpdateStatus = async () => {
for (let i = 0; i < value.length; i++) {
const cookie = value[i];
if (cookie.status === Status.) {
continue;
}
await sleep(1000);
location.pathname === '/cookie' && refreshStatus(cookie, i);
}
};
const sleep = (time: number) => {
return new Promise((resolve) => setTimeout(resolve, time));
};
const refreshStatus = (record: any, index: number) => { const refreshStatus = (record: any, index: number) => {
request request
.get(`${config.apiPrefix}cookies/${record._id}/refresh`) .get(`${config.apiPrefix}cookies/${record._id}/refresh`)
@ -322,24 +362,28 @@ const Config = () => {
}); });
}; };
const handleCancel = (cookie: any) => { const handleCancel = (cookies: any[]) => {
setIsModalVisible(false); setIsModalVisible(false);
if (cookie) { if (cookies && cookies.length > 0) {
handleCookies(cookie); handleCookies(cookies);
} }
}; };
const handleCookies = (cookie: any) => { const handleCookies = (cookies: any[]) => {
const index = value.findIndex((x) => x._id === cookie._id); for (let i = 0; i < cookies.length; i++) {
const result = [...value]; const cookie = cookies[i];
if (index === -1) { const index = value.findIndex((x) => x._id === cookie._id);
result.push(...cookie); const result = [...value];
} else { if (index === -1) {
result.splice(index, 1, { result.push(cookie);
...cookie, } else {
}); result.splice(index, 1, {
...cookie,
});
}
setValue(result);
refreshStatus(cookie, index);
} }
setValue(result);
}; };
const components = { const components = {
@ -388,7 +432,7 @@ const Config = () => {
return ( return (
<PageContainer <PageContainer
className="code-mirror-wrapper" className="cookie-wrapper"
title="Cookie管理" title="Cookie管理"
loading={loading} loading={loading}
extra={[ extra={[

View File

@ -13,6 +13,7 @@ const CookieModal = ({
handleCancel: (needUpdate?: boolean) => void; handleCancel: (needUpdate?: boolean) => void;
}) => { }) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const handleOk = async (values: any) => { const handleOk = async (values: any) => {
const cookies = values.value const cookies = values.value
@ -29,6 +30,7 @@ const CookieModal = ({
if (flag) { if (flag) {
return; return;
} }
setLoading(true);
const method = cookie ? 'put' : 'post'; const method = cookie ? 'put' : 'post';
const payload = cookie ? { value: cookies[0], _id: cookie._id } : cookies; const payload = cookie ? { value: cookies[0], _id: cookie._id } : cookies;
const { code, data } = await request[method](`${config.apiPrefix}cookies`, { const { code, data } = await request[method](`${config.apiPrefix}cookies`, {
@ -43,6 +45,7 @@ const CookieModal = ({
message: data, message: data,
}); });
} }
setLoading(false);
handleCancel(data); handleCancel(data);
}; };
@ -66,6 +69,7 @@ const CookieModal = ({
}); });
}} }}
onCancel={() => handleCancel()} onCancel={() => handleCancel()}
confirmLoading={loading}
destroyOnClose destroyOnClose
> >
<Form <Form

View File

@ -14,8 +14,10 @@ const CronModal = ({
handleCancel: (needUpdate?: boolean) => void; handleCancel: (needUpdate?: boolean) => void;
}) => { }) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const handleOk = async (values: any) => { const handleOk = async (values: any) => {
setLoading(true);
const method = cron ? 'put' : 'post'; const method = cron ? 'put' : 'post';
const payload = { ...values }; const payload = { ...values };
if (cron) { if (cron) {
@ -33,6 +35,7 @@ const CronModal = ({
message: data, message: data,
}); });
} }
setLoading(false);
handleCancel(data); handleCancel(data);
}; };
@ -60,6 +63,7 @@ const CronModal = ({
}); });
}} }}
onCancel={() => handleCancel()} onCancel={() => handleCancel()}
confirmLoading={loading}
destroyOnClose destroyOnClose
> >
<Form form={form} layout="vertical" name="form_in_modal" preserve={false}> <Form form={form} layout="vertical" name="form_in_modal" preserve={false}>

View File

@ -51,7 +51,7 @@
} }
.logo { .logo {
width: 40px; width: 30px;
margin-right: 8px; margin-right: 8px;
} }

View File

@ -4,7 +4,6 @@ import config from '@/utils/config';
import { history, Link } from 'umi'; import { history, Link } from 'umi';
import styles from './index.less'; import styles from './index.less';
import { request } from '@/utils/http'; import { request } from '@/utils/http';
import logo from '@/assets/logo.png';
const FormItem = Form.Item; const FormItem = Form.Item;
@ -48,7 +47,11 @@ const Login = () => {
<div className={styles.content}> <div className={styles.content}>
<div className={styles.top}> <div className={styles.top}>
<div className={styles.header}> <div className={styles.header}>
<img alt="logo" className={styles.logo} src={logo} /> <img
alt="logo"
className={styles.logo}
src="https://qinglong.whyour.cn/qinglong.png"
/>
<span className={styles.title}>{config.siteName}</span> <span className={styles.title}>{config.siteName}</span>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
export default { export default {
siteName: '脚本控制面板', siteName: '青龙控制面板',
apiPrefix: '/api/', apiPrefix: '/api/',
authKey: 'token', authKey: 'token',