mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-06 08:44:32 +08:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0caf53b147 | |||
| 4d1354d5f4 | |||
| fc4562eca2 | |||
| 5b048a5510 | |||
| d23049b2fe | |||
| caac5607b9 | |||
| fb2e69b12d | |||
| fc1838ff39 | |||
| 25689cf40f | |||
| c746a3d215 | |||
| 548d924fa0 | |||
| 18a6be3ecd | |||
| afef0cb72f | |||
| 1221b47eca | |||
| a356eeb5da | |||
| 25bb0972b9 | |||
| 0914075cf6 | |||
| 74b258e1b6 | |||
| 27eb0a21e4 | |||
| d77c6d50d8 | |||
| 6a64c18a7b | |||
| b97ff714ae | |||
| 1f23eea805 | |||
| cb0cb7df21 | |||
| f3e8628664 | |||
| 4758471f9f | |||
| c10bd5a549 | |||
| d53faf96eb | |||
| 2307b8ed1a | |||
| 711af8d4a5 | |||
| 458c9c456b | |||
| 29069df48c | |||
| 300e84ae2c | |||
| bf11768b92 | |||
| 91bd1d9263 | |||
| ac40f9ff12 | |||
| f930837220 | |||
| 1d5e3acc93 | |||
| 2aec1f9846 | |||
| b56a681b98 | |||
| 2713419b32 | |||
| 764a3492cc | |||
| 29fc471ac4 | |||
| 54a597aad3 | |||
| 778961d0b6 | |||
| 14b01ac761 | |||
| f87848c05b | |||
| 153e5a806d | |||
| ee1b8a906f | |||
| 734685d45a | |||
| e3b540c212 | |||
| 201f578bbc | |||
| ce9aa369d2 | |||
| c5c41c0859 | |||
| d3009f82dd | |||
| 0eb9a1fdb0 | |||
| 83f6d9926a | |||
| 14134842b0 | |||
| 4c094c4710 | |||
| 3ac41075fd | |||
| fdc97773d1 |
+2
-1
@@ -2,7 +2,8 @@ MONGODB_URI='mongodb://'
|
||||
YIYAN_MONGODB_URI=''
|
||||
|
||||
CRON_PORT=5500
|
||||
PORT=5600
|
||||
BACK_PORT=5600
|
||||
PORT=5700
|
||||
|
||||
LOG_LEVEL='debug'
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
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,linux/s390x
|
||||
context: .
|
||||
file: docker/base/Dockerfile
|
||||
push: true
|
||||
tags: whyour/ql:base
|
||||
@@ -28,8 +28,8 @@ jobs:
|
||||
- name: build front and back
|
||||
run: |
|
||||
yarn install
|
||||
yarn build
|
||||
yarn build-back
|
||||
yarn build:front
|
||||
yarn build:back
|
||||
|
||||
- name: copy to static repo
|
||||
env:
|
||||
|
||||
@@ -32,18 +32,9 @@ export default defineConfig({
|
||||
externals: {
|
||||
react: 'window.React',
|
||||
'react-dom': 'window.ReactDOM',
|
||||
darkreader: 'window.DarkReader',
|
||||
codemirror: 'window.CodeMirror',
|
||||
'sockjs-client': 'window.SockJS',
|
||||
},
|
||||
scripts: [
|
||||
'https://gw.alipayobjects.com/os/lib/react/16.13.1/umd/react.production.min.js',
|
||||
'https://gw.alipayobjects.com/os/lib/react-dom/16.13.1/umd/react-dom.production.min.js',
|
||||
'https://cdn.jsdelivr.net/npm/darkreader@4.9.40/darkreader.min.js',
|
||||
'https://cdn.jsdelivr.net/npm/codemirror@5/lib/codemirror.min.js',
|
||||
'https://cdn.jsdelivr.net/npm/codemirror@5/mode/shell/shell.js',
|
||||
'https://cdn.jsdelivr.net/npm/codemirror@5/mode/python/python.js',
|
||||
'https://cdn.jsdelivr.net/npm/codemirror@5/mode/javascript/javascript.js',
|
||||
'https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js',
|
||||
],
|
||||
});
|
||||
|
||||
@@ -153,11 +153,10 @@ $ git clone git@github.com:whyour/qinglong.git
|
||||
$ cd qinglong
|
||||
$ cp .env.example .env
|
||||
$ yarn install
|
||||
$ yarn start-back
|
||||
$ yarn start
|
||||
```
|
||||
|
||||
打开你的浏览器,访问 http://127.0.0.1:5601
|
||||
打开你的浏览器,访问 http://127.0.0.1:5700
|
||||
|
||||
## 交流
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ if (envFound.error) {
|
||||
}
|
||||
|
||||
export default {
|
||||
port: parseInt(process.env.PORT as string, 10),
|
||||
port: parseInt(process.env.BACK_PORT as string, 10),
|
||||
cronPort: parseInt(process.env.CRON_PORT as string, 10),
|
||||
secret: process.env.SECRET || createRandomString(16, 32),
|
||||
logs: {
|
||||
|
||||
+10
-4
@@ -49,12 +49,18 @@ export enum CrontabStatus {
|
||||
|
||||
interface CronInstance extends Model<Crontab, Crontab>, Crontab {}
|
||||
export const CrontabModel = sequelize.define<CronInstance>('Crontab', {
|
||||
name: DataTypes.STRING,
|
||||
command: {
|
||||
unique: 'command',
|
||||
name: {
|
||||
unique: 'compositeIndex',
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
command: {
|
||||
unique: 'compositeIndex',
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
schedule: {
|
||||
unique: 'compositeIndex',
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
schedule: DataTypes.STRING,
|
||||
timestamp: DataTypes.STRING,
|
||||
saved: DataTypes.BOOLEAN,
|
||||
status: DataTypes.NUMBER,
|
||||
|
||||
+2
-2
@@ -30,10 +30,10 @@ export const initEnvPosition = 9999999999;
|
||||
|
||||
interface EnvInstance extends Model<Env, Env>, Env {}
|
||||
export const EnvModel = sequelize.define<EnvInstance>('Env', {
|
||||
value: DataTypes.STRING,
|
||||
value: { type: DataTypes.STRING, unique: 'compositeIndex' },
|
||||
timestamp: DataTypes.STRING,
|
||||
status: DataTypes.NUMBER,
|
||||
position: DataTypes.NUMBER,
|
||||
name: DataTypes.STRING,
|
||||
name: { type: DataTypes.STRING, unique: 'compositeIndex' },
|
||||
remarks: DataTypes.STRING,
|
||||
});
|
||||
|
||||
@@ -5,4 +5,11 @@ export const sequelize = new Sequelize({
|
||||
dialect: 'sqlite',
|
||||
storage: `${config.dbPath}database.sqlite`,
|
||||
logging: false,
|
||||
pool: {
|
||||
max: 6,
|
||||
min: 0,
|
||||
idle: 30000,
|
||||
},
|
||||
});
|
||||
|
||||
export type ResponseType<T> = { code: number; data?: T; message?: string };
|
||||
|
||||
@@ -34,6 +34,8 @@ export class ServerChanNotification extends NotificationBaseInfo {
|
||||
|
||||
export class BarkNotification extends NotificationBaseInfo {
|
||||
public barkPush = '';
|
||||
public barkIcon =
|
||||
'https://img.gejiba.com/images/a3f551e09ac19add4c49ec16228729c5.png';
|
||||
public barkSound = '';
|
||||
public barkGroup = 'qinglong';
|
||||
}
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ export enum CrontabStatus {
|
||||
|
||||
interface AppInstance extends Model<App, App>, App {}
|
||||
export const AppModel = sequelize.define<AppInstance>('App', {
|
||||
name: DataTypes.STRING,
|
||||
name: { type: DataTypes.STRING, unique: 'name' },
|
||||
scopes: DataTypes.JSON,
|
||||
client_id: DataTypes.STRING,
|
||||
client_secret: DataTypes.STRING,
|
||||
|
||||
+69
-78
@@ -1,7 +1,6 @@
|
||||
import DataStore from 'nedb';
|
||||
import config from '../config';
|
||||
import Logger from './logger';
|
||||
import fs from 'fs';
|
||||
import { fileExist } from '../config/util';
|
||||
import { EnvModel } from '../data/env';
|
||||
import { CrontabModel } from '../data/cron';
|
||||
@@ -10,91 +9,85 @@ import { AppModel } from '../data/open';
|
||||
import { AuthModel } from '../data/auth';
|
||||
import { sequelize } from '../data';
|
||||
|
||||
interface Dbs {
|
||||
cronDb: DataStore;
|
||||
dependenceDb: DataStore;
|
||||
envDb: DataStore;
|
||||
appDb: DataStore;
|
||||
authDb: DataStore;
|
||||
}
|
||||
|
||||
const db: Dbs = {} as any;
|
||||
|
||||
async function truncateDb() {
|
||||
return new Promise(async (resolve) => {
|
||||
const files = [
|
||||
config.cronDbFile,
|
||||
config.dependenceDbFile,
|
||||
config.envDbFile,
|
||||
config.appDbFile,
|
||||
config.authDbFile,
|
||||
];
|
||||
|
||||
for (const file of files) {
|
||||
const _fileExist = await fileExist(file);
|
||||
if (_fileExist && fs.statSync(file).size >= 1024 * 1024 * 500) {
|
||||
fs.truncateSync(file, 1024 * 1024 * 500);
|
||||
}
|
||||
}
|
||||
resolve(null);
|
||||
});
|
||||
}
|
||||
export default async () => {
|
||||
try {
|
||||
await truncateDb();
|
||||
await sequelize.sync();
|
||||
await new Promise((resolve) => setTimeout(() => resolve(null), 5000));
|
||||
|
||||
db.cronDb = new DataStore({ filename: config.cronDbFile, autoload: true });
|
||||
db.dependenceDb = new DataStore({
|
||||
filename: config.dependenceDbFile,
|
||||
autoload: true,
|
||||
});
|
||||
db.envDb = new DataStore({ filename: config.envDbFile, autoload: true });
|
||||
db.appDb = new DataStore({ filename: config.appDbFile, autoload: true });
|
||||
db.authDb = new DataStore({ filename: config.authDbFile, autoload: true });
|
||||
// try {
|
||||
// const queryInterface = sequelize.getQueryInterface();
|
||||
// await queryInterface.addIndex('Crontabs', ['command'], { unique: true });
|
||||
// await queryInterface.addIndex('Envs', ['name', 'value'], { unique: true });
|
||||
// await queryInterface.addIndex('Apps', ['name'], { unique: true });
|
||||
// } catch (error) {
|
||||
|
||||
// compaction data file
|
||||
db.cronDb.persistence.compactDatafile();
|
||||
db.envDb.persistence.compactDatafile();
|
||||
db.dependenceDb.persistence.compactDatafile();
|
||||
db.appDb.persistence.compactDatafile();
|
||||
db.authDb.persistence.compactDatafile();
|
||||
// }
|
||||
|
||||
try {
|
||||
await sequelize.sync({ alter: true });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
const crondbExist = await fileExist(config.cronDbFile);
|
||||
const dependenceDbExist = await fileExist(config.dependenceDbFile);
|
||||
const envDbExist = await fileExist(config.envDbFile);
|
||||
const appDbExist = await fileExist(config.appDbFile);
|
||||
const authDbExist = await fileExist(config.authDbFile);
|
||||
|
||||
const cronCount = await CrontabModel.count();
|
||||
const dependenceCount = await DependenceModel.count();
|
||||
const envCount = await EnvModel.count();
|
||||
const appCount = await AppModel.count();
|
||||
const authCount = await AuthModel.count();
|
||||
if (crondbExist && cronCount === 0) {
|
||||
const cronDb = new DataStore({
|
||||
filename: config.cronDbFile,
|
||||
autoload: true,
|
||||
});
|
||||
cronDb.persistence.compactDatafile();
|
||||
cronDb.find({}).exec(async (err, docs) => {
|
||||
await CrontabModel.bulkCreate(docs, { ignoreDuplicates: true });
|
||||
});
|
||||
}
|
||||
|
||||
// migrate db to sqlite
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const count = await CrontabModel.count();
|
||||
if (count !== 0) {
|
||||
return;
|
||||
}
|
||||
db.cronDb.find({}).exec(async (err, docs) => {
|
||||
await CrontabModel.bulkCreate(docs);
|
||||
});
|
||||
if (dependenceDbExist && dependenceCount === 0) {
|
||||
const dependenceDb = new DataStore({
|
||||
filename: config.dependenceDbFile,
|
||||
autoload: true,
|
||||
});
|
||||
dependenceDb.persistence.compactDatafile();
|
||||
dependenceDb.find({}).exec(async (err, docs) => {
|
||||
await DependenceModel.bulkCreate(docs, { ignoreDuplicates: true });
|
||||
});
|
||||
}
|
||||
|
||||
db.dependenceDb.find({}).exec(async (err, docs) => {
|
||||
await DependenceModel.bulkCreate(docs);
|
||||
});
|
||||
if (envDbExist && envCount === 0) {
|
||||
const envDb = new DataStore({
|
||||
filename: config.envDbFile,
|
||||
autoload: true,
|
||||
});
|
||||
envDb.persistence.compactDatafile();
|
||||
envDb.find({}).exec(async (err, docs) => {
|
||||
await EnvModel.bulkCreate(docs, { ignoreDuplicates: true });
|
||||
});
|
||||
}
|
||||
|
||||
db.envDb.find({}).exec(async (err, docs) => {
|
||||
await EnvModel.bulkCreate(docs);
|
||||
});
|
||||
if (appDbExist && appCount === 0) {
|
||||
const appDb = new DataStore({
|
||||
filename: config.appDbFile,
|
||||
autoload: true,
|
||||
});
|
||||
appDb.persistence.compactDatafile();
|
||||
appDb.find({}).exec(async (err, docs) => {
|
||||
await AppModel.bulkCreate(docs, { ignoreDuplicates: true });
|
||||
});
|
||||
}
|
||||
|
||||
db.appDb.find({}).exec(async (err, docs) => {
|
||||
await AppModel.bulkCreate(docs);
|
||||
});
|
||||
|
||||
db.authDb.find({}).exec(async (err, docs) => {
|
||||
await AuthModel.bulkCreate(docs);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}, 5000);
|
||||
if (authDbExist && authCount === 0) {
|
||||
const authDb = new DataStore({
|
||||
filename: config.authDbFile,
|
||||
autoload: true,
|
||||
});
|
||||
authDb.persistence.compactDatafile();
|
||||
authDb.find({}).exec(async (err, docs) => {
|
||||
await AuthModel.bulkCreate(docs, { ignoreDuplicates: true });
|
||||
});
|
||||
}
|
||||
|
||||
Logger.info('✌️ DB loaded');
|
||||
} catch (error) {
|
||||
@@ -102,5 +95,3 @@ export default async () => {
|
||||
Logger.info(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const dbs: Dbs = db;
|
||||
|
||||
@@ -68,18 +68,19 @@ export default ({ app }: { app: Application }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const originPath = `${req.baseUrl}${req.path === '/' ? '' : req.path}`;
|
||||
if (
|
||||
!headerToken &&
|
||||
req.path &&
|
||||
config.apiWhiteList.includes(req.path) &&
|
||||
req.path !== '/api/crons/status'
|
||||
originPath &&
|
||||
config.apiWhiteList.includes(originPath) &&
|
||||
originPath !== '/api/crons/status'
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
const remoteAddress = req.socket.remoteAddress;
|
||||
if (
|
||||
remoteAddress === '::ffff:127.0.0.1' &&
|
||||
req.path === '/api/crons/status'
|
||||
originPath === '/api/crons/status'
|
||||
) {
|
||||
return next();
|
||||
}
|
||||
|
||||
+8
-10
@@ -5,7 +5,7 @@ import { Crontab, CrontabModel, CrontabStatus } from '../data/cron';
|
||||
import { exec, execSync, spawn } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import cron_parser from 'cron-parser';
|
||||
import { getFileContentByName, concurrentRun } from '../config/util';
|
||||
import { getFileContentByName, concurrentRun, fileExist } from '../config/util';
|
||||
import { promises, existsSync } from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import { Op } from 'sequelize';
|
||||
@@ -31,12 +31,6 @@ export default class CronService {
|
||||
}
|
||||
|
||||
public async insert(payload: Crontab): Promise<Crontab> {
|
||||
const cron = await CrontabModel.findOne({
|
||||
where: { command: payload.command },
|
||||
});
|
||||
if (cron) {
|
||||
return cron;
|
||||
}
|
||||
return await CrontabModel.create(payload, { returning: true });
|
||||
}
|
||||
|
||||
@@ -163,7 +157,10 @@ export default class CronService {
|
||||
}
|
||||
}
|
||||
try {
|
||||
const result = await CrontabModel.findAll({ where: query });
|
||||
const result = await CrontabModel.findAll({
|
||||
where: query,
|
||||
order: [['createdAt', 'DESC']],
|
||||
});
|
||||
return result as any;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
@@ -197,7 +194,8 @@ export default class CronService {
|
||||
}
|
||||
}
|
||||
const err = await this.killTask(doc.command);
|
||||
if (doc.log_path) {
|
||||
const logFileExist = await fileExist(doc.log_path);
|
||||
if (doc.log_path && logFileExist) {
|
||||
const str = err ? `\n${err}` : '';
|
||||
fs.appendFileSync(
|
||||
`${doc.log_path}`,
|
||||
@@ -209,7 +207,7 @@ export default class CronService {
|
||||
}
|
||||
|
||||
await CrontabModel.update(
|
||||
{ status: CrontabStatus.queued, pid: undefined },
|
||||
{ status: CrontabStatus.idle, pid: undefined },
|
||||
{ where: { id: ids } },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import winston from 'winston';
|
||||
import config from '../config';
|
||||
import DataStore from 'nedb';
|
||||
import {
|
||||
Dependence,
|
||||
InstallDependenceCommandTypes,
|
||||
@@ -64,6 +63,7 @@ export default class DependenceService {
|
||||
);
|
||||
const docs = await DependenceModel.findAll({ where: { id: ids } });
|
||||
this.installOrUninstallDependencies(docs, false);
|
||||
return docs;
|
||||
}
|
||||
|
||||
public async removeDb(ids: number[]) {
|
||||
|
||||
@@ -3,7 +3,6 @@ import winston from 'winston';
|
||||
import { getFileContentByName } from '../config/util';
|
||||
import config from '../config';
|
||||
import * as fs from 'fs';
|
||||
import DataStore from 'nedb';
|
||||
import { Env, EnvModel, EnvStatus, initEnvPosition } from '../data/env';
|
||||
import _ from 'lodash';
|
||||
import { Op } from 'sequelize';
|
||||
@@ -29,8 +28,12 @@ export default class EnvService {
|
||||
}
|
||||
|
||||
public async insert(payloads: Env[]): Promise<Env[]> {
|
||||
const docs = await EnvModel.bulkCreate(payloads);
|
||||
return docs;
|
||||
const result = [];
|
||||
for (const env of payloads) {
|
||||
const doc = await EnvModel.create(env, { returning: true });
|
||||
result.push(doc);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async update(payload: Env): Promise<Env> {
|
||||
@@ -46,6 +49,7 @@ export default class EnvService {
|
||||
|
||||
public async remove(ids: string[]) {
|
||||
await EnvModel.destroy({ where: { id: ids } });
|
||||
await this.set_envs();
|
||||
}
|
||||
|
||||
public async move(
|
||||
|
||||
@@ -91,11 +91,12 @@ export default class NotificationService {
|
||||
const { goCqHttpBotQq, goCqHttpBotToken, goCqHttpBotUrl } = this.params;
|
||||
const res: any = await got
|
||||
.post(
|
||||
`${goCqHttpBotUrl}?access_token=${goCqHttpBotToken}&${goCqHttpBotQq}`,
|
||||
`${goCqHttpBotUrl}?${goCqHttpBotQq}`,
|
||||
{
|
||||
timeout: this.timeout,
|
||||
retry: 0,
|
||||
json: { message: `${this.title}\n${this.content}` },
|
||||
headers: { 'Authorization': 'Bearer '+goCqHttpBotToken },
|
||||
},
|
||||
)
|
||||
.json();
|
||||
@@ -119,7 +120,7 @@ export default class NotificationService {
|
||||
}
|
||||
|
||||
private async bark() {
|
||||
let { barkPush, barkSound, barkGroup } = this.params;
|
||||
let { barkPush, barkIcon, barkSound, barkGroup } = this.params;
|
||||
if (!barkPush.startsWith('http') && !barkPush.startsWith('https')) {
|
||||
barkPush = `https://api.day.app/${barkPush}`;
|
||||
}
|
||||
@@ -127,7 +128,7 @@ export default class NotificationService {
|
||||
this.title,
|
||||
)}/${encodeURIComponent(
|
||||
this.content,
|
||||
)}?sound=${barkSound}&group=${barkGroup}`;
|
||||
)}?icon=${barkIcon}?sound=${barkSound}&group=${barkGroup}`;
|
||||
const res: any = await got
|
||||
.get(url, {
|
||||
timeout: this.timeout,
|
||||
|
||||
+13
-4
@@ -2,7 +2,6 @@ import { Service, Inject } from 'typedi';
|
||||
import winston from 'winston';
|
||||
import { createRandomString } from '../config/util';
|
||||
import config from '../config';
|
||||
import DataStore from 'nedb';
|
||||
import { App, AppModel } from '../data/open';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import sequelize, { Op } from 'sequelize';
|
||||
@@ -25,8 +24,8 @@ export default class OpenService {
|
||||
return { ...doc, tokens: [] };
|
||||
}
|
||||
|
||||
public async insert(payloads: App): Promise<App> {
|
||||
const doc = await AppModel.create(payloads, { returning: true });
|
||||
public async insert(payload: App): Promise<App> {
|
||||
const doc = await AppModel.create(payload, { returning: true });
|
||||
return doc.get({ plain: true }) as App;
|
||||
}
|
||||
|
||||
@@ -54,7 +53,17 @@ export default class OpenService {
|
||||
}
|
||||
|
||||
public async resetSecret(id: number): Promise<App> {
|
||||
const tab: any = { client_secret: createRandomString(24, 24), tokens: [] };
|
||||
const tab: any = {
|
||||
client_secret: createRandomString(24, 24),
|
||||
tokens: [],
|
||||
id,
|
||||
};
|
||||
// const doc = await this.get(id);
|
||||
// const tab = new App({ ...doc });
|
||||
// tab.client_secret = createRandomString(24, 24);
|
||||
// tab.tokens = [];
|
||||
// const newDoc = await this.updateDb(tab);
|
||||
// return newDoc;
|
||||
const newDoc = await this.updateDb(tab);
|
||||
return newDoc;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import ScheduleService from './schedule';
|
||||
import { spawn } from 'child_process';
|
||||
import SockService from './sock';
|
||||
import got from 'got';
|
||||
import { dbs } from '../loaders/db';
|
||||
|
||||
@Service()
|
||||
export default class SystemService {
|
||||
@@ -87,9 +86,9 @@ export default class SystemService {
|
||||
let lastLog = '';
|
||||
try {
|
||||
const result = await Promise.race([
|
||||
got.get(config.lastVersionFile, { timeout: 1000, retry: 0 }),
|
||||
got.get(config.lastVersionFile, { timeout: 6000, retry: 0 }),
|
||||
got.get(`https://ghproxy.com/${config.lastVersionFile}`, {
|
||||
timeout: 5000,
|
||||
timeout: 6000,
|
||||
retry: 0,
|
||||
}),
|
||||
]);
|
||||
|
||||
+10
-2
@@ -313,8 +313,16 @@ export default class UserService {
|
||||
}
|
||||
|
||||
private async updateAuthDb(payload: AuthInfo): Promise<any> {
|
||||
await AuthModel.upsert({ ...payload });
|
||||
const doc = await this.getDb({ type: payload.type });
|
||||
let doc = await AuthModel.findOne({ type: payload.type });
|
||||
if (doc) {
|
||||
const updateResult = await AuthModel.update(payload, {
|
||||
where: { id: doc.id },
|
||||
returning: true,
|
||||
});
|
||||
doc = updateResult[1][0];
|
||||
} else {
|
||||
doc = await AuthModel.create(payload, { returning: true });
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
|
||||
+8
-33
@@ -1,53 +1,28 @@
|
||||
FROM node:lts-alpine3.12
|
||||
FROM whyour/ql:base
|
||||
ARG QL_MAINTAINER="whyour"
|
||||
LABEL maintainer="${QL_MAINTAINER}"
|
||||
ARG QL_URL=https://github.com/${QL_MAINTAINER}/qinglong.git
|
||||
ARG QL_BRANCH=master
|
||||
|
||||
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 \$ " \
|
||||
QL_DIR=/ql \
|
||||
QL_BRANCH=${QL_BRANCH}
|
||||
|
||||
WORKDIR ${QL_DIR}
|
||||
RUN set -x \
|
||||
&& sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/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 clone -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
||||
&& git config --global user.email "qinglong@@users.noreply.github.com" \
|
||||
&& git config --global user.name "qinglong" \
|
||||
|
||||
RUN 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 \
|
||||
&& npm install -g pnpm \
|
||||
&& pnpm install -g pm2 \
|
||||
&& pnpm install -g ts-node typescript tslib \
|
||||
&& rm -rf /root/.npm \
|
||||
&& cp -rf /node_modules ./ \
|
||||
&& rm -rf /node_modules \
|
||||
&& pnpm install --prod \
|
||||
&& rm -rf /root/.pnpm-store \
|
||||
&& rm -rf /root/.cache \
|
||||
&& git clone -b ${QL_BRANCH} https://github.com/${QL_MAINTAINER}/qinglong-static.git /static \
|
||||
&& cp -rf /static/* ${QL_DIR} \
|
||||
&& rm -rf /static
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
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" ]
|
||||
+13
-13
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "umi dev",
|
||||
"build": "umi build",
|
||||
"build-back": "tsc -p tsconfig.back.json",
|
||||
"start-back": "nodemon",
|
||||
"panel": "npm run build-back && node build/app.js",
|
||||
"schedule": "npm run build-back && node build/schedule.js",
|
||||
"prepare": "umi generate tmp",
|
||||
"start": "concurrently -n w: npm:start:*",
|
||||
"start:front": "umi dev",
|
||||
"start:back": "nodemon",
|
||||
"build:front": "umi build",
|
||||
"build:back": "tsc -p tsconfig.back.json",
|
||||
"panel": "npm run build:back && node build/app.js",
|
||||
"schedule": "npm run build:back && node build/schedule.js",
|
||||
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
||||
"prepare": "umi generate tmp",
|
||||
"test": "umi-test",
|
||||
"test:coverage": "umi-test --coverage"
|
||||
},
|
||||
@@ -25,8 +26,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@otplib/preset-default": "^12.0.1",
|
||||
"@sentry/node": "^6.16.1",
|
||||
"@sentry/tracing": "^6.16.1",
|
||||
"@sentry/node": "^6.17.2",
|
||||
"@sentry/tracing": "^6.17.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"celebrate": "^13.0.3",
|
||||
"chokidar": "^3.5.2",
|
||||
@@ -60,8 +61,7 @@
|
||||
"@ant-design/icons": "^4.6.2",
|
||||
"@ant-design/pro-layout": "^6.26.0",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@sentry/react": "^6.16.1",
|
||||
"@sentry/webpack-plugin": "^1.18.3",
|
||||
"@sentry/react": "^6.17.2",
|
||||
"@types/cors": "^2.8.10",
|
||||
"@types/express": "^4.17.8",
|
||||
"@types/express-jwt": "^6.0.1",
|
||||
@@ -84,9 +84,9 @@
|
||||
"antd": "^4.17.0-alpha.6",
|
||||
"codemirror": "^5.62.2",
|
||||
"compression-webpack-plugin": "6.1.1",
|
||||
"darkreader": "^4.9.27",
|
||||
"concurrently": "^7.0.0",
|
||||
"darkreader": "4.9.40",
|
||||
"lint-staged": "^10.0.7",
|
||||
"@mapbox/node-pre-gyp": "1.0.8",
|
||||
"nodemon": "^2.0.4",
|
||||
"prettier": "^2.2.0",
|
||||
"qrcode.react": "^1.0.1",
|
||||
|
||||
@@ -16,8 +16,8 @@ DefaultCronRule=""
|
||||
## ql repo命令拉取脚本时需要拉取的文件后缀,直接写文件后缀名即可
|
||||
RepoFileExtensions="js py"
|
||||
|
||||
## 由于github仓库拉取较慢,所以会默认添加代理前缀,如不需要请移除
|
||||
GithubProxyUrl="https://ghproxy.com/"
|
||||
## 代理地址,支持http/https/socks,例如 http://127.0.0.1:7890
|
||||
ProxyUrl=""
|
||||
|
||||
## 设置定时任务执行的超时时间,默认1h,后缀"s"代表秒(默认值), "m"代表分, "h"代表小时, "d"代表天
|
||||
CommandTimeoutTime="1h"
|
||||
@@ -50,6 +50,8 @@ export PUSH_KEY=""
|
||||
## 2. BARK
|
||||
## 下方填写app提供的设备码,例如:https://api.day.app/123 那么此处的设备码就是123
|
||||
export BARK_PUSH=""
|
||||
## 下方填写推送图标设置,自定义推送图标(需iOS15或以上)
|
||||
export BARK_ICON="https://img.gejiba.com/images/a3f551e09ac19add4c49ec16228729c5.png"
|
||||
## 下方填写推送声音设置,例如choo,具体值请在bark-推送铃声-查看所有铃声
|
||||
export BARK_SOUND=""
|
||||
## 下方填写推送消息分组,默认为"QingLong"
|
||||
|
||||
+22
-12
@@ -25,9 +25,9 @@ let GOTIFY_PRIORITY = 0;
|
||||
//gobot_token 填写在go-cqhttp文件设置的访问密钥
|
||||
//gobot_qq 填写推送到个人QQ或者QQ群号
|
||||
//go-cqhttp相关API https://docs.go-cqhttp.org/api
|
||||
let GOBOT_URL = ''; // 推送到个人QQ: http://127.0.0.1/send_private_msg 群:http://127.0.0.1/send_group_msg
|
||||
let GOBOT_URL = ''; // 推送到个人QQ: http://127.0.0.1/send_private_msg 群:http://127.0.0.1/send_group_msg
|
||||
let GOBOT_TOKEN = ''; //访问密钥
|
||||
let GOBOT_QQ = ''; // 如果GOBOT_URL设置 /send_private_msg 则需要填入 user_id=个人QQ 相反如果是 /send_group_msg 则需要填入 group_id=QQ群
|
||||
let GOBOT_QQ = ''; // 如果GOBOT_URL设置 /send_private_msg 则需要填入 user_id=个人QQ 相反如果是 /send_group_msg 则需要填入 group_id=QQ群
|
||||
|
||||
// =======================================微信server酱通知设置区域===========================================
|
||||
//此处填你申请的SCKEY.
|
||||
@@ -37,6 +37,9 @@ let SCKEY = '';
|
||||
// =======================================Bark App通知设置区域===========================================
|
||||
//此处填你BarkAPP的信息(IP/设备码,例如:https://api.day.app/XXXXXXXX)
|
||||
let BARK_PUSH = '';
|
||||
//BARK app推送图标,自定义推送图标(需iOS15或以上)
|
||||
let BARK_ICON =
|
||||
'https://img.gejiba.com/images/a3f551e09ac19add4c49ec16228729c5.png';
|
||||
//BARK app推送铃声,铃声列表去APP查看复制填写
|
||||
let BARK_SOUND = '';
|
||||
//BARK app推送消息的分组, 默认为"QingLong"
|
||||
@@ -133,6 +136,9 @@ if (process.env.BARK_PUSH) {
|
||||
} else {
|
||||
BARK_PUSH = `https://api.day.app/${process.env.BARK_PUSH}`;
|
||||
}
|
||||
if (process.env.BARK_ICON) {
|
||||
BARK_ICON = process.env.BARK_ICON;
|
||||
}
|
||||
if (process.env.BARK_SOUND) {
|
||||
BARK_SOUND = process.env.BARK_SOUND;
|
||||
}
|
||||
@@ -216,8 +222,8 @@ async function sendNotify(
|
||||
qywxBotNotify(text, desp), //企业微信机器人
|
||||
qywxamNotify(text, desp), //企业微信应用消息推送
|
||||
iGotNotify(text, desp, params), //iGot
|
||||
gobotNotify(text, desp),//go-cqhttp
|
||||
gotifyNotify(text, desp),//gotify
|
||||
gobotNotify(text, desp), //go-cqhttp
|
||||
gotifyNotify(text, desp), //gotify
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -226,10 +232,12 @@ function gotifyNotify(text, desp) {
|
||||
if (GOTIFY_URL && GOTIFY_TOKEN) {
|
||||
const options = {
|
||||
url: `${GOTIFY_URL}/message?token=${GOTIFY_TOKEN}`,
|
||||
body: `title=${encodeURIComponent(text)}&message=${encodeURIComponent(desp)}&priority=${GOTIFY_PRIORITY}`,
|
||||
body: `title=${encodeURIComponent(text)}&message=${encodeURIComponent(
|
||||
desp,
|
||||
)}&priority=${GOTIFY_PRIORITY}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
},
|
||||
};
|
||||
$.post(options, (err, resp, data) => {
|
||||
try {
|
||||
@@ -261,7 +269,7 @@ function gobotNotify(text, desp, time = 2100) {
|
||||
if (GOBOT_URL) {
|
||||
const options = {
|
||||
url: `${GOBOT_URL}?access_token=${GOBOT_TOKEN}&${GOBOT_QQ}`,
|
||||
json: {message:`${text}\n${desp}`},
|
||||
json: { message: `${text}\n${desp}` },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
@@ -429,7 +437,9 @@ function BarkNotify(text, desp, params = {}) {
|
||||
const options = {
|
||||
url: `${BARK_PUSH}/${encodeURIComponent(text)}/${encodeURIComponent(
|
||||
desp,
|
||||
)}?sound=${BARK_SOUND}&group=${BARK_GROUP}&${querystring.stringify(params)}`,
|
||||
)}?icon=${BARK_ICON}?sound=${BARK_SOUND}&group=${BARK_GROUP}&${querystring.stringify(
|
||||
params,
|
||||
)}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
@@ -726,8 +736,8 @@ function qywxamNotify(text, desp) {
|
||||
if (err) {
|
||||
console.log(
|
||||
'成员ID:' +
|
||||
ChangeUserId(desp) +
|
||||
'企业微信应用消息发送通知消息失败!!\n',
|
||||
ChangeUserId(desp) +
|
||||
'企业微信应用消息发送通知消息失败!!\n',
|
||||
);
|
||||
console.log(err);
|
||||
} else {
|
||||
@@ -735,8 +745,8 @@ function qywxamNotify(text, desp) {
|
||||
if (data.errcode === 0) {
|
||||
console.log(
|
||||
'成员ID:' +
|
||||
ChangeUserId(desp) +
|
||||
'企业微信应用消息发送通知消息成功🎉。\n',
|
||||
ChangeUserId(desp) +
|
||||
'企业微信应用消息发送通知消息成功🎉。\n',
|
||||
);
|
||||
} else {
|
||||
console.log(`${data.errmsg}\n`);
|
||||
|
||||
@@ -35,6 +35,7 @@ push_config = {
|
||||
'BARK_ARCHIVE': '', # bark 推送是否存档
|
||||
'BARK_GROUP': '', # bark 推送分组
|
||||
'BARK_SOUND': '', # bark 推送声音
|
||||
'BARK_ICON': '', # bark 推送图标
|
||||
|
||||
'CONSOLE': True, # 控制台输出
|
||||
|
||||
@@ -104,6 +105,7 @@ def bark(title: str, content: str) -> None:
|
||||
"BARK_ARCHIVE": "isArchive",
|
||||
"BARK_GROUP": "group",
|
||||
"BARK_SOUND": "sound",
|
||||
"BARK_ICON": "icon",
|
||||
}
|
||||
params = ""
|
||||
for pair in filter(
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ dir_shell=/ql/shell
|
||||
. $dir_shell/share.sh
|
||||
|
||||
if [[ -z ${BotRepoUrl} ]]; then
|
||||
url="${github_proxy_url}https://github.com/SuMaiKaDe/bot.git"
|
||||
url="https://github.com/SuMaiKaDe/bot.git"
|
||||
repo_path="${dir_repo}/dockerbot"
|
||||
else
|
||||
url=${BotRepoUrl}
|
||||
|
||||
+2
-4
@@ -10,13 +10,11 @@ reset_env() {
|
||||
echo -e "---> 配置文件检测完成\n"
|
||||
|
||||
echo -e "---> 2. 开始安装青龙依赖\n"
|
||||
rm -rf $dir_root/node_modules
|
||||
npm_install_2 $dir_root
|
||||
echo -e "---> 青龙依赖安装完成\n"
|
||||
|
||||
echo -e "---> 3. 开始安装脚本依赖\n"
|
||||
cp -f $dir_sample/package.json $dir_scripts/package.json
|
||||
rm -rf $dir_scripts/node_modules
|
||||
npm_install_2 $dir_scripts
|
||||
echo -e "---> 脚本依赖安装完成\n"
|
||||
}
|
||||
@@ -50,8 +48,8 @@ pm2_log() {
|
||||
echo -e "---> pm2日志"
|
||||
local panelOut="/root/.pm2/logs/panel-out.log"
|
||||
local panelError="/root/.pm2/logs/panel-error.log"
|
||||
tail -n 20 "$panelOut"
|
||||
tail -n 20 "$panelError"
|
||||
tail -n 100 "$panelOut"
|
||||
tail -n 100 "$panelError"
|
||||
}
|
||||
|
||||
check_nginx() {
|
||||
|
||||
+24
-1
@@ -60,6 +60,7 @@ original_name=(
|
||||
|
||||
init_env() {
|
||||
export NODE_PATH=/usr/local/bin:/usr/local/pnpm-global/5/node_modules:/usr/local/lib/node_modules
|
||||
export PYTHONUNBUFFERED=1
|
||||
}
|
||||
|
||||
import_config() {
|
||||
@@ -67,8 +68,9 @@ import_config() {
|
||||
[[ -f $file_env ]] && . $file_env
|
||||
|
||||
command_timeout_time=${CommandTimeoutTime:-"1h"}
|
||||
github_proxy_url=${GithubProxyUrl:-""}
|
||||
proxy_url=${ProxyUrl:-""}
|
||||
file_extensions=${RepoFileExtensions:-"js py"}
|
||||
current_branch=${QL_BRANCH}
|
||||
|
||||
if [[ -n "${DefaultCronRule}" ]]; then
|
||||
default_cron="${DefaultCronRule}"
|
||||
@@ -77,6 +79,18 @@ import_config() {
|
||||
fi
|
||||
}
|
||||
|
||||
set_proxy() {
|
||||
if [[ $proxy_url ]]; then
|
||||
export http_proxy="${proxy_url}"
|
||||
export https_proxy="${proxy_url}"
|
||||
fi
|
||||
}
|
||||
|
||||
unset_proxy() {
|
||||
unset http_proxy
|
||||
unset https_proxy
|
||||
}
|
||||
|
||||
make_dir() {
|
||||
local dir=$1
|
||||
if [[ ! -d $dir ]]; then
|
||||
@@ -228,6 +242,7 @@ fix_config() {
|
||||
}
|
||||
|
||||
npm_install_sub() {
|
||||
set_proxy
|
||||
if [ $is_termux -eq 1 ]; then
|
||||
npm install --production --no-bin-links --registry=https://registry.npm.taobao.org || npm install --production --no-bin-links
|
||||
elif ! type pnpm &>/dev/null; then
|
||||
@@ -235,6 +250,7 @@ npm_install_sub() {
|
||||
else
|
||||
pnpm install --production --registry=https://registry.npm.taobao.org || pnpm install --production
|
||||
fi
|
||||
unset_proxy
|
||||
}
|
||||
|
||||
npm_install_1() {
|
||||
@@ -287,8 +303,11 @@ git_clone_scripts() {
|
||||
local branch=$3
|
||||
[[ $branch ]] && local part_cmd="-b $branch "
|
||||
echo -e "开始克隆仓库 $url 到 $dir\n"
|
||||
|
||||
set_proxy
|
||||
git clone $part_cmd $url $dir
|
||||
exit_status=$?
|
||||
unset_proxy
|
||||
}
|
||||
|
||||
git_pull_scripts() {
|
||||
@@ -298,10 +317,14 @@ git_pull_scripts() {
|
||||
[[ $branch ]] && local part_cmd="origin/${branch}"
|
||||
cd $dir_work
|
||||
echo -e "开始更新仓库:$dir_work\n"
|
||||
|
||||
set_proxy
|
||||
git fetch --all
|
||||
exit_status=$?
|
||||
git reset --hard $part_cmd
|
||||
git pull
|
||||
unset_proxy
|
||||
|
||||
cd $dir_current
|
||||
}
|
||||
|
||||
|
||||
+12
-24
@@ -151,11 +151,6 @@ update_repo() {
|
||||
make_dir "${dir_scripts}/${uniq_path}"
|
||||
|
||||
local formatUrl="$url"
|
||||
if [[ $github_proxy_url ]]; then
|
||||
if [[ $url =~ "github.com" ]] || [[ $url =~ "githubusercontent.com" ]]; then
|
||||
[[ ! $url =~ "https://ghproxy.com" ]] && formatUrl="${github_proxy_url}${url}"
|
||||
fi
|
||||
fi
|
||||
if [[ -d ${repo_path}/.git ]]; then
|
||||
reset_romote_url ${repo_path} "${formatUrl}" "${branch}"
|
||||
git_pull_scripts ${repo_path} "${branch}"
|
||||
@@ -166,15 +161,7 @@ update_repo() {
|
||||
echo -e "\n更新${repo_path}成功...\n"
|
||||
diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence"
|
||||
else
|
||||
echo -e "\n更新${repo_path}失败,重新下载全新仓库...\n"
|
||||
rm -rf ${repo_path}
|
||||
git_clone_scripts "${formatUrl}" ${repo_path} "${branch}"
|
||||
if [[ $exit_status -eq 0 ]]; then
|
||||
echo -e "\n更新${repo_path}成功...\n"
|
||||
diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence"
|
||||
else
|
||||
echo -e "\n更新${repo_path}失败,请检查网络...\n"
|
||||
fi
|
||||
echo -e "\n更新${repo_path}失败,请检查网络...\n"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -183,15 +170,14 @@ update_raw() {
|
||||
echo -e "--------------------------------------------------------------\n"
|
||||
local url="$1"
|
||||
local raw_url="$url"
|
||||
if [[ $github_proxy_url ]]; then
|
||||
if [[ $url =~ "github.com" ]] || [[ $url =~ "githubusercontent.com" ]]; then
|
||||
[[ ! $url =~ "https://ghproxy.com" ]] && raw_url="${github_proxy_url}${url}"
|
||||
fi
|
||||
fi
|
||||
local suffix="${raw_url##*.}"
|
||||
local raw_file_name="${uniq_path}.${suffix}"
|
||||
echo -e "开始下载:${raw_url} \n\n保存路径:$dir_raw/${raw_file_name}\n"
|
||||
|
||||
set_proxy
|
||||
wget -q --no-check-certificate -O "$dir_raw/${raw_file_name}.new" ${raw_url}
|
||||
unset_proxy
|
||||
|
||||
if [[ $? -eq 0 ]]; then
|
||||
mv "$dir_raw/${raw_file_name}.new" "$dir_raw/${raw_file_name}"
|
||||
echo -e "下载 ${raw_file_name} 成功...\n"
|
||||
@@ -259,8 +245,8 @@ update_qinglong() {
|
||||
|
||||
local no_restart="$1"
|
||||
[[ -f $dir_root/package.json ]] && ql_depend_old=$(cat $dir_root/package.json)
|
||||
reset_romote_url ${dir_root} "${github_proxy_url}https://github.com/whyour/qinglong.git" "master"
|
||||
git_pull_scripts $dir_root "master"
|
||||
reset_romote_url ${dir_root} "https://github.com/whyour/qinglong.git" ${current_branch}
|
||||
git_pull_scripts $dir_root ${current_branch}
|
||||
|
||||
if [[ $exit_status -eq 0 ]]; then
|
||||
echo -e "\n更新$dir_root成功...\n"
|
||||
@@ -274,10 +260,10 @@ update_qinglong() {
|
||||
echo -e "\n更新$dir_root失败,请检查原因...\n"
|
||||
fi
|
||||
|
||||
local url="${github_proxy_url}https://github.com/whyour/qinglong-static.git"
|
||||
local url="https://github.com/whyour/qinglong-static.git"
|
||||
if [[ -d ${ql_static_repo}/.git ]]; then
|
||||
reset_romote_url ${ql_static_repo} ${url} "master"
|
||||
git_pull_scripts ${ql_static_repo} "master"
|
||||
reset_romote_url ${ql_static_repo} ${url} ${current_branch}
|
||||
git_pull_scripts ${ql_static_repo} ${current_branch}
|
||||
else
|
||||
git_clone_scripts ${url} ${ql_static_repo}
|
||||
fi
|
||||
@@ -313,6 +299,8 @@ patch_version() {
|
||||
fi
|
||||
|
||||
git config --global pull.rebase false
|
||||
|
||||
cp -f /ql/.env.example /ql/.env
|
||||
}
|
||||
|
||||
reload_pm2() {
|
||||
|
||||
@@ -235,7 +235,7 @@ export default function (props: any) {
|
||||
logo={
|
||||
<Image
|
||||
preview={false}
|
||||
src="https://pic.imgdb.cn/item/61acd0dd2ab3f51d912b1986.png"
|
||||
src="https://img.gejiba.com/images/a3f551e09ac19add4c49ec16228729c5.png"
|
||||
/>
|
||||
}
|
||||
title={
|
||||
|
||||
+30
-16
@@ -31,7 +31,7 @@ import {
|
||||
import config from '@/utils/config';
|
||||
import { PageContainer } from '@ant-design/pro-layout';
|
||||
import { request } from '@/utils/http';
|
||||
import CronModal,{ CronLabelModal } from './modal';
|
||||
import CronModal, { CronLabelModal } from './modal';
|
||||
import CronLogModal from './logModal';
|
||||
import cron_parser from 'cron-parser';
|
||||
import { diffTime } from '@/utils/date';
|
||||
@@ -49,7 +49,7 @@ enum CrontabStatus {
|
||||
'queued',
|
||||
}
|
||||
|
||||
const CrontabSort: any = { 0: 0, 3: 1, 1: 2, 4: 3 };
|
||||
const CrontabSort: any = { 0: 0, 5: 1, 3: 2, 1: 3, 4: 4 };
|
||||
|
||||
enum OperationName {
|
||||
'启用',
|
||||
@@ -94,21 +94,30 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
||||
)}
|
||||
</a>
|
||||
<span>
|
||||
{record.labels?.length > 0 && record.labels[0] !== '' ?
|
||||
<Popover placement='right' trigger={isPhone ? 'click' : 'hover'}
|
||||
{record.labels?.length > 0 && record.labels[0] !== '' ? (
|
||||
<Popover
|
||||
placement="right"
|
||||
trigger={isPhone ? 'click' : 'hover'}
|
||||
content={
|
||||
<div>
|
||||
{record.labels?.map((label: string, i: number) => (
|
||||
<Tag color="blue"
|
||||
onClick={() => { onSearch(`label:${label}`) }}>
|
||||
<Tag
|
||||
color="blue"
|
||||
onClick={() => {
|
||||
onSearch(`label:${label}`);
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
}>
|
||||
}
|
||||
>
|
||||
<Tag color="blue">{record.labels[0]}</Tag>
|
||||
</Popover>
|
||||
: ''}
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</span>
|
||||
</>
|
||||
),
|
||||
@@ -381,14 +390,19 @@ const Crontab = ({ headerStyle, isPhone }: any) => {
|
||||
setValue(
|
||||
data.data
|
||||
.sort((a: any, b: any) => {
|
||||
const sortA = a.isDisabled ? 4 : a.status;
|
||||
const sortB = b.isDisabled ? 4 : b.status;
|
||||
a.isPinned = a.isPinned ? a.isPinned : 0;
|
||||
b.isPinned = b.isPinned ? b.isPinned : 0;
|
||||
if (a.isPinned === b.isPinned) {
|
||||
return CrontabSort[sortA] - CrontabSort[sortB];
|
||||
}
|
||||
return b.isPinned - a.isPinned;
|
||||
const sortA =
|
||||
a.isPinned && a.status !== 0
|
||||
? 5
|
||||
: a.isDisabled && a.status !== 0
|
||||
? 4
|
||||
: a.status;
|
||||
const sortB =
|
||||
b.isPinned && b.status !== 0
|
||||
? 5
|
||||
: b.isDisabled && b.status !== 0
|
||||
? 4
|
||||
: b.status;
|
||||
return CrontabSort[sortA] - CrontabSort[sortB];
|
||||
})
|
||||
.map((x) => {
|
||||
return {
|
||||
|
||||
@@ -61,7 +61,7 @@ const CronLogModal = ({
|
||||
<Countdown
|
||||
className="inline-countdown"
|
||||
format="ss"
|
||||
value={Date.now() + 1000 * 10}
|
||||
value={Date.now() + 1000 * 30}
|
||||
/>
|
||||
秒后自动刷新
|
||||
</span>
|
||||
@@ -70,7 +70,7 @@ const CronLogModal = ({
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 10000);
|
||||
}, 30000);
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -90,7 +90,7 @@ const CronLogModal = ({
|
||||
return (
|
||||
<>
|
||||
{(executing || loading) && <Loading3QuartersOutlined spin />}
|
||||
{!executing && <CheckCircleOutlined />}
|
||||
{!executing && !loading && <CheckCircleOutlined />}
|
||||
<span style={{ marginLeft: 5 }}>日志-{cron && cron.name}</span>{' '}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -94,7 +94,22 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
|
||||
dataIndex: 'timestamp',
|
||||
align: 'center' as const,
|
||||
render: (text: string, record: any) => {
|
||||
return <span>{new Date(record.timestamp).toLocaleString()}</span>;
|
||||
const language = navigator.language || navigator.languages[0];
|
||||
const time = record.createdAt || record.timestamp;
|
||||
const date = new Date(time)
|
||||
.toLocaleString(language, {
|
||||
hour12: false,
|
||||
})
|
||||
.replace(' 24:', ' 00:');
|
||||
return (
|
||||
<Tooltip
|
||||
placement="topLeft"
|
||||
title={date}
|
||||
trigger={['hover', 'click']}
|
||||
>
|
||||
<span>{date}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Vendored
+9
-1
@@ -149,9 +149,17 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
|
||||
ellipsis: {
|
||||
showTitle: false,
|
||||
},
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => {
|
||||
const updatedAtA = new Date(a.updatedAt || a.timestamp).getTime();
|
||||
const updatedAtB = new Date(b.updatedAt || b.timestamp).getTime();
|
||||
return updatedAtA - updatedAtB;
|
||||
},
|
||||
},
|
||||
render: (text: string, record: any) => {
|
||||
const language = navigator.language || navigator.languages[0];
|
||||
const date = new Date(text)
|
||||
const time = record.updatedAt || record.timestamp;
|
||||
const date = new Date(time)
|
||||
.toLocaleString(language, {
|
||||
hour12: false,
|
||||
})
|
||||
|
||||
Vendored
+13
-9
@@ -36,16 +36,20 @@ const EnvModal = ({
|
||||
} else {
|
||||
payload = { ...values, id: env.id };
|
||||
}
|
||||
const { code, data } = await request[method](`${config.apiPrefix}envs`, {
|
||||
data: payload,
|
||||
});
|
||||
if (code === 200) {
|
||||
message.success(env ? '更新变量成功' : '新建变量成功');
|
||||
} else {
|
||||
message.error(data);
|
||||
try {
|
||||
const { code, data } = await request[method](`${config.apiPrefix}envs`, {
|
||||
data: payload,
|
||||
});
|
||||
if (code === 200) {
|
||||
message.success(env ? '更新变量成功' : '新建变量成功');
|
||||
} else {
|
||||
message.error(data);
|
||||
}
|
||||
setLoading(false);
|
||||
handleCancel(data);
|
||||
} catch (error: any) {
|
||||
setLoading(false);
|
||||
}
|
||||
setLoading(false);
|
||||
handleCancel(data);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -233,7 +233,7 @@ const Initialization = () => {
|
||||
<img
|
||||
alt="logo"
|
||||
className={styles.logo}
|
||||
src="https://pic.imgdb.cn/item/61acd0dd2ab3f51d912b1986.png"
|
||||
src="https://img.gejiba.com/images/a3f551e09ac19add4c49ec16228729c5.png"
|
||||
/>
|
||||
<span className={styles.title}>初始化配置</span>
|
||||
</div>
|
||||
|
||||
@@ -128,7 +128,7 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
||||
value={select}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
treeData={data}
|
||||
placeholder="请选择日志文件"
|
||||
placeholder="请选择日志"
|
||||
showSearch
|
||||
onSelect={onSelect}
|
||||
/>,
|
||||
@@ -147,6 +147,8 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
|
||||
<Input.Search
|
||||
className={styles['left-tree-search']}
|
||||
onChange={onSearch}
|
||||
placeholder="请输入日志名"
|
||||
allowClear
|
||||
></Input.Search>
|
||||
<div className={styles['left-tree-scroller']} ref={treeDom}>
|
||||
<Tree
|
||||
|
||||
@@ -135,7 +135,7 @@ const Login = () => {
|
||||
<img
|
||||
alt="logo"
|
||||
className={styles.logo}
|
||||
src="https://pic.imgdb.cn/item/61acd0dd2ab3f51d912b1986.png"
|
||||
src="https://img.gejiba.com/images/a3f551e09ac19add4c49ec16228729c5.png"
|
||||
/>
|
||||
<span className={styles.title}>
|
||||
{twoFactor ? '两步验证' : config.siteName}
|
||||
|
||||
@@ -413,7 +413,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
value={select}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
treeData={data}
|
||||
placeholder="请选择脚本文件"
|
||||
placeholder="请选择脚本"
|
||||
showSearch
|
||||
onSelect={onSelect}
|
||||
/>,
|
||||
@@ -477,6 +477,8 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
|
||||
<Input.Search
|
||||
className={styles['left-tree-search']}
|
||||
onChange={onSearch}
|
||||
placeholder="请输入脚本名"
|
||||
allowClear
|
||||
></Input.Search>
|
||||
<div className={styles['left-tree-scroller']} ref={treeDom}>
|
||||
<Tree
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Typography, Input, Form, Button, message } from 'antd';
|
||||
import styles from './index.less';
|
||||
|
||||
const { Link } = Typography;
|
||||
|
||||
const About = () => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<img
|
||||
alt="logo"
|
||||
style={{ width: 140, marginRight: 20 }}
|
||||
src="https://img.gejiba.com/images/a3f551e09ac19add4c49ec16228729c5.png"
|
||||
/>
|
||||
<div className={styles.right}>
|
||||
<span className={styles.title}>青龙</span>
|
||||
<span className={styles.desc}>
|
||||
支持python3、javaScript、shell、typescript 的定时任务管理面板(A timed
|
||||
task management panel that supports typescript, javaScript, python3,
|
||||
and shell.)
|
||||
</span>
|
||||
<div>
|
||||
<Link
|
||||
href="https://github.com/whyour/qinglong"
|
||||
target="_blank"
|
||||
style={{ marginRight: 15 }}
|
||||
>
|
||||
Github
|
||||
</Link>
|
||||
<Link
|
||||
href="https://t.me/jiao_long"
|
||||
target="_blank"
|
||||
style={{ marginRight: 15 }}
|
||||
>
|
||||
Telegram频道
|
||||
</Link>
|
||||
<Link
|
||||
href="https://github.com/whyour/qinglong/issues"
|
||||
target="_blank"
|
||||
>
|
||||
提交BUG
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default About;
|
||||
@@ -172,7 +172,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
||||
<Countdown
|
||||
className="inline-countdown"
|
||||
format="ss"
|
||||
value={Date.now() + 1000 * 10}
|
||||
value={Date.now() + 1000 * 30}
|
||||
/>
|
||||
秒后自动刷新
|
||||
</span>
|
||||
@@ -181,7 +181,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 10000);
|
||||
}, 30000);
|
||||
}
|
||||
}, [socketMessage]);
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 50px 130px;
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
font-size: 25px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import SecuritySettings from './security';
|
||||
import LoginLog from './loginLog';
|
||||
import NotificationSetting from './notification';
|
||||
import CheckUpdate from './checkUpdate';
|
||||
import { useForm } from 'antd/lib/form/Form';
|
||||
import About from './about';
|
||||
|
||||
const { Text } = Typography;
|
||||
const optionsWithDisabled = [
|
||||
@@ -124,7 +124,7 @@ const Setting = ({
|
||||
const [loginLogData, setLoginLogData] = useState<any[]>([]);
|
||||
const [notificationInfo, setNotificationInfo] = useState<any>();
|
||||
const [logRemoveFrequency, setLogRemoveFrequency] = useState<number>();
|
||||
const [form] = useForm();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const themeChange = (e: any) => {
|
||||
setTheme(e.target.value);
|
||||
@@ -381,6 +381,9 @@ const Setting = ({
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="关于" key="about">
|
||||
<About />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
<AppModal
|
||||
visible={isModalVisible}
|
||||
|
||||
@@ -119,6 +119,7 @@ export default {
|
||||
tip: 'Bark的信息IP/设备码,例如:https://api.day.app/XXXXXXXX',
|
||||
required: true,
|
||||
},
|
||||
{ label: 'barkIcon', tip: 'BARK推送图标,自定义推送图标 (需iOS15或以上才能显示)' },
|
||||
{ label: 'barkSound', tip: 'BARK推送铃声,铃声列表去APP查看复制填写' },
|
||||
{ label: 'barkGroup', tip: 'BARK推送消息的分组, 默认为qinglong' },
|
||||
],
|
||||
|
||||
+10
-7
@@ -1,8 +1,11 @@
|
||||
export const version = '2.11.0';
|
||||
export const changeLogLink = 'https://t.me/jiao_long/254';
|
||||
export const changeLog = `2.11.0 版本说明
|
||||
1. 使用sqlite替换nedb,增加数据库稳定性
|
||||
2. 任务添加标签功能,感谢https://github.com/kilo5hz
|
||||
3. 支持自定义bot仓库,感谢https://github.com/chiupam
|
||||
5. 其他bug修复
|
||||
export const version = '2.11.3';
|
||||
export const changeLogLink = 'https://t.me/jiao_long/262';
|
||||
export const changeLog = `2.11.3 版本说明
|
||||
1. 修复定时任务排序
|
||||
2. 环境变量增加更新时间排序
|
||||
3. 修复logo地址
|
||||
4. 修改check命令
|
||||
5. 安装依赖默认使用Proxy_url代理地址
|
||||
6. 修改apk源地址
|
||||
7. bark支持自定义图标,感谢 https://github.com/Ukenn2112 PR
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user