Compare commits

..

3 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] 2c57ada739 Fix: Disable/enable associated cron tasks when subscription is disabled/enabled
When a subscription is disabled, the associated cron tasks (created by the subscription) were still running and updating scripts. This fix ensures that:
- When disabling a subscription, all cron tasks with matching sub_id are also disabled
- When enabling a subscription, all cron tasks with matching sub_id are also enabled

This addresses the actual root cause: subscription tasks don't run when disabled (as the owner correctly pointed out), but the cron tasks created by those subscriptions were still active.

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
2025-11-20 16:11:56 +00:00
copilot-swe-agent[bot] 87a1a3d2eb Fix disabled subscriptions still updating tasks
- Filter setSshConfig() to only configure SSH keys for enabled subscriptions
- Remove SSH keys when subscriptions are disabled
- This prevents disabled subscriptions from running scheduled tasks

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
2025-11-20 15:50:35 +00:00
copilot-swe-agent[bot] 99064bde10 Initial plan 2025-11-20 15:44:23 +00:00
39 changed files with 146 additions and 307 deletions
+25 -34
View File
@@ -9,13 +9,15 @@ on:
- "develop" - "develop"
tags: tags:
- "v*" - "v*"
schedule:
- cron: "00 20 * * *"
workflow_dispatch: workflow_dispatch:
jobs: jobs:
code_gitlab: code_gitlab:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: Yikun/hub-mirror-action@master - uses: Yikun/hub-mirror-action@master
@@ -30,7 +32,7 @@ jobs:
code_gitee: code_gitee:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: Yikun/hub-mirror-action@master - uses: Yikun/hub-mirror-action@master
@@ -45,12 +47,12 @@ jobs:
build-static: build-static:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v3
with: with:
version: "8.3.1" version: "8.3.1"
- uses: actions/setup-node@v6 - uses: actions/setup-node@v4
with: with:
cache: "pnpm" cache: "pnpm"
@@ -76,12 +78,12 @@ jobs:
git config --local user.email 'github-actions[bot]@users.noreply.github.com' git config --local user.email 'github-actions[bot]@users.noreply.github.com'
git commit --allow-empty -m "copy static at $(date +'%Y-%m-%d %H:%M:%S')" git commit --allow-empty -m "copy static at $(date +'%Y-%m-%d %H:%M:%S')"
git push --force --quiet "https://${{ secrets.API_TOKEN }}@${GITHUB_REPO}.git" ${GITHUB_BRANCH}:${GITHUB_BRANCH} git push --force --quiet "https://${{ secrets.API_TOKEN }}@${GITHUB_REPO}.git" ${GITHUB_BRANCH}:${GITHUB_BRANCH}
static_gitlab: static_gitlab:
needs: build-static needs: build-static
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: Yikun/hub-mirror-action@master - uses: Yikun/hub-mirror-action@master
@@ -97,7 +99,7 @@ jobs:
needs: build-static needs: build-static
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: Yikun/hub-mirror-action@master - uses: Yikun/hub-mirror-action@master
@@ -110,7 +112,6 @@ jobs:
force_update: true force_update: true
build: build:
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
needs: build-static needs: build-static
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@@ -120,21 +121,14 @@ jobs:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v3
with: with:
version: "8.3.1" version: "8.3.1"
- uses: actions/setup-node@v6 - uses: actions/setup-node@v4
with: with:
cache: "pnpm" cache: "pnpm"
- name: Read version from version.yaml
id: version
run: |
VERSION=$(grep '^version:' version.yaml | awk '{print $2}')
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Version: $VERSION"
- name: Setup timezone - name: Setup timezone
uses: szenius/set-timezone@v2.0 uses: szenius/set-timezone@v2.0
with: with:
@@ -160,13 +154,19 @@ jobs:
images: | images: |
${{ github.repository }} ${{ github.repository }}
ghcr.io/${{ github.repository }} ghcr.io/${{ github.repository }}
# generate Docker tags based on the following events/attributes
# nightly, master, pr-2, 1.2.3, 1.2, 1
flavor: | flavor: |
latest=false latest=false
tags: | tags: |
type=ref,event=branch,enable=${{ github.ref == format('refs/heads/{0}', 'develop') }} type=schedule,pattern=nightly
type=edge
type=ref,event=pr
type=ref,event=branch,enable=${{ github.ref != format('refs/heads/{0}', 'master') }}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }} type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
type=raw,value=${{ steps.version.outputs.version }},enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
@@ -208,21 +208,14 @@ jobs:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v4
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v3
with: with:
version: "8.3.1" version: "8.3.1"
- uses: actions/setup-node@v6 - uses: actions/setup-node@v4
with: with:
cache: "pnpm" cache: "pnpm"
- name: Read version from version.yaml
id: version
run: |
VERSION=$(grep '^version:' version.yaml | awk '{print $2}')
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Version: $VERSION"
- name: Setup timezone - name: Setup timezone
uses: szenius/set-timezone@v2.0 uses: szenius/set-timezone@v2.0
with: with:
@@ -261,9 +254,7 @@ jobs:
context: . context: .
file: ./docker/310.Dockerfile file: ./docker/310.Dockerfile
push: true push: true
tags: | tags: whyour/qinglong:python3.10
whyour/qinglong:python3.10
whyour/qinglong:${{ steps.version.outputs.version }}-python3.10
cache-from: type=registry,ref=whyour/qinglong:cache-python3.10 cache-from: type=registry,ref=whyour/qinglong:cache-python3.10
cache-to: type=registry,ref=whyour/qinglong:cache-python3.10,mode=max cache-to: type=registry,ref=whyour/qinglong:cache-python3.10,mode=max
+1 -1
View File
@@ -16,7 +16,7 @@ export default (app: Router) => {
searchValue: Joi.string().optional().allow(''), searchValue: Joi.string().optional().allow(''),
type: Joi.string().optional().allow(''), type: Joi.string().optional().allow(''),
status: Joi.string().optional().allow(''), status: Joi.string().optional().allow(''),
}).unknown(true), }),
}), }),
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger'); const logger: Logger = Container.get('logger');
+4 -6
View File
@@ -29,7 +29,7 @@ export default (app: Router) => {
celebrate({ celebrate({
query: Joi.object({ query: Joi.object({
path: Joi.string().optional().allow(''), path: Joi.string().optional().allow(''),
}).unknown(true), }),
}), }),
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger'); const logger: Logger = Container.get('logger');
@@ -79,7 +79,7 @@ export default (app: Router) => {
query: Joi.object({ query: Joi.object({
path: Joi.string().optional().allow(''), path: Joi.string().optional().allow(''),
file: Joi.string().required(), file: Joi.string().required(),
}).unknown(true), }),
}), }),
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
try { try {
@@ -103,7 +103,7 @@ export default (app: Router) => {
}), }),
query: Joi.object({ query: Joi.object({
path: Joi.string().optional().allow(''), path: Joi.string().optional().allow(''),
}).unknown(true), }),
}), }),
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
try { try {
@@ -129,8 +129,7 @@ export default (app: Router) => {
content: Joi.string().optional().allow(''), content: Joi.string().optional().allow(''),
originFilename: Joi.string().optional().allow(''), originFilename: Joi.string().optional().allow(''),
directory: Joi.string().optional().allow(''), directory: Joi.string().optional().allow(''),
file: Joi.string().optional().allow(''), }),
}).unknown(true),
}), }),
async (req: Request, res: Response, next: NextFunction) => { async (req: Request, res: Response, next: NextFunction) => {
try { try {
@@ -176,7 +175,6 @@ export default (app: Router) => {
path, path,
`${originFilename.replace(/\//g, '')}`, `${originFilename.replace(/\//g, '')}`,
); );
await fs.mkdir(path, { recursive: true });
const filePath = join(path, `${filename.replace(/\//g, '')}`); const filePath = join(path, `${filename.replace(/\//g, '')}`);
const fileExists = await fileExist(filePath); const fileExists = await fileExist(filePath);
if (fileExists) { if (fileExists) {
+1 -1
View File
@@ -3,7 +3,7 @@ import { Container } from 'typedi';
import { Logger } from 'winston'; import { Logger } from 'winston';
import SubscriptionService from '../services/subscription'; import SubscriptionService from '../services/subscription';
import { celebrate, Joi } from 'celebrate'; import { celebrate, Joi } from 'celebrate';
import CronExpressionParser from 'cron-parser'; import { CronExpressionParser } from 'cron-parser';
const route = Router(); const route = Router();
export default (app: Router) => { export default (app: Router) => {
-14
View File
@@ -64,19 +64,6 @@ if (!process.env.QL_DIR) {
const lastVersionFile = `https://qn.whyour.cn/version.yaml`; const lastVersionFile = `https://qn.whyour.cn/version.yaml`;
// Get and normalize QlBaseUrl
let baseUrl = process.env.QlBaseUrl || '';
if (baseUrl) {
// Ensure it starts with /
if (!baseUrl.startsWith('/')) {
baseUrl = `/${baseUrl}`;
}
// Remove trailing slash for consistency in route definitions
if (baseUrl.endsWith('/')) {
baseUrl = baseUrl.slice(0, -1);
}
}
const rootPath = process.env.QL_DIR as string; const rootPath = process.env.QL_DIR as string;
const envFound = dotenv.config({ path: path.join(rootPath, '.env') }); const envFound = dotenv.config({ path: path.join(rootPath, '.env') });
@@ -129,7 +116,6 @@ if (envFound.error) {
export default { export default {
...config, ...config,
jwt: config.jwt, jwt: config.jwt,
baseUrl,
rootPath, rootPath,
tmpPath, tmpPath,
dataPath, dataPath,
-1
View File
@@ -142,7 +142,6 @@ export class WebhookNotification extends NotificationBaseInfo {
export class LarkNotification extends NotificationBaseInfo { export class LarkNotification extends NotificationBaseInfo {
public larkKey = ''; public larkKey = '';
public larkSecret = '';
} }
export class NtfyNotification extends NotificationBaseInfo { export class NtfyNotification extends NotificationBaseInfo {
+5 -41
View File
@@ -13,35 +13,8 @@ import { isValidToken } from '../shared/auth';
import path from 'path'; import path from 'path';
export default ({ app }: { app: Application }) => { export default ({ app }: { app: Application }) => {
// Security: Enable strict routing to prevent case-insensitive path bypass
app.set('case sensitive routing', true);
app.set('strict routing', true);
app.set('trust proxy', 'loopback'); app.set('trust proxy', 'loopback');
app.use(cors()); app.use(cors());
// Security: Path normalization middleware to prevent case variation attacks
app.use((req, res, next) => {
const originalPath = req.path;
const normalizedPath = originalPath.toLowerCase();
// Block requests with case variations on protected paths
if (originalPath !== normalizedPath &&
(normalizedPath.startsWith('/api/') || normalizedPath.startsWith('/open/'))) {
return res.status(400).json({
code: 400,
message: 'Invalid path format'
});
}
next();
});
// Rewrite URLs to strip baseUrl prefix if configured
// This allows the rest of the app to work without baseUrl awareness
if (config.baseUrl) {
app.use(rewrite(`${config.baseUrl}/*`, '/$1'));
}
app.get(`${config.api.prefix}/env.js`, serveEnv); app.get(`${config.api.prefix}/env.js`, serveEnv);
app.use(`${config.api.prefix}/static`, express.static(config.uploadPath)); app.use(`${config.api.prefix}/static`, express.static(config.uploadPath));
@@ -56,7 +29,7 @@ export default ({ app }: { app: Application }) => {
secret: config.jwt.secret, secret: config.jwt.secret,
algorithms: ['HS384'], algorithms: ['HS384'],
}).unless({ }).unless({
path: [...config.apiWhiteList, /^(\/(?!api\/).*)$/i], path: [...config.apiWhiteList, /^\/(?!api\/).*/],
}), }),
); );
@@ -71,20 +44,19 @@ export default ({ app }: { app: Application }) => {
}); });
app.use(async (req: Request, res, next) => { app.use(async (req: Request, res, next) => {
const pathLower = req.path.toLowerCase(); if (!['/open/', '/api/'].some((x) => req.path.startsWith(x))) {
if (!['/open/', '/api/'].some((x) => pathLower.startsWith(x))) {
return next(); return next();
} }
const headerToken = getToken(req); const headerToken = getToken(req);
if (pathLower.startsWith('/open/')) { if (req.path.startsWith('/open/')) {
const apps = await shareStore.getApps(); const apps = await shareStore.getApps();
const doc = apps?.filter((x) => const doc = apps?.filter((x) =>
x.tokens?.find((y) => y.value === headerToken), x.tokens?.find((y) => y.value === headerToken),
)?.[0]; )?.[0];
if (doc && doc.tokens && doc.tokens.length > 0) { if (doc && doc.tokens && doc.tokens.length > 0) {
const currentToken = doc.tokens.find((x) => x.value === headerToken); const currentToken = doc.tokens.find((x) => x.value === headerToken);
const keyMatch = pathLower.match(/\/open\/([a-z]+)\/*/); const keyMatch = req.path.match(/\/open\/([a-z]+)\/*/);
const key = keyMatch && keyMatch[1]; const key = keyMatch && keyMatch[1];
if ( if (
doc.scopes.includes(key as any) && doc.scopes.includes(key as any) &&
@@ -119,15 +91,7 @@ export default ({ app }: { app: Application }) => {
}); });
app.use(async (req, res, next) => { app.use(async (req, res, next) => {
const pathLower = req.path.toLowerCase(); if (!['/api/user/init', '/api/user/notification/init'].includes(req.path)) {
if (
![
'/api/user/init',
'/api/user/notification/init',
'/open/user/init',
'/open/user/notification/init',
].includes(req.path)
) {
return next(); return next();
} }
const authInfo = const authInfo =
+2 -2
View File
@@ -13,7 +13,7 @@ import { AuthDataType, SystemModel } from '../data/system';
import SystemService from '../services/system'; import SystemService from '../services/system';
import UserService from '../services/user'; import UserService from '../services/user';
import { writeFile, readFile } from 'fs/promises'; import { writeFile, readFile } from 'fs/promises';
import { createRandomString, fileExist, isDemoEnv, safeJSONParse } from '../config/util'; import { createRandomString, fileExist, safeJSONParse } from '../config/util';
import OpenService from '../services/open'; import OpenService from '../services/open';
import { shareStore } from '../shared/store'; import { shareStore } from '../shared/store';
import Logger from './logger'; import Logger from './logger';
@@ -50,7 +50,7 @@ export default async () => {
const [authConfig] = await SystemModel.findOrCreate({ const [authConfig] = await SystemModel.findOrCreate({
where: { type: AuthDataType.authConfig }, where: { type: AuthDataType.authConfig },
}); });
if (!authConfig?.info || isDemoEnv()) { if (!authConfig?.info) {
let authInfo = { let authInfo = {
username: 'admin', username: 'admin',
password: 'admin', password: 'admin',
+1 -2
View File
@@ -5,10 +5,9 @@ import SockService from '../services/sock';
import { getPlatform } from '../config/util'; import { getPlatform } from '../config/util';
import { shareStore } from '../shared/store'; import { shareStore } from '../shared/store';
import { isValidToken } from '../shared/auth'; import { isValidToken } from '../shared/auth';
import config from '../config';
export default async ({ server }: { server: Server }) => { export default async ({ server }: { server: Server }) => {
const echo = sockJs.createServer({ prefix: `${config.baseUrl}/api/ws`, log: () => { } }); const echo = sockJs.createServer({ prefix: '/api/ws', log: () => {} });
const sockService = Container.get(SockService); const sockService = Container.get(SockService);
echo.on('connection', async (conn) => { echo.on('connection', async (conn) => {
-1
View File
@@ -231,7 +231,6 @@ message NotificationInfo {
optional string webhookContentType = 57; optional string webhookContentType = 57;
optional string larkKey = 58; optional string larkKey = 58;
optional string larkSecret = 69;
optional string ntfyUrl = 59; optional string ntfyUrl = 59;
optional string ntfyTopic = 60; optional string ntfyTopic = 60;
+1 -19
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT. // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions: // versions:
// protoc-gen-ts_proto v2.6.1 // protoc-gen-ts_proto v2.6.1
// protoc v3.21.12 // protoc v3.17.3
// source: back/protos/api.proto // source: back/protos/api.proto
/* eslint-disable */ /* eslint-disable */
@@ -382,7 +382,6 @@ export interface NotificationInfo {
webhookMethod?: string | undefined; webhookMethod?: string | undefined;
webhookContentType?: string | undefined; webhookContentType?: string | undefined;
larkKey?: string | undefined; larkKey?: string | undefined;
larkSecret?: string | undefined;
ntfyUrl?: string | undefined; ntfyUrl?: string | undefined;
ntfyTopic?: string | undefined; ntfyTopic?: string | undefined;
ntfyPriority?: string | undefined; ntfyPriority?: string | undefined;
@@ -2948,7 +2947,6 @@ function createBaseNotificationInfo(): NotificationInfo {
webhookMethod: undefined, webhookMethod: undefined,
webhookContentType: undefined, webhookContentType: undefined,
larkKey: undefined, larkKey: undefined,
larkSecret: undefined,
ntfyUrl: undefined, ntfyUrl: undefined,
ntfyTopic: undefined, ntfyTopic: undefined,
ntfyPriority: undefined, ntfyPriority: undefined,
@@ -3138,9 +3136,6 @@ export const NotificationInfo: MessageFns<NotificationInfo> = {
if (message.larkKey !== undefined) { if (message.larkKey !== undefined) {
writer.uint32(466).string(message.larkKey); writer.uint32(466).string(message.larkKey);
} }
if (message.larkSecret !== undefined) {
writer.uint32(554).string(message.larkSecret);
}
if (message.ntfyUrl !== undefined) { if (message.ntfyUrl !== undefined) {
writer.uint32(474).string(message.ntfyUrl); writer.uint32(474).string(message.ntfyUrl);
} }
@@ -3645,14 +3640,6 @@ export const NotificationInfo: MessageFns<NotificationInfo> = {
message.larkKey = reader.string(); message.larkKey = reader.string();
continue; continue;
} }
case 69: {
if (tag !== 554) {
break;
}
message.larkSecret = reader.string();
continue;
}
case 59: { case 59: {
if (tag !== 474) { if (tag !== 474) {
break; break;
@@ -3810,7 +3797,6 @@ export const NotificationInfo: MessageFns<NotificationInfo> = {
webhookMethod: isSet(object.webhookMethod) ? globalThis.String(object.webhookMethod) : undefined, webhookMethod: isSet(object.webhookMethod) ? globalThis.String(object.webhookMethod) : undefined,
webhookContentType: isSet(object.webhookContentType) ? globalThis.String(object.webhookContentType) : undefined, webhookContentType: isSet(object.webhookContentType) ? globalThis.String(object.webhookContentType) : undefined,
larkKey: isSet(object.larkKey) ? globalThis.String(object.larkKey) : undefined, larkKey: isSet(object.larkKey) ? globalThis.String(object.larkKey) : undefined,
larkSecret: isSet(object.larkSecret) ? globalThis.String(object.larkSecret) : undefined,
ntfyUrl: isSet(object.ntfyUrl) ? globalThis.String(object.ntfyUrl) : undefined, ntfyUrl: isSet(object.ntfyUrl) ? globalThis.String(object.ntfyUrl) : undefined,
ntfyTopic: isSet(object.ntfyTopic) ? globalThis.String(object.ntfyTopic) : undefined, ntfyTopic: isSet(object.ntfyTopic) ? globalThis.String(object.ntfyTopic) : undefined,
ntfyPriority: isSet(object.ntfyPriority) ? globalThis.String(object.ntfyPriority) : undefined, ntfyPriority: isSet(object.ntfyPriority) ? globalThis.String(object.ntfyPriority) : undefined,
@@ -4004,9 +3990,6 @@ export const NotificationInfo: MessageFns<NotificationInfo> = {
if (message.larkKey !== undefined) { if (message.larkKey !== undefined) {
obj.larkKey = message.larkKey; obj.larkKey = message.larkKey;
} }
if (message.larkSecret !== undefined) {
obj.larkSecret = message.larkSecret;
}
if (message.ntfyUrl !== undefined) { if (message.ntfyUrl !== undefined) {
obj.ntfyUrl = message.ntfyUrl; obj.ntfyUrl = message.ntfyUrl;
} }
@@ -4103,7 +4086,6 @@ export const NotificationInfo: MessageFns<NotificationInfo> = {
message.webhookMethod = object.webhookMethod ?? undefined; message.webhookMethod = object.webhookMethod ?? undefined;
message.webhookContentType = object.webhookContentType ?? undefined; message.webhookContentType = object.webhookContentType ?? undefined;
message.larkKey = object.larkKey ?? undefined; message.larkKey = object.larkKey ?? undefined;
message.larkSecret = object.larkSecret ?? undefined;
message.ntfyUrl = object.ntfyUrl ?? undefined; message.ntfyUrl = object.ntfyUrl ?? undefined;
message.ntfyTopic = object.ntfyTopic ?? undefined; message.ntfyTopic = object.ntfyTopic ?? undefined;
message.ntfyPriority = object.ntfyPriority ?? undefined; message.ntfyPriority = object.ntfyPriority ?? undefined;
+1 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT. // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions: // versions:
// protoc-gen-ts_proto v2.6.1 // protoc-gen-ts_proto v2.6.1
// protoc v3.21.12 // protoc v3.17.3
// source: back/protos/cron.proto // source: back/protos/cron.proto
/* eslint-disable */ /* eslint-disable */
+1 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-ts_proto. DO NOT EDIT. // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
// versions: // versions:
// protoc-gen-ts_proto v2.6.1 // protoc-gen-ts_proto v2.6.1
// protoc v3.21.12 // protoc v3.17.3
// source: back/protos/health.proto // source: back/protos/health.proto
/* eslint-disable */ /* eslint-disable */
+6 -13
View File
@@ -4,7 +4,7 @@ import config from '../config';
import { Crontab, CrontabModel, CrontabStatus } from '../data/cron'; import { Crontab, CrontabModel, CrontabStatus } from '../data/cron';
import { exec, execSync } from 'child_process'; import { exec, execSync } from 'child_process';
import fs from 'fs/promises'; import fs from 'fs/promises';
import CronExpressionParser from 'cron-parser'; import { CronExpressionParser } from 'cron-parser';
import { import {
getFileContentByName, getFileContentByName,
fileExist, fileExist,
@@ -29,7 +29,7 @@ import { logStreamManager } from '../shared/logStreamManager';
@Service() @Service()
export default class CronService { export default class CronService {
constructor(@Inject('logger') private logger: winston.Logger) { } constructor(@Inject('logger') private logger: winston.Logger) {}
private isNodeCron(cron: Crontab) { private isNodeCron(cron: Crontab) {
const { schedule, extra_schedules } = cron; const { schedule, extra_schedules } = cron;
@@ -165,7 +165,7 @@ export default class CronService {
let cron; let cron;
try { try {
cron = await this.getDb({ id }); cron = await this.getDb({ id });
} catch (err) { } } catch (err) {}
if (!cron) { if (!cron) {
continue; continue;
} }
@@ -467,10 +467,7 @@ export default class CronService {
for (const doc of docs) { for (const doc of docs) {
// Kill all running instances of this task // Kill all running instances of this task
try { try {
if (doc.pid) { const command = this.makeCommand(doc);
await killTask(doc.pid);
}
const command = doc.command.replace(/\s+/g, ' ').trim();
await killAllTasks(command); await killAllTasks(command);
this.logger.info( this.logger.info(
`[panel][停止所有运行中的任务实例] 任务ID: ${doc.id}, 命令: ${command}`, `[panel][停止所有运行中的任务实例] 任务ID: ${doc.id}, 命令: ${command}`,
@@ -510,7 +507,7 @@ export default class CronService {
let { id, command, log_name } = cron; let { id, command, log_name } = cron;
const uniqPath = const uniqPath =
log_name === '/dev/null' || !log_name log_name === '/dev/null'
? await getUniqPath(command, `${id}`) ? await getUniqPath(command, `${id}`)
: log_name; : log_name;
const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS'); const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
@@ -644,11 +641,7 @@ export default class CronService {
if (!command.startsWith(TASK_PREFIX) && !command.startsWith(QL_PREFIX)) { if (!command.startsWith(TASK_PREFIX) && !command.startsWith(QL_PREFIX)) {
command = `${TASK_PREFIX}${tab.command}`; command = `${TASK_PREFIX}${tab.command}`;
} }
let commandVariable = `real_time=${Boolean(realTime)} no_tee=true ID=${tab.id} `; let commandVariable = `real_time=${Boolean(realTime)} log_name=${tab.log_name} no_tee=true ID=${tab.id} `;
// Only include log_name if it has a truthy value to avoid passing null/undefined to shell
if (tab.log_name) {
commandVariable += `log_name=${tab.log_name} `;
}
if (tab.task_before) { if (tab.task_before) {
commandVariable += `task_before='${tab.task_before commandVariable += `task_before='${tab.task_before
.replace(/'/g, "'\\''") .replace(/'/g, "'\\''")
+1 -1
View File
@@ -107,7 +107,7 @@ export default class DependenceService {
query: any = {}, query: any = {},
): Promise<Dependence[]> { ): Promise<Dependence[]> {
let condition = query; let condition = query;
if (type && DependenceTypes[type] !== undefined) { if (DependenceTypes[type]) {
condition.type = DependenceTypes[type]; condition.type = DependenceTypes[type];
} }
if (status) { if (status) {
+2 -3
View File
@@ -13,11 +13,10 @@ import {
stepPosition, stepPosition,
} from '../data/env'; } from '../data/env';
import { writeFileWithLock } from '../shared/utils'; import { writeFileWithLock } from '../shared/utils';
import { sequelize } from '../data';
@Service() @Service()
export default class EnvService { export default class EnvService {
constructor(@Inject('logger') private logger: winston.Logger) { } constructor(@Inject('logger') private logger: winston.Logger) {}
public async create(payloads: Env[]): Promise<Env[]> { public async create(payloads: Env[]): Promise<Env[]> {
const envs = await this.envs(); const envs = await this.envs();
@@ -147,7 +146,7 @@ export default class EnvService {
} }
try { try {
const result = await this.find(condition, [ const result = await this.find(condition, [
[sequelize.literal('COALESCE(`isPinned`, 0)'), 'DESC'], ['isPinned', 'DESC'],
['position', 'DESC'], ['position', 'DESC'],
['createdAt', 'ASC'], ['createdAt', 'ASC'],
]); ]);
+5 -19
View File
@@ -550,33 +550,19 @@ export default class NotificationService {
} }
private async lark() { private async lark() {
let { larkKey, larkSecret } = this.params; let { larkKey } = this.params;
if (!larkKey.startsWith('http')) { if (!larkKey.startsWith('http')) {
larkKey = `https://open.feishu.cn/open-apis/bot/v2/hook/${larkKey}`; larkKey = `https://open.feishu.cn/open-apis/bot/v2/hook/${larkKey}`;
} }
const body: Record<string, any> = {
msg_type: 'text',
content: { text: `${this.title}\n\n${this.content}` },
};
// Add signature if secret is provided
// Note: Feishu's signature algorithm uses timestamp+"\n"+secret as the HMAC key
// and signs an empty message, which differs from typical HMAC usage
if (larkSecret) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const stringToSign = `${timestamp}\n${larkSecret}`;
const hmac = crypto.createHmac('sha256', stringToSign);
const sign = hmac.digest('base64');
body.timestamp = timestamp;
body.sign = sign;
}
try { try {
const res = await httpClient.post(larkKey, { const res = await httpClient.post(larkKey, {
...this.gotOption, ...this.gotOption,
json: body, json: {
msg_type: 'text',
content: { text: `${this.title}\n\n${this.content}` },
},
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}); });
if (res.StatusCode === 0 || res.code === 0) { if (res.StatusCode === 0 || res.code === 0) {
+4 -4
View File
@@ -133,15 +133,15 @@ export default class SshKeyService {
} }
public async addGlobalSSHKey(key: string, alias: string): Promise<void> { public async addGlobalSSHKey(key: string, alias: string): Promise<void> {
await this.generatePrivateKeyFile(`~global_${alias}`, key); await this.generatePrivateKeyFile(`global_${alias}`, key);
// Create a global SSH config entry that matches all hosts // Create a global SSH config entry that matches all hosts
// This allows the key to be used for any Git repository // This allows the key to be used for any Git repository
await this.generateGlobalSshConfig(`~global_${alias}`); await this.generateGlobalSshConfig(`global_${alias}`);
} }
public async removeGlobalSSHKey(alias: string): Promise<void> { public async removeGlobalSSHKey(alias: string): Promise<void> {
await this.removePrivateKeyFile(`~global_${alias}`); await this.removePrivateKeyFile(`global_${alias}`);
await this.removeSshConfig(`~global_${alias}`); await this.removeSshConfig(`global_${alias}`);
} }
private async generateGlobalSshConfig(alias: string) { private async generateGlobalSshConfig(alias: string) {
+10
View File
@@ -350,6 +350,11 @@ export default class SubscriptionService {
for (const doc of docs) { for (const doc of docs) {
await this.handleTask(doc.get({ plain: true }), false); await this.handleTask(doc.get({ plain: true }), false);
} }
// Disable associated cron tasks
const crons = await CrontabModel.findAll({ where: { sub_id: ids } });
if (crons?.length) {
await this.crontabService.disabled(crons.map((x) => x.id!));
}
} }
public async enabled(ids: number[]) { public async enabled(ids: number[]) {
@@ -359,6 +364,11 @@ export default class SubscriptionService {
for (const doc of docs) { for (const doc of docs) {
await this.handleTask(doc.get({ plain: true })); await this.handleTask(doc.get({ plain: true }));
} }
// Enable associated cron tasks
const crons = await CrontabModel.findAll({ where: { sub_id: ids } });
if (crons?.length) {
await this.crontabService.enabled(crons.map((x) => x.id!));
}
} }
public async log(id: number) { public async log(id: number) {
+3 -3
View File
@@ -15,11 +15,11 @@ export function runCron(cmd: string, cron: ICron): Promise<number | void> {
}); });
// Default to single instance mode (0) for backward compatibility // Default to single instance mode (0) for backward compatibility
const allowSingleInstances = const allowMultipleInstances =
existingCron?.allow_multiple_instances === 0; existingCron?.allow_multiple_instances === 1;
if ( if (
allowSingleInstances && !allowMultipleInstances &&
existingCron && existingCron &&
existingCron.pid && existingCron.pid &&
(existingCron.status === CrontabStatus.running || (existingCron.status === CrontabStatus.running ||
+2 -2
View File
@@ -1,5 +1,5 @@
import { Joi } from 'celebrate'; import { Joi } from 'celebrate';
import CronExpressionParser from 'cron-parser'; import { CronExpressionParser } from 'cron-parser';
import { ScheduleType } from '../interface/schedule'; import { ScheduleType } from '../interface/schedule';
import path from 'path'; import path from 'path';
import config from '../config'; import config from '../config';
@@ -81,5 +81,5 @@ export const commonCronSchema = {
'string.max': '日志名称不能超过100个字符', 'string.max': '日志名称不能超过100个字符',
'string.unsafePath': '绝对路径必须在日志目录内或使用 /dev/null', 'string.unsafePath': '绝对路径必须在日志目录内或使用 /dev/null',
}), }),
allow_multiple_instances: Joi.number().optional().valid(0, 1).allow(null), allow_multiple_instances: Joi.number().optional().valid(0, 1),
}; };
+2 -3
View File
@@ -69,10 +69,9 @@ RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \ ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \
PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 \ PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 \
PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3 \ PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3
HOME=/root
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin:${HOME}/bin \ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin \
NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules \ NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules \
PIP_CACHE_DIR=${PYTHON_HOME}/pip \ PIP_CACHE_DIR=${PYTHON_HOME}/pip \
PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages
+2 -3
View File
@@ -69,10 +69,9 @@ RUN git clone --depth=1 -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \ ENV PNPM_HOME=${QL_DIR}/data/dep_cache/node \
PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 \ PYTHON_HOME=${QL_DIR}/data/dep_cache/python3 \
PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3 \ PYTHONUSERBASE=${QL_DIR}/data/dep_cache/python3
HOME=/root
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin:${HOME}/bin \ ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PNPM_HOME}:${PYTHON_HOME}/bin \
NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules \ NODE_PATH=/usr/local/bin:/usr/local/lib/node_modules:${PNPM_HOME}/global/5/node_modules \
PIP_CACHE_DIR=${PYTHON_HOME}/pip \ PIP_CACHE_DIR=${PYTHON_HOME}/pip \
PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages PYTHONPATH=${PYTHON_HOME}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}:${PYTHON_HOME}/lib/python${PYTHON_SHORT_VERSION}/site-packages
+7 -5
View File
@@ -1,5 +1,7 @@
#!/bin/bash #!/bin/bash
export PATH="$HOME/bin:$PATH"
dir_shell=/ql/shell dir_shell=/ql/shell
. $dir_shell/share.sh . $dir_shell/share.sh
@@ -21,11 +23,11 @@ log_with_style() {
if [ -f /etc/alpine-release ]; then if [ -f /etc/alpine-release ]; then
if ! grep -q "^options ndots:0" /etc/resolv.conf 2>/dev/null; then if ! grep -q "^options ndots:0" /etc/resolv.conf 2>/dev/null; then
echo "options ndots:0" >> /etc/resolv.conf echo "options ndots:0" >> /etc/resolv.conf
log_with_style "INFO" "🔧 0. 已配置 DNS 解析优化 (ndots:0)" log_with_style "INFO" "🔧 已配置 DNS 解析优化 (ndots:0)"
fi fi
fi fi
log_with_style "INFO" "🚀 1. 检测配置文件..." log_with_style "INFO" "🚀 1. 检测配置文件..."
load_ql_envs load_ql_envs
export_ql_envs export_ql_envs
. $dir_shell/env.sh . $dir_shell/env.sh
@@ -39,16 +41,16 @@ log_with_style "INFO" "⚙️ 2. 启动 pm2 服务..."
reload_pm2 reload_pm2
if [[ $AutoStartBot == true ]]; then if [[ $AutoStartBot == true ]]; then
log_with_style "INFO" "🤖 3. 启动 bot..." log_with_style "INFO" "🤖 3. 启动 bot..."
nohup ql bot >$dir_log/bot.log 2>&1 & nohup ql bot >$dir_log/bot.log 2>&1 &
fi fi
if [[ $EnableExtraShell == true ]]; then if [[ $EnableExtraShell == true ]]; then
log_with_style "INFO" "🛠️ 4. 执行自定义脚本..." log_with_style "INFO" "🛠️ 4. 执行自定义脚本..."
nohup ql extra >$dir_log/extra.log 2>&1 & nohup ql extra >$dir_log/extra.log 2>&1 &
fi fi
log_with_style "SUCCESS" "🎉 容器启动成功!" log_with_style "SUCCESS" "🎉 容器启动成功!"
crond -f >/dev/null crond -f >/dev/null
+2 -2
View File
@@ -77,7 +77,7 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"multer": "2.1.1", "multer": "1.4.5-lts.1",
"node-schedule": "^2.1.0", "node-schedule": "^2.1.0",
"nodemailer": "^6.9.16", "nodemailer": "^6.9.16",
"p-queue-cjs": "7.3.4", "p-queue-cjs": "7.3.4",
@@ -118,7 +118,7 @@
"@types/js-yaml": "^4.0.5", "@types/js-yaml": "^4.0.5",
"@types/jsonwebtoken": "^8.5.8", "@types/jsonwebtoken": "^8.5.8",
"@types/lodash": "^4.14.185", "@types/lodash": "^4.14.185",
"@types/multer": "^2.0.0", "@types/multer": "^1.4.7",
"@types/node": "^17.0.21", "@types/node": "^17.0.21",
"@types/node-schedule": "^1.3.2", "@types/node-schedule": "^1.3.2",
"@types/nodemailer": "^6.4.4", "@types/nodemailer": "^6.4.4",
+22 -30
View File
@@ -1,5 +1,9 @@
lockfileVersion: '6.0' lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
overrides: overrides:
sqlite3: git+https://github.com/whyour/node-sqlite3.git#v1.0.3 sqlite3: git+https://github.com/whyour/node-sqlite3.git#v1.0.3
@@ -86,8 +90,8 @@ dependencies:
specifier: ^4.17.21 specifier: ^4.17.21
version: 4.17.21 version: 4.17.21
multer: multer:
specifier: 2.1.1 specifier: 1.4.5-lts.1
version: 2.1.1 version: 1.4.5-lts.1
node-schedule: node-schedule:
specifier: ^2.1.0 specifier: ^2.1.0
version: 2.1.1 version: 2.1.1
@@ -190,8 +194,8 @@ devDependencies:
specifier: ^4.14.185 specifier: ^4.14.185
version: 4.17.13 version: 4.17.13
'@types/multer': '@types/multer':
specifier: ^2.0.0 specifier: ^1.4.7
version: 2.0.0 version: 1.4.12
'@types/node': '@types/node':
specifier: ^17.0.21 specifier: ^17.0.21
version: 17.0.45 version: 17.0.45
@@ -3956,8 +3960,8 @@ packages:
resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
dev: false dev: false
/@types/multer@2.0.0: /@types/multer@1.4.12:
resolution: {integrity: sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==} resolution: {integrity: sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==}
dependencies: dependencies:
'@types/express': 4.17.21 '@types/express': 4.17.21
dev: true dev: true
@@ -6329,13 +6333,13 @@ packages:
/concat-map@0.0.1: /concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
/concat-stream@2.0.0: /concat-stream@1.6.2:
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==}
engines: {'0': node >= 6.0} engines: {'0': node >= 0.8}
dependencies: dependencies:
buffer-from: 1.1.2 buffer-from: 1.1.2
inherits: 2.0.4 inherits: 2.0.4
readable-stream: 3.6.2 readable-stream: 2.3.8
typedarray: 0.0.6 typedarray: 0.0.6
dev: false dev: false
@@ -6432,7 +6436,6 @@ packages:
/core-util-is@1.0.3: /core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
dev: true
/cors@2.8.5: /cors@2.8.5:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
@@ -8285,7 +8288,6 @@ packages:
/glob@10.4.5: /glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true hasBin: true
dependencies: dependencies:
foreground-child: 3.3.0 foreground-child: 3.3.0
@@ -8298,7 +8300,7 @@ packages:
/glob@7.2.3: /glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me deprecated: Glob versions prior to v9 are no longer supported
dependencies: dependencies:
fs.realpath: 1.0.0 fs.realpath: 1.0.0
inflight: 1.0.6 inflight: 1.0.6
@@ -9169,7 +9171,6 @@ packages:
/isarray@1.0.0: /isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
dev: true
/isarray@2.0.5: /isarray@2.0.5:
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
@@ -10036,7 +10037,6 @@ packages:
/minimist@1.2.8: /minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: true
/minipass-collect@1.0.2: /minipass-collect@1.0.2:
resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
@@ -10117,7 +10117,6 @@ packages:
hasBin: true hasBin: true
dependencies: dependencies:
minimist: 1.2.8 minimist: 1.2.8
dev: true
/mkdirp@1.0.4: /mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
@@ -10152,14 +10151,17 @@ packages:
/ms@2.1.3: /ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
/multer@2.1.1: /multer@1.4.5-lts.1:
resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} resolution: {integrity: sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==}
engines: {node: '>= 10.16.0'} engines: {node: '>= 6.0.0'}
dependencies: dependencies:
append-field: 1.0.0 append-field: 1.0.0
busboy: 1.6.0 busboy: 1.6.0
concat-stream: 2.0.0 concat-stream: 1.6.2
mkdirp: 0.5.6
object-assign: 4.1.1
type-is: 1.6.18 type-is: 1.6.18
xtend: 4.0.2
dev: false dev: false
/mz@2.7.0: /mz@2.7.0:
@@ -11528,7 +11530,6 @@ packages:
/process-nextick-args@2.0.1: /process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
dev: true
/process-okam@0.11.10: /process-okam@0.11.10:
resolution: {integrity: sha512-p8e5nl6/OCeMalVb9dSojND5B9m/nq64WsyUfRmrTdLMKcNYcDN++/2I8WV1mTQDqrh2PQ6tIIb2A7/A38eSvw==} resolution: {integrity: sha512-p8e5nl6/OCeMalVb9dSojND5B9m/nq64WsyUfRmrTdLMKcNYcDN++/2I8WV1mTQDqrh2PQ6tIIb2A7/A38eSvw==}
@@ -12950,7 +12951,6 @@ packages:
safe-buffer: 5.1.2 safe-buffer: 5.1.2
string_decoder: 1.1.1 string_decoder: 1.1.1
util-deprecate: 1.0.2 util-deprecate: 1.0.2
dev: true
/readable-stream@3.6.2: /readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
@@ -13221,7 +13221,6 @@ packages:
/safe-buffer@5.1.2: /safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
dev: true
/safe-buffer@5.2.1: /safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
@@ -13869,7 +13868,6 @@ packages:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
dependencies: dependencies:
safe-buffer: 5.1.2 safe-buffer: 5.1.2
dev: true
/string_decoder@1.3.0: /string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
@@ -14154,7 +14152,6 @@ packages:
/tar@6.2.1: /tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'} engines: {node: '>=10'}
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
dependencies: dependencies:
chownr: 2.0.0 chownr: 2.0.0
fs-minipass: 2.1.0 fs-minipass: 2.1.0
@@ -15143,7 +15140,6 @@ packages:
/xtend@4.0.2: /xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'} engines: {node: '>=0.4'}
dev: true
/y18n@5.0.8: /y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
@@ -15229,7 +15225,3 @@ packages:
- encoding - encoding
- supports-color - supports-color
dev: false dev: false
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
+5 -37
View File
@@ -52,7 +52,6 @@ const push_config = {
DD_BOT_TOKEN: '', // 钉钉机器人的 DD_BOT_TOKEN DD_BOT_TOKEN: '', // 钉钉机器人的 DD_BOT_TOKEN
FSKEY: '', // 飞书机器人的 FSKEY FSKEY: '', // 飞书机器人的 FSKEY
FSSECRET: '', // 飞书机器人的 FSSECRET,对应安全设置里的签名校验密钥
// 推送到个人QQhttp://127.0.0.1/send_private_msg // 推送到个人QQhttp://127.0.0.1/send_private_msg
// 群:http://127.0.0.1/send_group_msg // 群:http://127.0.0.1/send_group_msg
@@ -482,13 +481,9 @@ function tgBotNotify(text, desp) {
timeout, timeout,
}; };
if (TG_PROXY_HOST && TG_PROXY_PORT) { if (TG_PROXY_HOST && TG_PROXY_PORT) {
let proxyHost = TG_PROXY_HOST;
if (TG_PROXY_AUTH && !TG_PROXY_HOST.includes('@')) {
proxyHost = `${TG_PROXY_AUTH}@${TG_PROXY_HOST}`;
}
let agent; let agent;
agent = new ProxyAgent({ agent = new ProxyAgent({
uri: `http://${proxyHost}:${TG_PROXY_PORT}`, uri: `http://${TG_PROXY_AUTH}${TG_PROXY_HOST}:${TG_PROXY_PORT}`,
}); });
options.dispatcher = agent; options.dispatcher = agent;
} }
@@ -994,29 +989,11 @@ function aibotkNotify(text, desp) {
function fsBotNotify(text, desp) { function fsBotNotify(text, desp) {
return new Promise((resolve) => { return new Promise((resolve) => {
const { FSKEY, FSSECRET } = push_config; const { FSKEY } = push_config;
if (FSKEY) { if (FSKEY) {
const body = {
msg_type: 'text',
content: { text: `${text}\n\n${desp}` },
};
// Add signature if secret is provided
// Note: Feishu's signature algorithm uses timestamp+"\n"+secret as the HMAC key
// and signs an empty message, which differs from typical HMAC usage
if (FSSECRET) {
const crypto = require('crypto');
const timestamp = Math.floor(Date.now() / 1000).toString();
const stringToSign = `${timestamp}\n${FSSECRET}`;
const hmac = crypto.createHmac('sha256', stringToSign);
const sign = hmac.digest('base64');
body.timestamp = timestamp;
body.sign = sign;
}
const options = { const options = {
url: `https://open.feishu.cn/open-apis/bot/v2/hook/${FSKEY}`, url: `https://open.feishu.cn/open-apis/bot/v2/hook/${FSKEY}`,
json: body, json: { msg_type: 'text', content: { text: `${text}\n\n${desp}` } },
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
@@ -1285,15 +1262,7 @@ function ntfyNotify(text, desp) {
} }
return new Promise((resolve) => { return new Promise((resolve) => {
const { const { NTFY_URL, NTFY_TOPIC, NTFY_PRIORITY, NTFY_TOKEN, NTFY_USERNAME, NTFY_PASSWORD, NTFY_ACTIONS } = push_config;
NTFY_URL,
NTFY_TOPIC,
NTFY_PRIORITY,
NTFY_TOKEN,
NTFY_USERNAME,
NTFY_PASSWORD,
NTFY_ACTIONS,
} = push_config;
if (NTFY_TOPIC) { if (NTFY_TOPIC) {
const options = { const options = {
url: `${NTFY_URL || 'https://ntfy.sh'}/${NTFY_TOPIC}`, url: `${NTFY_URL || 'https://ntfy.sh'}/${NTFY_TOPIC}`,
@@ -1308,8 +1277,7 @@ function ntfyNotify(text, desp) {
if (NTFY_TOKEN) { if (NTFY_TOKEN) {
options.headers['Authorization'] = `Bearer ${NTFY_TOKEN}`; options.headers['Authorization'] = `Bearer ${NTFY_TOKEN}`;
} else if (NTFY_USERNAME && NTFY_PASSWORD) { } else if (NTFY_USERNAME && NTFY_PASSWORD) {
options.headers['Authorization'] = options.headers['Authorization'] = `Basic ${Buffer.from(`${NTFY_USERNAME}:${NTFY_PASSWORD}`).toString('base64')}`;
`Basic ${Buffer.from(`${NTFY_USERNAME}:${NTFY_PASSWORD}`).toString('base64')}`;
} }
if (NTFY_ACTIONS) { if (NTFY_ACTIONS) {
options.headers['Actions'] = encodeRFC2047(NTFY_ACTIONS); options.headers['Actions'] = encodeRFC2047(NTFY_ACTIONS);
-15
View File
@@ -49,7 +49,6 @@ push_config = {
'DD_BOT_TOKEN': '', # 钉钉机器人的 DD_BOT_TOKEN 'DD_BOT_TOKEN': '', # 钉钉机器人的 DD_BOT_TOKEN
'FSKEY': '', # 飞书机器人的 FSKEY 'FSKEY': '', # 飞书机器人的 FSKEY
'FSSECRET': '', # 飞书机器人的 FSSECRET,对应安全设置里的签名校验密钥
'GOBOT_URL': '', # go-cqhttp 'GOBOT_URL': '', # go-cqhttp
# 推送到个人QQhttp://127.0.0.1/send_private_msg # 推送到个人QQhttp://127.0.0.1/send_private_msg
@@ -234,20 +233,6 @@ def feishu_bot(title: str, content: str) -> None:
url = f'https://open.feishu.cn/open-apis/bot/v2/hook/{push_config.get("FSKEY")}' url = f'https://open.feishu.cn/open-apis/bot/v2/hook/{push_config.get("FSKEY")}'
data = {"msg_type": "text", "content": {"text": f"{title}\n\n{content}"}} data = {"msg_type": "text", "content": {"text": f"{title}\n\n{content}"}}
# Add signature if secret is provided
# Note: Feishu's signature algorithm uses timestamp+"\n"+secret as the HMAC key
# and signs an empty message, which differs from typical HMAC usage
if push_config.get("FSSECRET"):
timestamp = str(int(time.time()))
string_to_sign = f'{timestamp}\n{push_config.get("FSSECRET")}'
hmac_code = hmac.new(
string_to_sign.encode("utf-8"), digestmod=hashlib.sha256
).digest()
sign = base64.b64encode(hmac_code).decode("utf-8")
data["timestamp"] = timestamp
data["sign"] = sign
response = requests.post(url, data=json.dumps(data)).json() response = requests.post(url, data=json.dumps(data)).json()
if response.get("StatusCode") == 0 or response.get("code") == 0: if response.get("StatusCode") == 0 or response.get("code") == 0:
+1 -1
View File
@@ -92,7 +92,7 @@ run_normal() {
cd $dir_scripts cd $dir_scripts
local relative_path="${file_param%/*}" local relative_path="${file_param%/*}"
if [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then if [[ ${file_param} != /* ]] && [[ ! -z ${relative_path} ]] && [[ ${file_param} =~ "/" ]]; then
cd ${relative_path} cd ${relative_path}
file_param=${file_param/$relative_path\//} file_param=${file_param/$relative_path\//}
fi fi
+1 -1
View File
@@ -42,7 +42,7 @@ def run():
os.environ["PYTHONPATH"] = prev_pythonpath os.environ["PYTHONPATH"] = prev_pythonpath
split_str = "__sitecustomize__" split_str = "__sitecustomize__"
file_name = os.path.abspath(sys.argv[0]).replace(f"{os.getenv('dir_scripts')}/", "") file_name = sys.argv[0].replace(f"{os.getenv('dir_scripts')}/", "")
# 创建临时文件路径 # 创建临时文件路径
temp_file = f"/tmp/env_{os.getpid()}.json" temp_file = f"/tmp/env_{os.getpid()}.json"
-1
View File
@@ -389,7 +389,6 @@
"消息接收人": "message recipient", "消息接收人": "message recipient",
"调用版本;专业版填写pro,个人版填写personal,为空默认使用专业版": "Version, you can specify 'pro' for the Professional version and 'personal' for the Personal version. If left blank, it will default to the Professional version.", "调用版本;专业版填写pro,个人版填写personal,为空默认使用专业版": "Version, you can specify 'pro' for the Professional version and 'personal' for the Personal version. If left blank, it will default to the Professional version.",
"飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973": "Feishu group bot: https://www.feishu.cn/hc/zh-CN/articles/360024984973", "飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973": "Feishu group bot: https://www.feishu.cn/hc/zh-CN/articles/360024984973",
"飞书群组机器人加签密钥,安全设置中开启签名校验后获得": "Feishu group bot signature secret, obtained after enabling signature verification in security settings",
"邮箱服务名称,比如126、163、Gmail、QQ等,支持列表https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json": "Email service name, e.g., 126, 163, Gmail, QQ, etc. Supported list: https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json", "邮箱服务名称,比如126、163、Gmail、QQ等,支持列表https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json": "Email service name, e.g., 126, 163, Gmail, QQ, etc. Supported list: https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json",
"邮箱地址": "Email Address", "邮箱地址": "Email Address",
"SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定": "The SMTP login password may also be a special passphrase, depending on the specific email service provider's instructions", "SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定": "The SMTP login password may also be a special passphrase, depending on the specific email service provider's instructions",
-1
View File
@@ -389,7 +389,6 @@
"消息接收人": "消息接收人", "消息接收人": "消息接收人",
"调用版本;专业版填写pro,个人版填写personal,为空默认使用专业版": "调用版本;专业版填写pro,个人版填写personal,为空默认使用专业版", "调用版本;专业版填写pro,个人版填写personal,为空默认使用专业版": "调用版本;专业版填写pro,个人版填写personal,为空默认使用专业版",
"飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973": "飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973", "飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973": "飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973",
"飞书群组机器人加签密钥,安全设置中开启签名校验后获得": "飞书群组机器人加签密钥,安全设置中开启签名校验后获得",
"邮箱服务名称,比如126、163、Gmail、QQ等,支持列表https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json": "邮箱服务名称,比如126、163、Gmail、QQ等,支持列表https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json", "邮箱服务名称,比如126、163、Gmail、QQ等,支持列表https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json": "邮箱服务名称,比如126、163、Gmail、QQ等,支持列表https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json",
"邮箱地址": "邮箱地址", "邮箱地址": "邮箱地址",
"SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定": "SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定", "SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定": "SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定",
+4 -8
View File
@@ -3,7 +3,7 @@ import config from '@/utils/config';
import { request } from '@/utils/http'; import { request } from '@/utils/http';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Select, Space, message } from 'antd'; import { Button, Form, Input, Modal, Select, Space, message } from 'antd';
import CronExpressionParser from 'cron-parser'; import { CronExpressionParser } from 'cron-parser';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { getScheduleType, scheduleTypeMap } from './const'; import { getScheduleType, scheduleTypeMap } from './const';
@@ -91,14 +91,10 @@ const CronModal = ({
{ required: true }, { required: true },
{ {
validator: (_, value) => { validator: (_, value) => {
try { if (!value || CronExpressionParser.parse(value).hasNext()) {
if (!value || CronExpressionParser.parse(value).hasNext()) { return Promise.resolve();
return Promise.resolve();
}
return Promise.reject(intl.get('Cron表达式格式有误'));
} catch (e) {
return Promise.reject(intl.get('Cron表达式格式有误'));
} }
return Promise.reject(intl.get('Cron表达式格式有误'));
}, },
}, },
]} ]}
+1 -1
View File
@@ -16,7 +16,7 @@ const SaveModal = ({
const handleOk = async (values: any) => { const handleOk = async (values: any) => {
setLoading(true); setLoading(true);
const payload = { ...values, originFilename: file.title, content: file.content }; const payload = { ...file, ...values, originFilename: file.title };
request request
.post(`${config.apiPrefix}scripts`, payload) .post(`${config.apiPrefix}scripts`, payload)
.then(({ code, data }) => { .then(({ code, data }) => {
+8 -12
View File
@@ -12,7 +12,7 @@ import {
} from 'antd'; } from 'antd';
import { request } from '@/utils/http'; import { request } from '@/utils/http';
import config from '@/utils/config'; import config from '@/utils/config';
import CronExpressionParser from 'cron-parser'; import { CronExpressionParser } from 'cron-parser';
import isNil from 'lodash/isNil'; import isNil from 'lodash/isNil';
const { Option } = Select; const { Option } = Select;
@@ -378,17 +378,13 @@ const SubscriptionModal = ({
{ required: true }, { required: true },
{ {
validator: (rule, value) => { validator: (rule, value) => {
try { if (
if ( scheduleType === 'interval' ||
scheduleType === 'interval' || !value ||
!value || CronExpressionParser.parse(value).hasNext()
CronExpressionParser.parse(value).hasNext() ) {
) { return Promise.resolve();
return Promise.resolve(); } else {
} else {
return Promise.reject(intl.get('Subscription表达式格式有误'));
}
} catch (e) {
return Promise.reject(intl.get('Subscription表达式格式有误')); return Promise.reject(intl.get('Subscription表达式格式有误'));
} }
}, },
-6
View File
@@ -395,12 +395,6 @@ export default {
), ),
required: true, required: true,
}, },
{
label: 'larkSecret',
tip: intl.get(
'飞书群组机器人加签密钥,安全设置中开启签名校验后获得',
),
},
], ],
email: [ email: [
{ {
+6 -6
View File
@@ -84,12 +84,12 @@ let _request = axios.create({
}); });
const apiWhiteList = [ const apiWhiteList = [
`${config.baseUrl}api/user/login`, '/api/user/login',
`${config.baseUrl}open/auth/token`, '/open/auth/token',
`${config.baseUrl}api/user/two-factor/login`, '/api/user/two-factor/login',
`${config.baseUrl}api/system`, '/api/system',
`${config.baseUrl}api/user/init`, '/api/user/init',
`${config.baseUrl}api/user/notification/init`, '/api/user/notification/init',
]; ];
_request.interceptors.request.use((_config) => { _request.interceptors.request.use((_config) => {
+1 -1
View File
@@ -1,6 +1,6 @@
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { LANG_MAP, LOG_END_SYMBOL } from './const'; import { LANG_MAP, LOG_END_SYMBOL } from './const';
import CronExpressionParser from 'cron-parser'; import { CronExpressionParser } from 'cron-parser';
import { ICrontab } from '@/pages/crontab/type'; import { ICrontab } from '@/pages/crontab/type';
export default function browserType() { export default function browserType() {
+9 -5
View File
@@ -1,6 +1,10 @@
version: 2.20.2 version: 2.19.2
changeLogLink: https://t.me/jiao_long/434 changeLogLink: https://t.me/jiao_long/431
publishTime: 2026-03-01 1800 publishTime: 2025-06-27 23:59
changeLog: | changeLog: |
1. 修复 path 安全漏洞(重要) 1. 备份数据支持选择模块,支持清除依赖缓存
2. QLAPI 和 openapi 的 systemNotify 支持自定义通知类型和参数
3. ntfy 增加可选的认证与用户动作,感谢 https://github.com/liheji
4. 修复取消安装依赖
5. 修复环境变量过大解析报错
6. 修改服务启动方式