mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-06 16:54:33 +08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fcf0ee619b | |||
| f3ec352066 | |||
| 023d0f1cc4 | |||
| f526d3f972 | |||
| e28f746294 | |||
| d53437d169 | |||
| d526602d19 | |||
| 91b44914f6 |
+61
-4
@@ -535,12 +535,43 @@ export async function setSystemTimezone(timezone: string): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to check if a name is a GitHub URL
|
||||
function isGitHubUrl(name: string): boolean {
|
||||
// Support git+https://, git+http://, https://, and http:// URLs
|
||||
// This covers GitHub URLs and other git-compatible repositories
|
||||
return !!name.match(/^(git\+https?:\/\/|https?:\/\/)/i);
|
||||
}
|
||||
|
||||
// Helper function to check if a name is a requirements file
|
||||
function isRequirementsFile(name: string): boolean {
|
||||
return !!name.match(/requirements.*\.(txt|in)$/i);
|
||||
}
|
||||
|
||||
// Helper function to check if a name is a pyproject.toml file
|
||||
function isPyprojectToml(name: string): boolean {
|
||||
return name.endsWith('pyproject.toml');
|
||||
}
|
||||
|
||||
export function getGetCommand(type: DependenceTypes, name: string): string {
|
||||
const trimmedName = name.trim();
|
||||
|
||||
// For Python dependencies installed from GitHub or requirements files,
|
||||
// we can't reliably check if they're installed, so skip the check
|
||||
if (type === DependenceTypes.python3) {
|
||||
if (isGitHubUrl(trimmedName) ||
|
||||
isRequirementsFile(trimmedName) ||
|
||||
isPyprojectToml(trimmedName)) {
|
||||
// Return a command that will always indicate not installed
|
||||
// This ensures GitHub URLs and requirements files are always installed
|
||||
return 'echo ""';
|
||||
}
|
||||
}
|
||||
|
||||
const baseCommands = {
|
||||
[DependenceTypes.nodejs]: `pnpm ls -g | grep "${name}" | head -1`,
|
||||
[DependenceTypes.nodejs]: `pnpm ls -g | grep "${trimmedName}" | head -1`,
|
||||
[DependenceTypes.python3]: `
|
||||
python3 -c "exec('''
|
||||
name='${name}'
|
||||
name='${trimmedName}'
|
||||
try:
|
||||
from importlib.metadata import version
|
||||
print(version(name))
|
||||
@@ -550,7 +581,7 @@ except:
|
||||
spec=u.find_spec(name)
|
||||
print(name if spec else '')
|
||||
''')"`,
|
||||
[DependenceTypes.linux]: `apk info -es ${name}`,
|
||||
[DependenceTypes.linux]: `apk info -es ${trimmedName}`,
|
||||
};
|
||||
|
||||
return baseCommands[type];
|
||||
@@ -570,7 +601,33 @@ export function getInstallCommand(type: DependenceTypes, name: string): string {
|
||||
command = `${command} --prefix=${PYTHON_INSTALL_DIR}`;
|
||||
}
|
||||
|
||||
return `${command} ${name.trim()}`;
|
||||
const trimmedName = name.trim();
|
||||
|
||||
// Handle different installation methods for Python
|
||||
if (type === DependenceTypes.python3) {
|
||||
// Check if it's a GitHub URL (support both git+ and direct URLs)
|
||||
if (isGitHubUrl(trimmedName)) {
|
||||
return `${command} ${trimmedName}`;
|
||||
}
|
||||
// Check if it's a requirements file path
|
||||
if (isRequirementsFile(trimmedName)) {
|
||||
return `${command} -r ${trimmedName}`;
|
||||
}
|
||||
// Check if it's a pyproject.toml file
|
||||
if (isPyprojectToml(trimmedName)) {
|
||||
// For pyproject.toml, install from the directory containing it
|
||||
const pathMatch = trimmedName.match(/^(.+)\/pyproject\.toml$/);
|
||||
if (pathMatch) {
|
||||
// Has a path prefix, use the directory
|
||||
return `${command} ${pathMatch[1]}`;
|
||||
} else {
|
||||
// Just "pyproject.toml", install current directory
|
||||
return `${command} .`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return `${command} ${trimmedName}`;
|
||||
}
|
||||
|
||||
export function getUninstallCommand(
|
||||
|
||||
@@ -123,11 +123,7 @@ export default ({ app }: { app: Application }) => {
|
||||
app.use(rewrite('/open/*', '/api/$1'));
|
||||
app.use(config.api.prefix, routes());
|
||||
|
||||
app.get('*', (req, res, next) => {
|
||||
// Don't serve index.html for API routes
|
||||
if (req.path.startsWith('/api/')) {
|
||||
return next();
|
||||
}
|
||||
app.get('*', (_, res, next) => {
|
||||
const indexPath = path.join(frontendPath, 'index.html');
|
||||
res.sendFile(indexPath, (err) => {
|
||||
if (err) {
|
||||
|
||||
@@ -29,7 +29,7 @@ import { logStreamManager } from '../shared/logStreamManager';
|
||||
|
||||
@Service()
|
||||
export default class CronService {
|
||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||
constructor(@Inject('logger') private logger: winston.Logger) { }
|
||||
|
||||
private isNodeCron(cron: Crontab) {
|
||||
const { schedule, extra_schedules } = cron;
|
||||
@@ -165,7 +165,7 @@ export default class CronService {
|
||||
let cron;
|
||||
try {
|
||||
cron = await this.getDb({ id });
|
||||
} catch (err) {}
|
||||
} catch (err) { }
|
||||
if (!cron) {
|
||||
continue;
|
||||
}
|
||||
@@ -467,7 +467,10 @@ export default class CronService {
|
||||
for (const doc of docs) {
|
||||
// Kill all running instances of this task
|
||||
try {
|
||||
const command = this.makeCommand(doc);
|
||||
if (doc.pid) {
|
||||
await killTask(doc.pid);
|
||||
}
|
||||
const command = doc.command.replace(/\s+/g, ' ').trim();
|
||||
await killAllTasks(command);
|
||||
this.logger.info(
|
||||
`[panel][停止所有运行中的任务实例] 任务ID: ${doc.id}, 命令: ${command}`,
|
||||
|
||||
@@ -13,10 +13,11 @@ import {
|
||||
stepPosition,
|
||||
} from '../data/env';
|
||||
import { writeFileWithLock } from '../shared/utils';
|
||||
import { sequelize } from '../data';
|
||||
|
||||
@Service()
|
||||
export default class EnvService {
|
||||
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||
constructor(@Inject('logger') private logger: winston.Logger) { }
|
||||
|
||||
public async create(payloads: Env[]): Promise<Env[]> {
|
||||
const envs = await this.envs();
|
||||
@@ -146,7 +147,7 @@ export default class EnvService {
|
||||
}
|
||||
try {
|
||||
const result = await this.find(condition, [
|
||||
['isPinned', 'DESC'],
|
||||
[sequelize.literal('COALESCE(`isPinned`, 0)'), 'DESC'],
|
||||
['position', 'DESC'],
|
||||
['createdAt', 'ASC'],
|
||||
]);
|
||||
|
||||
@@ -111,6 +111,76 @@ add_cron() {
|
||||
notify_api "$path 新增任务" "$detail"
|
||||
}
|
||||
|
||||
## 自动安装订阅仓库中的Python依赖
|
||||
auto_install_python_deps() {
|
||||
local repo_path="$1"
|
||||
local uniq_path="$2"
|
||||
|
||||
echo -e "\n检测订阅仓库中的Python依赖文件...\n"
|
||||
|
||||
get_token
|
||||
|
||||
# 检查 requirements.txt
|
||||
if [[ -f "${repo_path}/requirements.txt" ]]; then
|
||||
echo -e "发现 requirements.txt,开始自动安装依赖...\n"
|
||||
local req_file="${dir_scripts}/${uniq_path}/requirements.txt"
|
||||
|
||||
# 确保目标目录存在
|
||||
make_dir "${dir_scripts}/${uniq_path}"
|
||||
|
||||
# 复制文件并检查结果
|
||||
if cp -f "${repo_path}/requirements.txt" "${req_file}" 2>/dev/null; then
|
||||
# 调用API添加依赖安装任务
|
||||
local dep_name="${uniq_path}/requirements.txt"
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local result=$(curl -s --noproxy "*" "http://127.0.0.1:${ql_port}/open/dependencies?t=$currentTimeStamp" \
|
||||
-X POST \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
-H "Authorization: Bearer ${__ql_token__}" \
|
||||
--data-raw "[{\"name\":\"${dep_name}\",\"type\":1,\"remark\":\"自动检测:${uniq_path} 订阅依赖\"}]" 2>/dev/null)
|
||||
|
||||
local code=$(echo "$result" | jq -r '.code' 2>/dev/null)
|
||||
if [[ "$code" == "200" ]]; then
|
||||
echo -e "已添加 requirements.txt 依赖安装任务\n"
|
||||
else
|
||||
echo -e "添加 requirements.txt 依赖失败,请手动添加\n"
|
||||
fi
|
||||
else
|
||||
echo -e "复制 requirements.txt 失败,跳过自动安装\n"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查 pyproject.toml
|
||||
if [[ -f "${repo_path}/pyproject.toml" ]]; then
|
||||
echo -e "发现 pyproject.toml,开始自动安装依赖...\n"
|
||||
local pyproject_file="${dir_scripts}/${uniq_path}/pyproject.toml"
|
||||
|
||||
# 确保目标目录存在
|
||||
make_dir "${dir_scripts}/${uniq_path}"
|
||||
|
||||
# 复制文件并检查结果
|
||||
if cp -f "${repo_path}/pyproject.toml" "${pyproject_file}" 2>/dev/null; then
|
||||
# 调用API添加依赖安装任务
|
||||
local dep_name="${uniq_path}/pyproject.toml"
|
||||
local currentTimeStamp=$(date +%s)
|
||||
local result=$(curl -s --noproxy "*" "http://127.0.0.1:${ql_port}/open/dependencies?t=$currentTimeStamp" \
|
||||
-X POST \
|
||||
-H "Content-Type: application/json;charset=UTF-8" \
|
||||
-H "Authorization: Bearer ${__ql_token__}" \
|
||||
--data-raw "[{\"name\":\"${dep_name}\",\"type\":1,\"remark\":\"自动检测:${uniq_path} 订阅依赖\"}]" 2>/dev/null)
|
||||
|
||||
local code=$(echo "$result" | jq -r '.code' 2>/dev/null)
|
||||
if [[ "$code" == "200" ]]; then
|
||||
echo -e "已添加 pyproject.toml 依赖安装任务\n"
|
||||
else
|
||||
echo -e "添加 pyproject.toml 依赖失败,请手动添加\n"
|
||||
fi
|
||||
else
|
||||
echo -e "复制 pyproject.toml 失败,跳过自动安装\n"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
## 更新仓库
|
||||
update_repo() {
|
||||
local url="$1"
|
||||
@@ -137,6 +207,10 @@ update_repo() {
|
||||
|
||||
if [[ $exit_status -eq 0 ]]; then
|
||||
echo -e "拉取 ${uniq_path} 成功...\n"
|
||||
|
||||
# 自动检测并安装Python依赖
|
||||
auto_install_python_deps "${repo_path}" "${uniq_path}"
|
||||
|
||||
diff_scripts "$repo_path" "$author" "$path" "$blackword" "$dependence" "$extensions" "$autoAddCron" "$autoDelCron"
|
||||
else
|
||||
echo -e "拉取 ${uniq_path} 失败,请检查日志...\n"
|
||||
|
||||
@@ -22,6 +22,9 @@ const DependenceModal = ({
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedType, setSelectedType] = useState(
|
||||
DependenceTypes[defaultType as any],
|
||||
);
|
||||
|
||||
const handleOk = async (values: any) => {
|
||||
setLoading(true);
|
||||
@@ -90,7 +93,7 @@ const DependenceModal = ({
|
||||
label={intl.get('依赖类型')}
|
||||
initialValue={DependenceTypes[defaultType as any]}
|
||||
>
|
||||
<Select>
|
||||
<Select onChange={(value) => setSelectedType(value)}>
|
||||
{config.dependenceTypes.map((x, i) => (
|
||||
<Option key={i} value={i}>
|
||||
{x}
|
||||
@@ -121,11 +124,24 @@ const DependenceModal = ({
|
||||
whitespace: true,
|
||||
},
|
||||
]}
|
||||
tooltip={
|
||||
selectedType === DependenceTypes.python3
|
||||
? intl.get(
|
||||
'Python支持多种安装方式:\n1. 包名(如:requests)\n2. GitHub链接(如:git+https://github.com/user/repo.git)\n3. requirements文件路径(如:path/to/requirements.txt)\n4. pyproject.toml文件路径',
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={4}
|
||||
autoSize={{ minRows: 1, maxRows: 5 }}
|
||||
placeholder={intl.get('请输入依赖名称')}
|
||||
placeholder={
|
||||
selectedType === DependenceTypes.python3
|
||||
? intl.get(
|
||||
'支持包名、GitHub链接、requirements.txt或pyproject.toml路径',
|
||||
)
|
||||
: intl.get('请输入依赖名称')
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="remark" label={intl.get('备注')}>
|
||||
|
||||
+10
-46
@@ -1,47 +1,11 @@
|
||||
version: 2.20.0
|
||||
changeLogLink: https://t.me/jiao_long/432
|
||||
publishTime: 2025-12-10 01:05
|
||||
version: 2.20.1
|
||||
changeLogLink: https://t.me/jiao_long/433
|
||||
publishTime: 2025-12-26 22:00
|
||||
changeLog: |
|
||||
1. 定时任务(cron / task)相关的大量修复 & 增强
|
||||
|
||||
修复 cron 解析错误(修复 parse cron / 升级 cron-parser)
|
||||
修复集群模式下定时任务可能不执行(race condition)
|
||||
定时任务支持订阅筛选
|
||||
定时任务支持排序调整
|
||||
定时任务支持自定义日志文件或无日志
|
||||
修复任务实例默认值
|
||||
任务支持单实例 / 多实例模式
|
||||
修复 task 命令软链可能失败问题
|
||||
|
||||
2. 日志系统相关的大更新
|
||||
|
||||
修复日志目录逻辑
|
||||
修复 pm2 日志目录
|
||||
优化日志写入(stream pooling)
|
||||
|
||||
3. 环境变量(env)系统的改进与修复
|
||||
|
||||
修复环境变量复制到剪贴板时可能失败
|
||||
添加环境变量“置顶”功能
|
||||
修复 QlPort 与 QlGrpcPort 环境变量在 host network 模式下被忽略
|
||||
增加全局 SSH 私钥配置
|
||||
|
||||
4. Docker / 非 root 用户 / Alpine 兼容性增强
|
||||
|
||||
新增非 root Docker 用户支持,自动初始化命令
|
||||
修复 Alpine 容器 DNS 解析失败(设置 ndots:0)
|
||||
修复 PM2 在 ARM 路由器(Node.js 不兼容)上的启动失败
|
||||
移除 nginx(可能是考虑更轻量的镜像运行)
|
||||
|
||||
5. API 安全与校验增强
|
||||
|
||||
Dependencies GET endpoint 增加校验
|
||||
Script API routes 增加输入校验
|
||||
修复 JWT 认证问题
|
||||
Feishu 机器人通知增加签名校验
|
||||
QLAPI 增加 cron task 管理功能
|
||||
修复 URIError(错误 cookie 导致白屏)
|
||||
|
||||
6. 系统设置
|
||||
|
||||
新增多终端/多平台的并发登录会话支持
|
||||
1. 修复获取依赖管理列表
|
||||
2. notify.js 修复 TG_PROXY_AUTH 参数拼接
|
||||
3. QLAPI.notify larkSecret 参数
|
||||
4. 修复 cron parser 定时规则校验
|
||||
5. 修复设置 baseUrl 后无法访问
|
||||
6. 修复环境变量排序
|
||||
7. 修复定时任务无法停止
|
||||
Reference in New Issue
Block a user