mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-13 04:02:56 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bac306f9b3 | ||
|
|
a6fd96e97b | ||
|
|
7edd91f923 | ||
|
|
66df445bd5 | ||
|
|
5c03034bb4 | ||
|
|
0eab181d46 | ||
|
|
f6f95308c7 | ||
|
|
e93cad91b4 | ||
|
|
08ee61b179 | ||
|
|
0ab756665e | ||
|
|
3570cddce0 | ||
|
|
a2d9f1a5db | ||
|
|
0c6a214e55 | ||
|
|
095fcdbe69 |
@@ -274,4 +274,31 @@ export default (app: Router) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
route.put(
|
||||||
|
'/rename',
|
||||||
|
celebrate({
|
||||||
|
body: Joi.object({
|
||||||
|
filename: Joi.string().required(),
|
||||||
|
path: Joi.string().allow(''),
|
||||||
|
newFilename: Joi.string().required(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
let { filename, path, type, newFilename } = req.body as {
|
||||||
|
filename: string;
|
||||||
|
path: string;
|
||||||
|
type: string;
|
||||||
|
newFilename: string;
|
||||||
|
};
|
||||||
|
const filePath = join(config.scriptPath, path, filename);
|
||||||
|
const newPath = join(config.scriptPath, path, newFilename);
|
||||||
|
fs.renameSync(filePath, newPath);
|
||||||
|
res.send({ code: 200 });
|
||||||
|
} catch (e) {
|
||||||
|
return next(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+6
-5
@@ -7,7 +7,7 @@ import SystemService from '../services/system';
|
|||||||
import { celebrate, Joi } from 'celebrate';
|
import { celebrate, Joi } from 'celebrate';
|
||||||
import UserService from '../services/user';
|
import UserService from '../services/user';
|
||||||
import { EnvModel } from '../data/env';
|
import { EnvModel } from '../data/env';
|
||||||
import { promiseExec } from '../config/util';
|
import { parseVersion, promiseExec } from '../config/util';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const route = Router();
|
const route = Router();
|
||||||
@@ -21,10 +21,9 @@ export default (app: Router) => {
|
|||||||
const userService = Container.get(UserService);
|
const userService = Container.get(UserService);
|
||||||
const authInfo = await userService.getUserInfo();
|
const authInfo = await userService.getUserInfo();
|
||||||
const envCount = await EnvModel.count();
|
const envCount = await EnvModel.count();
|
||||||
const versionRegx = /.*export const version = \'(.*)\'\;/;
|
const { version, changeLog, changeLogLink } = await parseVersion(
|
||||||
|
config.versionFile,
|
||||||
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
);
|
||||||
const version = currentVersionFile.match(versionRegx)![1];
|
|
||||||
const lastCommitTime = (
|
const lastCommitTime = (
|
||||||
await promiseExec(
|
await promiseExec(
|
||||||
`cd ${config.rootPath} && git show -s --format=%ai | head -1`,
|
`cd ${config.rootPath} && git show -s --format=%ai | head -1`,
|
||||||
@@ -56,6 +55,8 @@ export default (app: Router) => {
|
|||||||
lastCommitTime: dayjs(lastCommitTime).unix(),
|
lastCommitTime: dayjs(lastCommitTime).unix(),
|
||||||
lastCommitId,
|
lastCommitId,
|
||||||
branch,
|
branch,
|
||||||
|
changeLog,
|
||||||
|
changeLogLink,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
export const LOG_END_SYMBOL = '\n ';
|
export const LOG_END_SYMBOL = '\n ';
|
||||||
|
|
||||||
|
export const TASK_COMMAND = 'task';
|
||||||
|
export const QL_COMMAND = 'ql';
|
||||||
|
|
||||||
|
export const TASK_PREFIX = `${TASK_COMMAND} `;
|
||||||
|
export const QL_PREFIX = `${QL_COMMAND} `;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ if (!process.env.QL_DIR) {
|
|||||||
process.env.QL_DIR = qlHomePath.replace(/\/$/g, '');
|
process.env.QL_DIR = qlHomePath.replace(/\/$/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastVersionFile = `https://qn.whyour.cn/version.ts`;
|
const lastVersionFile = `https://qn.whyour.cn/version.yaml`;
|
||||||
|
|
||||||
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') });
|
||||||
@@ -40,7 +40,7 @@ const sqliteFile = path.join(samplePath, 'database.sqlite');
|
|||||||
const authError = '错误的用户名密码,请重试';
|
const authError = '错误的用户名密码,请重试';
|
||||||
const loginFaild = '请先登录!';
|
const loginFaild = '请先登录!';
|
||||||
const configString = 'config sample crontab shareCode diy';
|
const configString = 'config sample crontab shareCode diy';
|
||||||
const versionFile = path.join(rootPath, 'src/version.ts');
|
const versionFile = path.join(rootPath, 'version.yaml');
|
||||||
|
|
||||||
if (envFound.error) {
|
if (envFound.error) {
|
||||||
throw new Error("⚠️ Couldn't find .env file ⚠️");
|
throw new Error("⚠️ Couldn't find .env file ⚠️");
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { exec } from 'child_process';
|
|||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
import psTreeFun from 'pstree.remy';
|
import psTreeFun from 'pstree.remy';
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util';
|
||||||
|
import { load } from 'js-yaml';
|
||||||
|
|
||||||
export function getFileContentByName(fileName: string) {
|
export function getFileContentByName(fileName: string) {
|
||||||
if (fs.existsSync(fileName)) {
|
if (fs.existsSync(fileName)) {
|
||||||
@@ -482,3 +483,17 @@ export async function getPid(name: string) {
|
|||||||
let pid = (await execAsync(taskCommand)).stdout;
|
let pid = (await execAsync(taskCommand)).stdout;
|
||||||
return Number(pid);
|
return Number(pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IVersion {
|
||||||
|
version: string;
|
||||||
|
changeLogLink: string;
|
||||||
|
changeLog: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function parseVersion(path: string): Promise<IVersion> {
|
||||||
|
return load(await promisify(fs.readFile)(path, 'utf8')) as IVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function parseContentVersion(content: string): Promise<IVersion> {
|
||||||
|
return load(content) as IVersion;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ import * as Tracing from '@sentry/tracing';
|
|||||||
import Logger from './logger';
|
import Logger from './logger';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { parseVersion } from '../config/util';
|
||||||
|
|
||||||
export default ({ expressApp }: { expressApp: Application }) => {
|
export default async ({ expressApp }: { expressApp: Application }) => {
|
||||||
const versionRegx = /.*export const version = \'(.*)\'\;/;
|
const { version } = await parseVersion(config.versionFile);
|
||||||
|
|
||||||
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
|
||||||
const currentVersion = currentVersionFile.match(versionRegx)![1];
|
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://f4b5b55fb3c645b29a5dc2d70a1a4ef4@o1098464.ingest.sentry.io/6122819',
|
dsn: 'https://f4b5b55fb3c645b29a5dc2d70a1a4ef4@o1098464.ingest.sentry.io/6122819',
|
||||||
@@ -18,7 +16,7 @@ export default ({ expressApp }: { expressApp: Application }) => {
|
|||||||
new Tracing.Integrations.Express({ app: expressApp }),
|
new Tracing.Integrations.Express({ app: expressApp }),
|
||||||
],
|
],
|
||||||
tracesSampleRate: 0.1,
|
tracesSampleRate: 0.1,
|
||||||
release: currentVersion,
|
release: version,
|
||||||
});
|
});
|
||||||
|
|
||||||
expressApp.use(Sentry.Handlers.requestHandler());
|
expressApp.use(Sentry.Handlers.requestHandler());
|
||||||
|
|||||||
+2
-5
@@ -6,15 +6,12 @@ import config from './config';
|
|||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.get('/api/public/panel/log', (req, res) => {
|
app.get('/api/public/panel/log', (req, res) => {
|
||||||
exec(
|
exec('tail -n 300 ~/.pm2/logs/panel-error.log', (err, stdout, stderr) => {
|
||||||
'pm2 logs panel --lines 500 --nostream --timestamp',
|
|
||||||
(err, stdout, stderr) => {
|
|
||||||
if (err || stderr) {
|
if (err || stderr) {
|
||||||
return res.send({ code: 400, message: (err && err.message) || stderr });
|
return res.send({ code: 400, message: (err && err.message) || stderr });
|
||||||
}
|
}
|
||||||
return res.send({ code: 200, data: stdout });
|
return res.send({ code: 200, data: stdout });
|
||||||
},
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app
|
app
|
||||||
|
|||||||
+6
-2
@@ -4,6 +4,7 @@ import { exec } from 'child_process';
|
|||||||
import Logger from './loaders/logger';
|
import Logger from './loaders/logger';
|
||||||
import { CrontabModel, CrontabStatus } from './data/cron';
|
import { CrontabModel, CrontabStatus } from './data/cron';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
|
import { QL_PREFIX, TASK_PREFIX } from './config/const';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
@@ -23,8 +24,11 @@ const run = async () => {
|
|||||||
) {
|
) {
|
||||||
schedule.scheduleJob(task.schedule, function () {
|
schedule.scheduleJob(task.schedule, function () {
|
||||||
let command = task.command as string;
|
let command = task.command as string;
|
||||||
if (!command.includes('task ') && !command.includes('ql ')) {
|
if (
|
||||||
command = `task ${command}`;
|
!command.startsWith(TASK_PREFIX) &&
|
||||||
|
!command.startsWith(QL_PREFIX)
|
||||||
|
) {
|
||||||
|
command = `${TASK_PREFIX}${command}`;
|
||||||
}
|
}
|
||||||
exec(`ID=${task.id} ${command}`);
|
exec(`ID=${task.id} ${command}`);
|
||||||
});
|
});
|
||||||
|
|||||||
+15
-10
@@ -14,6 +14,7 @@ import {
|
|||||||
import { promises, existsSync } from 'fs';
|
import { promises, existsSync } from 'fs';
|
||||||
import { Op, where, col as colFn } from 'sequelize';
|
import { Op, where, col as colFn } from 'sequelize';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { TASK_PREFIX, QL_PREFIX } from '../config/const';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class CronService {
|
export default class CronService {
|
||||||
@@ -31,7 +32,7 @@ export default class CronService {
|
|||||||
const tab = new Crontab(payload);
|
const tab = new Crontab(payload);
|
||||||
tab.saved = false;
|
tab.saved = false;
|
||||||
const doc = await this.insert(tab);
|
const doc = await this.insert(tab);
|
||||||
await this.set_crontab(this.isSixCron(doc));
|
await this.set_crontab();
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ export default class CronService {
|
|||||||
public async update(payload: Crontab): Promise<Crontab> {
|
public async update(payload: Crontab): Promise<Crontab> {
|
||||||
payload.saved = false;
|
payload.saved = false;
|
||||||
const newDoc = await this.updateDb(payload);
|
const newDoc = await this.updateDb(payload);
|
||||||
await this.set_crontab(this.isSixCron(newDoc));
|
await this.set_crontab();
|
||||||
return newDoc;
|
return newDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +82,7 @@ export default class CronService {
|
|||||||
|
|
||||||
public async remove(ids: number[]) {
|
public async remove(ids: number[]) {
|
||||||
await CrontabModel.destroy({ where: { id: ids } });
|
await CrontabModel.destroy({ where: { id: ids } });
|
||||||
await this.set_crontab(true);
|
await this.set_crontab();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async pin(ids: number[]) {
|
public async pin(ids: number[]) {
|
||||||
@@ -361,8 +362,8 @@ export default class CronService {
|
|||||||
this.logger.silly('Original command: ' + command);
|
this.logger.silly('Original command: ' + command);
|
||||||
|
|
||||||
let cmdStr = command;
|
let cmdStr = command;
|
||||||
if (!cmdStr.includes('task ') && !cmdStr.includes('ql ')) {
|
if (!cmdStr.startsWith(TASK_PREFIX) && !cmdStr.startsWith(QL_PREFIX)) {
|
||||||
cmdStr = `task ${cmdStr}`;
|
cmdStr = `${TASK_PREFIX}${cmdStr}`;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
cmdStr.endsWith('.js') ||
|
cmdStr.endsWith('.js') ||
|
||||||
@@ -408,12 +409,12 @@ export default class CronService {
|
|||||||
|
|
||||||
public async disabled(ids: number[]) {
|
public async disabled(ids: number[]) {
|
||||||
await CrontabModel.update({ isDisabled: 1 }, { where: { id: ids } });
|
await CrontabModel.update({ isDisabled: 1 }, { where: { id: ids } });
|
||||||
await this.set_crontab(true);
|
await this.set_crontab();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async enabled(ids: number[]) {
|
public async enabled(ids: number[]) {
|
||||||
await CrontabModel.update({ isDisabled: 0 }, { where: { id: ids } });
|
await CrontabModel.update({ isDisabled: 0 }, { where: { id: ids } });
|
||||||
await this.set_crontab(true);
|
await this.set_crontab();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async log(id: number) {
|
public async log(id: number) {
|
||||||
@@ -519,11 +520,17 @@ export default class CronService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private make_command(tab: Crontab) {
|
private make_command(tab: Crontab) {
|
||||||
|
if (
|
||||||
|
!tab.command.startsWith(TASK_PREFIX) &&
|
||||||
|
!tab.command.startsWith(QL_PREFIX)
|
||||||
|
) {
|
||||||
|
tab.command = `${TASK_PREFIX}${tab.command}`;
|
||||||
|
}
|
||||||
const crontab_job_string = `ID=${tab.id} ${tab.command}`;
|
const crontab_job_string = `ID=${tab.id} ${tab.command}`;
|
||||||
return crontab_job_string;
|
return crontab_job_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async set_crontab(needReloadSchedule: boolean = false) {
|
private async set_crontab() {
|
||||||
const tabs = await this.crontabs();
|
const tabs = await this.crontabs();
|
||||||
var crontab_string = '';
|
var crontab_string = '';
|
||||||
tabs.data.forEach((tab) => {
|
tabs.data.forEach((tab) => {
|
||||||
@@ -546,9 +553,7 @@ export default class CronService {
|
|||||||
fs.writeFileSync(config.crontabFile, crontab_string);
|
fs.writeFileSync(config.crontabFile, crontab_string);
|
||||||
|
|
||||||
execSync(`crontab ${config.crontabFile}`);
|
execSync(`crontab ${config.crontabFile}`);
|
||||||
if (needReloadSchedule) {
|
|
||||||
exec(`pm2 reload schedule`);
|
exec(`pm2 reload schedule`);
|
||||||
}
|
|
||||||
await CrontabModel.update({ saved: true }, { where: {} });
|
await CrontabModel.update({ saved: true }, { where: {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import SockService from './sock';
|
|||||||
import CronService from './cron';
|
import CronService from './cron';
|
||||||
import ScheduleService, { TaskCallbacks } from './schedule';
|
import ScheduleService, { TaskCallbacks } from './schedule';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { LOG_END_SYMBOL } from '../config/const';
|
import { TASK_COMMAND } from '../config/const';
|
||||||
import { getPid, killTask } from '../config/util';
|
import { getPid, killTask } from '../config/util';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
@@ -42,7 +42,7 @@ export default class ScriptService {
|
|||||||
|
|
||||||
public async runScript(filePath: string) {
|
public async runScript(filePath: string) {
|
||||||
const relativePath = path.relative(config.scriptPath, filePath);
|
const relativePath = path.relative(config.scriptPath, filePath);
|
||||||
const command = `task -l ${relativePath} now`;
|
const command = `${TASK_COMMAND} -l ${relativePath} now`;
|
||||||
const pid = await this.scheduleService.runTask(
|
const pid = await this.scheduleService.runTask(
|
||||||
command,
|
command,
|
||||||
this.taskCallbacks(filePath),
|
this.taskCallbacks(filePath),
|
||||||
@@ -56,7 +56,7 @@ export default class ScriptService {
|
|||||||
let str = '';
|
let str = '';
|
||||||
if (!pid) {
|
if (!pid) {
|
||||||
const relativePath = path.relative(config.scriptPath, filePath);
|
const relativePath = path.relative(config.scriptPath, filePath);
|
||||||
pid = await getPid(`task -l ${relativePath} now`);
|
pid = await getPid(`${TASK_COMMAND} -l ${relativePath} now`);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await killTask(pid);
|
await killTask(pid);
|
||||||
|
|||||||
+15
-15
@@ -9,6 +9,7 @@ import ScheduleService from './schedule';
|
|||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import SockService from './sock';
|
import SockService from './sock';
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
|
import { parseContentVersion, parseVersion } from '../config/util';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class SystemService {
|
export default class SystemService {
|
||||||
@@ -78,14 +79,9 @@ export default class SystemService {
|
|||||||
|
|
||||||
public async checkUpdate() {
|
public async checkUpdate() {
|
||||||
try {
|
try {
|
||||||
const versionRegx = /.*export const version = \'(.*)\'\;/;
|
const currentVersionContent = await parseVersion(config.versionFile);
|
||||||
const logRegx = /.*export const changeLog = \`((.*\n.*)+)\`;/;
|
|
||||||
|
|
||||||
const currentVersionFile = fs.readFileSync(config.versionFile, 'utf8');
|
let lastVersionContent;
|
||||||
const currentVersion = currentVersionFile.match(versionRegx)![1];
|
|
||||||
|
|
||||||
let lastVersion = '';
|
|
||||||
let lastLog = '';
|
|
||||||
try {
|
try {
|
||||||
const result = await got.get(
|
const result = await got.get(
|
||||||
`${config.lastVersionFile}?t=${Date.now()}`,
|
`${config.lastVersionFile}?t=${Date.now()}`,
|
||||||
@@ -93,19 +89,23 @@ export default class SystemService {
|
|||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const lastVersionFileContent = result.body;
|
lastVersionContent = await parseContentVersion(result.body);
|
||||||
lastVersion = lastVersionFileContent.match(versionRegx)![1];
|
|
||||||
lastLog = lastVersionFileContent.match(logRegx)
|
|
||||||
? lastVersionFileContent.match(logRegx)![1]
|
|
||||||
: '';
|
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
|
|
||||||
|
if (!lastVersionContent) {
|
||||||
|
lastVersionContent = currentVersionContent;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: {
|
data: {
|
||||||
hasNewVersion: this.checkHasNewVersion(currentVersion, lastVersion),
|
hasNewVersion: this.checkHasNewVersion(
|
||||||
lastVersion,
|
currentVersionContent.version,
|
||||||
lastLog,
|
lastVersionContent.version,
|
||||||
|
),
|
||||||
|
lastVersion: lastVersionContent.version,
|
||||||
|
lastLog: lastVersionContent.changeLog,
|
||||||
|
lastLogLink: lastVersionContent.changeLogLink,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
+5
-2
@@ -4,7 +4,7 @@
|
|||||||
"start": "concurrently -n w: npm:start:*",
|
"start": "concurrently -n w: npm:start:*",
|
||||||
"start:front": "max dev",
|
"start:front": "max dev",
|
||||||
"start:back": "nodemon",
|
"start:back": "nodemon",
|
||||||
"start:public": "ts-node back/public.ts",
|
"start:public": "ts-node --transpile-only ./back/public.ts",
|
||||||
"build:front": "max build",
|
"build:front": "max build",
|
||||||
"build:back": "tsc -p tsconfig.back.json",
|
"build:back": "tsc -p tsconfig.back.json",
|
||||||
"panel": "npm run build:back && node static/build/app.js",
|
"panel": "npm run build:back && node static/build/app.js",
|
||||||
@@ -69,6 +69,7 @@
|
|||||||
"got": "^11.8.2",
|
"got": "^11.8.2",
|
||||||
"hpagent": "^0.1.2",
|
"hpagent": "^0.1.2",
|
||||||
"iconv-lite": "^0.6.3",
|
"iconv-lite": "^0.6.3",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"multer": "^1.4.4",
|
"multer": "^1.4.4",
|
||||||
@@ -90,13 +91,14 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/icons": "^4.7.0",
|
"@ant-design/icons": "^4.7.0",
|
||||||
"@ant-design/pro-layout": "^6.33.1",
|
"@ant-design/pro-layout": "^6.33.1",
|
||||||
"@monaco-editor/react": "4.2.1",
|
"@monaco-editor/react": "4.4.6",
|
||||||
"@react-hook/resize-observer": "^1.2.6",
|
"@react-hook/resize-observer": "^1.2.6",
|
||||||
"@sentry/react": "^7.12.1",
|
"@sentry/react": "^7.12.1",
|
||||||
"@types/body-parser": "^1.19.2",
|
"@types/body-parser": "^1.19.2",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/express-jwt": "^6.0.4",
|
"@types/express-jwt": "^6.0.4",
|
||||||
|
"@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": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
@@ -120,6 +122,7 @@
|
|||||||
"compression-webpack-plugin": "9.2.0",
|
"compression-webpack-plugin": "9.2.0",
|
||||||
"concurrently": "^7.0.0",
|
"concurrently": "^7.0.0",
|
||||||
"lint-staged": "^13.0.3",
|
"lint-staged": "^13.0.3",
|
||||||
|
"monaco-editor": "^0.34.1",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.15",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"qiniu": "^7.4.0",
|
"qiniu": "^7.4.0",
|
||||||
|
|||||||
Generated
+3343
-1936
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -5,14 +5,14 @@ const envFound = dotenv.config();
|
|||||||
const accessKey = process.env.QINIU_AK;
|
const accessKey = process.env.QINIU_AK;
|
||||||
const secretKey = process.env.QINIU_SK;
|
const secretKey = process.env.QINIU_SK;
|
||||||
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
|
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
|
||||||
const key = 'version.ts';
|
const key = 'version.yaml';
|
||||||
const options = {
|
const options = {
|
||||||
scope: `${process.env.QINIU_SCOPE}:${key}`,
|
scope: `${process.env.QINIU_SCOPE}:${key}`,
|
||||||
};
|
};
|
||||||
const putPolicy = new qiniu.rs.PutPolicy(options);
|
const putPolicy = new qiniu.rs.PutPolicy(options);
|
||||||
const uploadToken = putPolicy.uploadToken(mac);
|
const uploadToken = putPolicy.uploadToken(mac);
|
||||||
|
|
||||||
const localFile = 'src/version.ts';
|
const localFile = 'version.yaml';
|
||||||
const config = new qiniu.conf.Config({ zone: qiniu.zone.Zone_z1 });
|
const config = new qiniu.conf.Config({ zone: qiniu.zone.Zone_z1 });
|
||||||
const formUploader = new qiniu.form_up.FormUploader(config);
|
const formUploader = new qiniu.form_up.FormUploader(config);
|
||||||
const putExtra = new qiniu.form_up.PutExtra(
|
const putExtra = new qiniu.form_up.PutExtra(
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ random_delay() {
|
|||||||
done
|
done
|
||||||
|
|
||||||
local delay_second=$(($(gen_random_num "$random_delay_max") + 1))
|
local delay_second=$(($(gen_random_num "$random_delay_max") + 1))
|
||||||
echo -e "\n命令未添加 \"now\",随机延迟 $delay_second 秒后再执行任务,如需立即终止,请按 CTRL+C...\n"
|
echo -e "\n命令未添加 \"now\",随机延迟 $delay_second 秒后执行\n"
|
||||||
sleep $delay_second
|
sleep $delay_second
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ git push
|
|||||||
echo -e "更新cdn文件"
|
echo -e "更新cdn文件"
|
||||||
ts-node sample/tool.ts
|
ts-node sample/tool.ts
|
||||||
|
|
||||||
string=$(cat src/version.ts | grep "version" | egrep "[^\']*" -o | egrep "\d\.*")
|
string=$(cat version.yaml | grep "version" | egrep "[^ ]*" -o | egrep "\d\.*")
|
||||||
version="v$string"
|
version="v$string"
|
||||||
echo -e "当前版本$version"
|
echo -e "当前版本$version"
|
||||||
|
|
||||||
|
|||||||
+2
-30
@@ -26,33 +26,6 @@ diff_cron() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
## 检测配置文件版本
|
|
||||||
detect_config_version() {
|
|
||||||
## 识别出两个文件的版本号
|
|
||||||
ver_config_sample=$(grep " Version: " $file_config_sample | perl -pe "s|.+v((\d+\.?){3})|\1|")
|
|
||||||
[[ -f $file_config_user ]] && ver_config_user=$(grep " Version: " $file_config_user | perl -pe "s|.+v((\d+\.?){3})|\1|")
|
|
||||||
|
|
||||||
## 删除旧的发送记录文件
|
|
||||||
[[ -f $send_mark ]] && [[ $(cat $send_mark) != $ver_config_sample ]] && rm -f $send_mark
|
|
||||||
|
|
||||||
## 识别出更新日期和更新内容
|
|
||||||
update_date=$(grep " Date: " $file_config_sample | awk -F ": " '{print $2}')
|
|
||||||
update_content=$(grep " Update Content: " $file_config_sample | awk -F ": " '{print $2}')
|
|
||||||
|
|
||||||
## 如果是今天,并且版本号不一致,则发送通知
|
|
||||||
if [[ -f $file_config_user ]] && [[ $ver_config_user != $ver_config_sample ]] && [[ $update_date == $(date "+%Y-%m-%d") ]]; then
|
|
||||||
if [[ ! -f $send_mark ]]; then
|
|
||||||
local notify_title="配置文件更新通知"
|
|
||||||
local notify_content="更新日期: $update_date\n用户版本: $ver_config_user\n新的版本: $ver_config_sample\n更新内容: $update_content\n更新说明: 如需使用新功能请对照config.sample.sh,将相关新参数手动增加到你自己的config.sh中,否则请无视本消息。本消息只在该新版本配置文件更新当天发送一次。\n"
|
|
||||||
echo -e $notify_content
|
|
||||||
notify_api "$notify_title" "$notify_content"
|
|
||||||
[[ $? -eq 0 ]] && echo $ver_config_sample >$send_mark
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
[[ -f $send_mark ]] && rm -f $send_mark
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
## 输出是否有新的或失效的定时任务,$1:新的或失效的任务清单文件路径,$2:新/失效
|
## 输出是否有新的或失效的定时任务,$1:新的或失效的任务清单文件路径,$2:新/失效
|
||||||
output_list_add_drop() {
|
output_list_add_drop() {
|
||||||
local list=$1
|
local list=$1
|
||||||
@@ -265,8 +238,8 @@ update_qinglong() {
|
|||||||
|
|
||||||
if [[ $exit_status -eq 0 ]]; then
|
if [[ $exit_status -eq 0 ]]; then
|
||||||
echo -e "\n更新青龙源文件成功...\n"
|
echo -e "\n更新青龙源文件成功...\n"
|
||||||
|
reset_romote_url ${dir_root} "https://${mirror}.com/whyour/qinglong.git" ${primary_branch}
|
||||||
cp -f $file_config_sample $dir_config/config.sample.sh
|
cp -f $file_config_sample $dir_config/config.sample.sh
|
||||||
detect_config_version
|
|
||||||
update_depend
|
update_depend
|
||||||
|
|
||||||
[[ -f $dir_root/package.json ]] && ql_depend_new=$(cat $dir_root/package.json)
|
[[ -f $dir_root/package.json ]] && ql_depend_new=$(cat $dir_root/package.json)
|
||||||
@@ -291,8 +264,7 @@ update_qinglong_static() {
|
|||||||
fi
|
fi
|
||||||
if [[ $exit_status -eq 0 ]]; then
|
if [[ $exit_status -eq 0 ]]; then
|
||||||
echo -e "\n更新青龙静态资源成功...\n"
|
echo -e "\n更新青龙静态资源成功...\n"
|
||||||
local static_version=$(cat $dir_root/src/version.ts | perl -pe "s|.*\'(.*)\';\.*|\1|" | head -1)
|
reset_romote_url ${ql_static_repo} ${url} ${primary_branch}
|
||||||
echo -e "\n当前版本 $static_version...\n"
|
|
||||||
|
|
||||||
rm -rf $dir_static/*
|
rm -rf $dir_static/*
|
||||||
cp -rf $ql_static_repo/* $dir_static
|
cp -rf $ql_static_repo/* $dir_static
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createFromIconfontCN } from '@ant-design/icons';
|
import { createFromIconfontCN } from '@ant-design/icons';
|
||||||
|
|
||||||
const IconFont = createFromIconfontCN({
|
const IconFont = createFromIconfontCN({
|
||||||
scriptUrl: ['//at.alicdn.com/t/c/font_3354854_z0d9rbri1ci.js'],
|
scriptUrl: ['//at.alicdn.com/t/c/font_3354854_ob5y15ewlyq.js'],
|
||||||
});
|
});
|
||||||
|
|
||||||
export default IconFont;
|
export default IconFont;
|
||||||
|
|||||||
@@ -347,4 +347,5 @@ select:-webkit-autofill:focus {
|
|||||||
pre {
|
pre {
|
||||||
word-break: break-all !important;
|
word-break: break-all !important;
|
||||||
white-space: break-spaces !important;
|
white-space: break-spaces !important;
|
||||||
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import config from '@/utils/config';
|
|||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import vhCheck from 'vh-check';
|
import vhCheck from 'vh-check';
|
||||||
import { version, changeLogLink, changeLog } from '../version';
|
|
||||||
import { useCtx, useTheme } from '@/utils/hooks';
|
import { useCtx, useTheme } from '@/utils/hooks';
|
||||||
import {
|
import {
|
||||||
message,
|
message,
|
||||||
@@ -52,6 +51,8 @@ interface TSystemInfo {
|
|||||||
lastCommitId: string;
|
lastCommitId: string;
|
||||||
lastCommitTime: number;
|
lastCommitTime: number;
|
||||||
version: string;
|
version: string;
|
||||||
|
changeLog: string;
|
||||||
|
changeLogLink: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
@@ -89,6 +90,7 @@ export default function () {
|
|||||||
if (!data.isInitialized) {
|
if (!data.isInitialized) {
|
||||||
history.push('/initialization');
|
history.push('/initialization');
|
||||||
} else {
|
} else {
|
||||||
|
init(data.version);
|
||||||
getUser();
|
getUser();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,7 +145,6 @@ export default function () {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
vhCheck();
|
vhCheck();
|
||||||
init();
|
|
||||||
|
|
||||||
const _theme = localStorage.getItem('qinglong_dark_theme') || 'auto';
|
const _theme = localStorage.getItem('qinglong_dark_theme') || 'auto';
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
@@ -269,7 +270,7 @@ export default function () {
|
|||||||
<>
|
<>
|
||||||
<span style={{ fontSize: 16 }}>控制面板</span>
|
<span style={{ fontSize: 16 }}>控制面板</span>
|
||||||
<a
|
<a
|
||||||
href={changeLogLink}
|
href={systemInfo?.changeLogLink}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -289,7 +290,7 @@ export default function () {
|
|||||||
letterSpacing: isQQBrowser ? -2 : 0,
|
letterSpacing: isQQBrowser ? -2 : 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
v{version}
|
v{systemInfo?.version}
|
||||||
</span>
|
</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import config from '@/utils/config';
|
|||||||
import CronLogModal from './logModal';
|
import CronLogModal from './logModal';
|
||||||
import Editor from '@monaco-editor/react';
|
import Editor from '@monaco-editor/react';
|
||||||
import IconFont from '@/components/iconfont';
|
import IconFont from '@/components/iconfont';
|
||||||
|
import { getCommandScript } from '@/utils';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
@@ -147,22 +148,10 @@ const CronDetailModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getScript = () => {
|
const getScript = () => {
|
||||||
const cmd = cron.command.split(' ') as string[];
|
const result = getCommandScript(cron.command);
|
||||||
if (cmd[0] === 'task') {
|
if (Array.isArray(result)) {
|
||||||
setValidTabs(validTabs);
|
setValidTabs(validTabs);
|
||||||
if (cmd[1].startsWith('/ql/data/scripts')) {
|
const [s, p] = result;
|
||||||
cmd[1] = cmd[1].replace('/ql/data/scripts/', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
let p: string, s: string;
|
|
||||||
let index = cmd[1].lastIndexOf('/');
|
|
||||||
if (index >= 0) {
|
|
||||||
s = cmd[1].slice(index + 1);
|
|
||||||
p = cmd[1].slice(0, index);
|
|
||||||
} else {
|
|
||||||
s = cmd[1];
|
|
||||||
p = '';
|
|
||||||
}
|
|
||||||
setScriptInfo({ parent: p, filename: s });
|
setScriptInfo({ parent: p, filename: s });
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}scripts/${s}?path=${p || ''}`)
|
.get(`${config.apiPrefix}scripts/${s}?path=${p || ''}`)
|
||||||
@@ -171,7 +160,7 @@ const CronDetailModal = ({
|
|||||||
setValue(data);
|
setValue(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else if (result) {
|
||||||
setValidTabs([validTabs[0]]);
|
setValidTabs([validTabs[0]]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import ViewManageModal from './viewManageModal';
|
|||||||
import { FilterValue, SorterResult } from 'antd/lib/table/interface';
|
import { FilterValue, SorterResult } from 'antd/lib/table/interface';
|
||||||
import { SharedContext } from '@/layouts';
|
import { SharedContext } from '@/layouts';
|
||||||
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
|
import useTableScrollHeight from '@/hooks/useTableScrollHeight';
|
||||||
|
import { getCommandScript } from '@/utils';
|
||||||
|
|
||||||
const { Text, Paragraph } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
const { Search } = Input;
|
const { Search } = Input;
|
||||||
@@ -390,24 +391,12 @@ const Crontab = () => {
|
|||||||
const tableScrollHeight = useTableScrollHeight(tableRef);
|
const tableScrollHeight = useTableScrollHeight(tableRef);
|
||||||
|
|
||||||
const goToScriptManager = (record: any) => {
|
const goToScriptManager = (record: any) => {
|
||||||
const cmd = record.command.split(' ') as string[];
|
const result = getCommandScript(record.command);
|
||||||
if (cmd[0] === 'task') {
|
if (Array.isArray(result)) {
|
||||||
if (cmd[1].startsWith('/ql/data/scripts')) {
|
const [s, p] = result;
|
||||||
cmd[1] = cmd[1].replace('/ql/data/scripts/', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
let p: string, s: string;
|
|
||||||
let index = cmd[1].lastIndexOf('/');
|
|
||||||
if (index >= 0) {
|
|
||||||
s = cmd[1].slice(index + 1);
|
|
||||||
p = cmd[1].slice(0, index);
|
|
||||||
} else {
|
|
||||||
s = cmd[1];
|
|
||||||
p = '';
|
|
||||||
}
|
|
||||||
history.push(`/script?p=${p}&s=${s}`);
|
history.push(`/script?p=${p}&s=${s}`);
|
||||||
} else if (cmd[1] === 'repo') {
|
} else if (result) {
|
||||||
location.href = cmd[2];
|
location.href = result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ const CronModal = ({
|
|||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
rows={4}
|
rows={4}
|
||||||
autoSize={true}
|
autoSize={true}
|
||||||
placeholder="请输入要执行的命令"
|
placeholder="使用task命令运行脚本或其他任意LINUX可执行命令"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
@@ -9,20 +9,37 @@ import './index.less';
|
|||||||
import { SharedContext } from '@/layouts';
|
import { SharedContext } from '@/layouts';
|
||||||
|
|
||||||
const Error = () => {
|
const Error = () => {
|
||||||
const { user, theme } = useOutletContext<SharedContext>();
|
const { user, theme, reloadUser } = useOutletContext<SharedContext>();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [data, setData] = useState('暂无日志');
|
const [data, setData] = useState('暂无日志');
|
||||||
|
|
||||||
const getLog = () => {
|
const getTimes = () => {
|
||||||
setLoading(true);
|
return parseInt(localStorage.getItem('error_retry_times') || '0', 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
let times = getTimes();
|
||||||
|
|
||||||
|
const getLog = (needLoading: boolean = true) => {
|
||||||
|
needLoading && setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}public/panel/log`)
|
.get(`${config.apiPrefix}public/panel/log`)
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
setData(data);
|
setData(data);
|
||||||
|
if (!data) {
|
||||||
|
times = getTimes();
|
||||||
|
if (times > 5) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
localStorage.setItem('error_retry_times', `${times + 1}`);
|
||||||
|
setTimeout(() => {
|
||||||
|
reloadUser();
|
||||||
|
getLog(false);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => needLoading && setLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -39,7 +56,7 @@ const Error = () => {
|
|||||||
<div className="error-wrapper">
|
<div className="error-wrapper">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<PageLoading />
|
<PageLoading />
|
||||||
) : (
|
) : data ? (
|
||||||
<Terminal
|
<Terminal
|
||||||
name="服务错误"
|
name="服务错误"
|
||||||
colorMode={theme === 'vs-dark' ? ColorMode.Dark : ColorMode.Light}
|
colorMode={theme === 'vs-dark' ? ColorMode.Dark : ColorMode.Light}
|
||||||
@@ -55,6 +72,10 @@ const Error = () => {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
) : times > 5 ? (
|
||||||
|
<>服务启动超时,请手动进入容器执行 ql -l check 后刷新再试</>
|
||||||
|
) : (
|
||||||
|
<PageLoading tip="启动中,请稍后..." />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ const EditModal = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
if (!cNode || !cNode.title) {
|
if (!cNode || !cNode.title || !currentPid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
request
|
request
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ import { depthFirstSearch } from '@/utils';
|
|||||||
import { SharedContext } from '@/layouts';
|
import { SharedContext } from '@/layouts';
|
||||||
import useFilterTreeData from '@/hooks/useFilterTreeData';
|
import useFilterTreeData from '@/hooks/useFilterTreeData';
|
||||||
import uniq from 'lodash/uniq';
|
import uniq from 'lodash/uniq';
|
||||||
|
import IconFont from '@/components/iconfont';
|
||||||
|
import RenameModal from './renameModal';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
@@ -64,20 +66,22 @@ const Script = () => {
|
|||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const editorRef = useRef<any>(null);
|
const editorRef = useRef<any>(null);
|
||||||
const [isAddFileModalVisible, setIsAddFileModalVisible] = useState(false);
|
const [isAddFileModalVisible, setIsAddFileModalVisible] = useState(false);
|
||||||
|
const [isRenameFileModalVisible, setIsRenameFileModalVisible] = useState(false);
|
||||||
const [currentNode, setCurrentNode] = useState<any>();
|
const [currentNode, setCurrentNode] = useState<any>();
|
||||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
||||||
|
|
||||||
const getScripts = () => {
|
const getScripts = (needLoading: boolean = true) => {
|
||||||
setLoading(true);
|
needLoading && setLoading(true);
|
||||||
request
|
request
|
||||||
.get(`${config.apiPrefix}scripts`)
|
.get(`${config.apiPrefix}scripts`)
|
||||||
.then(({ code, data }) => {
|
.then(({ code, data }) => {
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
setData(data);
|
setData(data);
|
||||||
|
initState();
|
||||||
initGetScript();
|
initGetScript();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => needLoading && setLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDetail = (node: any) => {
|
const getDetail = (node: any) => {
|
||||||
@@ -287,6 +291,15 @@ const Script = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renameFile = () => {
|
||||||
|
setIsRenameFileModalVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRenameFileCancel = () => {
|
||||||
|
setIsRenameFileModalVisible(false);
|
||||||
|
getScripts(false);
|
||||||
|
}
|
||||||
|
|
||||||
const addFile = () => {
|
const addFile = () => {
|
||||||
setIsAddFileModalVisible(true);
|
setIsAddFileModalVisible(true);
|
||||||
};
|
};
|
||||||
@@ -381,6 +394,9 @@ const Script = () => {
|
|||||||
case 'delete':
|
case 'delete':
|
||||||
deleteFile();
|
deleteFile();
|
||||||
break;
|
break;
|
||||||
|
case 'rename':
|
||||||
|
renameFile();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -407,6 +423,12 @@ const Script = () => {
|
|||||||
icon: <EditOutlined />,
|
icon: <EditOutlined />,
|
||||||
disabled: !select,
|
disabled: !select,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '重命名',
|
||||||
|
key: 'rename',
|
||||||
|
icon: <IconFont type="ql-icon-rename" />,
|
||||||
|
disabled: !select,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '删除',
|
label: '删除',
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
@@ -471,6 +493,14 @@ const Script = () => {
|
|||||||
icon={<EditOutlined />}
|
icon={<EditOutlined />}
|
||||||
/>
|
/>
|
||||||
</Tooltip>,
|
</Tooltip>,
|
||||||
|
<Tooltip title="重命名">
|
||||||
|
<Button
|
||||||
|
disabled={!select}
|
||||||
|
type="primary"
|
||||||
|
onClick={renameFile}
|
||||||
|
icon={<IconFont type="ql-icon-rename" />}
|
||||||
|
/>
|
||||||
|
</Tooltip>,
|
||||||
<Tooltip title="删除">
|
<Tooltip title="删除">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -585,6 +615,11 @@ const Script = () => {
|
|||||||
treeData={data}
|
treeData={data}
|
||||||
handleCancel={addFileModalClose}
|
handleCancel={addFileModalClose}
|
||||||
/>
|
/>
|
||||||
|
<RenameModal
|
||||||
|
visible={isRenameFileModalVisible}
|
||||||
|
handleCancel={handleRenameFileCancel}
|
||||||
|
currentNode={currentNode}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Modal, message, Input, Form } from 'antd';
|
||||||
|
import { request } from '@/utils/http';
|
||||||
|
import config from '@/utils/config';
|
||||||
|
|
||||||
|
const RenameModal = ({
|
||||||
|
currentNode,
|
||||||
|
handleCancel,
|
||||||
|
visible,
|
||||||
|
}: {
|
||||||
|
currentNode?: any;
|
||||||
|
visible: boolean;
|
||||||
|
handleCancel: () => void;
|
||||||
|
}) => {
|
||||||
|
console.log(currentNode);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleOk = async (values: any) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const { code, data } = await request.put(
|
||||||
|
`${config.apiPrefix}scripts/rename`,
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
filename: currentNode.title,
|
||||||
|
path: currentNode.parent || '',
|
||||||
|
newFilename: values.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (code === 200) {
|
||||||
|
message.success('更新名称成功');
|
||||||
|
handleCancel();
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.resetFields();
|
||||||
|
}, [currentNode, visible]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="重命名"
|
||||||
|
open={visible}
|
||||||
|
forceRender
|
||||||
|
centered
|
||||||
|
maskClosable={false}
|
||||||
|
onOk={() => {
|
||||||
|
form
|
||||||
|
.validateFields()
|
||||||
|
.then((values) => {
|
||||||
|
handleOk(values);
|
||||||
|
})
|
||||||
|
.catch((info) => {
|
||||||
|
console.log('Validate Failed:', info);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onCancel={() => handleCancel()}
|
||||||
|
confirmLoading={loading}
|
||||||
|
>
|
||||||
|
<Form form={form} layout="vertical" name="edit_name_modal">
|
||||||
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: '请输入新名称' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入新名称" />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RenameModal;
|
||||||
@@ -38,6 +38,14 @@ const About = ({ systemInfo }: { systemInfo: SharedContext['systemInfo'] }) => {
|
|||||||
<Descriptions.Item label="更新ID" span={3}>
|
<Descriptions.Item label="更新ID" span={3}>
|
||||||
{systemInfo.lastCommitId}
|
{systemInfo.lastCommitId}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="更新日志" span={3}>
|
||||||
|
<Link
|
||||||
|
href={`https://qn.whyour.cn/version.yaml?t=${Date.now()}`}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
查看
|
||||||
|
</Link>
|
||||||
|
</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
<div>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ import React, { useEffect, useState, useRef } from 'react';
|
|||||||
import { Statistic, Modal, Tag, Button, Spin, message } from 'antd';
|
import { Statistic, Modal, Tag, Button, Spin, message } from 'antd';
|
||||||
import { request } from '@/utils/http';
|
import { request } from '@/utils/http';
|
||||||
import config from '@/utils/config';
|
import config from '@/utils/config';
|
||||||
import { version } from '../../version';
|
|
||||||
|
|
||||||
const { Countdown } = Statistic;
|
const { Countdown } = Statistic;
|
||||||
|
|
||||||
const CheckUpdate = ({ socketMessage }: any) => {
|
const CheckUpdate = ({ socketMessage, systemInfo }: any) => {
|
||||||
const [updateLoading, setUpdateLoading] = useState(false);
|
const [updateLoading, setUpdateLoading] = useState(false);
|
||||||
const [value, setValue] = useState('');
|
const [value, setValue] = useState('');
|
||||||
const modalRef = useRef<any>();
|
const modalRef = useRef<any>();
|
||||||
@@ -23,7 +22,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
if (data.hasNewVersion) {
|
if (data.hasNewVersion) {
|
||||||
showConfirmUpdateModal(data);
|
showConfirmUpdateModal(data);
|
||||||
} else {
|
} else {
|
||||||
showForceUpdateModal();
|
showForceUpdateModal(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -36,7 +35,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const showForceUpdateModal = () => {
|
const showForceUpdateModal = (data: any) => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
width: 500,
|
width: 500,
|
||||||
title: '更新',
|
title: '更新',
|
||||||
@@ -44,7 +43,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
<>
|
<>
|
||||||
<div>已经是最新版了!</div>
|
<div>已经是最新版了!</div>
|
||||||
<div style={{ fontSize: 12, fontWeight: 400, marginTop: 5 }}>
|
<div style={{ fontSize: 12, fontWeight: 400, marginTop: 5 }}>
|
||||||
青龙 {version} 是目前检测到的最新可用版本了。
|
青龙 {data.lastVersion} 是目前检测到的最新可用版本了。
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
@@ -70,14 +69,13 @@ const CheckUpdate = ({ socketMessage }: any) => {
|
|||||||
<>
|
<>
|
||||||
<div>更新可用</div>
|
<div>更新可用</div>
|
||||||
<div style={{ fontSize: 12, fontWeight: 400, marginTop: 5 }}>
|
<div style={{ fontSize: 12, fontWeight: 400, marginTop: 5 }}>
|
||||||
新版本{lastVersion}可用。你使用的版本为{version}。
|
新版本 {lastVersion} 可用,你使用的版本为 {systemInfo.version}。
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
content: (
|
content: (
|
||||||
<pre
|
<pre
|
||||||
style={{
|
style={{
|
||||||
paddingTop: 15,
|
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -416,7 +416,10 @@ const Setting = () => {
|
|||||||
</Input.Group>
|
</Input.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="检查更新" name="update">
|
<Form.Item label="检查更新" name="update">
|
||||||
<CheckUpdate socketMessage={socketMessage} />
|
<CheckUpdate
|
||||||
|
systemInfo={systemInfo}
|
||||||
|
socketMessage={socketMessage}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ _request.interceptors.request.use((url, options) => {
|
|||||||
_request.interceptors.response.use(async (response) => {
|
_request.interceptors.response.use(async (response) => {
|
||||||
const responseStatus = response.status;
|
const responseStatus = response.status;
|
||||||
if ([502, 504].includes(responseStatus)) {
|
if ([502, 504].includes(responseStatus)) {
|
||||||
message.error('服务异常,请稍后刷新!');
|
|
||||||
history.push('/error');
|
history.push('/error');
|
||||||
} else if (responseStatus === 401) {
|
} else if (responseStatus === 401) {
|
||||||
if (history.location.pathname !== '/login') {
|
if (history.location.pathname !== '/login') {
|
||||||
|
|||||||
@@ -276,3 +276,31 @@ export function logEnded(log: string): boolean {
|
|||||||
const endTips = [LOG_END_SYMBOL, '执行结束'];
|
const endTips = [LOG_END_SYMBOL, '执行结束'];
|
||||||
return endTips.some((x) => log.includes(x));
|
return endTips.some((x) => log.includes(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCommandScript(
|
||||||
|
command: string,
|
||||||
|
): [string, string] | string | undefined {
|
||||||
|
const cmd = command.split(' ') as string[];
|
||||||
|
if (cmd[0] === 'task') {
|
||||||
|
let scriptsPart = cmd.find((x) =>
|
||||||
|
['.js', '.ts', '.sh', '.py'].some((y) => x.endsWith(y)),
|
||||||
|
);
|
||||||
|
if (!scriptsPart) return;
|
||||||
|
if (scriptsPart.startsWith('/ql/data/scripts')) {
|
||||||
|
scriptsPart = scriptsPart.replace('/ql/data/scripts/', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
let p: string, s: string;
|
||||||
|
let index = scriptsPart.lastIndexOf('/');
|
||||||
|
if (index >= 0) {
|
||||||
|
s = scriptsPart.slice(index + 1);
|
||||||
|
p = scriptsPart.slice(0, index);
|
||||||
|
} else {
|
||||||
|
s = scriptsPart;
|
||||||
|
p = '';
|
||||||
|
}
|
||||||
|
return [s, p];
|
||||||
|
} else if (cmd[1] === 'repo') {
|
||||||
|
return cmd[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+3
-5
@@ -1,9 +1,9 @@
|
|||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import { Integrations } from '@sentry/tracing';
|
import { Integrations } from '@sentry/tracing';
|
||||||
import { loader } from '@monaco-editor/react';
|
import { loader } from '@monaco-editor/react';
|
||||||
import { version } from '../version';
|
import * as monaco from 'monaco-editor';
|
||||||
|
|
||||||
export function init() {
|
export function init(version: string) {
|
||||||
// sentry监控 init
|
// sentry监控 init
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: 'https://3406424fb1dc4813a62d39e844a9d0ac@o1098464.ingest.sentry.io/6122818',
|
dsn: 'https://3406424fb1dc4813a62d39e844a9d0ac@o1098464.ingest.sentry.io/6122818',
|
||||||
@@ -27,9 +27,7 @@ export function init() {
|
|||||||
|
|
||||||
// monaco 编辑器配置cdn和locale
|
// monaco 编辑器配置cdn和locale
|
||||||
loader.config({
|
loader.config({
|
||||||
paths: {
|
monaco,
|
||||||
vs: 'https://cdn.staticfile.org/monaco-editor/0.33.0/min/vs',
|
|
||||||
},
|
|
||||||
'vs/nls': {
|
'vs/nls': {
|
||||||
availableLanguages: {
|
availableLanguages: {
|
||||||
'*': 'zh-cn',
|
'*': 'zh-cn',
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
export const version = '2.15.3';
|
|
||||||
export const changeLogLink = 'https://t.me/jiao_long/354';
|
|
||||||
export const changeLog = `2.15.3 版本说明
|
|
||||||
1. 任务视图增加标签筛选
|
|
||||||
2. 修改默认镜像python版本为3.10
|
|
||||||
3. 修复notify.js中智能微秘书,感谢 https://github.com/CoolClash
|
|
||||||
4. 修复超时任务日志打印
|
|
||||||
5. 修复调试脚本页保存脚本父目录
|
|
||||||
6. 其他优化
|
|
||||||
`;
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
version: 2.15.5
|
||||||
|
changeLogLink: https://t.me/jiao_long/356
|
||||||
|
changeLog: |
|
||||||
|
1. 修复定时任务命令不以task开头时,不能自动执行
|
||||||
|
2. 修改服务异常逻辑
|
||||||
|
3. 修复退出调试报错
|
||||||
|
4. 修复脚本编辑器加载慢
|
||||||
Reference in New Issue
Block a user