mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-11 19:05:20 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acb43f3ad8 | ||
|
|
b160f914ad | ||
|
|
6116aa6d12 | ||
|
|
d43cee53c9 | ||
|
|
defea8ff9b | ||
|
|
b290b60d46 | ||
|
|
706b5e8108 | ||
|
|
b55c7d5747 | ||
|
|
6b8fd94f0c | ||
|
|
e18710a30a | ||
|
|
c0f0da4e96 | ||
|
|
49ef5cd61f |
@@ -29,7 +29,13 @@ export default (app: Router) => {
|
||||
body: Joi.object({
|
||||
type: Joi.string().required(),
|
||||
schedule: Joi.string().optional().allow('').allow(null),
|
||||
interval_schedule: Joi.object().optional().allow('').allow(null),
|
||||
interval_schedule: Joi.object({
|
||||
type: Joi.string().required(),
|
||||
value: Joi.number().min(1).required(),
|
||||
})
|
||||
.optional()
|
||||
.allow('')
|
||||
.allow(null),
|
||||
name: Joi.string().optional().allow('').allow(null),
|
||||
url: Joi.string().required(),
|
||||
whitelist: Joi.string().optional().allow('').allow(null),
|
||||
|
||||
@@ -21,15 +21,18 @@ export default async () => {
|
||||
);
|
||||
|
||||
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
|
||||
DependenceModel.findAll({ where: {}, raw: true }).then(async (docs) => {
|
||||
DependenceModel.findAll({
|
||||
where: {},
|
||||
order: [['type', 'DESC']],
|
||||
raw: true,
|
||||
}).then(async (docs) => {
|
||||
const groups = _.groupBy(docs, 'type');
|
||||
for (const key in groups) {
|
||||
if (Object.prototype.hasOwnProperty.call(groups, key)) {
|
||||
const keys = Object.keys(groups).sort((a, b) => parseInt(b) - parseInt(a));
|
||||
for (const key of keys) {
|
||||
const group = groups[key];
|
||||
const depIds = group.map((x) => x.id);
|
||||
await dependenceService.reInstall(depIds as number[]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化时执行一次所有的ql repo 任务
|
||||
|
||||
@@ -26,6 +26,11 @@ export default async () => {
|
||||
// 运行所有订阅
|
||||
const subs = await subscriptionService.list();
|
||||
for (const sub of subs) {
|
||||
await subscriptionService.handleTask(sub, true, true, true);
|
||||
await subscriptionService.handleTask(
|
||||
sub,
|
||||
!sub.is_disabled,
|
||||
true,
|
||||
!sub.is_disabled,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ interface ScheduleTaskType {
|
||||
}
|
||||
|
||||
export interface TaskCallbacks {
|
||||
onBefore?: (startTime: dayjs.Dayjs) => Promise<void>;
|
||||
onStart?: (
|
||||
cp: ChildProcessWithoutNullStreams,
|
||||
startTime: dayjs.Dayjs,
|
||||
@@ -45,6 +46,8 @@ export default class ScheduleService {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const startTime = dayjs();
|
||||
await callbacks.onBefore?.(startTime);
|
||||
|
||||
const cp = spawn(command, { shell: '/bin/bash' });
|
||||
|
||||
// TODO:
|
||||
|
||||
+13
-5
@@ -23,6 +23,13 @@ export default class SshKeyService {
|
||||
}
|
||||
}
|
||||
|
||||
private getConfigRegx(alias: string) {
|
||||
return new RegExp(
|
||||
`Host ${alias}\n.*[^StrictHostKeyChecking]*.*[\n]*.*StrictHostKeyChecking no`,
|
||||
'g',
|
||||
);
|
||||
}
|
||||
|
||||
private removePrivateKeyFile(alias: string): void {
|
||||
try {
|
||||
fs.unlinkSync(`${this.sshPath}/${alias}`);
|
||||
@@ -47,24 +54,25 @@ export default class SshKeyService {
|
||||
}
|
||||
}
|
||||
|
||||
private removeSshConfig(config: string) {
|
||||
private removeSshConfig(alias: string) {
|
||||
try {
|
||||
const configRegx = this.getConfigRegx(alias);
|
||||
const data = fs
|
||||
.readFileSync(this.sshConfigFilePath, { encoding: 'utf8' })
|
||||
.replace(config, '')
|
||||
.replace(/\n\n+/, '\n\n');
|
||||
.replace(configRegx, '')
|
||||
.replace(/\n[\n]+/g, '\n');
|
||||
fs.writeFileSync(this.sshConfigFilePath, data, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(`删除ssh配置文件${config}失败`, error);
|
||||
this.logger.error(`删除ssh配置文件${alias}失败`, error);
|
||||
}
|
||||
}
|
||||
|
||||
public addSSHKey(key: string, alias: string, host: string): void {
|
||||
this.generatePrivateKeyFile(alias, key);
|
||||
const config = this.generateSingleSshConfig(alias, host);
|
||||
this.removeSshConfig(config);
|
||||
this.removeSshConfig(alias);
|
||||
this.generateSshConfig([config]);
|
||||
}
|
||||
|
||||
|
||||
@@ -192,14 +192,13 @@ export default class SubscriptionService {
|
||||
|
||||
private taskCallbacks(doc: Subscription): TaskCallbacks {
|
||||
return {
|
||||
onStart: async (cp: ChildProcessWithoutNullStreams, startTime) => {
|
||||
onBefore: async (startTime) => {
|
||||
const logTime = startTime.format('YYYY-MM-DD-HH-mm-ss');
|
||||
const logPath = `${doc.alias}/${logTime}.log`;
|
||||
await SubscriptionModel.update(
|
||||
{
|
||||
status: SubscriptionStatus.running,
|
||||
log_path: logPath,
|
||||
pid: cp.pid,
|
||||
},
|
||||
{ where: { id: doc.id } },
|
||||
);
|
||||
@@ -220,9 +219,17 @@ export default class SubscriptionService {
|
||||
(error.stderr && error.stderr.toString()) || JSON.stringify(error);
|
||||
}
|
||||
if (beforeStr) {
|
||||
fs.appendFileSync(absolutePath, `${beforeStr}\n\n`);
|
||||
fs.appendFileSync(absolutePath, `${beforeStr}\n`);
|
||||
}
|
||||
},
|
||||
onStart: async (cp: ChildProcessWithoutNullStreams, startTime) => {
|
||||
await SubscriptionModel.update(
|
||||
{
|
||||
pid: cp.pid,
|
||||
},
|
||||
{ where: { id: doc.id } },
|
||||
);
|
||||
},
|
||||
onEnd: async (cp, endTime, diff) => {
|
||||
const sub = await this.getDb({ id: doc.id });
|
||||
const absolutePath = await this.handleLogPath(sub.log_path as string);
|
||||
@@ -231,7 +238,7 @@ export default class SubscriptionService {
|
||||
let afterStr = '';
|
||||
try {
|
||||
if (sub.sub_after) {
|
||||
fs.appendFileSync(absolutePath, `\n\n## 执行after命令...\n`);
|
||||
fs.appendFileSync(absolutePath, `\n\n## 执行after命令...\n\n`);
|
||||
afterStr = await this.promiseExec(sub.sub_after);
|
||||
}
|
||||
} catch (error: any) {
|
||||
@@ -286,7 +293,7 @@ export default class SubscriptionService {
|
||||
|
||||
public async update(payload: Subscription): Promise<Subscription> {
|
||||
const newDoc = await this.updateDb(payload);
|
||||
await this.handleTask(newDoc);
|
||||
await this.handleTask(newDoc, !newDoc.is_disabled);
|
||||
return newDoc;
|
||||
}
|
||||
|
||||
@@ -360,7 +367,6 @@ export default class SubscriptionService {
|
||||
this.logger.silly(error);
|
||||
}
|
||||
}
|
||||
await this.handleTask(doc, false);
|
||||
const command = this.formatCommand(doc);
|
||||
const err = await this.killTask(command);
|
||||
const absolutePath = await this.handleLogPath(doc.log_path as string);
|
||||
|
||||
+2
-3
@@ -5,7 +5,8 @@ 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:/root/.local/share/pnpm:/root/.local/share/pnpm/global/5/node_modules \
|
||||
ENV PNPM_HOME=/root/.local/share/pnpm \
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/share/pnpm:/root/.local/share/pnpm/global/5/node_modules:$PNPM_HOME \
|
||||
LANG=zh_CN.UTF-8 \
|
||||
SHELL=/bin/bash \
|
||||
PS1="\u@\h:\w \$ " \
|
||||
@@ -38,8 +39,6 @@ RUN set -x \
|
||||
&& git config --global user.email "qinglong@@users.noreply.github.com" \
|
||||
&& git config --global user.name "qinglong" \
|
||||
&& npm install -g pnpm \
|
||||
&& pnpm setup \
|
||||
&& source ~/.bashrc \
|
||||
&& pnpm add -g pm2 ts-node typescript tslib \
|
||||
&& git clone -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
|
||||
&& cd ${QL_DIR} \
|
||||
|
||||
+3
-3
@@ -40,8 +40,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@otplib/preset-default": "^12.0.1",
|
||||
"@sentry/node": "^6.18.1",
|
||||
"@sentry/tracing": "^6.18.1",
|
||||
"@sentry/node": "^7.0.0",
|
||||
"@sentry/tracing": "^7.0.0",
|
||||
"body-parser": "^1.19.2",
|
||||
"celebrate": "^15.0.1",
|
||||
"chokidar": "^3.5.3",
|
||||
@@ -77,7 +77,7 @@
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
"@ant-design/pro-layout": "^6.33.1",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@sentry/react": "^6.18.1",
|
||||
"@sentry/react": "^7.0.0",
|
||||
"@types/body-parser": "^1.19.2",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/express": "^4.17.13",
|
||||
|
||||
@@ -107,12 +107,18 @@ const SubscriptionModal = ({
|
||||
const [intervalNumber, setIntervalNumber] = useState<number>();
|
||||
const intervalTypeChange = (type: string) => {
|
||||
setIntervalType(type);
|
||||
if (intervalNumber && intervalNumber > 0) {
|
||||
onChange?.({ type, value: intervalNumber });
|
||||
}
|
||||
};
|
||||
|
||||
const numberChange = (value: number) => {
|
||||
setIntervalNumber(value);
|
||||
if (!value) {
|
||||
onChange?.(null);
|
||||
} else {
|
||||
onChange?.({ type: intervalType, value });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
+8
-2
@@ -7,9 +7,15 @@ export function init() {
|
||||
// sentry监控 init
|
||||
Sentry.init({
|
||||
dsn: 'https://3406424fb1dc4813a62d39e844a9d0ac@o1098464.ingest.sentry.io/6122818',
|
||||
integrations: [new Integrations.BrowserTracing()],
|
||||
integrations: [
|
||||
new Integrations.BrowserTracing({
|
||||
shouldCreateSpanForRequest(url) {
|
||||
return !url.includes('/api/ws') && !url.includes('/api/static');
|
||||
},
|
||||
}),
|
||||
],
|
||||
release: version,
|
||||
tracesSampleRate: 1.0,
|
||||
tracesSampleRate: 0.1,
|
||||
beforeBreadcrumb(breadcrumb, hint?) {
|
||||
if (breadcrumb.data && breadcrumb.data.url) {
|
||||
const url = breadcrumb.data.url.replace(/token=.*/, '');
|
||||
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
export const version = '2.13.1';
|
||||
export const version = '2.13.2';
|
||||
export const changeLogLink = 'https://t.me/jiao_long/303';
|
||||
export const changeLog = `2.13.1 版本说明
|
||||
1. 修复新建订阅repo命令快捷导入
|
||||
2. 修复执行订阅任务含有before/after命令时,造成面板无法访问
|
||||
3. 修复任务详情日志列表访问
|
||||
4. 修复安装依赖造成CPU满负荷,服务器崩溃
|
||||
export const changeLog = `2.13.2 版本说明
|
||||
1. 修复nodejs依赖安装失败pnpm setup
|
||||
2. 修复订阅禁用失效
|
||||
3. 修复新建订阅定时验证
|
||||
4. 修复ssh配置文件更新逻辑
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user