Compare commits

..
687 Commits
Author SHA1 Message Date
whyour 31a5386cfe 更新版本 v2.14.0 2022-09-03 15:26:04 +08:00
whyour 0198ebbbff 修复任务视图tab激活状态 2022-09-03 15:16:35 +08:00
whyour ce3df8704d 修复任务视图tab样式 2022-09-03 15:02:05 +08:00
whyour 1b4ca3a684 修复视图拖拽数据同步 2022-09-03 14:56:32 +08:00
whyour e31c2c0955 完善定时任务视图拖拽 2022-09-03 12:39:47 +08:00
whyour 4e389865b5 定时任务视图tab高亮 2022-09-03 11:58:21 +08:00
whyour e5d8adf955 修复定时任务视图排序 2022-09-03 02:15:32 +08:00
whyour 9a3181bc44 任务视图增加状态筛选 2022-09-03 01:56:47 +08:00
whyour 2f05c95422 完善定时任务视图 2022-09-02 17:52:06 +08:00
whyour bc5a3a2028 修复nginx配置文件 2022-09-02 11:28:08 +08:00
whyour 1fe91508f6 修复baseUrl 2022-09-02 10:54:44 +08:00
whyour b9253c8191 修复定时任务搜索api 2022-09-02 00:35:20 +08:00
whyour 7516acbc41 修复定时任务搜索 2022-08-31 00:02:35 +08:00
whyour 3b3d45da29 修复定时任务排序 2022-08-28 15:45:30 +08:00
whyour 918d68d140 修复布局样式 2022-08-28 00:44:17 +08:00
whyour a3f56be299 修改整体布局,减少无用间隔 2022-08-28 00:38:25 +08:00
whyour f8f63890e5 定时任务增加分组 2022-08-27 14:50:27 +08:00
whyour 9e997410ab 测试dockerfile 2022-08-26 18:42:25 +08:00
whyour 529880642f 修复typescript类型错误 2022-08-26 17:52:37 +08:00
景大侠andGitHub d2590edab3 支持非js任务随机延迟 (#1580)
* 修复非JS任务不能随机延迟的问题
2022-08-26 17:11:16 +08:00
whyour 32eec68278 更新版本 v2.13.9 2022-08-26 00:04:59 +08:00
whyour 05ed8c9f4b 修改定时任务列表布局 2022-08-25 23:58:20 +08:00
whyour bc281ee4d7 修改定时任务pageSize,增加全部展示为一页 2022-08-25 23:12:12 +08:00
whyour 3e88314d0a 修改二次验证交互 2022-08-23 22:38:03 +08:00
whyour 141defd845 修复定时任务默认pageSize 2022-08-20 23:21:31 +08:00
whyour b9e49b181a 修改定时任务排序 2022-08-20 20:27:19 +08:00
whyour 102e447f78 修改定时任务分页数据 2022-08-20 20:05:35 +08:00
whyour f3de8435f1 修改 favicon 2022-08-20 17:31:01 +08:00
whyour 06723407d2 更新版本 v2.13.8 2022-08-11 19:41:59 +08:00
whyour db8dfb32cf 修改生成系统token逻辑 2022-08-11 19:37:14 +08:00
whyour a7117e4442 修改内置token获取方式 2022-08-11 13:13:37 +08:00
whyour 53414e5d70 更新版本 v2.13.7 2022-07-31 15:58:34 +08:00
whyour 7bbc776759 修复环境变量转义 2022-07-31 15:35:13 +08:00
whyour f123c0bfe1 更新dockerfile 2022-07-31 15:26:08 +08:00
whyour 830865ff77 修改dockerfile 2022-07-31 15:22:17 +08:00
whyour 15fc6f975c repo命令后缀参数竖线分割 2022-07-31 15:09:32 +08:00
whyour 4582711867 修复搜索订阅 2022-07-31 15:00:27 +08:00
迷人的幽幽andGitHub 1eb64720d1 fix: 🐛 修复ts运行环境下发送企业微信通知错误问题 (#1553) 2022-07-24 13:33:44 +08:00
whyour 29f5134fb4 更新版本 v2.13.6 2022-07-18 19:52:37 +08:00
whyour 55ffd85d48 修改环境变量加载路径 2022-07-18 16:16:12 +08:00
whyour 075165f89d 修复ql_dir环境变量 2022-07-18 16:09:34 +08:00
whyour 42dabbf4c0 修复内置token获取 2022-07-18 16:06:04 +08:00
whyour 999b5d325f 修复初始化环境变量 2022-07-18 16:05:57 +08:00
whyour 027c3f584c 修改QL_DIR环境变量 2022-07-18 16:05:42 +08:00
whyour 2d3ebe6087 更新版本 v2.13.5 2022-07-17 22:29:25 +08:00
whyour f016e6734a 修复shell获取时间 2022-07-17 13:24:59 +08:00
whyour 9f0503f138 更新版本 v2.13.4 2022-07-16 12:31:50 +08:00
whyour f74821895e 修改npm镜像源 2022-07-16 12:25:14 +08:00
whyour 872519a213 修复获取环境变量详情 2022-07-16 12:16:33 +08:00
whyour dcd39ea6d8 移除内置token判断 2022-07-11 21:21:35 +08:00
whyour a4a76ec235 任务详情编辑脚本支持全屏 2022-07-04 22:44:51 +08:00
whyour 87a265191c 环境变量增加导出功能 2022-07-04 22:24:50 +08:00
whyour 6f4736bddf 脚本管理按文件类型排序 2022-06-26 22:25:21 +08:00
whyour e85742a293 修复mac和linux兼容性 2022-06-26 18:59:39 +08:00
whyour 28ea160004 修复手动停止任务耗时日志 2022-06-19 14:32:09 +08:00
whyour 99d881fc0d 修复系统token验证 2022-06-18 19:24:56 +08:00
whyour 5d6ac7f07c 修复系统token权限 2022-06-18 19:18:14 +08:00
whyour a23dbfe5e1 修复repo命令 2022-06-18 19:03:50 +08:00
whyour 2d818a19d1 修复repo命令 2022-06-18 19:00:22 +08:00
whyour be89fd4ff7 修改结束message 2022-06-18 12:45:38 +08:00
whyour 6d2edaaaf8 修改日期打印 2022-06-18 12:43:23 +08:00
whyour 19afbd06c0 Merge branch 'develop' of github.com:whyour/qinglong into develop 2022-06-18 12:30:11 +08:00
whyour 718964ab86 修复node kill进程逻辑 2022-06-18 12:30:02 +08:00
二毛andGitHub 0dadd4e6cc 区分原生termux和termux中安装的Linux发行版 (#1498)
检测termux时只看环境变量,以便于区分原生termux和termux中安装的Linux发行版
测试发现${ANDROID_RUNTIME_ROOT}${ANDROID_ROOT}在termux中安装的Linux发行版中仍旧存在,但是发行版中应该不用设置软连接吧
2022-06-14 22:55:49 +08:00
whyour 57e7d756cb 修改系统内部获取token方式 2022-06-14 22:43:18 +08:00
whyour 9f5fb30334 修改更新定时任务参数验证 2022-06-12 21:12:01 +08:00
whyour ec8c339f6e 移除notify软链命令 2022-06-10 21:23:43 +08:00
whyour 728d84cb15 修复根目录环境变量 2022-06-09 16:44:58 +08:00
whyour a7b5a0f4c5 修复shell本地开发兼容性 2022-06-09 12:10:41 +08:00
whyour b009d3e3fd 修复无法更新脚本内容为空 2022-06-08 23:57:22 +08:00
whyour 559defc8d6 修改默认pip源 2022-06-08 22:54:44 +08:00
whyour 98cc983473 更新版本 v2.13.3 2022-06-08 18:39:51 +08:00
whyour 6d42a680b1 移除console 2022-06-08 18:39:10 +08:00
whyour 1602f829ba 修改npm peer dependence策略 2022-06-08 11:19:54 +08:00
whyour c4a4764762 移除无用日志 2022-06-08 11:12:15 +08:00
whyour ac32117e78 修复调试脚本结束条件 2022-06-06 21:25:09 +08:00
whyour 3b5bcf8f16 修复调试脚本目录 2022-06-05 21:25:09 +08:00
whyour 8735321e34 修复调试脚本运行需要先保存 2022-06-05 19:26:32 +08:00
whyour be76ff4675 修复脚本管理和日志管理树字段 2022-06-05 18:44:35 +08:00
whyour 15678b029c 修复粘贴订阅私有仓库字段 2022-06-05 11:56:48 +08:00
whyour 05d47be1c8 修复创建脚本选择目录 2022-06-04 01:26:01 +08:00
whyour 106b34e33a 新建脚本支持文件上传 2022-06-04 00:12:26 +08:00
whyour 488fbd2bbb 更新版本 v2.13.2 2022-06-01 00:28:35 +08:00
whyour b82c630e3f 修改订阅执行逻辑 2022-06-01 00:25:32 +08:00
whyour 5a01c41bbb 修复私钥配置文件匹配 2022-06-01 00:03:58 +08:00
whyour 471e778a61 修改interval_schedule验证 2022-05-30 10:26:30 +08:00
whyour 68e576a5e3 修复订阅添加定时规则 2022-05-30 10:21:15 +08:00
whyour 8c74fca362 修复新建订阅定时 2022-05-30 09:58:51 +08:00
whyour 026fc43695 修复订阅任务创建逻辑 2022-05-29 22:51:36 +08:00
whyour 4f925b92d0 修复依赖安装顺序,优先linux依赖安装 2022-05-29 15:01:48 +08:00
whyour 4200d5e530 系统启动安装依赖优先安装linux依赖 2022-05-29 11:35:31 +08:00
whyour 32ca72133a 更新dockerfile 2022-05-29 11:13:32 +08:00
whyour 7708638267 修复dockerfile 2022-05-29 10:18:08 +08:00
whyour cb24d3c9fa 修复pnpm环境变量 2022-05-29 10:02:03 +08:00
whyour 07c63e285e 更新版本 v2.13.1 2022-05-29 01:50:46 +08:00
whyour a5e8d5cb80 修复pnpm版本兼容 2022-05-29 01:50:05 +08:00
whyour 8892e8833e 修复前端列表查询 2022-05-28 21:49:34 +08:00
whyour 25af639eef 修复依赖日志查看 2022-05-28 21:10:24 +08:00
whyour 58383e9022 修复依赖安装日志 2022-05-28 20:51:25 +08:00
whyour d244650432 修复订阅日志查看 2022-05-28 20:11:38 +08:00
whyour f338537ea0 修复系统启动执行订阅任务 2022-05-28 19:56:12 +08:00
whyour 8d2c62d6d1 依赖改为串行安装 2022-05-28 19:41:37 +08:00
whyour 8b1906c786 修改订阅日志排版 2022-05-28 19:04:46 +08:00
whyour 3f29c1aa4e 修复execSync卡死主进程 2022-05-28 18:55:52 +08:00
whyour 31ff0476a6 修复任务详情日志访问,修复新建订阅粘贴repo命令 2022-05-28 17:36:41 +08:00
whyour b3ed824b2e 修复七牛sdk参数 2022-05-28 00:11:43 +08:00
whyour ec97242415 更新版本 2.13.0 2022-05-28 00:08:48 +08:00
whyour d54faf8044 移除sentry上报信息可能出现的token 2022-05-27 23:47:17 +08:00
whyour 5f380b55d1 修改workflow 2022-05-26 15:12:45 +08:00
whyour 8d46115823 修复切换订阅interval 2022-05-26 14:41:22 +08:00
whyour cc24e51cd7 修改workflow 2022-05-26 00:25:31 +08:00
whyour 7a5e6dba51 更新readme 2022-05-26 00:23:25 +08:00
whyour ad1ee0d2ef 修复定时任务标签默认值 2022-05-26 00:18:04 +08:00
whyour e4f7461b62 修复安装依赖逻辑 2022-05-25 02:04:13 +08:00
whyour a9e0a934a7 修改linux依赖安装命令 2022-05-25 01:23:33 +08:00
whyour 2a23b838f6 修改node依赖安装命令 2022-05-25 01:20:23 +08:00
whyour 1afc791e0b 修改文本 2022-05-25 01:10:04 +08:00
whyour 05cf51afa0 新建订阅支持粘贴导入 2022-05-25 01:09:05 +08:00
whyour 76b6677f5a 修复日志获取 2022-05-24 10:00:42 +08:00
whyour af74afd10d 修复获取脚本列表 2022-05-23 19:22:20 +08:00
whyour 46512142fa 修复主题切换 2022-05-23 12:03:01 +08:00
whyour 6f13cc6e6f 修改订阅before/after执行时机 2022-05-22 22:14:32 +08:00
whyour 9dab6660d0 修改新建订阅文件后缀提示 2022-05-22 21:35:02 +08:00
whyour 89ddfd20cd 修复错误界面user判断 2022-05-22 21:28:30 +08:00
whyour c9bd27ec4f 修复登录跳转 2022-05-22 18:07:48 +08:00
whyour 2e937ad5ae 修改antd样式目录 2022-05-22 00:44:13 +08:00
whyour 39d3d53614 使用ssr-darkreader代替darkreader 2022-05-21 21:19:44 +08:00
whyour 456b4c4136 修复根组件刷新异常 2022-05-21 11:51:16 +08:00
whyour a90e536403 修复spawn监听事件 2022-05-21 00:47:05 +08:00
whyour d96206ef66 修改新建订阅字段提示 2022-05-21 00:12:12 +08:00
whyour ef71e13139 订阅同步表字段 2022-05-20 11:18:40 +08:00
whyour 189826c5db 订阅增加before/after 2022-05-20 01:15:45 +08:00
whyour bdecdce118 修复登录成功获取用户信息 2022-05-19 23:40:19 +08:00
whyour 460a1750f4 增加初始化订阅任务 2022-05-18 23:30:59 +08:00
whyour d2c2ea8d3b 修改生成ssh配置 2022-05-18 20:10:28 +08:00
whyour cb12d8ffec 修复删除私钥 2022-05-18 20:06:46 +08:00
whyour 5bb1afe5c6 修复私钥权限 2022-05-18 19:23:32 +08:00
whyour 7caabe9063 完善拉取私有仓库 2022-05-18 01:14:10 +08:00
whyour 1695b8c0fe 统一api错误提示 2022-05-17 23:31:29 +08:00
whyour 0927682abf 修复订阅执行日志输出 2022-05-17 22:27:41 +08:00
whyour cde4646fe1 修复环境变量PATH 2022-05-17 13:24:19 +08:00
whyour 8c410377b9 修改dockerfile 2022-05-17 12:38:58 +08:00
whyour 312aaf18de action安装pnpm 2022-05-17 12:18:29 +08:00
whyour ac2979381f 移除pnpm action缓存 2022-05-17 12:14:44 +08:00
whyour 03c5c7bbea pnpm action缓存 2022-05-17 12:13:26 +08:00
whyour 513de1a792 pnpm action缓存 2022-05-17 12:12:25 +08:00
whyour c6a8b93b70 pnpm action缓存 2022-05-17 12:10:59 +08:00
whyour 4c8e097448 删除pnpm缓存 2022-05-17 12:06:29 +08:00
whyour 4e7a5c637d 完善拉取公开仓库 2022-05-17 00:42:18 +08:00
whyour f1a51638a5 兼容antd Menu 2022-05-16 01:47:03 +08:00
whyour 9dcc547ac7 重构任务执行逻辑 2022-05-15 20:40:29 +08:00
whyour bb47d67d0b 修复编译错误 2022-05-15 15:35:52 +08:00
whyour f6a122e5ea 更新新建文件订阅 2022-05-15 15:25:23 +08:00
whyour 5523d537dc 完善订阅接口 2022-05-14 21:38:26 +08:00
whyour 5a21247cbb 完善新建订阅页面 2022-05-14 19:21:35 +08:00
whyour 419c5a7c5b 增加订阅api 2022-05-10 19:54:05 +08:00
whyour 468ff9c1af 修复编译错误 2022-05-10 14:42:25 +08:00
whyour ebb6290468 增加新建订阅 2022-05-10 14:38:06 +08:00
whyour 85d8565241 修复设置头像 2022-05-09 15:33:18 +08:00
whyour bd35725b3c 修复设置头像 2022-05-09 15:32:44 +08:00
whyour fb6a80e306 支持更换头像 2022-05-09 15:31:41 +08:00
whyour 2c9b283b75 增加ssh key操作service 2022-05-08 22:41:56 +08:00
whyour 0218405998 更新readme 2022-05-08 11:33:39 +08:00
whyour aa59e9a44b 修复用户名显示 2022-05-08 10:06:56 +08:00
whyour f9e2e22b85 添加订阅管理前端 2022-05-08 09:41:06 +08:00
whyour d27ca56a2d 修复生产环境安装依赖prepare失败 2022-05-03 18:44:00 +08:00
whyour bf40447ba4 修复npm prepare命令 2022-05-03 18:25:52 +08:00
whyour 89f7b99a94 修复pnpm全局安装 2022-05-03 18:07:12 +08:00
whyour c71d34cbac 修复peerDependence 2022-05-03 15:55:39 +08:00
whyour b97ad5cb6b 修复pnpm依赖 2022-05-03 15:43:15 +08:00
whyour 081793e1fb 修改action依赖 2022-05-03 15:06:28 +08:00
whyour f76b7d3b35 修改依赖 2022-05-03 14:59:48 +08:00
whyour 95448418c0 测试pnpm版本 2022-05-03 14:47:59 +08:00
whyour 199070f829 测试pnpm版本 2022-05-03 14:44:10 +08:00
whyour 1fa296a53e 添加依赖rc-tween-one 2022-05-01 14:20:00 +08:00
whyour caa8bd7e7f 修复pnpm版本 2022-05-01 14:15:29 +08:00
whyour 22d097aa08 修改定时任务添加标签交互 2022-05-01 13:51:33 +08:00
whyour 5dbfc65e7f 修复环境变量转义 2022-04-25 23:55:25 +08:00
whyour 03e6f18e54 修复获取最新version文件 2022-04-24 18:43:09 +08:00
whyour 84357e9dcc 更新版本 v2.12.2 2022-04-24 17:50:03 +08:00
whyour 8bb8a29400 任务详情增加运行、禁用、置顶操作 2022-04-24 17:45:50 +08:00
whyour 1b893f35ba 依赖管理增加直接强制删除 2022-04-23 15:02:54 +08:00
JerryWnandGitHub 5be421b4ea 修复双引号转义逻辑 #1331 (#1337) 2022-04-18 22:55:37 +08:00
whyour 8ad325686d 修复定时删除日志设置,主持设置大于24天 2022-04-18 22:29:14 +08:00
whyour 54e58c1e08 修复shell发送通知 2022-04-09 11:13:08 +08:00
whyour 8818a779b5 修复并发日志目录 2022-04-06 12:18:16 +08:00
whyour cc65b6c8bb 更新readme 2022-04-05 23:24:27 +08:00
whyour 523f583516 更新发布脚本 2022-04-05 23:17:03 +08:00
whyour 89539deff1 更新发布脚本 2022-04-05 23:14:47 +08:00
whyour 6907d520ed 增加发布脚本 2022-04-05 23:11:48 +08:00
whyour eae2d6d17a 修复日志目录判断 2022-04-05 00:28:14 +08:00
whyour 82dd9114fd 修复日志目录获取 2022-04-05 00:21:32 +08:00
whyour 58e047fe0e 修复task日志打印 2022-04-04 23:32:27 +08:00
whyour d703769d43 修复日志创建目录 2022-04-04 22:40:02 +08:00
whyour be6ca54d9c 修复目录获取 2022-04-04 18:06:21 +08:00
whyour a5cadfbcab 修改版本说明 2022-04-04 17:28:15 +08:00
whyour 5e3be8fcd1 更新版本号 v2.12.1 2022-04-04 17:24:42 +08:00
whyour 8b5347dd44 修改获取版本链接 2022-04-04 17:17:34 +08:00
whyour 24f6070f9e 修复shell日志路径截取 2022-04-03 22:31:11 +08:00
whyour d00f2cd33b 修改yarn命令 2022-04-03 21:49:20 +08:00
whyour 8276a19012 修复定时任务获取日志 2022-04-03 20:00:31 +08:00
whyour c769c598b8 删除console 2022-04-03 19:50:44 +08:00
whyour d687e79b6c 修复日志路径拼接规则 2022-04-03 19:32:44 +08:00
whyour 8fac58d73f 七牛云改为http访问 2022-04-01 08:29:16 +08:00
whyour b2ba86b212 修复获取日志相对目录 2022-03-30 11:34:36 +08:00
whyour 92673b4a17 修改日志路径合并方式 2022-03-30 11:17:11 +08:00
whyour 85facc039a 修复获取任务日志路径 2022-03-30 10:57:43 +08:00
kan3GitandGitHub 24f33431de tg发送消息改json (#1269)
发现有带&的消息都会漏掉&后面的内容,改为JSON发送
2022-03-26 17:23:54 +08:00
chen310andGitHub 36533ab7e2 修复PushDeer推送Python版本错误 (#1270)
* 修复PushDeer推送Python版本错误
2022-03-26 17:20:02 +08:00
whyour 97d170c8b1 修复系统pushDeer通知 2022-03-26 17:17:13 +08:00
whyour 1413999ac3 修改任务最大内存占用200兆 2022-03-26 16:53:03 +08:00
whyour 0d23f8a962 修复定时任务获取日志目录 2022-03-26 00:29:42 +08:00
whyour d0b13a04cc 更新版本 v2.12.0 2022-03-24 08:47:35 +08:00
whyour fca7b02656 修复bot依赖安装目录 2022-03-19 16:44:00 +08:00
whyour 2afbfa08c6 更新bot.sh 2022-03-17 23:45:37 +08:00
whyour 5fe63666b0 修复任务详情展示脚本逻辑 2022-03-13 13:21:00 +08:00
whyour 9ee08acf29 修复任务详情交互样式 2022-03-13 11:09:31 +08:00
whyour bc7f406968 修复定时任务名称标签显示 2022-03-13 00:06:24 +08:00
whyour 00ee90792f 修复系统更新时间弹窗 2022-03-13 00:03:58 +08:00
whyour 4a8c90365e 修复任务列表名称标签提示 2022-03-12 23:57:38 +08:00
whyour d0e46224dc 修复定时任务详情日志查看 2022-03-12 23:49:09 +08:00
whyour 54dd90e7c5 修复退出登录样式 2022-03-12 18:15:42 +08:00
whyour 7ef3de1367 修复退出登录和定时任务搜索 2022-03-12 16:14:18 +08:00
whyour ce6aa23d5b 修改退出登录样式 2022-03-10 23:58:57 +08:00
whyour dc819bcf8e 修改退出登录样式 2022-03-10 23:53:39 +08:00
whyour b0595656bb 修复编辑应用初始值 2022-03-10 22:37:44 +08:00
whyour 1dac54b7e7 添加podman安装 2022-03-10 00:29:28 +08:00
whyour 95ea085e1e 修复任务详情样式 2022-03-09 23:40:59 +08:00
whyour ec5f6d20cb 修复任务详情手机样式 2022-03-09 22:32:50 +08:00
whyour aa288c89b3 修复dockerfile 2022-03-09 00:24:36 +08:00
whyour ea419a98ae 修复dockerfile 2022-03-09 00:17:33 +08:00
whyour 0f0c79f52f 任务详情脚本增加编辑功能 2022-03-08 00:11:37 +08:00
whyour 38df66a5b1 修复任务详情样式 2022-03-07 23:56:15 +08:00
whyour 0051260171 修改更新青龙日志 2022-03-07 23:47:20 +08:00
whyour f6dc0e020f 修改重置分支shell 2022-03-07 23:40:49 +08:00
whyour 6f2e7b9459 更新git pull参数 2022-03-07 23:27:16 +08:00
whyour 384e3f09da 修复编译静态资源目录 2022-03-07 23:21:18 +08:00
whyour 747f4552e0 修改shell根目录获取,移除notify.sh 2022-03-07 23:16:36 +08:00
whyour 1db13653bf 修复更新青龙命令 2022-03-07 22:33:15 +08:00
whyour d05304f839 修复重置repo地址命令 2022-03-07 22:31:46 +08:00
whyour 0b551ddc0d 修复更新青龙命令 2022-03-07 22:27:04 +08:00
whyour f117f6b39c 修复shell更新命令 2022-03-06 11:27:48 +08:00
whyour 0c54ca95d9 修改定时任务初始化查询条件 2022-03-06 00:07:59 +08:00
whyour bcbc8a633f 更新版本拷贝2.11.3以前版本数据到data目录 2022-03-06 00:07:08 +08:00
whyour 73963c89fc 自动更新定时任务2.11.3以前脚本路径 2022-03-06 00:01:03 +08:00
whyour 793d14f04b 修改任务详情样式 2022-03-05 20:52:21 +08:00
whyour ef32181f36 移除s390x支持,修改sqlite3包 2022-03-05 14:39:06 +08:00
whyour 419f59818b 修改sqlite3包 2022-03-05 13:42:45 +08:00
whyour 2bda3a276a 修改sqlite3包 2022-03-05 12:57:48 +08:00
whyour 5e5b28ef29 修改 sqlite3 依赖 2022-03-05 12:18:30 +08:00
whyour 8dfbe93135 修改update对应分支 2022-03-05 11:46:21 +08:00
whyour 810c6ca76c 任务详情添加日志列表,更新依赖 2022-03-05 01:24:52 +08:00
whyour 912b4ddb76 logo使用七牛云 2022-03-04 00:15:42 +08:00
whyour 6b41df3497 增加任务详情 2022-03-04 00:08:33 +08:00
NekoMioandGitHub 3e62fedc73 增加PushDeer推送 #1161 (#1194)
* 增加PushDeer推送
2022-03-03 18:42:11 +08:00
whyour a10ffef38f 修复task修改环境变量时机 2022-03-02 21:37:45 +08:00
whyour 816ce31279 修改openapi获取token逻辑 2022-02-28 22:30:58 +08:00
whyour 4e58a640e4 修改error页面样式 2022-02-26 14:24:23 +08:00
whyour 43925d6630 修复error页面样式 2022-02-26 14:16:51 +08:00
whyour b688461137 修改错误日志页面 2022-02-26 14:14:10 +08:00
whyour 1e85528889 修改默认跳转 2022-02-26 00:13:52 +08:00
whyour eb4976ed6b 添加系统错误页面 2022-02-26 00:12:12 +08:00
whyour 7dd87220ab 修改public服务 2022-02-25 23:06:35 +08:00
whyour 9927461ea9 修复public服务 2022-02-25 22:53:24 +08:00
whyour 84c7ffc509 增加2.10-2.11升级代码 2022-02-25 22:49:17 +08:00
whyour ee52f09bcb 增加public服务,查询panel日志 2022-02-24 23:50:04 +08:00
whyour 531fc48bdf 修改pane样式 2022-02-24 23:24:21 +08:00
whyour 0581a7ab10 更新git pull参数 2022-02-24 22:36:49 +08:00
whyour f0d2d57584 修复git reset 2022-02-24 22:22:33 +08:00
whyour cb154437ac 增加使用系统通知配置发送通知api 2022-02-24 22:10:03 +08:00
whyour 7ea9a8aa57 修改编辑器cdn 2022-02-20 21:36:01 +08:00
whyour 7e34f4990d 修复sql查询 2022-02-20 10:15:17 +08:00
whyour f0f2c6b537 修改杀死进程逻辑 2022-02-19 22:54:27 +08:00
whyour f71b3d0378 版本文件改为七牛云存储 2022-02-19 22:48:06 +08:00
whyour 7d6e1d3e3d 修复定时删除日志 2022-02-19 17:36:49 +08:00
whyour cf5f1b6f25 调整数据目录 2022-02-19 13:08:14 +08:00
whyour 903007e6ca 修复删除环境变量无效 2022-02-17 11:14:43 +08:00
whyour d6614acf66 更新版本 v2.11.3 2022-02-16 23:09:33 +08:00
whyour 86df495b67 修改check命令,安装依赖使用代理
修改apk源地址
2022-02-16 23:06:12 +08:00
whyour 71b0c10997 修改logo地址 2022-02-16 22:56:54 +08:00
whyour e273c59d78 移除esbuild 2022-02-11 13:52:12 +08:00
whyour baea40f157 环境变量增加更新时间排序 2022-02-11 13:41:48 +08:00
Ghost LeeandGitHub a809b89bb0 修复goCqHttpBotToken中存在&字符导致认证失败的问题 (#993)
修改goCqHttpBot鉴权方式,调整为使用Header模式
2022-02-08 18:45:24 +08:00
UkennandGitHub 89e08953cb bark支持自定义推送图标 (#1146)
* bark添加自定义推送图标

* 脚本bark通知添加自定义推送图标
2022-02-08 18:21:37 +08:00
whyour e154cf65b6 修复定时任务排序 2022-02-01 22:33:18 +08:00
whyour d7162a24be 修复.env 2022-01-30 20:22:00 +08:00
whyour 16532720d7 更新版本 v2.11.2 2022-01-30 18:25:18 +08:00
whyour 012ee2419d 修改placeholder 2022-01-30 17:39:27 +08:00
whyour 7e4fe2f1b3 更新dockerfile 2022-01-30 14:59:41 +08:00
whyour 5a95b72607 修改dockerfile 2022-01-30 14:03:57 +08:00
whyour 4d27055605 修改base context 2022-01-30 12:37:20 +08:00
whyour e2b9578253 修改dockerfile 2022-01-30 12:31:49 +08:00
whyour 57fd8f378c 修改定时任务唯一键 2022-01-30 10:11:47 +08:00
whyour a726f224f8 修改定时任务排序 2022-01-30 09:59:33 +08:00
whyour d502ac6e0f 修改dockerfile 2022-01-29 14:23:19 +08:00
whyour f6a828f0af 修改dockerfile 2022-01-29 12:53:21 +08:00
whyour 0a53a7001e 修改dockerfile 2022-01-29 12:15:25 +08:00
whyour 0ab5494744 修复dockerfile 2022-01-29 11:45:06 +08:00
whyour e60cf15f39 更新dockerfile 2022-01-29 11:20:43 +08:00
whyour b53db97b98 修改dockerfile 2022-01-29 00:09:37 +08:00
whyour f12c75f91b 移除prepare scripts 2022-01-28 23:46:17 +08:00
whyour 3df1a14f72 修改sentry依赖 2022-01-28 23:19:39 +08:00
whyour 712f105d34 修改sentry cli版本 2022-01-28 23:14:12 +08:00
whyour 4ea757fece 修改sentry依赖 2022-01-28 22:47:46 +08:00
whyour d8b32f63ea 增加esbuild 2022-01-28 21:53:05 +08:00
whyour 771b81afaf 修改dockfile 2022-01-28 20:54:00 +08:00
whyour a4f4c6ac15 修改dockerfile 2022-01-28 20:27:38 +08:00
whyour ae22cf064e 修复dockerfile 2022-01-28 19:33:01 +08:00
whyour fbb8e1bd31 修改dockerfile 2022-01-28 19:23:57 +08:00
whyour 47202ef66c 修改base镜像 2022-01-27 23:09:29 +08:00
whyour f46c70dcbd 测试青龙基础镜像 2022-01-27 23:06:35 +08:00
whyour c7a837e33d 测试最新alpine 2022-01-27 22:21:34 +08:00
whyour 544e2e8331 修复获取克隆仓库状态 2022-01-27 22:15:45 +08:00
whyour a62ccbc0db 去除sql执行日志 2022-01-27 00:56:37 +08:00
whyour 891619ad55 修改表唯一约束 2022-01-27 00:49:44 +08:00
whyour a3a62f65dd python默认实时输出 2022-01-25 23:03:56 +08:00
whyour 3131f197b8 修复修改任务状态本地免认证 2022-01-25 22:52:27 +08:00
whyour e13d9ed51b 添加关于页 2022-01-24 23:17:12 +08:00
whyour 1b5f4a0f9d 修复日志加载状态 2022-01-24 22:50:01 +08:00
whyour c9c4043a4e 修改logo地址 2022-01-23 16:55:11 +08:00
whyour bb59dd977a 修改node基础镜像版本 2022-01-22 22:09:16 +08:00
whyour c2b213a37c 测试基础镜像node版本 2022-01-22 21:57:02 +08:00
whyour 559f4418d1 修改darkreader版本 2022-01-22 21:09:01 +08:00
whyour 2149bca7b8 增加sqlite连接池 2022-01-22 20:53:22 +08:00
whyour be5db82b79 移除cdn依赖 2022-01-22 20:25:22 +08:00
whyour 01163e3ea3 定时任务默认改为创建时间倒序 2022-01-21 22:38:13 +08:00
whyour 7f986fdb2d 修复更新系统通知 2022-01-21 22:27:50 +08:00
whyour 38e1ba67af 修复sqlite加载顺序 2022-01-20 22:19:39 +08:00
whyour d3d2e0887e 更新版本 v2.11.1 2022-01-20 19:11:41 +08:00
whyour a2650715b3 修改cdn地址,增加重启服务延迟 2022-01-20 19:02:29 +08:00
whyour 8c0685202e 移除默认github代理,增加http或者socks代理参数 2022-01-19 23:29:55 +08:00
whyour 5b6843b5c0 定时任务默认倒序 2022-01-18 23:50:18 +08:00
whyour ba341b69ac 修改迁移数据库逻辑 2022-01-18 22:56:12 +08:00
whyour ca89f03724 修复环境变量更新时间显示 2022-01-17 22:47:23 +08:00
whyour 5461325e69 修复重置应用秘钥 2022-01-16 18:55:18 +08:00
whyour e96a2818b7 修复停止任务后任务状态修改 2022-01-14 23:44:22 +08:00
whyour 355f06a3cf 修改数据迁移逻辑 2022-01-14 23:17:16 +08:00
whyour f6e6cfa71c 修复环境变量搜索 2022-01-13 22:38:20 +08:00
ChiupamandGitHub 9cdfa2ef79 支持运行pyc格式文件 (#1072) 2022-01-13 14:39:49 +08:00
whyour 6fab22099d 修复更新定时任务 2022-01-13 14:37:56 +08:00
whyour 925819b3aa 修复环境变量搜索 2022-01-13 13:15:00 +08:00
whyour 6f96900ab9 修复openapi token 查询 2022-01-13 12:02:36 +08:00
whyour 43bc29b25c 修复openapi认证,环境变量排序 2022-01-13 11:13:04 +08:00
whyour e13ff86ff6 更新版本 v2.11.0 2022-01-12 23:43:32 +08:00
whyour 3f3d572932 更新task命令获取时间戳 2022-01-12 23:39:40 +08:00
whyour a0ce1562ac 更新dockerfile 2022-01-12 00:55:14 +08:00
whyour ee84cbca51 修复dockerfile 2022-01-11 00:44:22 +08:00
whyour 3ed5cb48b5 修复dockerfile 2022-01-11 00:35:30 +08:00
whyour 499ba5fb99 修复dockerfile 2022-01-10 23:35:47 +08:00
whyour c2f036c370 修复dockerfile 2022-01-10 23:17:33 +08:00
whyour 326580b3c8 测试编译sqlite 2022-01-10 23:10:59 +08:00
whyour bf59351cf9 测试nodejs14 sqlite 2022-01-09 23:17:50 +08:00
whyour a954fc3942 测试打包镜像时间 2022-01-09 12:38:20 +08:00
whyour 160a66af23 更新dockerfile 2022-01-08 23:56:45 +08:00
whyour 4edbd29b19 测试arm/v7 2022-01-08 23:29:20 +08:00
whyour b6b97478d1 修改dockerfile 2022-01-08 22:49:17 +08:00
whyour 27a871e537 更新node gyp 2022-01-08 22:19:21 +08:00
whyour 738b0f9a4a 修改node gyp版本 2022-01-08 21:48:32 +08:00
whyour 1a56dd8ac6 添加node-pre-gyp依赖 2022-01-08 21:12:38 +08:00
whyour 672d440725 修复方法名称 2022-01-08 01:26:34 +08:00
whyour 3625b7c0de 更新dockerfile 2022-01-08 01:10:10 +08:00
whyour e75a683173 修复sqlite数据操作类型 2022-01-08 01:09:38 +08:00
kilo5hzandGitHub 89ed8527d6 添加标签功能 (#1026)
* 添加标签功能
2022-01-07 22:01:13 +08:00
whyour 5a5f4b8065 dockerfile使用yarn install 2022-01-07 00:18:12 +08:00
whyour d2e23341eb 修改sqlite版本 2022-01-06 23:25:48 +08:00
whyour 233081149b 修复param类型 2022-01-06 22:59:29 +08:00
whyour 5d19ee0ab5 使用sqlite替换nedb 2022-01-06 22:51:12 +08:00
ChiupamandGitHub 653b1cef20 支持自定义BOT仓库 (#1051) 2022-01-04 13:26:16 +08:00
whyour f3ecf6e7a0 更新版本 v2.10.13 2021-12-25 21:31:52 +08:00
whyour 35d8f0a7e8 添加sentry-cli cdn 2021-12-25 20:55:13 +08:00
whyour e863dd322a 修改热更新目录 2021-12-25 20:13:49 +08:00
whyour fa6dec969f 修改两步验证界面逻辑 2021-12-25 20:09:02 +08:00
whyour 58cd05fd09 修改批量运行任务逻辑 2021-12-24 23:31:11 +08:00
whyour 2e6ca5419d 调试脚本增加停止,修复调试交互 2021-12-23 23:25:39 +08:00
whyour a29392bb08 修改文本 2021-12-23 13:39:16 +08:00
whyour 4dc9c9aa75 优化初始化通知设置交互 2021-12-21 23:49:52 +08:00
whyour 03c9f79549 修复登录退出接口路径 2021-12-21 23:42:05 +08:00
whyour 46aaeb4eac 增加系统openapi 2021-12-21 23:22:34 +08:00
whyour 4d20bb0de6 system接口增加版本号,应用权限增加依赖管理 2021-12-21 22:53:19 +08:00
琚涛涛andGitHub e001686627 优化定时任务名称展示效果,提升用户体验 (#1017) 2021-12-21 22:39:17 +08:00
whyour 931f02ef8e 修复任务跳转脚本参数,task运行脚本支持更多参数 2021-12-21 00:23:20 +08:00
whyour a9be3e6376 更新版本号 v2.10.12 2021-12-09 23:24:10 +08:00
whyour aedd369fc4 修复darkreader最新版bug 2021-12-09 23:22:17 +08:00
whyour a1016b7f22 更新readme 2021-12-06 23:42:14 +08:00
whyour 603432aa87 修复logo样式 2021-12-06 11:18:19 +08:00
whyour cd23967ae0 更新版本号 v2.10.11 2021-12-05 23:15:49 +08:00
whyour dfb159c4ad 修复搜索环境变量urlencode 2021-12-05 23:04:26 +08:00
whyour 0318f03283 修复内容区高度和logo样式 2021-11-29 23:02:42 +08:00
whyour 9ddf6986e4 修复最后运行时长被覆盖 2021-11-29 21:49:32 +08:00
雪狐andGitHub a28ed9281e 避免环境变量中出现"字符的时候提前截断字符串 (#966) 2021-11-29 21:43:29 +08:00
whyour 9cc776aded 更新版本 v2.10.10 2021-11-27 20:43:18 +08:00
colinxuandGitHub 349f14a7fc 修复使用旧pushplus推送后结果解析失败的问题 (#958) 2021-11-27 20:33:49 +08:00
whyour fae26efb88 检测更新增加强制更新 2021-11-27 20:19:49 +08:00
whyour 64d0bb18b1 依赖目录通知文件没有时,拷贝示例的通知文件 2021-11-27 17:41:26 +08:00
whyour 864ce071f6 修复deps映射目录 2021-11-27 17:19:50 +08:00
whyour 5a55934029 修复build args 2021-11-27 17:12:45 +08:00
whyour 1f061ad059 测试yml 2021-11-27 17:05:55 +08:00
whyour 32f0774df2 添加deps目录软链 2021-11-27 16:20:24 +08:00
whyour 3206ff6a1e 修复删除JavaScript进程,修改依赖重新安装逻辑 2021-11-27 13:41:38 +08:00
whyour 2f5e946979 修复调试运行时文件路径 2021-11-23 23:36:43 +08:00
phoenixandGitHub 5a6d3739d4 fix nginx can not run without /run/nginx (#944)
alpine3.12 和 alpine3.14 兼容性问题
2021-11-23 18:32:22 +08:00
whyour fa018b84a7 更新版本 v2.10.9 2021-11-22 22:27:15 +08:00
whyour a686206e21 默认拷贝deps目录下所有文件 2021-11-22 22:20:13 +08:00
风之凌殇andGitHub 6132d13261 表格选择pageSize增加200/500/1000 (#934) 2021-11-22 19:26:02 +08:00
kilo5hzandGitHub f64c78919d 系统通知增加gotify (#936) 2021-11-22 19:23:40 +08:00
whyour 1a1067966c 修改docker基础镜像为alpine3.12,解决arm32无法启动 2021-11-22 19:22:39 +08:00
whyour ec545cabcd 更新readme 2021-11-21 22:26:22 +08:00
whyour 40b0e90c0d 修复调试功能 2021-11-20 01:06:25 +08:00
kilo5hzandwhyour 5e94be21aa 点击任务名跳转脚本管理编辑脚本 (#929)
* 增加捷径:点击任务名跳转脚本管理编辑脚本

* 修正树节点选中,父节点自动展开
2021-11-20 01:06:25 +08:00
whyour 417ae8bbde 更新readme图片链接 2021-11-20 01:06:25 +08:00
whyour acde7ebef6 更新readme 2021-11-20 01:06:25 +08:00
whyour a424e8df7c 更新readme 2021-11-20 01:06:25 +08:00
whyour 2eed5cba14 增加初始化依赖目录及文件 2021-11-20 01:06:25 +08:00
风之凌殇andwhyour 19e7c238f6 移除dockerfile中不必要的环境变量 (#925)
* 移除上次pr新增的不必要的环境变量
2021-11-20 01:06:16 +08:00
whyour f3c939e442 更新readme 2021-11-17 19:22:40 +08:00
whyour 3bcf3431d3 修复拷贝deps已存在依赖 2021-11-17 10:23:45 +08:00
whyour 20d6308bc7 更新版本 v2.10.8 2021-11-16 14:36:13 +08:00
whyour 96b0127811 修复脚本管理删除、新建增加选择父目录 2021-11-16 14:18:02 +08:00
kilo5hzandGitHub 68cc792d6c 增加gotify推送 (#905)
* 增加gotify推送
2021-11-15 23:04:55 +08:00
whyour d0f7449b57 修复拷贝deps 2021-11-13 22:05:54 +08:00
whyour b41c1f1d2e 修复dep目录 2021-11-13 21:54:45 +08:00
whyour 9a8b72fe3f 更新版本 v2.10.7 2021-11-13 21:21:35 +08:00
whyour b16f8b96dc 修复bot安装依赖判断 2021-11-13 20:58:27 +08:00
whyour 128429a6a9 修改拷贝依赖 2021-11-13 19:05:34 +08:00
whyour b849d8b526 修复脚本管理搜索 2021-11-13 17:42:06 +08:00
whyour 47b06c3487 外部依赖改为系统直接分发通知文件js和py版 2021-11-13 17:21:27 +08:00
whyour 40e0d143f4 修复环境变量表格样式 2021-11-13 16:44:21 +08:00
whyour c1269d9dde 修复schedule服务 2021-11-13 12:34:49 +08:00
whyour 6a81ba9069 修复脚本管理树结构 2021-11-13 11:14:43 +08:00
whyour 61883776f2 更新版本 v2.10.6 2021-11-13 00:03:12 +08:00
whyour f7752cec3a 修复检查更新 2021-11-12 23:55:57 +08:00
whyour 6f96c84a16 修复定时匹配 2021-11-12 23:42:11 +08:00
whyour a2ce3304de 修复脚本管理数据 2021-11-12 23:18:24 +08:00
whyour ee0b47d101 修改workflow变量 2021-11-12 22:09:02 +08:00
MiniersandGitHub 3b3dd4c26d 增加环境变量更新时间 (#887) 2021-11-12 21:55:03 +08:00
风之凌殇andGitHub bee1f9968e 增加在添加语义化版本tag时自动生成static并构建多个不同tag的容器的workflow,简化版本发布流程 (#888) 2021-11-12 21:54:42 +08:00
whyour 65691cf7c9 更新版本号 v2.10.5 2021-11-12 14:23:16 +08:00
whyour 7b7bcf600d 修复文件判断引起服务异常 2021-11-12 14:22:21 +08:00
whyour 18cd7bfa6f 更新版本号 2021-11-12 13:18:32 +08:00
whyour 8c14a31967 修复entrypoint启动失败 2021-11-12 13:16:53 +08:00
whyour 6c840b435e 修改版本说明序号 2021-11-11 23:04:29 +08:00
whyour eca615c066 更新版本号 v2.10.3 2021-11-11 23:03:28 +08:00
whyour 22bb785139 服务重启时依赖改为单独安装,避免同时失败 2021-11-11 22:56:47 +08:00
whyour 62fca3dfe2 修复获取日志判断 2021-11-11 22:50:28 +08:00
whyour 9936097086 单个数据库文件大于500兆时,裁剪文件,避免服务无法启动 2021-11-11 22:41:12 +08:00
whyour 50dd235e39 脚本管理改成树结构 2021-11-11 00:10:05 +08:00
whyour 606bac2e43 修复pushplus通知设置 2021-11-10 23:33:19 +08:00
whyour 90ede97797 修复依赖文件获取 2021-11-10 23:26:21 +08:00
whyour 1545cde58a 修复repo依赖路径 2021-11-10 10:35:53 +08:00
whyour 8481d81257 修复shell语法 2021-11-09 23:55:50 +08:00
whyour 0688d4f318 修复repo文件比对 2021-11-09 23:23:07 +08:00
whyour ab5ab951d7 修改ql repo逻辑 2021-11-09 23:07:43 +08:00
whyour 0ca640acbd 添加动态引入 2021-11-09 22:34:24 +08:00
whyour 235f99c0ca 修改退出下拉样式 2021-11-08 22:56:53 +08:00
whyour 31fdf7f12c 修改数据库加载 2021-11-08 22:47:46 +08:00
whyour bd6d711212 修复centos -x兼容性 2021-11-07 21:54:31 +08:00
whyour 62596f27ef 回退shell [ 2021-11-07 19:18:36 +08:00
whyour 1b88853a8d 修改pnpm安装源 2021-11-07 12:51:10 +08:00
whyour 3c5925ff67 修复entrypoint 2021-11-07 11:25:18 +08:00
whyour 7d5687d504 修复 arm 没有nginx conf.d 目录 2021-11-07 11:08:21 +08:00
OreoandGitHub 6b74cccc34 更新 Python 通知文件 (#832)
已在 Python3.6.x(云函数平台),3.8.x,3.10.0 测试通过
特性如下
1. 支持 11 种推送方式,未来还会继续添加
2. 多线程通知
3. 支持一言(一句话)和控制台输出开关
4. 对于现有推送方式,补充了配置变量
2021-11-06 20:45:53 +08:00
whyour 1becb26aaf 更新dockerfile 2021-11-06 20:19:07 +08:00
whyour bd2917822e 修改dockerfile 2021-11-06 19:04:01 +08:00
whyour 9c82db4471 更新docker yml 2021-11-06 18:58:18 +08:00
whyour e4f11156f6 修复任务日志结束时间展示 2021-11-06 00:50:44 +08:00
whyour c7de19d3b5 修复toLocaleString 0点显示24点 2021-11-05 23:56:16 +08:00
whyour 65ade4b996 修改shell语法 2021-11-05 23:39:22 +08:00
whyour 2b70851074 修复移动端展开导航 2021-11-04 19:24:55 +08:00
whyour b7b983229b 更新版本号 v2.10.2 2021-11-04 18:10:43 +08:00
whyour ac548433c0 修复基础镜像pip3依赖 2021-11-04 18:10:14 +08:00
whyour 02a1afb477 修复openapi路由 2021-11-04 18:07:34 +08:00
whyour e959466898 更新版本号 v2.10.1 2021-11-04 15:09:07 +08:00
whyour 5c5aef64b0 添加白屏时间及错误上报 2021-11-04 15:05:34 +08:00
whyour 86e8b1383c 修复check命令 2021-11-03 16:35:18 +08:00
whyour 9f7bf1f6b8 修改失败任务日志 2021-11-01 23:06:23 +08:00
whyour 2ff3869608 shell 中 [ 改为 [[ 2021-11-01 23:04:10 +08:00
whyour 8d21401f88 移动退出登录位置 2021-11-01 15:23:06 +08:00
whyour b92daedea2 修复表格滚动高度 2021-10-31 19:45:51 +08:00
whyour 021d89379a 修复无法覆盖安装依赖 2021-10-31 17:34:19 +08:00
2b61204233 修改错误文字 (#809)
Co-authored-by: 14K <9112790+the14k@user.noreply.gitee.com>
2021-10-29 23:26:44 +08:00
whyour 1da1659b51 支持服务端端口直接访问前端 2021-10-28 23:35:21 +08:00
whyour 1302a6daa0 修复置顶后编辑置顶消失 2021-10-28 22:38:34 +08:00
whyour 160dbe780c 更新版本号 v2.10.0 2021-10-24 17:51:20 +08:00
whyour d047cf0d77 修复python移除依赖命令 2021-10-24 17:19:14 +08:00
whyour 0c2a2af308 添加依赖管理 2021-10-24 17:07:16 +08:00
whyour bd8953ea81 依赖安装日志增加安装时间 2021-10-24 11:11:23 +08:00
whyour a479f0fbce 修复表格初次获取滚动高度 2021-10-23 23:44:11 +08:00
whyour 2e00e80e96 由于浏览器兼容性,移除dynamicImport 2021-10-23 23:31:46 +08:00
whyour 0bf4aec74e 修复python3任务无法停止 2021-10-23 23:22:08 +08:00
whyour bcac3deb6e 修复service引入路径 2021-10-23 18:46:24 +08:00
whyour 596f93623e 初始化安装所有依赖 2021-10-23 18:44:25 +08:00
whyour c74a1bc20c 修改依赖安装结束标识 2021-10-23 18:27:20 +08:00
whyour 795d1b938d 添加依赖管理 2021-10-23 18:23:32 +08:00
whyour bad247c51c 两步验证输入框自动聚焦 2021-10-21 22:25:15 +08:00
whyour 950aed3522 更新版本号 v2.9.10 2021-10-21 21:59:24 +08:00
longjuanandGitHub 8a7bd53f24 更新install.md (#789) 2021-10-21 21:55:26 +08:00
whyour 04230cbd1c 修改websocket日志 2021-10-21 21:52:06 +08:00
whyour d0a7e67138 修复table滚动高度 2021-10-21 19:13:38 +08:00
whyour 56517fd345 更新monaco 2021-10-19 22:41:59 +08:00
whyour 6315425a7b 检查密码默认值 2021-10-19 21:46:24 +08:00
whyour c42299766c 修复tg系统通知认证参数 2021-10-18 19:18:46 +08:00
whyour ef9fa02145 修复检查更新日志展示 2021-10-18 17:05:38 +08:00
whyour 51869b6a84 修复shell变量重复 2021-10-16 19:32:23 +08:00
whyour d12953a345 升级umi 2021-10-16 17:21:19 +08:00
whyour 9f727cd54b 添加 yarn.lock 2021-10-16 17:07:05 +08:00
whyour 3de567191e 更新版本号 v2.9.9 2021-10-16 15:19:53 +08:00
whyour b212762417 脚本管理移动端支持添加脚本、编辑脚本 2021-10-16 15:11:23 +08:00
hanhh d5f619ff74 移除无用代码 2021-10-16 13:39:18 +08:00
hanhh 87027f8a8c 修复表格样式 2021-10-16 10:53:14 +08:00
hanhh 6fa8a678bf 修改pm2任务日志 2021-10-15 22:32:32 +08:00
hanhh f2aa355389 修复更新日志匹配正则 2021-10-15 22:14:42 +08:00
hanhh b3ef432c6f 放开ql update删除操作 2021-10-15 22:01:34 +08:00
hanhh 860a1e4dc6 更新版本号 v2.9.8-001 2021-10-15 11:47:32 +08:00
hanhh f5b322649d 修复设置删除日志 2021-10-15 10:53:27 +08:00
phoenixandGitHub 746650b49a 修复nginx.conf错误 (#776)
Fix nginx: [emerg] unknown directive.
Log
`nginx: [emerg] unknown directive "client_body_buffer_size:" in /etc/nginx/nginx.conf:18
`
2021-10-15 10:40:58 +08:00
hanhh 43ddc54a70 更新版本 2.9.8 2021-10-14 22:07:21 +08:00
hanhh bdfa08bb78 修复初始化流程 2021-10-14 22:00:36 +08:00
hanhh 07e58a484c 增加日志删除频率设置 2021-10-14 10:58:54 +08:00
hanhh a652e1e190 修改版本更新执行命令 2021-10-13 23:50:25 +08:00
hanhh ec0deeff06 添加ql -l update 2021-10-13 23:45:46 +08:00
hanhh 7335e2c299 更新nginx配置文件 2021-10-13 23:31:38 +08:00
hanhh 003cdd1e06 修复websocket连接域名 2021-10-13 23:09:48 +08:00
hanhh efa1fcfbdf 修复版本号匹配正则 2021-10-13 22:53:10 +08:00
hanhh 7acda0c039 修复版本号匹配 2021-10-13 22:45:24 +08:00
hanhh cb86fda169 移除远程版本更新链接 2021-10-13 22:41:59 +08:00
hanhh 324d106693 修复获取最后版本文件 2021-10-13 22:39:59 +08:00
hanhh 0644edb62e 修复获取最新版本号 2021-10-13 22:05:54 +08:00
hanhh 0f31cb5fcc 容器启动忽略config和env中的错误 2021-10-13 14:25:38 +08:00
hanhh c80a9d1e65 修复版本检查 2021-10-12 23:06:28 +08:00
hanhh b26d0b4887 完善检查更新操作 2021-10-12 22:54:17 +08:00
hanhh ed93b919ba 并发命令支持指定账号 2021-10-12 21:58:41 +08:00
hanhh b1077443a3 添加系统更新操作和设置删除日志频率 2021-10-12 00:27:42 +08:00
hanhh 9455ca64a2 添加检查更新和删除日志频率操作 2021-10-08 23:02:45 +08:00
hanhh d6ebd2699e 修复表格滚动样式和交互 2021-10-08 22:39:17 +08:00
hanhh 632514d36e 增加新版本检测 2021-10-07 15:26:25 +08:00
hanhh 686baa56f4 增加环境变量名称正则校验 2021-10-06 16:38:46 +08:00
hanhh a3006af7b8 添加新版本提醒 2021-10-06 16:22:53 +08:00
hanhh ec529bfca6 更新版本号 v2.9.7 2021-10-03 22:26:16 +08:00
SowevoandGitHub a661b6bd38 增加企业微信机器人推送的python版本 (#748) 2021-10-03 22:16:07 +08:00
hanhh 73e85c2def 修复登录两步提醒样式 2021-10-03 22:05:56 +08:00
hanhh 6819487a43 修改初始化判断条件 2021-10-03 22:01:44 +08:00
hanhh 14b20873c7 增加初始化过程 2021-10-03 20:58:55 +08:00
hanhh bd1ca7e975 修复entrypoint启动nginx配置 2021-09-30 23:49:19 +08:00
hanhh c01ec828e2 ql update添加patch 2021-09-30 19:14:40 +08:00
hanhh 47abdb0f36 移除check中init git 2021-09-30 19:08:13 +08:00
hanhh 6d4568cdcd 更新版本号 v2.9.6 2021-09-29 23:16:59 +08:00
hanhh 2068ef7053 修复类型检查 2021-09-29 23:15:09 +08:00
hanhh 5655591215 修复退出登录获取token出错 2021-09-29 23:09:13 +08:00
hanhh feb22f25b8 对比工具支持对比config目录下的文件 2021-09-29 22:48:49 +08:00
hanhh c054259806 重启服务时自动压缩db 2021-09-29 21:40:35 +08:00
hanhh b179649078 修复loading状态位置 2021-09-29 21:19:25 +08:00
hanhh a75561399d 修复清空文件操作 2021-09-27 22:04:47 +08:00
hanhh fa02df0d8f task和ql命令添加基础目录检测 2021-09-27 19:38:06 +08:00
hanhh f17642b243 测试动态加载 2021-09-25 12:11:13 +08:00
hanhh 43773222f8 修改定时任务列表滚动宽度 2021-09-25 11:40:55 +08:00
hanhh 91a5e13cf2 修复status接口参数 2021-09-25 02:00:50 +08:00
hanhh be4efbad5d 修复并发参数 2021-09-25 01:51:18 +08:00
hanhh da3e63e6c3 更新版本号 2021-09-25 00:43:05 +08:00
hanhh 2d427c0670 增加登录设备展示 2021-09-25 00:42:03 +08:00
hanhh 97184d22c3 修复更新任务状态api 2021-09-25 00:28:18 +08:00
hanhh f1cc7641fb 修复ql resettfa命令 2021-09-25 00:15:23 +08:00
hanhh ca9d742359 ql resettfa增加兼容字段 2021-09-24 23:33:52 +08:00
hanhh 1d82310ca1 移除定时任务左右列固定n 2021-09-24 23:31:50 +08:00
hanhh 687afce245 ql bot移除自动更新远程仓库 2021-09-24 23:22:40 +08:00
hanhh 26d986d4a8 修复服务端ts类型检查 2021-09-24 22:49:14 +08:00
hanhh 120599c778 更新版本号 2021-09-24 22:30:09 +08:00
hanhh 54945f2183 修复定时任务操作宽度 2021-09-24 22:28:25 +08:00
hanhh f48b91dc0a 支持多端登录 2021-09-24 22:24:39 +08:00
hanhh 4c1551c309 修复task运行指定账号 2021-09-24 00:24:54 +08:00
hanhh 558478be16 修复定时任务列表下次运行时间 2021-09-24 00:11:14 +08:00
hanhh 4e403d3d8a task支持指定账号运行 2021-09-23 23:59:53 +08:00
hanhh a79fd74130 修改定时任务表格布局 2021-09-23 22:12:19 +08:00
hanhh af2f3deeab 添加最后运行时间和运行时长 2021-09-23 13:42:32 +08:00
hanhh c15ff89746 修复主题切换全局class未变化 2021-09-21 23:29:38 +08:00
hanhh 5b585e0cda 定时任务添加下次运行时间 2021-09-21 20:11:39 +08:00
hanhh 6619374571 修改登录通知消息 2021-09-20 23:56:20 +08:00
hanhh 9fc8fdd9d1 修复pushplus通知参数 2021-09-20 22:23:56 +08:00
hanhh 900d279bf6 修复新建文件 2021-09-20 00:59:38 +08:00
hanhh 265760146f 添加bak目录ignore 2021-09-20 00:47:58 +08:00
hanhh 74fa8ffcd1 修改测试通知内容 2021-09-19 23:10:58 +08:00
hanhh e5313e63a9 更新版本号 2021-09-19 23:00:53 +08:00
hanhh f482796cdf 修复通知 2021-09-19 23:00:21 +08:00
hanhh aa08f489d7 定时任务增加置顶功能 2021-09-19 22:25:57 +08:00
hanhh 1cc54b5da6 添加全局主题className 2021-09-19 21:27:20 +08:00
hanhh 0baf0d23ae 脚本管理支持新增文件 2021-09-19 21:23:03 +08:00
hanhh 646443deb5 修复pageTitle 2021-09-18 23:27:50 +08:00
hanhh 39fb23c025 任务详情日志添加loading 2021-09-18 16:33:12 +08:00
hanhh 698c463c3a 添加全局loading 2021-09-18 15:08:45 +08:00
hanhh 6c87634bb3 修复关闭通知 2021-09-18 02:09:06 +08:00
hanhh 0a9e9e972d 添加通知设置页面 2021-09-18 01:44:55 +08:00
whyour bb0ad6c325 添加通知api 2021-09-17 18:06:14 +08:00
hanhh f2dac51a62 更新通知服务 2021-09-17 00:35:27 +08:00
whyour 9534cda1f9 更新通知服务 2021-09-16 21:01:29 +08:00
whyour 27226e7222 添加通知服务 2021-09-16 18:01:25 +08:00
prcxuandGitHub c51621ee04 更新安装说明 (#705) 2021-09-15 11:18:58 +08:00
JH2628andGitHub c78e4447fa 修复config sample序号 (#694) 2021-09-14 19:25:00 +08:00
hanhh 2d7e525024 修改系统异常提醒 2021-09-13 00:12:44 +08:00
hanhh ecee9e2acd 更新tg链接 2021-09-12 20:28:43 +08:00
hanhh d607619f8f 更新版本号 2021-09-11 13:53:58 +08:00
hanhh e8e275cd79 修复写入任务错误日志逻辑 2021-09-11 13:53:24 +08:00
hanhh 4147025406 修改获取用户数据逻辑 2021-09-11 02:03:31 +08:00
hanhh 35539a4ccc 移除自动压缩db数据 2021-09-09 13:02:49 +08:00
hanhh b7a359714f 更新版本号 2021-09-08 12:26:21 +08:00
hanhh bf43bfbd92 修复开启两步失败提醒 2021-09-08 12:26:05 +08:00
hanhh 1265e465d0 添加应用名称改为选填 2021-09-08 12:15:30 +08:00
hanhh 8f195b964f 修复两步验证逻辑 2021-09-08 12:13:19 +08:00
hanhh a285efdd85 更新版本号 2021-09-07 23:53:01 +08:00
hanhh fafb68dbf0 修改登陆日志title 2021-09-07 23:52:40 +08:00
L1n3andGitHub 3dc87329f1 添加安装说明 (#667) 2021-09-07 17:46:53 +08:00
LeonandGitHub 7fc15518b3 修复 Python 和 JavaScript Server酱和企业微信推送的环境变量不一致 (#673) 2021-09-07 17:46:14 +08:00
hanhh eb196bd40d 增加登录日志 2021-09-07 01:40:41 +08:00
Zy143LandGitHub 0cbfca979e 修复python和JavaScript钉钉推送的环境变量不一致 (#659) 2021-09-05 23:53:18 +08:00
hanhh b3ac81b7b7 config文件读取添加黑名单 2021-09-05 23:48:58 +08:00
hanhh bb40111d38 添加环境变量命令input改为textarea 2021-09-05 23:38:36 +08:00
hanhh 29a3bea6e4 修改ql check依赖安装逻辑 2021-09-03 22:30:24 +08:00
hanhh ab4f167bda 修改两步验证逻辑 2021-09-03 19:09:09 +08:00
hanhh 035c69898f 修复环境变量拖拽 2021-09-02 19:16:28 +08:00
hanhh 00cb915f48 更新两步验证逻辑 2021-09-01 23:03:27 +08:00
L1n3andGitHub 97baff8baf 添加 docker-compose.yml (#629) 2021-08-31 15:52:24 +08:00
hanhh e4da2edf56 增加上次登录状态 2021-08-31 15:29:23 +08:00
hanhh c1e08ba9f0 增加ql resettfa禁用两步验证 2021-08-31 11:06:20 +08:00
hanhh f00566624d 更新版本号 2021-08-31 10:56:37 +08:00
hanhh 2bb55294fc 两步验证失败增加ip记录 2021-08-31 10:54:46 +08:00
hanhh 86c3e9a843 添加两步验证 2021-08-30 23:37:26 +08:00
hanhh 3a998a37f0 增加nedb数据自定比对,测试对db文件不断增大的影响 2021-08-29 00:52:21 +08:00
hanhh 7eed47cd8e 修复移动端无法打开脚本管理、任务日志 2021-08-29 00:34:47 +08:00
hanhh 8cef0b6216 更换ip地址查询接口 2021-08-27 14:00:11 +08:00
hanhh 930541d5d6 更换ip查询接口 2021-08-27 13:27:32 +08:00
hanhh f3518d0cad 更新版本号 2021-08-27 11:08:08 +08:00
hanhh 45e2c43997 更新nginx配置 2021-08-27 11:04:31 +08:00
hanhh 6c0f932ae8 修改新建应用placeholder 2021-08-27 10:52:49 +08:00
hanhh 5832bf9140 搜索时去掉前后空格 2021-08-27 10:47:30 +08:00
HeroWongandGitHub 47bba19bec fix: 只有一页时不隐藏分页栏,确保用户可以更改 pagesize (#613) 2021-08-26 21:27:22 +08:00
hanhh 1e58254f4c 添加openapi模块 2021-08-26 19:01:39 +08:00
hanhh 7739cef7b8 更新版本号 2021-08-25 23:55:16 +08:00
hanhh 9b73392c5e 修复cron分隔符,修复登录获取ip逻辑 2021-08-25 23:54:42 +08:00
hanhh 79e5b2fb84 修复登录倒计时颜色 2021-08-25 18:58:45 +08:00
hanhh 8ce1a33bd3 更新版本号 2021-08-25 18:20:29 +08:00
hanhh 8ae61f1b23 修复6位cron判断 2021-08-25 18:19:22 +08:00
hanhh a973c93c04 修复群辉update之后403 2021-08-25 17:58:21 +08:00
hanhh 9c0f14aa89 更新版本号 2021-08-24 18:21:19 +08:00
hanhh f78d72db4c 添加重置登录错误次数命令 2021-08-24 18:20:36 +08:00
hanhh fb0c1c6993 添加默认定时规则配置 2021-08-23 19:10:57 +08:00
hanhh 4d99695e4c task传递参数给task_before和task_after 2021-08-23 15:58:25 +08:00
hanhh 18321943d6 更新readme图片 2021-08-23 15:31:13 +08:00
hanhh 67819d2817 修复readme图片 2021-08-23 15:26:30 +08:00
hanhh ad74e014ef 更新版本号 2021-08-23 13:50:44 +08:00
hanhh bf661dffcf 修复获取ip api失败时,无法登陆
修复禁用或者运行定时任务出现重复
2021-08-23 13:44:54 +08:00
hanhh 11484175c8 修复定时任务搜索后页码 2021-08-23 12:29:29 +08:00
hanhh 7bc5004683 修复error_log文件读取 2021-08-23 11:41:34 +08:00
hanhh 1d01508164 更新版本号 2021-08-23 10:47:11 +08:00
hanhh 4cae9bcfb9 回退update.sh 2021-08-23 10:43:26 +08:00
hanhh 9c84c7bd38 修复task_before eval 2021-08-22 22:33:07 +08:00
hanhh 5a5e81eae0 修复未产生日志文件cat报错 2021-08-22 22:02:31 +08:00
hanhh b0f6bb3ea6 ql命令增加-l参数 2021-08-22 21:57:10 +08:00
hanhh 0c004606b5 修改task.sh任务顺序 2021-08-22 20:54:40 +08:00
hanhh fa4978389c 修复task语法 2021-08-22 18:57:52 +08:00
hanhh 8b8359b08f 修复task.sh执行语法 2021-08-22 18:21:31 +08:00
hanhh 1d4fec3979 修改task.sh中参数名 2021-08-22 16:05:58 +08:00
hanhh bb4123805c 修复最后登录ip读取 2021-08-22 15:25:16 +08:00
hanhh f49f605c11 更新版本号 2021-08-22 15:23:58 +08:00
hanhh 5496689d7c 修复任务执行参数出错 2021-08-22 15:23:12 +08:00
hanhh bad8bbdb7c 修改登陆成功提示文本 2021-08-22 14:47:07 +08:00
hanhh 76184227a4 更新版本号 2021-08-22 14:07:43 +08:00
hanhh bbc9ee120d 修复获取访问ip逻辑 2021-08-22 14:07:00 +08:00
hanhh 85093b2856 更新版本号 2021-08-22 12:59:54 +08:00
hanhh 8c355b6159 修复登录重试判断 2021-08-22 12:59:34 +08:00
hanhh 622037ee98 登录增加错误延迟提醒 2021-08-22 12:28:45 +08:00
hanhh 54bb13aad0 登录增加最后登录时间地点,登录失败增加重试间隔 2021-08-22 01:22:23 +08:00
hanhh 61e41269ba 默认定时改为随机时间 2021-08-21 19:26:37 +08:00
hanhh f7cffe3d2d 修复更新环境变量备注无法删除 2021-08-21 18:34:53 +08:00
hanhh 0cd39f0c97 增加命令行运行日志实时打印 2021-08-21 17:26:41 +08:00
hanhh c09b22358b 定时任务,环境变量添加状态筛选 2021-08-20 16:31:24 +08:00
hanhh 18215b8336 更新版本号 2021-08-20 16:17:04 +08:00
hanhh f0fae716cc 移除console 2021-08-20 14:26:12 +08:00
hanhh 7092be97eb 手动运行任务添加错误捕获 2021-08-20 10:59:59 +08:00
hanhh d9a9d2ee4f 修复脚本管理编辑交互,修复ico无法加载 2021-08-19 23:46:26 +08:00
hanhh f86892cef0 更新版本号 2021-08-19 14:47:07 +08:00
hanhh a48b915675 定时任务支持任务名,定时,命令排序
环境变量支持名称排序
2021-08-19 14:45:42 +08:00
hanhh 5e13264393 脚本管理支持编辑删除文件 2021-08-19 14:18:25 +08:00
hanhh ff481ef7f3 修复未勾选拆分环境变量参数验证失败 2021-08-17 22:31:39 +08:00
hanhh 82a4485792 修复未勾选拆分环境变量参数验证失败 2021-08-17 22:26:33 +08:00
hanhh 538876f529 修复手动运行任务进程 2021-08-17 22:06:04 +08:00
hanhh 609a31455b 更新版本号 2021-08-17 18:35:53 +08:00
hanhh 2d6da9ebfe 支持批量创建同名称环境变量,日志和脚本管理PC页支持宽度拖拽 2021-08-17 18:34:55 +08:00
hanhh 594e871744 修改暗黑模式默认值 2021-08-16 18:05:52 +08:00
DerekandGitHub 95074eb028 更正ql"本脚本用法"中的序号 (#552) 2021-08-16 17:17:45 +08:00
70 changed files with 2113 additions and 600 deletions
+3 -3
View File
@@ -7,6 +7,6 @@ LOG_LEVEL='debug'
SECRET='whyour'
QINIU_AK = ''
QINIU_SK = ''
QINIU_SCOPE = ''
QINIU_AK=''
QINIU_SK=''
QINIU_SCOPE=''
+2 -1
View File
@@ -1 +1,2 @@
sentrycli_cdnurl=https://cdn.npm.taobao.org/dist/sentry-cli
sentrycli_cdnurl=https://npmmirror.com/mirrors/sentry-cli/
strict-peer-dependencies=false
+1 -5
View File
@@ -9,16 +9,12 @@ export default defineConfig({
type: 'none',
},
fastRefresh: {},
// antd: {
// dark: true,
// },
mfsu: {},
esbuild: {},
webpack5: {},
dynamicImport: {
loading: '@/components/pageLoading',
},
favicon: '/images/g5.ico',
favicon: '/images/favicon.svg',
proxy: {
'/api/public': {
target: 'http://127.0.0.1:5400/',
+6 -2
View File
@@ -57,7 +57,7 @@ https://podman.io/getting-started/installation
```bash
podman run -dit \
--network bridge \
-v $PWD/ql:/ql/data \
-v $PWD/ql/data:/ql/data \
-p 5700:5700 \
--name qinglong \
--hostname qinglong \
@@ -96,7 +96,7 @@ systemctl restart docker
```bash
docker run -dit \
-v $PWD/ql:/ql/data \
-v $PWD/ql/data:/ql/data \
-p 5700:5700 \
--name qinglong \
--hostname qinglong \
@@ -124,6 +124,10 @@ docker-compose up -d
docker-compose down
```
3. access
Open your browser and visit http://{ip}:5700
## Use
1. built-in commands
+10 -6
View File
@@ -57,7 +57,7 @@ https://podman.io/getting-started/installation
```bash
podman run -dit \
--network bridge \
-v $PWD/ql:/ql/data \
-v $PWD/ql/data:/ql/data \
-p 5700:5700 \
--name qinglong \
--hostname qinglong \
@@ -97,7 +97,7 @@ systemctl restart docker
```bash
docker run -dit \
-v $PWD/ql:/ql/data \
-v $PWD/ql/data:/ql/data \
-p 5700:5700 \
--name qinglong \
--hostname qinglong \
@@ -125,6 +125,10 @@ docker-compose up -d
docker-compose down
```
3. 访问
打开你的浏览器,访问 http://{ip}:5700
## 使用
1. 内置命令
@@ -163,11 +167,11 @@ task <file_path> desi <env_name> <account_number>
* file_url: 脚本地址
* repo_url: 仓库地址
* whitelist: 拉取仓库时的白名单,即就是需要拉取的脚本的路径包含的字符串
* blacklist: 拉取仓库时的黑名单,即就是需要拉取的脚本的路径不包含的字符串
* dependence: 拉取仓库需要的依赖文件,会直接从仓库拷贝到scripts下的仓库目录,不受黑名单影响
* whitelist: 拉取仓库时的白名单,即就是需要拉取的脚本的路径包含的字符串,多个竖线分割
* blacklist: 拉取仓库时的黑名单,即就是需要拉取的脚本的路径不包含的字符串,多个竖线分割
* dependence: 拉取仓库需要的依赖文件,会直接从仓库拷贝到scripts下的仓库目录,不受黑名单影响,多个竖线分割
* extensions: 拉取仓库的文件后缀,多个竖线分割
* branch: 拉取仓库的分支
* extensions: 拉取仓库的文件后缀
* days: 需要保留的日志的天数
* file_path: 任务执行时的文件路径
* env_name: 任务执行时需要并发或者指定时的环境变量名称
-3
View File
@@ -25,7 +25,6 @@ export default (app: Router) => {
}),
});
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -51,7 +50,6 @@ export default (app: Router) => {
}
res.send({ code: 200, data: content });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -76,7 +74,6 @@ export default (app: Router) => {
fs.writeFileSync(path, content);
res.send({ code: 200, message: '保存成功' });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
+133 -24
View File
@@ -2,6 +2,7 @@ import { Router, Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
import { Logger } from 'winston';
import CronService from '../services/cron';
import CronViewService from '../services/cronView';
import { celebrate, Joi } from 'celebrate';
import cron_parser from 'cron-parser';
const route = Router();
@@ -9,11 +10,135 @@ const route = Router();
export default (app: Router) => {
app.use('/crons', route);
route.get(
'/views',
async (req: Request, res: Response, next: NextFunction) => {
try {
const cronViewService = Container.get(CronViewService);
const data = await cronViewService.list();
return res.send({ code: 200, data });
} catch (e) {
return next(e);
}
},
);
route.post(
'/views',
celebrate({
body: Joi.object({
name: Joi.string().required(),
sorts: Joi.array().optional(),
filters: Joi.array().optional(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
try {
const cronViewService = Container.get(CronViewService);
const data = await cronViewService.create(req.body);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
}
},
);
route.put(
'/views',
celebrate({
body: Joi.object({
name: Joi.string().required(),
id: Joi.number().required(),
sorts: Joi.array().optional(),
filters: Joi.array().optional(),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
try {
const cronViewService = Container.get(CronViewService);
const data = await cronViewService.update(req.body);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
}
},
);
route.delete(
'/views',
celebrate({
body: Joi.array().items(Joi.number().required()),
}),
async (req: Request, res: Response, next: NextFunction) => {
try {
const cronViewService = Container.get(CronViewService);
const data = await cronViewService.remove(req.body);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
}
},
);
route.put(
'/views/move',
celebrate({
body: Joi.object({
fromIndex: Joi.number().required(),
toIndex: Joi.number().required(),
id: Joi.number().required(),
}),
}),
async (req: Request<{ id: number }>, res: Response, next: NextFunction) => {
try {
const cronViewService = Container.get(CronViewService);
const data = await cronViewService.move(req.body);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
}
},
);
route.put(
'/views/disable',
celebrate({
body: Joi.array().items(Joi.number().required()),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cronViewService = Container.get(CronViewService);
const data = await cronViewService.disabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
}
},
);
route.put(
'/views/enable',
celebrate({
body: Joi.array().items(Joi.number().required()),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cronViewService = Container.get(CronViewService);
const data = await cronViewService.enabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
return next(e);
}
},
);
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const cronService = Container.get(CronService);
const data = await cronService.crontabs(req.query.searchValue as string);
const data = await cronService.crontabs(req.query as any);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
@@ -42,7 +167,6 @@ export default (app: Router) => {
return res.send({ code: 400, message: 'param schedule error' });
}
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -60,7 +184,6 @@ export default (app: Router) => {
const data = await cronService.run(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -78,7 +201,6 @@ export default (app: Router) => {
const data = await cronService.stop(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -102,7 +224,6 @@ export default (app: Router) => {
);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -123,7 +244,6 @@ export default (app: Router) => {
const data = await cronService.addLabels(req.body.ids, req.body.labels);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -141,7 +261,6 @@ export default (app: Router) => {
const data = await cronService.disabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -159,7 +278,6 @@ export default (app: Router) => {
const data = await cronService.enabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -179,7 +297,6 @@ export default (app: Router) => {
const data = await cronService.log(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -190,9 +307,9 @@ export default (app: Router) => {
celebrate({
body: Joi.object({
labels: Joi.array().optional().allow(null),
command: Joi.string().optional(),
schedule: Joi.string().optional(),
name: Joi.string().optional(),
command: Joi.string().required(),
schedule: Joi.string().required(),
name: Joi.string().optional().allow(null),
id: Joi.number().required(),
}),
}),
@@ -210,7 +327,6 @@ export default (app: Router) => {
return res.send({ code: 400, message: 'param schedule error' });
}
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -228,7 +344,6 @@ export default (app: Router) => {
const data = await cronService.remove(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -246,7 +361,6 @@ export default (app: Router) => {
const data = await cronService.pin(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -264,7 +378,6 @@ export default (app: Router) => {
const data = await cronService.unPin(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -279,7 +392,6 @@ export default (app: Router) => {
const data = await cronService.import_crontab();
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -299,7 +411,6 @@ export default (app: Router) => {
const data = await cronService.getDb({ id: req.params.id });
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -311,10 +422,10 @@ export default (app: Router) => {
body: Joi.object({
ids: Joi.array().items(Joi.number().required()),
status: Joi.string().required(),
pid: Joi.string().optional(),
log_path: Joi.string().optional(),
last_running_time: Joi.number().optional(),
last_execution_time: Joi.number().optional(),
pid: Joi.string().optional().allow(null),
log_path: Joi.string().optional().allow(null),
last_running_time: Joi.number().optional().allow(null),
last_execution_time: Joi.number().optional().allow(null),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
@@ -328,7 +439,6 @@ export default (app: Router) => {
});
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -348,7 +458,6 @@ export default (app: Router) => {
const data = await cronService.logs(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
-6
View File
@@ -38,7 +38,6 @@ export default (app: Router) => {
const data = await dependenceService.create(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -61,7 +60,6 @@ export default (app: Router) => {
const data = await dependenceService.update(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -79,7 +77,6 @@ export default (app: Router) => {
const data = await dependenceService.remove(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -97,7 +94,6 @@ export default (app: Router) => {
const data = await dependenceService.remove(req.body, true);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -117,7 +113,6 @@ export default (app: Router) => {
const data = await dependenceService.getDb({ id: req.params.id });
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -135,7 +130,6 @@ export default (app: Router) => {
const data = await dependenceService.reInstall(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
+1 -9
View File
@@ -38,7 +38,6 @@ export default (app: Router) => {
const data = await envService.create(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -61,7 +60,6 @@ export default (app: Router) => {
const data = await envService.update(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -79,7 +77,6 @@ export default (app: Router) => {
const data = await envService.remove(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -103,7 +100,6 @@ export default (app: Router) => {
const data = await envService.move(req.params.id, req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -121,7 +117,6 @@ export default (app: Router) => {
const data = await envService.disabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -139,7 +134,6 @@ export default (app: Router) => {
const data = await envService.enabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -160,7 +154,6 @@ export default (app: Router) => {
const data = await envService.updateNames(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -177,10 +170,9 @@ export default (app: Router) => {
const logger: Logger = Container.get('logger');
try {
const envService = Container.get(EnvService);
const data = await envService.getDb(req.params.id);
const data = await envService.getDb({ id: req.params.id });
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
+5 -2
View File
@@ -6,6 +6,7 @@ import config from '../config';
import { getFileContentByName, readDirs } from '../config/util';
import { join } from 'path';
const route = Router();
const blacklist = ['.tmp'];
export default (app: Router) => {
app.use('/logs', route);
@@ -13,7 +14,7 @@ export default (app: Router) => {
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const result = readDirs(config.logPath, config.logPath);
const result = readDirs(config.logPath, config.logPath, blacklist);
res.send({
code: 200,
data: result,
@@ -29,6 +30,9 @@ export default (app: Router) => {
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
if (blacklist.includes(req.path)) {
return res.send({ code: 403, message: '暂无权限' });
}
const filePath = join(
config.logPath,
(req.query.path || '') as string,
@@ -37,7 +41,6 @@ export default (app: Router) => {
const content = getFileContentByName(filePath);
res.send({ code: 200, data: content });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
+1 -7
View File
@@ -16,7 +16,6 @@ export default (app: Router) => {
const data = await openService.list();
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -26,7 +25,7 @@ export default (app: Router) => {
'/apps',
celebrate({
body: Joi.object({
name: Joi.string().optional().allow(''),
name: Joi.string().optional().allow('').disallow('system'),
scopes: Joi.array().items(Joi.string().required()),
}),
}),
@@ -37,7 +36,6 @@ export default (app: Router) => {
const data = await openService.create(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -59,7 +57,6 @@ export default (app: Router) => {
const data = await openService.update(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -77,7 +74,6 @@ export default (app: Router) => {
const data = await openService.remove(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -97,7 +93,6 @@ export default (app: Router) => {
const data = await openService.resetSecret(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -118,7 +113,6 @@ export default (app: Router) => {
const result = await openService.authToken(req.query as any);
return res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
+44 -31
View File
@@ -3,6 +3,7 @@ import {
getFileContentByName,
readDirs,
getLastModifyFilePath,
readDir,
} from '../config/util';
import { Router, Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
@@ -10,17 +11,38 @@ import { Logger } from 'winston';
import config from '../config';
import * as fs from 'fs';
import { celebrate, Joi } from 'celebrate';
import path, { join } from 'path';
import path, { join, parse } from 'path';
import ScriptService from '../services/script';
import multer from 'multer';
const route = Router();
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, config.scriptPath);
},
filename: function (req, file, cb) {
cb(null, file.originalname);
},
});
const upload = multer({ storage: storage });
export default (app: Router) => {
app.use('/scripts', route);
route.get('/', async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
const result = readDirs(config.scriptPath, config.scriptPath);
let result = [];
const blacklist = ['node_modules', '.git'];
if (req.query.path) {
const targetPath = path.join(
config.scriptPath,
req.query.path as string,
);
result = readDir(targetPath, config.scriptPath, blacklist);
} else {
result = readDirs(config.scriptPath, config.scriptPath, blacklist);
}
res.send({
code: 200,
data: result,
@@ -44,7 +66,6 @@ export default (app: Router) => {
const content = getFileContentByName(filePath);
res.send({ code: 200, data: content });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -52,14 +73,7 @@ export default (app: Router) => {
route.post(
'/',
celebrate({
body: Joi.object({
filename: Joi.string().required(),
path: Joi.string().optional().allow(''),
content: Joi.string().allow(''),
originFilename: Joi.string().allow(''),
}),
}),
upload.single('file'),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
@@ -69,6 +83,7 @@ export default (app: Router) => {
content: string;
originFilename: string;
};
if (!path) {
path = config.scriptPath;
}
@@ -84,15 +99,18 @@ export default (app: Router) => {
message: '文件路径禁止访问',
});
}
if (req.file) {
fs.renameSync(req.file.path, join(path, req.file.filename));
return res.send({ code: 200 });
}
if (!originFilename) {
originFilename = filename;
}
const originFilePath = `${path}${originFilename.replace(/\//g, '')}`;
const filePath = `${path}${filename.replace(/\//g, '')}`;
if (fs.existsSync(originFilePath)) {
if (!fs.existsSync(config.bakPath)) {
fs.mkdirSync(config.bakPath);
}
fs.copyFileSync(
originFilePath,
`${config.bakPath}${originFilename.replace(/\//g, '')}`,
@@ -104,7 +122,6 @@ export default (app: Router) => {
fs.writeFileSync(filePath, content);
return res.send({ code: 200 });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -116,7 +133,7 @@ export default (app: Router) => {
body: Joi.object({
filename: Joi.string().required(),
path: Joi.string().optional().allow(''),
content: Joi.string().required(),
content: Joi.string().required().allow(''),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
@@ -131,7 +148,6 @@ export default (app: Router) => {
fs.writeFileSync(filePath, content);
return res.send({ code: 200 });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -156,7 +172,6 @@ export default (app: Router) => {
fs.unlinkSync(filePath);
res.send({ code: 200 });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -187,7 +202,6 @@ export default (app: Router) => {
return next(err);
});
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -198,22 +212,22 @@ export default (app: Router) => {
celebrate({
body: Joi.object({
filename: Joi.string().required(),
content: Joi.string().optional().allow(''),
path: Joi.string().optional().allow(''),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
let { filename, path } = req.body as {
filename: string;
path: string;
};
const filePath = join(path, filename);
let { filename, content, path } = req.body;
const { name, ext } = parse(filename);
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
fs.writeFileSync(filePath, content || '', { encoding: 'utf8' });
const scriptService = Container.get(ScriptService);
const result = await scriptService.runScript(filePath);
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -224,22 +238,21 @@ export default (app: Router) => {
celebrate({
body: Joi.object({
filename: Joi.string().required(),
content: Joi.string().optional().allow(''),
path: Joi.string().optional().allow(''),
}),
}),
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
try {
let { filename, path } = req.body as {
filename: string;
path: string;
};
const filePath = join(path, filename);
let { filename, content, path } = req.body;
const { name, ext } = parse(filename);
const filePath = join(config.scriptPath, path, `${name}.swap${ext}`);
const scriptService = Container.get(ScriptService);
const result = await scriptService.stopScript(filePath);
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
+7 -12
View File
@@ -29,7 +29,13 @@ export default (app: Router) => {
body: Joi.object({
type: Joi.string().required(),
schedule: Joi.string().optional().allow('').allow(null),
interval_schedule: Joi.object().optional().allow('').allow(null),
interval_schedule: Joi.object({
type: Joi.string().required(),
value: Joi.number().min(1).required(),
})
.optional()
.allow('')
.allow(null),
name: Joi.string().optional().allow('').allow(null),
url: Joi.string().required(),
whitelist: Joi.string().optional().allow('').allow(null),
@@ -59,7 +65,6 @@ export default (app: Router) => {
return res.send({ code: 400, message: 'param schedule error' });
}
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -77,7 +82,6 @@ export default (app: Router) => {
const data = await subscriptionService.run(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -95,7 +99,6 @@ export default (app: Router) => {
const data = await subscriptionService.stop(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -113,7 +116,6 @@ export default (app: Router) => {
const data = await subscriptionService.disabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -131,7 +133,6 @@ export default (app: Router) => {
const data = await subscriptionService.enabled(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -151,7 +152,6 @@ export default (app: Router) => {
const data = await subscriptionService.log(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -195,7 +195,6 @@ export default (app: Router) => {
return res.send({ code: 400, message: 'param schedule error' });
}
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -213,7 +212,6 @@ export default (app: Router) => {
const data = await subscriptionService.remove(req.body);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -233,7 +231,6 @@ export default (app: Router) => {
const data = await subscriptionService.getDb({ id: req.params.id });
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -260,7 +257,6 @@ export default (app: Router) => {
});
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -280,7 +276,6 @@ export default (app: Router) => {
const data = await subscriptionService.logs(req.params.id);
return res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
-5
View File
@@ -54,7 +54,6 @@ export default (app: Router) => {
const data = await systemService.getLogRemoveFrequency();
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -76,7 +75,6 @@ export default (app: Router) => {
);
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -91,7 +89,6 @@ export default (app: Router) => {
const result = await systemService.checkUpdate();
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -106,7 +103,6 @@ export default (app: Router) => {
const result = await systemService.updateSystem();
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -127,7 +123,6 @@ export default (app: Router) => {
const result = await systemService.notify(req.body);
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
-13
View File
@@ -39,7 +39,6 @@ export default (app: Router) => {
const data = await userService.login({ ...req.body }, req);
return res.send(data);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -54,7 +53,6 @@ export default (app: Router) => {
await userService.logout(req.platform);
res.send({ code: 200 });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -75,7 +73,6 @@ export default (app: Router) => {
await userService.updateUsernameAndPassword(req.body);
res.send({ code: 200, message: '更新成功' });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -109,7 +106,6 @@ export default (app: Router) => {
const data = await userService.initTwoFactor();
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -129,7 +125,6 @@ export default (app: Router) => {
const data = await userService.activeTwoFactor(req.body.code);
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -144,7 +139,6 @@ export default (app: Router) => {
const data = await userService.deactiveTwoFactor();
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -166,7 +160,6 @@ export default (app: Router) => {
const data = await userService.twoFactorLogin(req.body, req);
res.send(data);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -181,7 +174,6 @@ export default (app: Router) => {
const data = await userService.getLoginLog();
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -196,7 +188,6 @@ export default (app: Router) => {
const data = await userService.getNotificationMode();
res.send({ code: 200, data });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -211,7 +202,6 @@ export default (app: Router) => {
const result = await userService.updateNotificationMode(req.body);
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -232,7 +222,6 @@ export default (app: Router) => {
await userService.updateUsernameAndPassword(req.body);
res.send({ code: 200, message: '更新成功' });
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -247,7 +236,6 @@ export default (app: Router) => {
const result = await userService.updateNotificationMode(req.body);
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
@@ -263,7 +251,6 @@ export default (app: Router) => {
const result = await userService.updateAvatar(req.file!.filename);
res.send(result);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
+3 -5
View File
@@ -1,20 +1,18 @@
import 'reflect-metadata'; // We need this in order to use @Decorators
import config from './config';
import express from 'express';
import Logger from './loaders/logger';
import path from 'path';
async function startServer() {
const app = express();
await require('./loaders/db').default();
await require('./loaders/initFile').default();
await require('./loaders/sentry').default({ expressApp: app });
await require('./loaders/db').default();
await require('./loaders/app').default({ expressApp: app });
const server = app
+14 -2
View File
@@ -4,10 +4,21 @@ import { createRandomString } from './util';
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
if (!process.env.QL_DIR) {
// 声明QL_DIR环境变量
let qlHomePath = path.join(__dirname, '../../');
// 生产环境
if (qlHomePath.endsWith('/static/')) {
qlHomePath = path.join(qlHomePath, '../');
}
process.env.QL_DIR = qlHomePath;
}
const lastVersionFile = `http://qn.whyour.cn/version.ts?v=${Date.now()}`;
const envFound = dotenv.config();
const rootPath = process.cwd();
const rootPath = process.env.QL_DIR as string;
const envFound = dotenv.config({ path: path.join(rootPath, '.env') });
const dataPath = path.join(rootPath, 'data/');
const samplePath = path.join(rootPath, 'sample/');
const configPath = path.join(dataPath, 'config/');
@@ -69,6 +80,7 @@ export default {
'cookie.sh',
'crontab.list',
'env.sh',
'token.json',
],
writePathList: [configPath, scriptPath],
bakPath,
+55 -5
View File
@@ -2,6 +2,7 @@ import * as fs from 'fs';
import * as path from 'path';
import got from 'got';
import iconv from 'iconv-lite';
import { exec } from 'child_process';
export function getFileContentByName(fileName: string) {
if (fs.existsSync(fileName)) {
@@ -277,31 +278,80 @@ export async function concurrentRun(
return replyList;
}
export function readDirs(dir: string, baseDir: string = '') {
enum FileType {
'directory',
'file',
}
export function readDirs(
dir: string,
baseDir: string = '',
blacklist: string[] = [],
) {
const relativePath = path.relative(baseDir, dir);
const files = fs.readdirSync(dir);
const result: any = files.map((file: string) => {
const result: any = files
.filter((x) => !blacklist.includes(x))
.map((file: string) => {
const subPath = path.join(dir, file);
const stats = fs.statSync(subPath);
const key = path.join(relativePath, file);
if (stats.isDirectory()) {
return {
title: file,
value: file,
key,
type: 'directory',
disabled: true,
parent: relativePath,
children: readDirs(subPath, baseDir),
children: readDirs(subPath, baseDir).sort(
(a: any, b: any) =>
(FileType as any)[a.type] - (FileType as any)[b.type],
),
};
}
return {
title: file,
value: file,
type: 'file',
key,
parent: relativePath,
};
});
return result.sort(
(a: any, b: any) => (FileType as any)[a.type] - (FileType as any)[b.type],
);
}
export function readDir(
dir: string,
baseDir: string = '',
blacklist: string[] = [],
) {
const relativePath = path.relative(baseDir, dir);
const files = fs.readdirSync(dir);
const result: any = files
.filter((x) => !blacklist.includes(x))
.map((file: string) => {
const subPath = path.join(dir, file);
const stats = fs.statSync(subPath);
const key = path.join(relativePath, file);
return {
title: file,
type: stats.isDirectory() ? 'directory' : 'file',
key,
parent: relativePath,
};
});
return result;
}
export function promiseExec(command: string): Promise<string> {
return new Promise((resolve, reject) => {
exec(
command,
{ maxBuffer: 200 * 1024 * 1024, encoding: 'utf8' },
(err, stdout, stderr) => {
resolve(stdout || stderr || JSON.stringify(err));
},
);
});
}
+47
View File
@@ -0,0 +1,47 @@
import { sequelize } from '.';
import { DataTypes, Model } from 'sequelize';
interface SortType {
type: 'ASC' | 'DESC';
value: string;
}
interface FilterType {
type: 'or' | 'and';
value: string;
}
export class CrontabView {
name?: string;
id?: number;
position?: number;
isDisabled?: 1 | 0;
filters?: FilterType[];
sorts?: SortType[];
constructor(options: CrontabView) {
this.name = options.name;
this.id = options.id;
this.position = options.position;
this.isDisabled = options.isDisabled || 0;
this.filters = options.filters;
this.sorts = options.sorts;
}
}
interface CronViewInstance
extends Model<CrontabView, CrontabView>,
CrontabView {}
export const CrontabViewModel = sequelize.define<CronViewInstance>(
'CrontabView',
{
name: {
unique: 'name',
type: DataTypes.STRING,
},
position: DataTypes.NUMBER,
isDisabled: DataTypes.NUMBER,
filters: DataTypes.JSON,
sorts: DataTypes.JSON,
},
);
+3 -4
View File
@@ -6,9 +6,9 @@ import { CrontabModel } from '../data/cron';
import { DependenceModel } from '../data/dependence';
import { AppModel } from '../data/open';
import { AuthModel } from '../data/auth';
import { sequelize } from '../data';
import { fileExist } from '../config/util';
import { SubscriptionModel } from '../data/subscription';
import { CrontabViewModel } from '../data/cronView';
import config from '../config';
export default async () => {
@@ -18,9 +18,8 @@ export default async () => {
await AppModel.sync();
await AuthModel.sync();
await EnvModel.sync();
await SubscriptionModel.sync({ alter: true });
await sequelize.sync();
await SubscriptionModel.sync();
await CrontabViewModel.sync();
// try {
// const queryInterface = sequelize.getQueryInterface();
+26
View File
@@ -2,6 +2,7 @@ import path from 'path';
import fs from 'fs';
import chokidar from 'chokidar';
import config from '../config/index';
import { promiseExec } from '../config/util';
function linkToNodeModule(src: string, dst?: string) {
const target = path.join(config.rootPath, 'node_modules', dst || src);
@@ -16,7 +17,32 @@ function linkToNodeModule(src: string, dst?: string) {
});
}
async function linkCommand() {
const commandPath = await promiseExec('which node');
const commandDir = path.dirname(commandPath);
const linkShell = [
{
src: 'update.sh',
dest: 'ql',
},
{
src: 'task.sh',
dest: 'task',
},
];
for (const link of linkShell) {
const source = path.join(config.rootPath, 'shell', link.src);
const target = path.join(commandDir, link.dest);
if (fs.existsSync(target)) {
fs.unlinkSync(target);
}
fs.symlink(source, target, (err) => {});
}
}
export default async (src: string = 'deps') => {
await linkCommand();
linkToNodeModule(src);
const source = path.join(config.rootPath, src);
+23 -8
View File
@@ -80,13 +80,6 @@ export default ({ app }: { app: Application }) => {
) {
return next();
}
const remoteAddress = req.socket.remoteAddress;
if (
remoteAddress === '::ffff:127.0.0.1' &&
originPath === '/api/crons/status'
) {
return next();
}
const data = fs.readFileSync(config.authConfigFile, 'utf8');
if (data) {
@@ -136,7 +129,6 @@ export default ({ app }: { app: Application }) => {
});
app.use(errors());
app.use(Sentry.Handlers.errorHandler());
app.use(
(
@@ -155,6 +147,29 @@ export default ({ app }: { app: Application }) => {
},
);
app.use(
(
err: Error & { errors: any[] },
req: Request,
res: Response,
next: NextFunction,
) => {
if (err.name.includes('Sequelize')) {
return res
.status(500)
.send({
code: 400,
message: `${err.name} ${err.message}`,
validation: err.errors,
})
.end();
}
return next(err);
},
);
app.use(Sentry.Handlers.errorHandler());
app.use(
(
err: Error & { status: number },
+7 -4
View File
@@ -21,15 +21,18 @@ export default async () => {
);
// 初始化时安装所有处于安装中,安装成功,安装失败的依赖
DependenceModel.findAll({ where: {}, raw: true }).then(async (docs) => {
DependenceModel.findAll({
where: {},
order: [['type', 'DESC']],
raw: true,
}).then(async (docs) => {
const groups = _.groupBy(docs, 'type');
for (const key in groups) {
if (Object.prototype.hasOwnProperty.call(groups, key)) {
const keys = Object.keys(groups).sort((a, b) => parseInt(b) - parseInt(a));
for (const key of keys) {
const group = groups[key];
const depIds = group.map((x) => x.id);
await dependenceService.reInstall(depIds as number[]);
}
}
});
// 初始化时执行一次所有的ql repo 任务
+7 -1
View File
@@ -5,12 +5,13 @@ import dotenv from 'dotenv';
import Logger from './logger';
import { fileExist } from '../config/util';
const rootPath = process.cwd();
const rootPath = process.env.QL_DIR as string;;
const dataPath = path.join(rootPath, 'data/');
const configPath = path.join(dataPath, 'config/');
const scriptPath = path.join(dataPath, 'scripts/');
const logPath = path.join(dataPath, 'log/');
const uploadPath = path.join(dataPath, 'upload/');
const bakPath = path.join(dataPath, 'bak/');
const samplePath = path.join(rootPath, 'sample/');
const confFile = path.join(configPath, 'config.sh');
const authConfigFile = path.join(configPath, 'auth.json');
@@ -27,6 +28,7 @@ export default async () => {
const configDirExist = await fileExist(configPath);
const uploadDirExist = await fileExist(uploadPath);
const sshDirExist = await fileExist(sshPath);
const bakDirExist = await fileExist(bakPath);
if (!configDirExist) {
fs.mkdirSync(configPath);
@@ -56,6 +58,10 @@ export default async () => {
fs.mkdirSync(sshPath);
}
if (!bakDirExist) {
fs.mkdirSync(bakPath);
}
dotenv.config({ path: confFile });
Logger.info('✌️ Init file down');
+25 -2
View File
@@ -3,12 +3,30 @@ import _ from 'lodash';
import SystemService from '../services/system';
import ScheduleService from '../services/schedule';
import SubscriptionService from '../services/subscription';
import config from '../config';
import { fileExist } from '../config/util';
export default async () => {
const systemService = Container.get(SystemService);
const scheduleService = Container.get(ScheduleService);
const subscriptionService = Container.get(SubscriptionService);
// 生成内置token
let tokenCommand = `ts-node-transpile-only ${config.rootPath}/back/token.ts`;
const tokenFile = `${config.rootPath}/static/build/token.js`;
if (await fileExist(tokenFile)) {
tokenCommand = `node ${tokenFile}`;
}
const cron = {
id: 'token',
name: '生成token',
command: tokenCommand,
};
scheduleService.createIntervalTask(cron as any, {
days: 28,
runImmediately: true,
});
// 运行删除日志任务
const data = await systemService.getLogRemoveFrequency();
if (data && data.info && data.info.frequency) {
@@ -17,7 +35,7 @@ export default async () => {
name: '删除日志',
command: `ql rmlog ${data.info.frequency}`,
};
await scheduleService.createIntervalTask(cron, {
scheduleService.createIntervalTask(cron, {
days: data.info.frequency,
runImmediately: true,
});
@@ -26,6 +44,11 @@ export default async () => {
// 运行所有订阅
const subs = await subscriptionService.list();
for (const sub of subs) {
await subscriptionService.handleTask(sub, true, true, true);
await subscriptionService.handleTask(
sub,
!sub.is_disabled,
true,
!sub.is_disabled,
);
}
};
+123 -19
View File
@@ -10,6 +10,7 @@ import { promises, existsSync } from 'fs';
import { promisify } from 'util';
import { Op } from 'sequelize';
import path from 'path';
import dayjs from 'dayjs';
@Service()
export default class CronService {
@@ -112,8 +113,56 @@ export default class CronService {
}
}
public async crontabs(searchText?: string): Promise<Crontab[]> {
let query = {};
private formatViewQuery(query: any, viewQuery: any) {
if (viewQuery.filters && viewQuery.filters.length > 0) {
for (const col of viewQuery.filters) {
const { property, value, operation } = col;
let operate = null;
switch (operation) {
case 'Reg':
operate = Op.like;
break;
case 'NotReg':
operate = Op.notLike;
break;
case 'In':
query[Op.or] = [
{
[property]: value,
},
property === 'status' && value.includes(2)
? { isDisabled: 1 }
: {},
];
break;
case 'Nin':
query[Op.and] = [
{
[property]: {
[Op.notIn]: value,
},
},
property === 'status' && value.includes(2)
? { isDisabled: { [Op.ne]: 1 } }
: {},
];
break;
default:
break;
}
if (operate) {
query[property] = {
[Op.or]: [
{ [operate]: `%${value}%` },
{ [operate]: `%${encodeURIComponent(value)}%` },
],
};
}
}
}
}
private formatSearchText(query: any, searchText: string | undefined) {
if (searchText) {
const textArray = searchText.split(':');
switch (textArray[0]) {
@@ -157,12 +206,58 @@ export default class CronService {
break;
}
}
try {
const result = await CrontabModel.findAll({
}
private formatViewSort(order: string[][], viewQuery: any) {
if (viewQuery.sorts && viewQuery.sorts.length > 0) {
for (const { property, type } of viewQuery.sorts) {
order.unshift([property, type]);
}
}
}
public async crontabs(params?: {
searchValue: string;
page: string;
size: string;
sortField: string;
sortType: string;
queryString: string;
}): Promise<{ data: Crontab[]; total: number }> {
const searchText = params?.searchValue;
const page = Number(params?.page || '0');
const size = Number(params?.size || '0');
const sortField = params?.sortField || '';
const sortType = params?.sortType || '';
const viewQuery = JSON.parse(params?.queryString || '{}');
let query: any = {};
let order = [
['isPinned', 'DESC'],
['isDisabled', 'ASC'],
['status', 'ASC'],
['createdAt', 'DESC'],
];
this.formatViewQuery(query, viewQuery);
this.formatSearchText(query, searchText);
this.formatViewSort(order, viewQuery);
if (sortType && sortField) {
order.unshift([sortField, sortType]);
}
let condition: any = {
where: query,
order: [['createdAt', 'DESC']],
});
return result as any;
order: order,
};
if (page && size) {
condition.offset = (page - 1) * size;
condition.limit = size;
}
try {
const result = await CrontabModel.findAll(condition);
const count = await CrontabModel.count({ where: query });
return { data: result, total: count };
} catch (error) {
throw error;
}
@@ -197,13 +292,21 @@ export default class CronService {
const err = await this.killTask(doc.command);
const absolutePath = path.resolve(config.logPath, `${doc.log_path}`);
const logFileExist = doc.log_path && (await fileExist(absolutePath));
const endTime = dayjs();
const diffTimeStr = doc.last_execution_time
? `,耗时 ${endTime.diff(
dayjs(doc.last_execution_time * 1000),
'second',
)}`
: '';
if (logFileExist) {
const str = err ? `\n${err}` : '';
fs.appendFileSync(
`${absolutePath}`,
`${str}\n## 执行结束... ${new Date()
.toLocaleString('zh', { hour12: false })
.replace(' 24:', ' 00:')} `,
`${str}\n## 执行结束... ${endTime.format(
'YYYY-MM-DD HH:mm:ss',
)}${diffTimeStr}`,
);
}
}
@@ -228,6 +331,7 @@ export default class CronService {
const killLogs = [];
if (pids && pids.length > 0) {
// node 执行脚本时还会有10个子进程,但是ps -ef中不存在,所以截取前三个
pids = pids.slice(0, 3);
for (const id of pids) {
const c = `kill -9 ${id.slice(1)}`;
try {
@@ -269,7 +373,13 @@ export default class CronService {
if (!cmdStr.includes('task ') && !cmdStr.includes('ql ')) {
cmdStr = `task ${cmdStr}`;
}
if (cmdStr.endsWith('.js')) {
if (
cmdStr.endsWith('.js') ||
cmdStr.endsWith('.py') ||
cmdStr.endsWith('.pyc') ||
cmdStr.endsWith('.sh') ||
cmdStr.endsWith('.ts')
) {
cmdStr = `${cmdStr} now`;
}
@@ -292,16 +402,10 @@ export default class CronService {
cp.on('exit', async (code, signal) => {
this.logger.info(
`${command} pid: ${cp.pid} exit ${code} signal ${signal}`,
`任务 ${command} 进程id: ${cp.pid} 退出,退出码 ${code}`,
);
await CrontabModel.update(
{ status: CrontabStatus.idle, pid: undefined },
{ where: { id } },
);
resolve();
});
cp.on('close', async (code) => {
this.logger.info(`${command} pid: ${cp.pid} closed ${code}`);
await CrontabModel.update(
{ status: CrontabStatus.idle, pid: undefined },
{ where: { id } },
@@ -431,7 +535,7 @@ export default class CronService {
private async set_crontab(needReloadSchedule: boolean = false) {
const tabs = await this.crontabs();
var crontab_string = '';
tabs.forEach((tab) => {
tabs.data.forEach((tab) => {
const _schedule = tab.schedule && tab.schedule.split(/ +/);
if (tab.isDisabled === 1 || _schedule!.length !== 5) {
crontab_string += '# ';
+92
View File
@@ -0,0 +1,92 @@
import { Service, Inject } from 'typedi';
import winston from 'winston';
import { CrontabView, CrontabViewModel } from '../data/cronView';
import { initEnvPosition } from '../data/env';
@Service()
export default class CronViewService {
constructor(@Inject('logger') private logger: winston.Logger) {}
public async create(payload: CrontabView): Promise<CrontabView> {
let position = initEnvPosition;
const views = await this.list();
if (views && views.length > 0 && views[views.length - 1].position) {
position = views[views.length - 1].position as number;
}
position = position / 2;
const tab = new CrontabView({ ...payload, position });
const doc = await this.insert(tab);
return doc;
}
public async insert(payload: CrontabView): Promise<CrontabView> {
return await CrontabViewModel.create(payload, { returning: true });
}
public async update(payload: CrontabView): Promise<CrontabView> {
const newDoc = await this.updateDb(payload);
return newDoc;
}
public async updateDb(payload: CrontabView): Promise<CrontabView> {
await CrontabViewModel.update(payload, { where: { id: payload.id } });
return await this.getDb({ id: payload.id });
}
public async remove(ids: number[]) {
await CrontabViewModel.destroy({ where: { id: ids } });
}
public async list(): Promise<CrontabView[]> {
try {
const result = await CrontabViewModel.findAll({
where: {},
order: [['position', 'DESC']],
});
return result;
} catch (error) {
throw error;
}
}
public async getDb(query: any): Promise<CrontabView> {
const doc: any = await CrontabViewModel.findOne({ where: { ...query } });
return doc && (doc.get({ plain: true }) as CrontabView);
}
public async disabled(ids: number[]) {
await CrontabViewModel.update({ isDisabled: 1 }, { where: { id: ids } });
}
public async enabled(ids: number[]) {
await CrontabViewModel.update({ isDisabled: 0 }, { where: { id: ids } });
}
public async move({
id,
fromIndex,
toIndex,
}: {
fromIndex: number;
toIndex: number;
id: number;
}): Promise<CrontabView> {
let targetPosition: number;
const isUpward = fromIndex > toIndex;
const views = await this.list();
if (toIndex === 0 || toIndex === views.length - 1) {
targetPosition = isUpward
? views[0].position * 2
: views[toIndex].position / 2;
} else {
targetPosition = isUpward
? (views[toIndex].position + views[toIndex - 1].position) / 2
: (views[toIndex].position + views[toIndex + 1].position) / 2;
}
const newDoc = await this.update({
id,
position: targetPosition,
});
return newDoc;
}
}
+14 -20
View File
@@ -14,6 +14,7 @@ import { spawn } from 'child_process';
import SockService from './sock';
import { Op } from 'sequelize';
import { concurrentRun } from '../config/util';
import dayjs from 'dayjs';
@Service()
export default class DependenceService {
@@ -161,20 +162,17 @@ export default class DependenceService {
)[dependencies[0].type as any];
const actionText = isInstall ? '安装' : '删除';
const depIds = dependencies.map((x) => x.id) as number[];
const startTime = Date.now();
const startTime = dayjs();
const message = `开始${actionText}依赖 ${depNames},开始时间 ${startTime.format(
'YYYY-MM-DD HH:mm:ss',
)}\n\n`;
this.sockService.sendMessage({
type: socketMessageType,
message: `开始${actionText}依赖 ${depNames},开始时间 ${new Date(
startTime,
).toLocaleString()}\n\n`,
message,
references: depIds,
});
await this.updateLog(
depIds,
`开始${actionText}依赖 ${depNames},开始时间 ${new Date(
startTime,
).toLocaleString()}\n\n`,
);
await this.updateLog(depIds, message);
const cp = spawn(`${depRunCommand} ${depNames}`, { shell: '/bin/bash' });
@@ -207,23 +205,19 @@ export default class DependenceService {
});
cp.on('close', async (code) => {
const endTime = Date.now();
const endTime = dayjs();
const isSucceed = code === 0;
const resultText = isSucceed ? '成功' : '失败';
const message = `\n依赖${actionText}${resultText},结束时间 ${endTime.format(
'YYYY-MM-DD HH:mm:ss',
)},耗时 ${endTime.diff(startTime, 'second')}`;
this.sockService.sendMessage({
type: socketMessageType,
message: `\n依赖${actionText}${resultText},结束时间 ${new Date(
endTime,
).toLocaleString()},耗时 ${(endTime - startTime) / 1000}`,
message,
references: depIds,
});
await this.updateLog(
depIds,
`\n依赖${actionText}${resultText},结束时间 ${new Date(
endTime,
).toLocaleString()},耗时 ${(endTime - startTime) / 1000}`,
);
await this.updateLog(depIds, message);
let status = null;
if (isSucceed) {
+1 -1
View File
@@ -168,7 +168,7 @@ export default class EnvService {
.filter((x) => x.status !== EnvStatus.disabled)
.map('value')
.join('&')
.replace(/(\\)[^\n]/g, '\\\\')
.replace(/(\\)[^n]/g, '\\\\')
.replace(/(\\$)/, '\\\\')
.replace(/"/g, '\\"')
.trim();
+2 -2
View File
@@ -245,7 +245,7 @@ export default class NotificationService {
const [corpid, corpsecret, touser, agentid, thumb_media_id = '1'] =
weWorkAppKey.split(',');
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken`;
const { access_token } = await got
const tokenRes: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
@@ -296,7 +296,7 @@ export default class NotificationService {
const res: any = await got
.post(
`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${access_token}`,
`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${tokenRes.access_token}`,
{
timeout: this.timeout,
retry: 0,
+26 -1
View File
@@ -90,7 +90,9 @@ export default class OpenService {
}
try {
const result = await this.find(condition);
return result.map((x) => ({ ...x, tokens: [] }));
return result
.filter((x) => x.name !== 'system')
.map((x) => ({ ...x, tokens: [] }));
} catch (error) {
throw error;
}
@@ -142,4 +144,27 @@ export default class OpenService {
return { code: 400, message: 'client_id或client_seret有误' };
}
}
public async generateSystemToken(): Promise<{
value: string;
expiration: number;
}> {
let systemApp = (await AppModel.findOne({
where: { name: 'system' },
})) as App;
if (!systemApp) {
systemApp = await this.create({
name: 'system',
scopes: ['crons', 'system'],
} as App);
}
const { data } = await this.authToken({
client_id: systemApp.client_id,
client_secret: systemApp.client_secret,
});
return {
...data,
value: data.token,
};
}
}
+5 -3
View File
@@ -18,6 +18,7 @@ interface ScheduleTaskType {
}
export interface TaskCallbacks {
onBefore?: (startTime: dayjs.Dayjs) => Promise<void>;
onStart?: (
cp: ChildProcessWithoutNullStreams,
startTime: dayjs.Dayjs,
@@ -45,6 +46,8 @@ export default class ScheduleService {
return new Promise(async (resolve, reject) => {
try {
const startTime = dayjs();
await callbacks.onBefore?.(startTime);
const cp = spawn(command, { shell: '/bin/bash' });
// TODO:
@@ -76,13 +79,12 @@ export default class ScheduleService {
cp.on('exit', async (code, signal) => {
this.logger.info(
`${command} pid: ${cp.pid} exit ${code} signal ${signal}`,
`任务 ${command} 进程id: ${cp.pid} 退出,退出码 ${code}`,
);
});
cp.on('close', async (code) => {
const endTime = dayjs();
this.logger.info(`${command} pid: ${cp.pid} closed ${code}`);
await callbacks.onEnd?.(
cp,
endTime,
@@ -166,7 +168,7 @@ export default class ScheduleService {
);
const job = new LongIntervalJob(
{ ...schedule, runImmediately: false },
{ runImmediately: false, ...schedule },
task,
_id,
);
+28 -26
View File
@@ -1,8 +1,11 @@
import { Service, Inject } from 'typedi';
import winston from 'winston';
import { spawn } from 'child_process';
import fs from 'fs';
import path from 'path';
import SockService from './sock';
import CronService from './cron';
import ScheduleService, { TaskCallbacks } from './schedule';
import config from '../config';
@Service()
export default class ScriptService {
@@ -10,44 +13,43 @@ export default class ScriptService {
@Inject('logger') private logger: winston.Logger,
private sockService: SockService,
private cronService: CronService,
private scheduleService: ScheduleService,
) {}
public async runScript(path: string) {
const cp = spawn(`task -l ${path} now`, { shell: '/bin/bash' });
cp.stdout.on('data', (data) => {
private taskCallbacks(filePath: string): TaskCallbacks {
return {
onEnd: async (cp, endTime, diff) => {
try {
fs.unlinkSync(filePath);
} catch (error) {}
},
onError: async (message: string) => {
this.sockService.sendMessage({
type: 'manuallyRunScript',
message: data.toString(),
message,
});
});
cp.stderr.on('data', (data) => {
},
onLog: async (message: string) => {
this.sockService.sendMessage({
type: 'manuallyRunScript',
message: data.toString(),
});
message,
});
},
};
}
cp.on('error', (err) => {
this.sockService.sendMessage({
type: 'manuallyRunScript',
message: JSON.stringify(err),
});
});
cp.on('close', (err) => {
this.sockService.sendMessage({
type: 'manuallyRunScript',
message: `执行结束`,
});
});
public async runScript(filePath: string) {
const relativePath = path.relative(config.scriptPath, filePath);
const command = `task -l ${relativePath} now`;
this.scheduleService.runTask(command, this.taskCallbacks(filePath));
return { code: 200 };
}
public async stopScript(path: string) {
const err = await this.cronService.killTask(`task -l ${path} now`);
public async stopScript(filePath: string) {
const relativePath = path.relative(config.scriptPath, filePath);
const err = await this.cronService.killTask(`task -l ${relativePath} now`);
const str = err ? `\n${err}` : '';
this.sockService.sendMessage({
type: 'manuallyRunScript',
+13 -5
View File
@@ -23,6 +23,13 @@ export default class SshKeyService {
}
}
private getConfigRegx(alias: string) {
return new RegExp(
`Host ${alias}\n.*[^StrictHostKeyChecking]*.*[\n]*.*StrictHostKeyChecking no`,
'g',
);
}
private removePrivateKeyFile(alias: string): void {
try {
fs.unlinkSync(`${this.sshPath}/${alias}`);
@@ -47,24 +54,25 @@ export default class SshKeyService {
}
}
private removeSshConfig(config: string) {
private removeSshConfig(alias: string) {
try {
const configRegx = this.getConfigRegx(alias);
const data = fs
.readFileSync(this.sshConfigFilePath, { encoding: 'utf8' })
.replace(config, '')
.replace(/\n\n+/, '\n\n');
.replace(configRegx, '')
.replace(/\n[\n]+/g, '\n');
fs.writeFileSync(this.sshConfigFilePath, data, {
encoding: 'utf8',
});
} catch (error) {
this.logger.error(`删除ssh配置文件${config}失败`, error);
this.logger.error(`删除ssh配置文件${alias}失败`, error);
}
}
public addSSHKey(key: string, alias: string, host: string): void {
this.generatePrivateKeyFile(alias, key);
const config = this.generateSingleSshConfig(alias, host);
this.removeSshConfig(config);
this.removeSshConfig(alias);
this.generateSshConfig([config]);
}
+16 -35
View File
@@ -28,6 +28,7 @@ import ScheduleService, { TaskCallbacks } from './schedule';
import { SimpleIntervalSchedule } from 'toad-scheduler';
import SockService from './sock';
import SshKeyService from './sshKey';
import dayjs from 'dayjs';
@Service()
export default class SubscriptionService {
@@ -41,23 +42,6 @@ export default class SubscriptionService {
public async list(searchText?: string): Promise<Subscription[]> {
let query = {};
if (searchText) {
const textArray = searchText.split(':');
switch (textArray[0]) {
case 'name':
case 'command':
case 'schedule':
case 'label':
const column = textArray[0] === 'label' ? 'labels' : textArray[0];
query = {
[column]: {
[Op.or]: [
{ [Op.like]: `%${textArray[1]}%` },
{ [Op.like]: `%${encodeURIComponent(textArray[1])}%` },
],
},
};
break;
default:
const reg = {
[Op.or]: [
{ [Op.like]: `%${searchText}%` },
@@ -70,18 +54,10 @@ export default class SubscriptionService {
name: reg,
},
{
command: reg,
},
{
schedule: reg,
},
{
labels: reg,
url: reg,
},
],
};
break;
}
}
try {
const result = await SubscriptionModel.findAll({
@@ -192,14 +168,13 @@ export default class SubscriptionService {
private taskCallbacks(doc: Subscription): TaskCallbacks {
return {
onStart: async (cp: ChildProcessWithoutNullStreams, startTime) => {
onBefore: async (startTime) => {
const logTime = startTime.format('YYYY-MM-DD-HH-mm-ss');
const logPath = `${doc.alias}/${logTime}.log`;
await SubscriptionModel.update(
{
status: SubscriptionStatus.running,
log_path: logPath,
pid: cp.pid,
},
{ where: { id: doc.id } },
);
@@ -220,9 +195,17 @@ export default class SubscriptionService {
(error.stderr && error.stderr.toString()) || JSON.stringify(error);
}
if (beforeStr) {
fs.appendFileSync(absolutePath, `${beforeStr}\n\n`);
fs.appendFileSync(absolutePath, `${beforeStr}\n`);
}
},
onStart: async (cp: ChildProcessWithoutNullStreams, startTime) => {
await SubscriptionModel.update(
{
pid: cp.pid,
},
{ where: { id: doc.id } },
);
},
onEnd: async (cp, endTime, diff) => {
const sub = await this.getDb({ id: doc.id });
const absolutePath = await this.handleLogPath(sub.log_path as string);
@@ -231,7 +214,7 @@ export default class SubscriptionService {
let afterStr = '';
try {
if (sub.sub_after) {
fs.appendFileSync(absolutePath, `\n\n## 执行after命令...\n`);
fs.appendFileSync(absolutePath, `\n\n## 执行after命令...\n\n`);
afterStr = await this.promiseExec(sub.sub_after);
}
} catch (error: any) {
@@ -286,7 +269,7 @@ export default class SubscriptionService {
public async update(payload: Subscription): Promise<Subscription> {
const newDoc = await this.updateDb(payload);
await this.handleTask(newDoc);
await this.handleTask(newDoc, !newDoc.is_disabled);
return newDoc;
}
@@ -360,16 +343,14 @@ export default class SubscriptionService {
this.logger.silly(error);
}
}
await this.handleTask(doc, false);
const command = this.formatCommand(doc);
const err = await this.killTask(command);
const absolutePath = await this.handleLogPath(doc.log_path as string);
const str = err ? `\n${err}` : '';
fs.appendFileSync(
`${absolutePath}`,
`${str}\n## 执行结束... ${new Date()
.toLocaleString('zh', { hour12: false })
.replace(' 24:', ' 00:')} `,
`${str}\n## 执行结束... ${dayjs().format('YYYY-MM-DD HH:mm:ss')} `,
);
}
+3 -3
View File
@@ -13,7 +13,7 @@ import { Request } from 'express';
import ScheduleService from './schedule';
import { spawn } from 'child_process';
import SockService from './sock';
import got from 'got';
import dayjs from 'dayjs';
@Service()
export default class UserService {
@@ -111,7 +111,7 @@ export default class UserService {
});
await this.notificationService.notify(
'登录通知',
`你于${new Date(timestamp).toLocaleString()}${address} ${
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${
req.platform
}端 登录成功,ip地址 ${ip}`,
);
@@ -140,7 +140,7 @@ export default class UserService {
});
await this.notificationService.notify(
'登录通知',
`你于${new Date(timestamp).toLocaleString()}${address} ${
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${
req.platform
}端 登录失败,ip地址 ${ip}`,
);
Executable
+34
View File
@@ -0,0 +1,34 @@
import 'reflect-metadata';
import OpenService from './services/open';
import { Container } from 'typedi';
import LoggerInstance from './loaders/logger';
import fs from 'fs';
import config from './config';
import path from 'path';
const tokenFile = path.join(config.configPath, 'token.json');
async function getToken() {
try {
Container.set('logger', LoggerInstance);
const openService = Container.get(OpenService);
const appToken = await openService.generateSystemToken();
console.log(appToken.value);
await writeFile({
value: appToken.value,
expiration: appToken.expiration,
});
} catch (error) {
console.log(error);
}
}
async function writeFile(data: any) {
return new Promise<void>((resolve, reject) => {
fs.writeFile(tokenFile, JSON.stringify(data), { encoding: 'utf8' }, () => {
resolve();
});
});
}
getToken();
+8 -7
View File
@@ -1,11 +1,12 @@
FROM node:alpine
FROM python:alpine
ARG QL_MAINTAINER="whyour"
LABEL maintainer="${QL_MAINTAINER}"
ARG QL_URL=https://github.com/${QL_MAINTAINER}/qinglong.git
ARG QL_BRANCH=master
ARG QL_BRANCH=develop
ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/share/pnpm:/root/.local/share/pnpm/global/5/node_modules \
ENV PNPM_HOME=/root/.local/share/pnpm \
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/share/pnpm:/root/.local/share/pnpm/global/5/node_modules:$PNPM_HOME \
LANG=zh_CN.UTF-8 \
SHELL=/bin/bash \
PS1="\u@\h:\w \$ " \
@@ -28,18 +29,18 @@ RUN set -x \
perl \
openssl \
nginx \
python3 \
nodejs \
jq \
openssh \
py3-pip \
npm \
&& rm -rf /var/cache/apk/* \
&& apk update \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& git config --global user.email "qinglong@@users.noreply.github.com" \
&& git config --global user.name "qinglong" \
&& git config --global http.postBuffer 524288000 \
&& npm install -g pnpm \
&& pnpm setup \
&& source ~/.bashrc \
&& pnpm add -g pm2 ts-node typescript tslib \
&& git clone -b ${QL_BRANCH} ${QL_URL} ${QL_DIR} \
&& cd ${QL_DIR} \
+1
View File
@@ -9,6 +9,7 @@ make_dir /etc/nginx/conf.d
make_dir /run/nginx
cp -fv $nginx_conf /etc/nginx/nginx.conf
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
sed -i "s,QL_BASE_URL,${qlBaseUrl},g" /etc/nginx/conf.d/front.conf
pm2 l &>/dev/null
echo
+10 -10
View File
@@ -1,8 +1,8 @@
upstream api {
upstream baseApi {
server 0.0.0.0:5600;
}
upstream public {
upstream publicApi {
server 0.0.0.0:5400;
}
@@ -16,28 +16,28 @@ server {
root /ql/static/dist;
ssl_session_timeout 5m;
location /api/public {
location QL_BASE_URL/api/public/ {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://public;
proxy_pass http://publicApi/api/public/;
}
location /api {
location QL_BASE_URL/api/ {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://api;
proxy_pass http://baseApi/api/;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
location /open {
location QL_BASE_URL/open/ {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://api;
proxy_pass http://baseApi/open/;
}
gzip on;
@@ -49,9 +49,9 @@ server {
gzip_buffers 16 8k;
gzip_http_version 1.0;
location / {
location QL_BASE_URL/ {
index index.html index.htm;
try_files $uri $uri/ /index.html;
try_files $uri $uri/ QL_BASE_URL/index.html;
}
location ~ .*\.(html)$ {
+7 -10
View File
@@ -31,17 +31,13 @@
"allowedVersions": {
"react": "17",
"react-dom": "17"
},
"ignoreMissing": [
"monaco-editor",
"react-router"
]
}
}
},
"dependencies": {
"@otplib/preset-default": "^12.0.1",
"@sentry/node": "^6.18.1",
"@sentry/tracing": "^6.18.1",
"@sentry/node": "^7.0.0",
"@sentry/tracing": "^7.0.0",
"body-parser": "^1.19.2",
"celebrate": "^15.0.1",
"chokidar": "^3.5.3",
@@ -63,10 +59,10 @@
"nodemailer": "^6.7.2",
"p-queue": "7.2.0",
"reflect-metadata": "^0.1.13",
"sequelize": "^6.17.0",
"sequelize": "^6.20.1",
"serve-handler": "^6.1.3",
"sockjs": "^0.3.24",
"sqlite3": "npm:@louislam/sqlite3@^15.0.3",
"sqlite3": "npm:@louislam/sqlite3@^15.0.6",
"toad-scheduler": "^1.6.0",
"typedi": "^0.10.0",
"uuid": "^8.3.2",
@@ -77,7 +73,7 @@
"@ant-design/icons": "^4.7.0",
"@ant-design/pro-layout": "^6.33.1",
"@monaco-editor/react": "^4.3.1",
"@sentry/react": "^6.18.1",
"@sentry/react": "^7.0.0",
"@types/body-parser": "^1.19.2",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
@@ -121,6 +117,7 @@
"react-split-pane": "^0.1.92",
"sockjs-client": "^1.6.0",
"ts-node": "^10.6.0",
"tslib": "^2.4.0",
"typescript": "^4.6.2",
"umi": "^3.5.21",
"umi-request": "^1.4.0",
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

+9 -1
View File
@@ -29,6 +29,14 @@ MaxConcurrentNum="5"
## 默认给javascript任务加随机延迟,如 RandomDelay="300" ,表示任务将在 1-300 秒内随机延迟一个秒数,然后再运行,取消延迟赋值为空
RandomDelay="300"
## 需要随机延迟运行任务的文件后缀,直接写后缀名即可,多个后缀用空格分开,例如: js py ts
## 默认仅给javascript任务加随机延迟,其它任务按定时规则准点运行。全部任务随机延迟赋值为空
RandomDelayFileExtensions="js"
## 每小时的第几分钟准点运行任务,当在这些时间运行任务时将忽略 RandomDelay 配置,不会被随机延迟
## 默认是第0分钟和第30分钟,例如21:00或21:30分的任务将会准点运行。不需要准点运行赋值为空
RandomDelayIgnoredMinutes="0 30"
## 如果你自己会写shell脚本,并且希望在每次运行 ql update 命令时,额外运行你的 shell 脚本,请赋值为 "true",默认为true
EnableExtraShell="true"
@@ -39,7 +47,7 @@ AutoStartBot=""
BotRepoUrl=""
## 安装bot依赖时指定pip源,默认使用清华源,如不需要源,设置此参数为空
PipMirror="https://pypi.tuna.tsinghua.edu.cn/simple"
PipMirror="https://pypi.doubanio.com/simple/"
## 通知环境变量
## 1. Server酱
+10 -3
View File
@@ -97,6 +97,13 @@ let IGOT_PUSH_KEY = '';
let PUSH_PLUS_TOKEN = '';
let PUSH_PLUS_USER = '';
// =======================================Cool Push设置区域=======================================
//官方文档:https://cp.xuthus.cc/docs
//QQ_SKEY: Cool Push登录授权后推送消息的调用代码Skey
//QQ_MODE: 推送模式详情请登录获取QQ_SKEY后见https://cp.xuthus.cc/feat
let QQ_SKEY = '';
let QQ_MODE = '';
//==========================云端环境变量的判断与接收=========================
if (process.env.GOTIFY_URL) {
GOTIFY_URL = process.env.GOTIFY_URL;
@@ -723,9 +730,9 @@ function qywxamNotify(text, desp) {
timeout,
};
$.post(options_accesstoken, (err, resp, data) => {
html = desp.replace(/\n/g, '<br/>');
var json = JSON.parse(data);
accesstoken = json.access_token;
let html = desp.replace(/\n/g, '<br/>');
let json = JSON.parse(data);
let accesstoken = json.access_token;
let options;
switch (QYWX_AM_AY[4]) {
+8 -26
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
get_token() {
token=$(cat $file_auth_user | jq -r .token)
token=$(cat $file_auth_token | jq -r .value)
}
add_cron_api() {
@@ -17,7 +17,7 @@ add_cron_api() {
fi
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
-H "Accept: application/json" \
-H "Authorization: Bearer $token" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" \
@@ -52,7 +52,7 @@ update_cron_api() {
fi
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
-X 'PUT' \
-H "Accept: application/json" \
-H "Authorization: Bearer $token" \
@@ -84,7 +84,7 @@ update_cron_command_api() {
fi
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
-X 'PUT' \
-H "Accept: application/json" \
-H "Authorization: Bearer $token" \
@@ -109,7 +109,7 @@ del_cron_api() {
local ids=$1
local currentTimeStamp=$(date +%s)
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons?t=$currentTimeStamp" \
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons?t=$currentTimeStamp" \
-X 'DELETE' \
-H "Accept: application/json" \
-H "Authorization: Bearer $token" \
@@ -130,24 +130,6 @@ del_cron_api() {
fi
}
get_user_info() {
local currentTimeStamp=$(date +%s)
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/user?t=$currentTimeStamp" \
-H 'Accept: */*' \
-H "Authorization: Bearer $token" \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36' \
-H 'Referer: http://0.0.0.0:5700/crontab' \
-H 'Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7' \
--compressed
)
code=$(echo $api | jq -r .code)
if [[ $code != 200 ]]; then
echo -e "请先登录!"
exit 0
fi
}
update_cron() {
local ids="$1"
local status="$2"
@@ -157,7 +139,7 @@ update_cron() {
local runningTime="${6:-0}"
local currentTimeStamp=$(date +%s)
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/crons/status?t=$currentTimeStamp" \
curl -s --noproxy "*" "http://0.0.0.0:5600/open/crons/status?t=$currentTimeStamp" \
-X 'PUT' \
-H "Accept: application/json" \
-H "Authorization: Bearer $token" \
@@ -172,7 +154,7 @@ update_cron() {
code=$(echo $api | jq -r .code)
message=$(echo $api | jq -r .message)
if [[ $code != 200 ]]; then
echo -e "\n## 更新任务状态失败(${message})\n" >> $log_path
echo -e "\n## 更新任务状态失败(${message})\n" >> $dir_log/$log_path
fi
}
@@ -181,7 +163,7 @@ notify_api() {
local content=$2
local currentTimeStamp=$(date +%s)
local api=$(
curl -s --noproxy "*" "http://0.0.0.0:5600/api/system/notify?t=$currentTimeStamp" \
curl -s --noproxy "*" "http://0.0.0.0:5600/open/system/notify?t=$currentTimeStamp" \
-X 'PUT' \
-H "Accept: application/json" \
-H "Authorization: Bearer $token" \
+2
View File
@@ -31,6 +31,7 @@ copy_dep() {
echo -e "---> 2. 复制nginx配置文件\n"
cp -fv $nginx_conf /etc/nginx/nginx.conf
cp -fv $nginx_app_conf /etc/nginx/conf.d/front.conf
sed -i "s,QL_BASE_URL,${qlBaseUrl},g" /etc/nginx/conf.d/front.conf
echo -e "---> 配置文件复制完成\n"
}
@@ -88,6 +89,7 @@ check_pm2() {
main() {
echo -e "=====> 开始检测"
npm i -g pnpm
pnpm add -g pm2
copy_dep
check_ql
+52 -7
View File
@@ -14,7 +14,6 @@ dir_log=$dir_data/log
dir_db=$dir_data/db
dir_dep=$dir_data/deps
dir_list_tmp=$dir_log/.tmp
dir_code=$dir_log/code
dir_update_log=$dir_log/update
ql_static_repo=$dir_repo/static
@@ -25,6 +24,7 @@ file_sharecode=$dir_config/sharecode.sh
file_config_user=$dir_config/config.sh
file_auth_sample=$dir_sample/auth.sample.json
file_auth_user=$dir_config/auth.json
file_auth_token=$dir_config/token.json
file_extra_shell=$dir_config/extra.sh
file_task_before=$dir_config/task_before.sh
file_task_after=$dir_config/task_after.sh
@@ -52,12 +52,10 @@ list_own_drop=$dir_list_tmp/own_drop.list
link_name=(
task
ql
notify
)
original_name=(
task.sh
update.sh
notify.sh
)
init_env() {
@@ -69,6 +67,7 @@ import_config() {
[[ -f $file_config_user ]] && . $file_config_user
[[ -f $file_env ]] && . $file_env
ql_base_url=${QlBaseUrl:-""}
command_timeout_time=${CommandTimeoutTime:-"1h"}
proxy_url=${ProxyUrl:-""}
file_extensions=${RepoFileExtensions:-"js py"}
@@ -101,7 +100,7 @@ make_dir() {
}
detect_termux() {
if [[ ${ANDROID_RUNTIME_ROOT}${ANDROID_ROOT} ]] || [[ $PATH == *com.termux* ]]; then
if [[ $PATH == *com.termux* ]]; then
is_termux=1
else
is_termux=0
@@ -248,11 +247,11 @@ fix_config() {
npm_install_sub() {
set_proxy
if [ $is_termux -eq 1 ]; then
npm install --production --no-bin-links --registry=https://registry.npm.taobao.org || npm install --production --no-bin-links
npm install --production --no-bin-links --registry=https://registry.npmmirror.com || npm install --production --no-bin-links
elif ! type pnpm &>/dev/null; then
npm install --production --registry=https://registry.npm.taobao.org || npm install --production
npm install --production --registry=https://registry.npmmirror.com || npm install --production
else
pnpm install --loglevel error --production --registry=https://registry.npm.taobao.org || pnpm install --production --loglevel error
pnpm install --loglevel error --production --registry=https://registry.npmmirror.com || pnpm install --production --loglevel error
fi
unset_proxy
}
@@ -380,6 +379,52 @@ reload_pm2() {
pm2 start $dir_static/build/public.js -n public --source-map-support --time &>/dev/null
}
diff_time() {
local format="$1"
local begin_time="$2"
local end_time="$3"
if [[ $is_macos -eq 1 ]]; then
diff_time=$(($(date -j -f "$format" "$end_time" +%s) - $(date -j -f "$format" "$begin_time" +%s)))
else
diff_time=$(($(date +%s -d "$end_time") - $(date +%s -d "$begin_time")))
fi
echo "$diff_time"
}
format_time() {
local format="$1"
local time="$2"
if [[ $is_macos -eq 1 ]]; then
echo $(date -j -f "$format" "$time" "+%Y-%m-%d %H:%M:%S")
else
echo $(date -d "$time" "+%Y-%m-%d %H:%M:%S")
fi
}
format_log_time() {
local format="$1"
local time="$2"
if [[ $is_macos -eq 1 ]]; then
echo $(date -j -f "$format" "$time" "+%Y-%m-%d-%H-%M-%S")
else
echo $(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
fi
}
format_timestamp() {
local format="$1"
local time="$2"
if [[ $is_macos -eq 1 ]]; then
echo $(date -j -f "$format" "$time" "+%s")
else
echo $(date -d "$time" "+%s")
fi
}
init_env
detect_termux
detect_macos
+56 -33
View File
@@ -24,13 +24,30 @@ define_program() {
random_delay() {
local random_delay_max=$RandomDelay
if [[ $random_delay_max ]] && [[ $random_delay_max -gt 0 ]]; then
local current_min=$(date "+%-M")
if [[ $current_min -ne 0 ]] && [[ $current_min -ne 30 ]]; then
delay_second=$(($(gen_random_num $random_delay_max) + 1))
local file_param=$1
local file_extensions=${RandomDelayFileExtensions-"js"}
local ignored_minutes=${RandomDelayIgnoredMinutes-"0 30"}
if [[ -n $file_extensions ]]; then
if ! echo "$file_param" | grep -qE "\.${file_extensions// /$|\\.}$"; then
# echo -e "\n当前文件需要准点运行, 放弃随机延迟\n"
return
fi
fi
local current_min
current_min=$(date "+%-M")
for minute in $ignored_minutes; do
if [[ $current_min -eq $minute ]]; then
# echo -e "\n当前时间需要准点运行, 放弃随机延迟\n"
return
fi
done
local delay_second=$(($(gen_random_num "$random_delay_max") + 1))
echo -e "\n命令未添加 \"now\",随机延迟 $delay_second 秒后再执行任务,如需立即终止,请按 CTRL+C...\n"
sleep $delay_second
fi
fi
}
## scripts目录下所有可运行脚本数组
@@ -78,14 +95,12 @@ run_nohup() {
run_normal() {
local file_param=$1
define_program "$file_param"
if [[ $file_param == *.js ]]; then
if [[ $# -eq 1 ]]; then
random_delay
fi
random_delay "$file_param"
fi
local time=$(date)
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
local time=$(date "+$time_format")
log_time=$(format_log_time "$time_format" "$time")
log_dir_tmp="${file_param##*/}"
if [[ $file_param =~ "/" ]]; then
if [[ $file_param == /* ]]; then
@@ -99,12 +114,13 @@ run_normal() {
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log"
cmd="&>> $dir_log/$log_path"
cmd=">> $dir_log/$log_path 2>&1"
[[ "$show_log" == "true" ]] && cmd=""
make_dir "$dir_log/$log_dir"
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
local begin_timestamp=$(date -d "$time" "+%s")
local begin_time=$(format_time "$time_format" "$time")
local begin_timestamp=$(format_timestamp "$time_format" "$time")
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
@@ -118,12 +134,13 @@ run_normal() {
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
eval timeout -k 10s $command_timeout_time $which_program $file_param $cmd
eval $timeoutCmd $which_program $file_param $cmd
eval . $file_task_after "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
local end_timestamp=$(date "+%s")
local diff_time=$(( $end_timestamp - $begin_timestamp ))
local diff_time=$(expr $end_timestamp - $begin_timestamp)
[[ $id ]] && update_cron "\"$id\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time" $cmd
}
@@ -154,8 +171,8 @@ run_concurrent() {
[[ ! -z $cookieStr ]] && export ${env_param}=${cookieStr}
define_program "$file_param"
local time=$(date)
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
local time=$(date "+$time_format")
log_time=$(format_log_time "$time_format" "$time")
log_dir_tmp="${file_param##*/}"
if [[ $file_param =~ "/" ]]; then
if [[ $file_param == /* ]]; then
@@ -169,12 +186,12 @@ run_concurrent() {
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log"
cmd="&>> $dir_log/$log_path"
cmd=">> $dir_log/$log_path 2>&1"
[[ "$show_log" == "true" ]] && cmd=""
make_dir "$dir_log/$log_dir"
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
local begin_timestamp=$(date -d "$time" "+%s")
local begin_time=$(format_time "$time_format" "$time")
local begin_timestamp=$(format_timestamp "$time_format" "$time")
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
@@ -196,7 +213,7 @@ run_concurrent() {
for i in "${!array[@]}"; do
export ${env_param}=${array[i]}
single_log_path="$dir_log/$log_dir/${single_log_time}_$((i + 1)).log"
timeout -k 10s $command_timeout_time $which_program $file_param &>$single_log_path &
eval $timeoutCmd $which_program $file_param &>$single_log_path &
done
wait
@@ -224,8 +241,8 @@ run_designated() {
fi
define_program "$file_param"
local time=$(date)
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
local time=$(date "+$time_format")
log_time=$(format_log_time "$time_format" "$time")
log_dir_tmp="${file_param##*/}"
if [[ $file_param =~ "/" ]]; then
if [[ $file_param == /* ]]; then
@@ -239,12 +256,12 @@ run_designated() {
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log"
cmd="&>> $dir_log/$log_path"
cmd=">> $dir_log/$log_path 2>&1"
[[ "$show_log" == "true" ]] && cmd=""
make_dir "$dir_log/$log_dir"
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
local begin_timestamp=$(date -d "$time" "+%s")
local begin_time=$(format_time "$time_format" "$time")
local begin_timestamp=$(format_timestamp "$time_format" "$time")
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
@@ -274,7 +291,7 @@ run_designated() {
cd ${relative_path}
file_param=${file_param/$relative_path\//}
fi
eval timeout -k 10s $command_timeout_time $which_program $file_param $cmd
eval $timeoutCmd $which_program $file_param $cmd
eval . $file_task_after "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
@@ -288,8 +305,8 @@ run_designated() {
run_else() {
local file_param="$1"
define_program "$file_param"
local time=$(date)
log_time=$(date -d "$time" "+%Y-%m-%d-%H-%M-%S")
local time=$(date "+$time_format")
log_time=$(format_log_time "$time_format" "$time")
log_dir_tmp="${file_param##*/}"
if [[ $file_param =~ "/" ]]; then
if [[ $file_param == /* ]]; then
@@ -303,12 +320,12 @@ run_else() {
[[ $log_dir_tmp_path ]] && log_dir_tmp="${log_dir_tmp_path}_${log_dir_tmp}"
log_dir="${log_dir_tmp%.*}"
log_path="$log_dir/$log_time.log"
cmd="&>> $dir_log/$log_path"
cmd=">> $dir_log/$log_path 2>&1"
[[ "$show_log" == "true" ]] && cmd=""
make_dir "$dir_log/$log_dir"
local begin_time=$(date -d "$time" "+%Y-%m-%d %H:%M:%S")
local begin_timestamp=$(date -d "$time" "+%s")
local begin_time=$(format_time "$time_format" "$time")
local begin_timestamp=$(format_timestamp "$time_format" "$time")
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
@@ -325,7 +342,7 @@ run_else() {
fi
shift
eval timeout -k 10s $command_timeout_time $which_program "$file_param" "$@" $cmd
eval $timeoutCmd $which_program "$file_param" "$@" $cmd
eval . $file_task_after "$file_param" "$@" $cmd
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
@@ -348,7 +365,13 @@ main() {
done
[[ "$show_log" == "true" ]] && shift $(($OPTIND - 1))
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
timeoutCmd=""
if type timeout &>/dev/null; then
timeoutCmd="timeout -k 10s $command_timeout_time "
fi
time_format="%Y-%m-%d %H:%M:%S"
if [[ $1 == *.js ]] || [[ $1 == *.py ]] || [[ $1 == *.pyc ]] || [[ $1 == *.sh ]] || [[ $1 == *.ts ]]; then
case $# in
1)
run_normal "$1"
+11 -9
View File
@@ -12,14 +12,16 @@ diff_cron() {
local list_task="$2"
local list_add="$3"
local list_drop="$4"
if [[ -s $list_task ]]; then
if [[ -s $list_task ]] && [[ -s $list_scripts ]]; then
grep -vwf $list_task $list_scripts >$list_add
elif [[ ! -s $list_task ]] && [[ -s $list_scripts ]]; then
grep -vwf $list_scripts $list_task >$list_drop
fi
if [[ ! -s $list_task ]] && [[ -s $list_scripts ]]; then
cp -f $list_scripts $list_add
fi
if [[ -s $list_scripts ]]; then
grep -vwf $list_scripts $list_task >$list_drop
else
if [[ ! -s $list_scripts ]] && [[ -s $list_task ]]; then
cp -f $list_task $list_drop
fi
}
@@ -305,7 +307,6 @@ patch_version() {
fi
# 兼容pnpm@7
npm i -g pnpm
pnpm setup
source ~/.bashrc
@@ -387,6 +388,9 @@ gen_list_repo() {
local index=0
if [[ $6 ]]; then
file_extensions="$6"
if [[ $file_extensions =~ "|" ]]; then
file_extensions=$(echo $file_extensions | sed 's/|/ /g')
fi
fi
for extension in $file_extensions; do
if [[ $index -eq 0 ]]; then
@@ -487,7 +491,6 @@ main() {
run_extra_shell >>$log_path
;;
repo)
get_user_info
get_uniq_path "$p2" "$p6"
if [[ -n $p2 ]]; then
update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7"
@@ -497,7 +500,6 @@ main() {
fi
;;
raw)
get_user_info
get_uniq_path "$p2"
if [[ -n $p2 ]]; then
update_raw "$p2"
@@ -539,7 +541,7 @@ main() {
;;
esac
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
local diff_time=$(($(date +%s -d "$end_time") - $(date +%s -d "$begin_time")))
local diff_time=$(diff_time "%Y-%m-%d %H:%M:%S" "$begin_time" "$end_time")
if [[ $p1 != "repo" ]] && [[ $p1 != "raw" ]]; then
echo -e "\n## 执行结束... $end_time 耗时 $diff_time" >>$log_path
cat $log_path
+26 -8
View File
@@ -56,7 +56,13 @@
overflow: auto;
height: 100%;
background-color: @component-background;
padding: 16px;
padding: 12px;
}
}
.ql-container-wrapper-has-tab {
.ant-pro-grid-content.wide .ant-pro-page-container-children-content {
padding-top: 0;
}
}
@@ -136,7 +142,7 @@
flex: 1;
.ant-pro-grid-content-children {
height: calc(100% - 36px);
height: 100%;
> div,
.log-container,
@@ -264,16 +270,24 @@
}
}
.ant-pro-page-container-children-content {
margin: 18px 18px 18px;
}
.ant-pro-basicLayout-content {
margin: 18px;
margin: 12px;
.ant-pro-page-container {
margin: -18px -18px -18px;
margin: -12px;
}
.ant-pro-page-container-warp .ant-page-header {
border-bottom: 1px solid #eee;
}
.ant-pro-page-container-children-content {
margin: 0;
}
}
.ant-pro-global-header {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.03);
}
.ant-menu-item.ant-pro-sider-collapsed-button {
@@ -312,6 +326,10 @@
background-color: #373739;
}
.ant-layout-sider {
border-right: 1px solid rgba(0, 0, 0, 0.06) !important;
}
.ant-pro-sider-logo {
padding: 16px 8px !important;
+3 -4
View File
@@ -186,8 +186,7 @@ export default function (props: any) {
if (
['/login', '/initialization', '/error'].includes(props.location.pathname)
) {
document.title = `${
(config.documentTitleMap as any)[props.location.pathname]
document.title = `${(config.documentTitleMap as any)[props.location.pathname]
} - 控制面板`;
if (
systemInfo?.isInitialized &&
@@ -282,7 +281,7 @@ export default function (props: any) {
shape="square"
size="small"
icon={<UserOutlined />}
src={`/api/static/${user.avatar}`}
src={user.avatar ? `/api/static/${user.avatar}` : ''}
/>
<span style={{ marginLeft: 5 }}>{user.username}</span>
</span>
@@ -304,7 +303,7 @@ export default function (props: any) {
shape="square"
size="small"
icon={<UserOutlined />}
src={`/api/static/${user.avatar}`}
src={user.avatar ? `/api/static/${user.avatar}` : ''}
/>
<span style={{ marginLeft: 5 }}>{user.username}</span>
</span>
+14 -1
View File
@@ -20,6 +20,7 @@ import {
FileOutlined,
PlayCircleOutlined,
PauseCircleOutlined,
FullscreenOutlined,
} from '@ant-design/icons';
import { CrontabStatus } from './index';
import { diffTime } from '@/utils/date';
@@ -106,7 +107,7 @@ const CronDetailModal = ({
glyphMargin: false,
wordWrap: 'on',
}}
onMount={(editor) => {
onMount={(editor, monaco) => {
editorRef.current = editor;
}}
/>
@@ -352,6 +353,11 @@ const CronDetailModal = ({
});
};
const fullscreen = () => {
const editorElement = editorRef.current._domElement as HTMLElement;
editorElement.parentElement?.requestFullscreen();
};
useEffect(() => {
if (cron && cron.id) {
setCurrentCron(cron);
@@ -529,6 +535,7 @@ const CronDetailModal = ({
}}
tabBarExtraContent={
activeTabKey === 'script' && (
<>
<Button
type="primary"
style={{ marginRight: 8 }}
@@ -536,6 +543,12 @@ const CronDetailModal = ({
>
</Button>
<Button
type="primary"
icon={<FullscreenOutlined />}
onClick={fullscreen}
/>
</>
)
}
>
+73
View File
@@ -101,3 +101,76 @@
background: #fafafa;
}
}
.crontab-view {
.ant-tabs-nav-wrap {
flex: unset !important;
}
.ant-tabs-nav-operations {
position: absolute;
visibility: hidden;
pointer-events: none;
}
.view-more {
margin-left: 32px;
padding: 8px 0;
cursor: pointer;
.ant-tabs-ink-bar {
width: 0;
}
&:hover,
&:focus-visible {
color: #1890ff;
.ant-tabs-ink-bar {
width: 50px;
}
}
&.active {
color: #1890ff;
.ant-tabs-ink-bar {
width: 50px;
}
}
}
&.more-active {
.ant-tabs-nav-list {
.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
color: unset;
}
.ant-tabs-ink-bar {
width: 0 !important;
}
}
}
}
.view-create-modal-filters {
display: flex;
.ant-space-item:nth-child(3) {
flex: 1;
}
}
tr.drop-over-downward td {
border-bottom: 2px dashed #1890ff;
}
tr.drop-over-upward td {
border-top: 2px dashed #1890ff;
}
.view-manage-modal {
.ant-modal-body {
padding-top: 10px;
}
}
+230 -65
View File
@@ -12,6 +12,8 @@ import {
Typography,
Input,
Popover,
Tabs,
TablePaginationConfig,
} from 'antd';
import {
ClockCircleOutlined,
@@ -27,6 +29,11 @@ import {
PauseCircleOutlined,
FieldTimeOutlined,
PushpinOutlined,
DownOutlined,
SettingOutlined,
PlusOutlined,
UnorderedListOutlined,
CheckOutlined,
} from '@ant-design/icons';
import config from '@/utils/config';
import { PageContainer } from '@ant-design/pro-layout';
@@ -39,6 +46,10 @@ import { diffTime } from '@/utils/date';
import { getTableScroll } from '@/utils/index';
import { history } from 'umi';
import './index.less';
import ViewCreateModal from './viewCreateModal';
import ViewManageModal from './viewManageModal';
import pagination from 'antd/lib/pagination';
import { FilterValue, SorterResult } from 'antd/lib/table/interface';
const { Text, Paragraph } = Typography;
const { Search } = Input;
@@ -122,8 +133,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
</>
),
sorter: {
compare: (a: any, b: any) => a.name.localeCompare(b.name),
multiple: 2,
compare: (a: any, b: any) => a?.name?.localeCompare(b?.name),
},
},
{
@@ -148,7 +158,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
},
sorter: {
compare: (a: any, b: any) => a.command.localeCompare(b.command),
multiple: 3,
},
},
{
@@ -159,7 +168,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
align: 'center' as const,
sorter: {
compare: (a: any, b: any) => a.schedule.localeCompare(b.schedule),
multiple: 1,
},
},
{
@@ -346,12 +354,24 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
const [logCron, setLogCron] = useState<any>();
const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [pageSize, setPageSize] = useState(20);
const [pageConf, setPageConf] = useState<{
page: number;
size: number;
sorter: any;
}>({} as any);
const [viewConf, setViewConf] = useState<any>();
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
const [isDetailModalVisible, setIsDetailModalVisible] = useState(false);
const [detailCron, setDetailCron] = useState<any>();
const [searchValue, setSearchValue] = useState('');
const [total, setTotal] = useState<number>();
const [isCreateViewModalVisible, setIsCreateViewModalVisible] =
useState(false);
const [isViewManageModalVisible, setIsViewManageModalVisible] =
useState(false);
const [cronViews, setCronViews] = useState<any[]>([]);
const [enabledCronViews, setEnabledCronViews] = useState<any[]>([]);
const [moreMenuActive, setMoreMenuActive] = useState(false);
const goToScriptManager = (record: any) => {
const cmd = record.command.split(' ') as string[];
@@ -373,27 +393,25 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
const getCrons = () => {
setLoading(true);
const { page, size, sorter } = pageConf;
let url = `${config.apiPrefix}crons?searchValue=${searchText}&page=${page}&size=${size}`;
if (sorter && sorter.field) {
url += `&sortField=${sorter.field}&sortType=${
sorter.order === 'ascend' ? 'ASC' : 'DESC'
}`;
}
if (viewConf) {
url += `&queryString=${JSON.stringify({
filters: viewConf.filters,
sorts: viewConf.sorts,
})}`;
}
request
.get(`${config.apiPrefix}crons?searchValue=${searchText}`)
.then((data: any) => {
.get(url)
.then((_data: any) => {
const { data, total } = _data.data;
setValue(
data.data
.sort((a: any, b: any) => {
const sortA =
a.isPinned && a.status !== 0
? 5
: a.isDisabled && a.status !== 0
? 4
: a.status;
const sortB =
b.isPinned && b.status !== 0
? 5
: b.isDisabled && b.status !== 0
? 4
: b.status;
return CrontabSort[sortA] - CrontabSort[sortB];
})
.map((x) => {
data.map((x) => {
return {
...x,
nextRunTime: cron_parser
@@ -403,7 +421,7 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
};
}),
);
setCurrentPage(1);
setTotal(total);
})
.finally(() => setLoading(false));
};
@@ -743,11 +761,6 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
const rowSelection = {
selectedRowIds,
onChange: onSelectChange,
selections: [
Table.SELECTION_ALL,
Table.SELECTION_INVERT,
Table.SELECTION_NONE,
],
};
const delCrons = () => {
@@ -796,10 +809,14 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
});
};
const onPageChange = (page: number, pageSize: number | undefined) => {
setCurrentPage(page);
setPageSize(pageSize as number);
localStorage.setItem('pageSize', pageSize + '');
const onPageChange = (
pagination: TablePaginationConfig,
filters: Record<string, FilterValue | null>,
sorter: SorterResult<any> | SorterResult<any>[],
) => {
const { current, pageSize } = pagination;
setPageConf({ page: current as number, size: pageSize as number, sorter });
localStorage.setItem('pageSize', String(pageSize));
};
const getRowClassName = (record: any, index: number) => {
@@ -814,39 +831,36 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
}, [logCron]);
useEffect(() => {
getCrons();
setPageConf({ ...pageConf, page: 1 });
}, [searchText]);
useEffect(() => {
setPageSize(parseInt(localStorage.getItem('pageSize') || '20'));
if (pageConf.page && pageConf.size) {
getCrons();
}
}, [pageConf, viewConf]);
useEffect(() => {
if (viewConf && enabledCronViews && enabledCronViews.length > 0) {
const view = enabledCronViews.slice(2).find((x) => x.id === viewConf.id);
setMoreMenuActive(!!view);
}
}, [viewConf, enabledCronViews]);
useEffect(() => {
setPageConf({
page: 1,
size: parseInt(localStorage.getItem('pageSize') || '20'),
sorter: {},
});
setTimeout(() => {
setTableScrollHeight(getTableScroll());
});
getCronViews();
}, []);
return (
<PageContainer
className="ql-container-wrapper crontab-wrapper"
title="定时任务"
extra={[
<Search
placeholder="请输入名称或者关键词"
style={{ width: 'auto' }}
enterButton
allowClear
loading={loading}
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
onSearch={onSearch}
/>,
<Button key="2" type="primary" onClick={() => addCron()}>
</Button>,
]}
header={{
style: headerStyle,
}}
>
const panelContent = (
<>
{selectedRowIds.length > 0 && (
<div style={{ marginBottom: 16 }}>
<Button type="primary" style={{ marginBottom: 5 }} onClick={delCrons}>
@@ -906,15 +920,16 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
<Table
columns={columns}
pagination={{
current: currentPage,
onChange: onPageChange,
pageSize: pageSize,
current: pageConf.page,
pageSize: pageConf.size,
showSizeChanger: true,
simple: isPhone,
defaultPageSize: 20,
total,
showTotal: (total: number, range: number[]) =>
`${range[0]}-${range[1]} 条/总共 ${total}`,
pageSizeOptions: [20, 100, 500, 1000] as any,
pageSizeOptions: [10, 20, 50, 100, 200, 500, total || 10000].sort(
(a, b) => a - b,
),
}}
onRow={(record) => {
return {
@@ -931,7 +946,134 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
loading={loading}
rowSelection={rowSelection}
rowClassName={getRowClassName}
onChange={onPageChange}
/>
</>
);
const viewAction = (key: string) => {
switch (key) {
case 'new':
setIsCreateViewModalVisible(true);
break;
case 'manage':
setIsViewManageModalVisible(true);
break;
default:
tabClick(key);
break;
}
};
const menu = (
<Menu
onClick={({ key, domEvent }) => {
domEvent.stopPropagation();
viewAction(key);
}}
items={[
...[...enabledCronViews].slice(2).map((x) => ({
label: (
<Space style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{x.name}</span>
{viewConf?.id === x.id && (
<CheckOutlined style={{ color: '#1890ff' }} />
)}
</Space>
),
key: x.id,
icon: <UnorderedListOutlined />,
})),
{
type: 'divider',
},
{
label: '新建视图',
key: 'new',
icon: <PlusOutlined />,
},
{
label: '视图管理',
key: 'manage',
icon: <SettingOutlined />,
},
]}
/>
);
const getCronViews = () => {
setLoading(true);
request
.get(`${config.apiPrefix}crons/views`)
.then((data: any) => {
setCronViews(data.data);
setEnabledCronViews(data.data.filter((x) => !x.isDisabled));
})
.finally(() => {
setLoading(false);
});
};
const tabClick = (key: string) => {
const view = enabledCronViews.find((x) => x.id == key);
setViewConf(view ? view : null);
};
return (
<PageContainer
className="ql-container-wrapper crontab-wrapper ql-container-wrapper-has-tab"
title="定时任务"
extra={[
<Search
placeholder="请输入名称或者关键词"
style={{ width: 'auto' }}
enterButton
allowClear
loading={loading}
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
onSearch={onSearch}
/>,
<Button key="2" type="primary" onClick={() => addCron()}>
</Button>,
]}
header={{
style: headerStyle,
}}
>
<Tabs
defaultActiveKey="all"
size="small"
tabPosition="top"
className={`crontab-view ${moreMenuActive ? 'more-active' : ''}`}
tabBarExtraContent={
<Dropdown
overlay={menu}
trigger={['click']}
overlayStyle={{ minWidth: 200 }}
>
<div className={`view-more ${moreMenuActive ? 'active' : ''}`}>
<Space>
<DownOutlined />
</Space>
<div className="ant-tabs-ink-bar ant-tabs-ink-bar-animated"></div>
</div>
</Dropdown>
}
onTabClick={tabClick}
>
<Tabs.TabPane tab="全部任务" key="all">
{panelContent}
</Tabs.TabPane>
{[...enabledCronViews].slice(0, 2).map((x) => (
<Tabs.TabPane tab={x.name} key={x.id}>
{panelContent}
</Tabs.TabPane>
))}
</Tabs>
<CronLogModal
visible={isLogModalVisible}
handleCancel={() => {
@@ -964,6 +1106,29 @@ const Crontab = ({ headerStyle, isPhone, theme }: any) => {
theme={theme}
isPhone={isPhone}
/>
<ViewCreateModal
visible={isCreateViewModalVisible}
handleCancel={(data) => {
setIsCreateViewModalVisible(false);
getCronViews();
if (data && data.id === viewConf.id) {
setViewConf({ ...viewConf, ...data });
}
}}
/>
<ViewManageModal
cronViews={cronViews}
visible={isViewManageModalVisible}
handleCancel={() => {
setIsViewManageModalVisible(false);
}}
cronViewChange={(data) => {
getCronViews();
if (data && data.id === viewConf.id) {
setViewConf({ ...viewConf, ...data });
}
}}
/>
</PageContainer>
);
};
+263
View File
@@ -0,0 +1,263 @@
import React, { useEffect, useState } from 'react';
import {
Modal,
message,
Input,
Form,
Statistic,
Button,
Space,
Select,
} from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
const PROPERTIES = [
{ name: '命令', value: 'command' },
{ name: '名称', value: 'name' },
{ name: '定时规则', value: 'schedule' },
{ name: '状态', value: 'status' },
];
const OPERATIONS = [
{ name: '包含', value: 'Reg' },
{ name: '不包含', value: 'NotReg' },
{ name: '属于', value: 'In' },
{ name: '不属于', value: 'Nin' },
// { name: '等于', value: 'Eq' },
// { name: '不等于', value: 'Ne' },
// { name: '为空', value: 'IsNull' },
// { name: '不为空', value: 'NotNull' },
];
const SORTTYPES = [
{ name: '顺序', value: 'ASC' },
{ name: '倒序', value: 'DESC' },
];
const STATUS = [
{ name: '运行中', value: 0 },
{ name: '空闲中', value: 1 },
{ name: '已禁用', value: 2 },
];
const ViewCreateModal = ({
view,
handleCancel,
visible,
}: {
view?: any;
visible: boolean;
handleCancel: (param?: any) => void;
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [operationMap, setOperationMap] = useState<any>();
const handleOk = async (values: any) => {
setLoading(true);
const method = view ? 'put' : 'post';
try {
const { code, data } = await request[method](
`${config.apiPrefix}crons/views`,
{
data: view ? { ...values, id: view.id } : values,
},
);
if (code !== 200) {
message.error(data);
}
setLoading(false);
handleCancel(data);
} catch (error: any) {
setLoading(false);
}
};
useEffect(() => {
if (!view) {
form.resetFields();
}
form.setFieldsValue(
view || {
filters: [{ property: 'command', operation: 'Reg' }],
},
);
}, [view, visible]);
const operationElement = (
<Select
style={{ width: 100 }}
onChange={() => {
setOperationMap({});
}}
>
{OPERATIONS.map((x) => (
<Select.Option key={x.name} value={x.value}>
{x.name}
</Select.Option>
))}
</Select>
);
const propertyElement = (props: any) => {
return (
<Select style={{ width: 120 }}>
{props.map((x) => (
<Select.Option key={x.name} value={x.value}>
{x.name}
</Select.Option>
))}
</Select>
);
};
const typeElement = (
<Select style={{ width: 120 }}>
{SORTTYPES.map((x) => (
<Select.Option key={x.name} value={x.value}>
{x.name}
</Select.Option>
))}
</Select>
);
const statusElement = (
<Select mode="multiple" allowClear placeholder="请选择状态">
{STATUS.map((x) => (
<Select.Option key={x.name} value={x.value}>
{x.name}
</Select.Option>
))}
</Select>
);
return (
<Modal
title={view ? '编辑视图' : '新建视图'}
visible={visible}
forceRender
width={580}
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="env_modal">
<Form.Item
name="name"
label="视图名称"
rules={[{ required: true, message: '请输入视图名称' }]}
>
<Input placeholder="请输入视图名称" />
</Form.Item>
<Form.List name="filters">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }, index) => (
<Form.Item
label={index === 0 ? '筛选条件' : ''}
key={key}
style={{ marginBottom: 0 }}
>
<Space className="view-create-modal-filters" align="baseline">
<Form.Item
{...restField}
name={[name, 'property']}
rules={[{ required: true }]}
>
{propertyElement(PROPERTIES)}
</Form.Item>
<Form.Item
{...restField}
name={[name, 'operation']}
rules={[{ required: true }]}
>
{operationElement}
</Form.Item>
<Form.Item
{...restField}
name={[name, 'value']}
rules={[{ required: true, message: '请输入内容' }]}
>
{['In', 'Nin'].includes(
form.getFieldValue(['filters', index, 'operation']),
) ? (
statusElement
) : (
<Input placeholder="请输入内容" />
)}
</Form.Item>
{index !== 0 && (
<MinusCircleOutlined onClick={() => remove(name)} />
)}
</Space>
</Form.Item>
))}
<Form.Item>
<a
onClick={() => add({ property: 'command', operation: 'Reg' })}
>
<PlusOutlined />
</a>
</Form.Item>
</>
)}
</Form.List>
<Form.List name="sorts">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }, index) => (
<Form.Item
label={index === 0 ? '排序方式' : ''}
key={key}
style={{ marginBottom: 0 }}
>
<Space className="view-create-modal-filters" align="baseline">
<Form.Item
{...restField}
name={[name, 'property']}
rules={[{ required: true }]}
>
{propertyElement(PROPERTIES)}
</Form.Item>
<Form.Item
{...restField}
name={[name, 'type']}
rules={[{ required: true }]}
>
{typeElement}
</Form.Item>
{index !== 0 && (
<MinusCircleOutlined onClick={() => remove(name)} />
)}
</Space>
</Form.Item>
))}
<Form.Item>
<a onClick={() => add({ property: 'command', type: 'ASC' })}>
<PlusOutlined />
</a>
</Form.Item>
</>
)}
</Form.List>
</Form>
</Modal>
);
};
export default ViewCreateModal;
+269
View File
@@ -0,0 +1,269 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
Modal,
message,
Space,
Table,
Tag,
Typography,
Button,
Switch,
} from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import ViewCreateModal from './viewCreateModal';
const { Text } = Typography;
const type = 'DragableBodyRow';
const DragableBodyRow = ({
index,
moveRow,
className,
style,
...restProps
}: any) => {
const ref = useRef();
const [{ isOver, dropClassName }, drop] = useDrop({
accept: type,
collect: (monitor) => {
const { index: dragIndex } = (monitor.getItem() as any) || {};
if (dragIndex === index) {
return {};
}
return {
isOver: monitor.isOver(),
dropClassName:
dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
};
},
drop: (item: any) => {
moveRow(item.index, index);
},
});
const [, drag] = useDrag({
type,
item: { index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
drop(drag(ref));
return (
<tr
ref={ref}
className={`${className}${isOver ? dropClassName : ''}`}
style={{ cursor: 'move', ...style }}
{...restProps}
/>
);
};
const ViewManageModal = ({
cronViews,
handleCancel,
visible,
cronViewChange,
}: {
cronViews: any[];
visible: boolean;
handleCancel: () => void;
cronViewChange: (data?: any) => void;
}) => {
const columns: any = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
align: 'center' as const,
},
{
title: '显示',
key: 'isDisabled',
dataIndex: 'isDisabled',
align: 'center' as const,
width: 100,
render: (text: string, record: any, index: number) => {
return (
<Switch
checked={!record.isDisabled}
onChange={(checked) => onShowChange(checked, record, index)}
/>
);
},
},
{
title: '操作',
key: 'action',
width: 140,
align: 'center' as const,
render: (text: string, record: any, index: number) => {
return (
<Space size="middle">
<a onClick={() => editView(record, index)}>
<EditOutlined />
</a>
<a onClick={() => deleteView(record, index)}>
<DeleteOutlined />
</a>
</Space>
);
},
},
];
const [list, setList] = useState<any[]>([]);
const [isCreateViewModalVisible, setIsCreateViewModalVisible] =
useState<boolean>(false);
const [editedView, setEditedView] = useState<any>(null);
const editView = (record: any, index: number) => {
setEditedView(record);
setIsCreateViewModalVisible(true);
};
const deleteView = (record: any, index: number) => {
Modal.confirm({
title: '确认删除',
content: (
<>
{' '}
<Text style={{ wordBreak: 'break-all' }} type="warning">
{record.name}
</Text>{' '}
</>
),
onOk() {
request
.delete(`${config.apiPrefix}crons/views`, { data: [record.id] })
.then((data: any) => {
if (data.code === 200) {
message.success('删除成功');
cronViewChange();
} else {
message.error(data);
}
});
},
onCancel() {
console.log('Cancel');
},
});
};
const onShowChange = (checked: boolean, record: any, index: number) => {
console.log(checked);
request
.put(`${config.apiPrefix}crons/views/${checked ? 'enable' : 'disable'}`, {
data: [record.id],
})
.then((data: any) => {
if (data.code === 200) {
const _list = [...list];
_list.splice(index, 1, { ...list[index], isDisabled: !checked });
setList(_list);
cronViewChange();
} else {
message.error(data);
}
});
};
const components = {
body: {
row: DragableBodyRow,
},
};
const moveRow = useCallback(
(dragIndex, hoverIndex) => {
if (dragIndex === hoverIndex) {
return;
}
const dragRow = list[dragIndex];
request
.put(`${config.apiPrefix}crons/views/move`, {
data: { fromIndex: dragIndex, toIndex: hoverIndex, id: dragRow.id },
})
.then((data: any) => {
if (data.code === 200) {
const newData = [...list];
newData.splice(dragIndex, 1);
newData.splice(hoverIndex, 0, { ...dragRow, ...data.data });
setList(newData);
cronViewChange();
} else {
message.error(data);
}
});
},
[list],
);
useEffect(() => {
setList(cronViews);
}, [cronViews]);
return (
<Modal
title="视图管理"
visible={visible}
centered
width={620}
onCancel={() => handleCancel()}
className="view-manage-modal"
forceRender
footer={false}
maskClosable={false}
>
<Space
style={{
display: 'flex',
justifyContent: 'flex-end',
marginBottom: 10,
}}
>
<Button
key="2"
type="primary"
onClick={() => setIsCreateViewModalVisible(true)}
>
</Button>
</Space>
<DndProvider backend={HTML5Backend}>
<Table
bordered
columns={columns}
pagination={false}
dataSource={list}
rowKey="id"
size="middle"
style={{ marginBottom: 20 }}
components={components}
onRow={(record: any, index: number) => {
return {
index,
moveRow,
} as any;
}}
/>
</DndProvider>
<ViewCreateModal
view={editedView}
visible={isCreateViewModalVisible}
handleCancel={(data) => {
cronViewChange(data);
setIsCreateViewModalVisible(false);
}}
/>
</Modal>
);
};
export default ViewManageModal;
+1 -1
View File
@@ -444,7 +444,7 @@ const Dependence = ({ headerStyle, isPhone, socketMessage }: any) => {
return (
<PageContainer
className="ql-container-wrapper dependence-wrapper"
className="ql-container-wrapper dependence-wrapper ql-container-wrapper-has-tab"
title="依赖管理"
extra={[
<Search
+15 -1
View File
@@ -25,7 +25,7 @@ import EditNameModal from './editNameModal';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import './index.less';
import { getTableScroll } from '@/utils/index';
import { exportJson, getTableScroll } from '@/utils/index';
const { Text, Paragraph } = Typography;
const { Search } = Input;
@@ -462,6 +462,13 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
});
};
const exportEnvs = () => {
const envs = value
.filter((x) => selectedRowIds.includes(x.id))
.map((x) => ({ value: x.value, name: x.name, remarks: x.remarks }));
exportJson('env.json', JSON.stringify(envs));
};
const modifyName = () => {
setIsEditNameModalVisible(true);
};
@@ -516,6 +523,13 @@ const Env = ({ headerStyle, isPhone, theme }: any) => {
>
</Button>
<Button
type="primary"
onClick={() => exportEnvs()}
style={{ marginLeft: 8, marginRight: 8 }}
>
</Button>
<Button
type="primary"
onClick={() => operateEnvs(0)}
+2 -1
View File
@@ -60,7 +60,7 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
const getLog = (node: any) => {
request
.get(`${config.apiPrefix}logs/${node.value}?path=${node.parent || ''}`)
.get(`${config.apiPrefix}logs/${node.title}?path=${node.parent || ''}`)
.then((data) => {
setValue(data.data);
});
@@ -113,6 +113,7 @@ const Log = ({ headerStyle, isPhone, theme }: any) => {
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
treeData={data}
placeholder="请选择日志"
fieldNames={{ value: 'key', label: 'title' }}
showSearch
onSelect={onSelect}
/>,
+1
View File
@@ -154,6 +154,7 @@ const Login = ({ reloadUser }: any) => {
message: '验证码为6位数字',
},
]}
validateTrigger="onBlur"
>
<Input
placeholder="6位数字"
+11 -7
View File
@@ -66,7 +66,7 @@ const EditModal = ({
const getDetail = (node: any) => {
request
.get(`${config.apiPrefix}scripts/${node.value}?path=${node.parent || ''}`)
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
.then((data) => {
setValue(data.data);
});
@@ -74,11 +74,13 @@ const EditModal = ({
const run = () => {
setLog('');
const content = editorRef.current.getValue().replace(/\r\n/g, '\n');
request
.put(`${config.apiPrefix}scripts/run`, {
data: {
filename: cNode.value,
filename: cNode.title,
path: cNode.parent || '',
content,
},
})
.then((data) => {
@@ -87,14 +89,16 @@ const EditModal = ({
};
const stop = () => {
if (!cNode || !cNode.value) {
if (!cNode || !cNode.title) {
return;
}
const content = editorRef.current.getValue().replace(/\r\n/g, '\n');
request
.put(`${config.apiPrefix}scripts/stop`, {
data: {
filename: cNode.value,
filename: cNode.title,
path: cNode.parent || '',
content,
},
})
.then((data) => {
@@ -113,8 +117,7 @@ const EditModal = ({
return;
}
if (_message === '执行结束') {
_message = '';
if (_message.includes('执行结束')) {
setTimeout(() => {
setIsRunning(false);
}, 300);
@@ -146,6 +149,7 @@ const EditModal = ({
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
treeData={treeData}
placeholder="请选择脚本文件"
fieldNames={{ value: 'key', label: 'title' }}
showSearch
onSelect={onSelect}
/>
@@ -243,7 +247,7 @@ const EditModal = ({
content:
editorRef.current &&
editorRef.current.getValue().replace(/\r\n/g, '\n'),
filename: cNode?.value,
filename: cNode?.title,
}}
/>
<SettingModal
+85 -18
View File
@@ -1,7 +1,17 @@
import React, { useEffect, useState } from 'react';
import { Modal, message, Input, Form, Select } from 'antd';
import {
Modal,
message,
Input,
Form,
Select,
Upload,
Radio,
TreeSelect,
} from 'antd';
import { request } from '@/utils/http';
import config from '@/utils/config';
import { UploadOutlined } from '@ant-design/icons';
const { Option } = Select;
@@ -21,21 +31,30 @@ const EditScriptNameModal = ({
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [dirs, setDirs] = useState<any[]>([]);
const [file, setFile] = useState<File>();
const [type, setType] = useState<'blank' | 'upload'>('blank');
const handleOk = async (values: any) => {
setLoading(true);
values.path = values.path || '';
const formData = new FormData();
formData.append('file', file as any);
formData.append('filename', values.filename);
formData.append('path', values.path);
formData.append('content', '');
request
.post(`${config.apiPrefix}scripts`, {
data: { filename: values.filename, path: values.path, content: '' },
data: formData,
})
.then(({ code, data }) => {
if (code === 200) {
message.success('保存文件成功');
message.success('新建文件成功');
const key = values.path ? `${values.path}/` : '';
const filename = file ? file.name : values.filename;
handleCancel({
filename: values.filename,
filename,
path: values.path,
key: `${values.path}-${values.filename}`,
key: `${key}${filename}`,
});
} else {
message.error(data);
@@ -45,15 +64,42 @@ const EditScriptNameModal = ({
.finally(() => setLoading(false));
};
const beforeUpload = (file: File) => {
setFile(file);
return false;
};
const typeChange = (e) => {
setType(e.target.value);
};
const getDirs = (data) => {
for (const item of data) {
if (item.children && item.children.length > 0) {
item.children = item.children
.filter((x) => x.type === 'directory')
.map((x) => ({ ...x, disabled: false }));
getDirs(item.children);
}
}
return data;
};
useEffect(() => {
const originDirs = treeData
.filter((x) => x.type === 'directory')
.map((x) => ({ ...x, disabled: false }));
const dirs = getDirs(originDirs);
setDirs(dirs);
}, [treeData]);
useEffect(() => {
form.resetFields();
const originDirs = treeData.filter((x) => x.disabled);
setDirs([{ key: '' }, ...originDirs]);
}, [visible]);
return (
<Modal
title="新建文件"
title="新建脚本"
visible={visible}
forceRender
centered
@@ -72,6 +118,18 @@ const EditScriptNameModal = ({
confirmLoading={loading}
>
<Form form={form} layout="vertical" name="edit_name_modal">
<Form.Item
name="type"
label="类型"
rules={[{ required: true }]}
initialValue={'blank'}
>
<Radio.Group onChange={typeChange}>
<Radio value="blank"></Radio>
<Radio value="upload"></Radio>
</Radio.Group>
</Form.Item>
{type === 'blank' && (
<Form.Item
name="filename"
label="文件名"
@@ -79,17 +137,26 @@ const EditScriptNameModal = ({
>
<Input placeholder="请输入文件名" />
</Form.Item>
<Form.Item
label="父目录"
name="path"
initialValue={dirs && dirs.length > 0 ? dirs[0].key : ''}
>
<Select placeholder="请选择父目录">
{dirs.map((x) => (
<Option value={x.key}>{x.key || '根'}</Option>
))}
</Select>
)}
<Form.Item label="父目录" name="path">
<TreeSelect
allowClear
treeData={dirs}
fieldNames={{ value: 'key', label: 'title' }}
placeholder="请选择父目录"
treeDefaultExpandAll
/>
</Form.Item>
{type === 'upload' && (
<Form.Item label="文件" name="file">
<Upload.Dragger beforeUpload={beforeUpload} maxCount={1}>
<p className="ant-upload-drag-icon">
<UploadOutlined />
</p>
<p className="ant-upload-text"></p>
</Upload.Dragger>
</Form.Item>
)}
</Form>
</Modal>
);
+10 -9
View File
@@ -104,7 +104,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
const getDetail = (node: any) => {
request
.get(`${config.apiPrefix}scripts/${node.value}?path=${node.parent || ''}`)
.get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`)
.then((data) => {
setValue(data.data);
});
@@ -117,7 +117,6 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
const obj = {
node: {
title: s,
value: s,
key: p ? vkey : s,
parent: p,
},
@@ -209,7 +208,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
<>
<Text style={{ wordBreak: 'break-all' }} type="warning">
{currentNode.value}
{currentNode.title}
</Text>{' '}
</>
@@ -222,7 +221,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
request
.put(`${config.apiPrefix}scripts`, {
data: {
filename: currentNode.value,
filename: currentNode.title,
path: currentNode.parent || '',
content,
},
@@ -262,7 +261,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
request
.delete(`${config.apiPrefix}scripts`, {
data: {
filename: currentNode.value,
filename: currentNode.title,
path: currentNode.parent || '',
},
})
@@ -315,8 +314,9 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
) => {
if (filename) {
const newData = [...data];
const _file = { title: filename, key, value: filename, parent: path };
const _file = { title: filename, key, parent: path };
if (path) {
// TODO: 更新左侧树数据
const parentNodeIndex = newData.findIndex((x) => x.key === path);
if (parentNodeIndex !== -1) {
const parentNode = newData[parentNodeIndex];
@@ -331,7 +331,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
newData.unshift(_file);
}
setData(newData);
onSelect(_file.value, _file);
onSelect(_file.title, _file);
setIsEditing(true);
}
setIsAddFileModalVisible(false);
@@ -341,7 +341,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
request
.post(`${config.apiPrefix}scripts/download`, {
data: {
filename: currentNode.value,
filename: currentNode.title,
},
})
.then((_data: any) => {
@@ -349,7 +349,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = currentNode.value;
a.download = currentNode.title;
document.documentElement.appendChild(a);
a.click();
document.documentElement.removeChild(a);
@@ -451,6 +451,7 @@ const Script = ({ headerStyle, isPhone, theme, socketMessage }: any) => {
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
treeData={data}
placeholder="请选择脚本"
fieldNames={{ value: 'key', label: 'title' }}
showSearch
onSelect={onSelect}
/>,
+1 -1
View File
@@ -313,7 +313,7 @@ const Setting = ({
return (
<PageContainer
className="ql-container-wrapper"
className="ql-container-wrapper ql-container-wrapper-has-tab"
title="系统设置"
header={{
style: headerStyle,
+12 -3
View File
@@ -107,12 +107,18 @@ const SubscriptionModal = ({
const [intervalNumber, setIntervalNumber] = useState<number>();
const intervalTypeChange = (type: string) => {
setIntervalType(type);
if (intervalNumber && intervalNumber > 0) {
onChange?.({ type, value: intervalNumber });
}
};
const numberChange = (value: number) => {
setIntervalNumber(value);
if (!value) {
onChange?.(null);
} else {
onChange?.({ type: intervalType, value });
}
};
useEffect(() => {
@@ -198,13 +204,15 @@ const SubscriptionModal = ({
] = text
.split(' ')
.map((x) => x.trim().replace(/\"/g, '').replace(/\'/, ''));
form.setFieldsValue({
type:
const _type =
type === 'raw'
? 'file'
: url.startsWith('http')
? 'public-repo'
: 'private-repo',
: 'private-repo';
form.setFieldsValue({
type: _type,
url,
whitelist,
blacklist,
@@ -213,6 +221,7 @@ const SubscriptionModal = ({
extensions,
alias: formatAlias(url, branch),
});
setType(_type);
}
}, []);
+38 -2
View File
@@ -181,8 +181,8 @@ export function getTableScroll({
id,
}: { extraHeight?: number; id?: string } = {}) {
if (typeof extraHeight == 'undefined') {
// 47 + 40 + 10 + 24
extraHeight = 121;
// 47 + 40 + 12
extraHeight = 99;
}
let tHeader = null;
if (id) {
@@ -205,3 +205,39 @@ export function getTableScroll({
let height = document.body.clientHeight - mainTop - extraHeight;
return height;
}
// 自动触发点击事件
function automaticClick(elment: HTMLElement) {
const ev = document.createEvent('MouseEvents');
ev.initMouseEvent(
'click',
true,
false,
window,
0,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null,
);
elment.dispatchEvent(ev);
}
// 导出文件
export function exportJson(name: string, data: string) {
const urlObject = window.URL || window.webkitURL || window;
const export_blob = new Blob([data]);
const createA = document.createElementNS(
'http://www.w3.org/1999/xhtml',
'a',
) as any;
createA.href = urlObject.createObjectURL(export_blob);
createA.download = name;
automaticClick(createA);
}
+8 -2
View File
@@ -7,9 +7,15 @@ export function init() {
// sentry监控 init
Sentry.init({
dsn: 'https://3406424fb1dc4813a62d39e844a9d0ac@o1098464.ingest.sentry.io/6122818',
integrations: [new Integrations.BrowserTracing()],
integrations: [
new Integrations.BrowserTracing({
shouldCreateSpanForRequest(url) {
return !url.includes('/api/ws') && !url.includes('/api/static');
},
}),
],
release: version,
tracesSampleRate: 1.0,
tracesSampleRate: 0.1,
beforeBreadcrumb(breadcrumb, hint?) {
if (breadcrumb.data && breadcrumb.data.url) {
const url = breadcrumb.data.url.replace(/token=.*/, '');
+7 -7
View File
@@ -1,8 +1,8 @@
export const version = '2.13.1';
export const changeLogLink = 'https://t.me/jiao_long/303';
export const changeLog = `2.13.1 版本说明
1. 修复新建订阅repo命令快捷导入
2. 修复执行订阅任务含有before/after命令时,造成面板无法访问
3. 修复任务详情日志列表访问
4. 修复安装依赖造成CPU满负荷,服务器崩溃
export const version = '2.14.0';
export const changeLogLink = 'https://t.me/jiao_long/325';
export const changeLog = `2.14.0 版本说明
1. 定时任务自定义视图功能来啦,支持自定义筛选条件、排序规则等
2. 配置文件可设置需要延迟的脚本后缀,默认只有js脚本随机延迟执行,感谢 https://github.com/cddjr
3. 修复定时任务搜索及搜索分页
4. 其他bug修复
`;