Compare commits

..
7 Commits
Author SHA1 Message Date
whyour e93cad91b4 更新版本 v2.15.4 2022-12-28 11:15:20 +08:00
whyour 08ee61b179 修改 pub 脚本 2022-12-28 11:14:57 +08:00
whyour 0ab756665e 修改版本文件 2022-12-28 11:06:47 +08:00
whyour 3570cddce0 关于增加更新日志查看 2022-12-27 17:10:21 +08:00
whyour a2d9f1a5db 脚本管理支持重命名文件/文件夹 2022-12-27 16:02:15 +08:00
whyour 0c6a214e55 修复启动public服务命令 2022-12-27 12:24:24 +08:00
whyour 095fcdbe69 修复任务脚本快捷跳转 2022-12-17 12:46:26 +08:00
24 changed files with 270 additions and 126 deletions
+27
View File
@@ -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
View File
@@ -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) {
+2 -2
View File
@@ -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 ⚠️");
+15
View 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 -6
View File
@@ -4,13 +4,11 @@ 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',
integrations: [ integrations: [
@@ -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());
+15 -15
View File
@@ -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) {
+3 -1
View File
@@ -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",
@@ -97,6 +98,7 @@
"@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",
+8 -2
View File
@@ -13,6 +13,7 @@ specifiers:
'@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
@@ -49,6 +50,7 @@ specifiers:
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
lint-staged: ^13.0.3 lint-staged: ^13.0.3
lodash: ^4.17.21 lodash: ^4.17.21
@@ -107,6 +109,7 @@ dependencies:
got: 11.8.5 got: 11.8.5
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
@@ -135,6 +138,7 @@ devDependencies:
'@types/cors': 2.8.12 '@types/cors': 2.8.12
'@types/express': 4.17.14 '@types/express': 4.17.14
'@types/express-jwt': 6.0.4 '@types/express-jwt': 6.0.4
'@types/js-yaml': 4.0.5
'@types/jsonwebtoken': 8.5.9 '@types/jsonwebtoken': 8.5.9
'@types/lodash': 4.14.185 '@types/lodash': 4.14.185
'@types/multer': 1.4.7 '@types/multer': 1.4.7
@@ -2630,6 +2634,10 @@ packages:
'@types/istanbul-lib-report': 3.0.0 '@types/istanbul-lib-report': 3.0.0
dev: true dev: true
/@types/js-yaml/4.0.5:
resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==}
dev: true
/@types/json-schema/7.0.11: /@types/json-schema/7.0.11:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true dev: true
@@ -3841,7 +3849,6 @@ packages:
/argparse/2.0.1: /argparse/2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
dev: true
/aria-hidden/1.2.1_jobltc72ducwijlk5r742icaw4: /aria-hidden/1.2.1_jobltc72ducwijlk5r742icaw4:
resolution: {integrity: sha512-PN344VAf9j1EAi+jyVHOJ8XidQdPVssGco39eNcsGdM4wcsILtxrKLkbuiMfLWYROK1FjRQasMWCBttrhjnr6A==} resolution: {integrity: sha512-PN344VAf9j1EAi+jyVHOJ8XidQdPVssGco39eNcsGdM4wcsILtxrKLkbuiMfLWYROK1FjRQasMWCBttrhjnr6A==}
@@ -7994,7 +8001,6 @@ packages:
hasBin: true hasBin: true
dependencies: dependencies:
argparse: 2.0.1 argparse: 2.0.1
dev: true
/jsbn/0.1.1: /jsbn/0.1.1:
resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==}
+2 -2
View File
@@ -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
View File
@@ -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"
-30
View File
@@ -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
@@ -266,7 +239,6 @@ update_qinglong() {
if [[ $exit_status -eq 0 ]]; then if [[ $exit_status -eq 0 ]]; then
echo -e "\n更新青龙源文件成功...\n" echo -e "\n更新青龙源文件成功...\n"
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 +263,6 @@ 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)
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 -1
View File
@@ -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;
+5 -4
View File
@@ -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>
+5 -16
View File
@@ -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]]);
} }
}; };
+6 -17
View File
@@ -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;
} }
}; };
+38 -3
View File
@@ -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>
); );
+79
View File
@@ -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;
+8
View File
@@ -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
+5 -7
View File
@@ -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,
}} }}
+4 -1
View File
@@ -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>
), ),
+29 -1
View File
@@ -198,7 +198,7 @@ export function getTableScroll({
if (tHeader) { if (tHeader) {
mainTop = tHeader.getBoundingClientRect().top; mainTop = tHeader.getBoundingClientRect().top;
} }
//窗体高度-表格内容顶部的高度-表格内容底部的高度 //窗体高度-表格内容顶部的高度-表格内容底部的高度
let height = document.body.clientHeight - mainTop - extraHeight; let height = document.body.clientHeight - mainTop - extraHeight;
return height; return height;
@@ -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];
}
}
+1 -2
View File
@@ -1,9 +1,8 @@
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';
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',
-10
View File
@@ -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. 其他优化
`;
+6
View File
@@ -0,0 +1,6 @@
version: 2.15.4
changeLogLink: https://t.me/jiao_long/355
changeLog: |
1. 脚本管理支持重命名文件/文件夹
2. 关于增加更新日志查看
3. 修复定时任务脚本识别和跳转