mirror of
https://github.com/whyour/qinglong.git
synced 2026-11-09 08:45:19 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ebbe17aa8 |
@@ -66,16 +66,5 @@ export async function migrateSchema(database: Sequelize): Promise<void> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Older databases can contain non-JSON values in Envs.labels. Sequelize
|
||||
// parses this column on every environment read, including HTTP startup.
|
||||
await database.query(
|
||||
`UPDATE "Envs" SET "labels" = '[]'
|
||||
WHERE CASE WHEN json_valid("labels")
|
||||
THEN json_type("labels") <> 'array'
|
||||
ELSE 1
|
||||
END`,
|
||||
{ transaction },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -15,7 +15,7 @@
|
||||
"@types/node": "22.19.0",
|
||||
"commander": "15.0.0",
|
||||
"esbuild": "0.28.2",
|
||||
"tar": "7.5.20",
|
||||
"tar": "7.5.21",
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -573,9 +573,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.20",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.20.tgz",
|
||||
"integrity": "sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==",
|
||||
"version": "7.5.21",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.21.tgz",
|
||||
"integrity": "sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
"esbuild": "0.28.2",
|
||||
"typescript": "5.2.2",
|
||||
"@types/node": "22.19.0",
|
||||
"tar": "7.5.20"
|
||||
"tar": "7.5.21"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
|
||||
+10
-6
@@ -116,6 +116,10 @@
|
||||
"@codemirror/view": "6.39.16",
|
||||
"proxy-addr": "2.0.8",
|
||||
"websocket-driver": "0.7.5",
|
||||
"body-parser": "1.20.9",
|
||||
"lodash": "4.18.1",
|
||||
"protobufjs": "7.6.1",
|
||||
"undici@6": "6.29.1",
|
||||
"brace-expansion@1": "1.1.21",
|
||||
"brace-expansion@2": "2.1.7"
|
||||
}
|
||||
@@ -127,7 +131,7 @@
|
||||
"@grpc/proto-loader": "^0.8.0",
|
||||
"@keyv/sqlite": "^4.0.1",
|
||||
"@otplib/preset-default": "^12.0.1",
|
||||
"body-parser": "^1.20.3",
|
||||
"body-parser": "^1.20.9",
|
||||
"celebrate": "^15.0.3",
|
||||
"chokidar": "^4.0.1",
|
||||
"compression": "1.8.2",
|
||||
@@ -148,10 +152,10 @@
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"keyv": "^5.2.3",
|
||||
"lodash": "^4.17.21",
|
||||
"multer": "2.4.0",
|
||||
"lodash": "^4.18.1",
|
||||
"multer": "2.5.0",
|
||||
"node-cron": "4.6.0",
|
||||
"nodemailer": "^10.0.9",
|
||||
"nodemailer": "^10.1.4",
|
||||
"p-queue-cjs": "7.3.4",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
"ps-tree": "^1.2.0",
|
||||
@@ -162,11 +166,11 @@
|
||||
"sqlite3": "npm:@whyour/sqlite3@1.1.4",
|
||||
"toad-scheduler": "^3.0.1",
|
||||
"typedi": "^0.10.0",
|
||||
"undici": "^7.9.0",
|
||||
"undici": "^7.30.1",
|
||||
"uuid": "^11.0.3",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
"tar": "7.5.20"
|
||||
"tar": "7.5.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^5.0.1",
|
||||
|
||||
Generated
+345
-542
File diff suppressed because it is too large
Load Diff
+30
-78
@@ -18,11 +18,6 @@ from email.utils import formataddr
|
||||
|
||||
import requests
|
||||
|
||||
# 单次 HTTP 请求的默认超时(秒),避免推送服务无响应时永久阻塞
|
||||
REQUEST_TIMEOUT = 10
|
||||
# 等待推送线程结束的最长时间(秒),超时后跳过并继续退出
|
||||
NOTIFY_JOIN_TIMEOUT = 30
|
||||
|
||||
# 原先的 print 函数和主线程的锁
|
||||
_print = print
|
||||
mutex = threading.Lock()
|
||||
@@ -204,7 +199,7 @@ def bark(title: str, content: str) -> Optional[bool]:
|
||||
data[bark_params.get(pair[0])] = pair[1]
|
||||
headers = {"Content-Type": "application/json;charset=utf-8"}
|
||||
response = requests.post(
|
||||
url=url, data=json.dumps(data), headers=headers, timeout=REQUEST_TIMEOUT
|
||||
url=url, data=json.dumps(data), headers=headers, timeout=15
|
||||
).json()
|
||||
|
||||
if response["code"] == 200:
|
||||
@@ -246,7 +241,7 @@ def dingding_bot(title: str, content: str) -> Optional[bool]:
|
||||
headers = {"Content-Type": "application/json;charset=utf-8"}
|
||||
data = {"msgtype": "text", "text": {"content": f"{title}\n\n{content}"}}
|
||||
response = requests.post(
|
||||
url=url, data=json.dumps(data), headers=headers, timeout=REQUEST_TIMEOUT
|
||||
url=url, data=json.dumps(data), headers=headers, timeout=15
|
||||
).json()
|
||||
|
||||
if not response["errcode"]:
|
||||
@@ -283,9 +278,7 @@ def feishu_bot(title: str, content: str) -> Optional[bool]:
|
||||
data["timestamp"] = timestamp
|
||||
data["sign"] = sign
|
||||
|
||||
response = requests.post(
|
||||
url, data=json.dumps(data), timeout=REQUEST_TIMEOUT
|
||||
).json()
|
||||
response = requests.post(url, data=json.dumps(data)).json()
|
||||
|
||||
if response.get("StatusCode") == 0 or response.get("code") == 0:
|
||||
audit_results.append(True)
|
||||
@@ -306,7 +299,7 @@ def go_cqhttp(title: str, content: str) -> Optional[bool]:
|
||||
print("go-cqhttp 服务启动")
|
||||
|
||||
url = f'{push_config.get("GOBOT_URL")}?access_token={push_config.get("GOBOT_TOKEN")}&{push_config.get("GOBOT_QQ")}&message=标题:{title}\n内容:{content}'
|
||||
response = requests.get(url, timeout=REQUEST_TIMEOUT).json()
|
||||
response = requests.get(url).json()
|
||||
|
||||
if response["status"] == "ok":
|
||||
audit_results.append(True)
|
||||
@@ -332,7 +325,7 @@ def gotify(title: str, content: str) -> Optional[bool]:
|
||||
"message": content,
|
||||
"priority": push_config.get("GOTIFY_PRIORITY"),
|
||||
}
|
||||
response = requests.post(url, data=data, timeout=REQUEST_TIMEOUT).json()
|
||||
response = requests.post(url, data=data).json()
|
||||
|
||||
if response.get("id"):
|
||||
audit_results.append(True)
|
||||
@@ -355,9 +348,7 @@ def iGot(title: str, content: str) -> Optional[bool]:
|
||||
url = f'https://push.hellyw.com/{push_config.get("IGOT_PUSH_KEY")}'
|
||||
data = {"title": title, "content": content}
|
||||
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
response = requests.post(
|
||||
url, data=data, headers=headers, timeout=REQUEST_TIMEOUT
|
||||
).json()
|
||||
response = requests.post(url, data=data, headers=headers).json()
|
||||
|
||||
if response["ret"] == 0:
|
||||
audit_results.append(True)
|
||||
@@ -386,7 +377,7 @@ def serverJ(title: str, content: str) -> Optional[bool]:
|
||||
else:
|
||||
url = f'https://sctapi.ftqq.com/{push_config.get("PUSH_KEY")}.send'
|
||||
|
||||
response = requests.post(url, data=data, timeout=REQUEST_TIMEOUT).json()
|
||||
response = requests.post(url, data=data).json()
|
||||
|
||||
if response.get("errno") == 0 or response.get("code") == 0:
|
||||
audit_results.append(True)
|
||||
@@ -415,7 +406,7 @@ def pushdeer(title: str, content: str) -> Optional[bool]:
|
||||
if push_config.get("DEER_URL"):
|
||||
url = push_config.get("DEER_URL")
|
||||
|
||||
response = requests.post(url, data=data, timeout=REQUEST_TIMEOUT).json()
|
||||
response = requests.post(url, data=data).json()
|
||||
|
||||
if len(response.get("content").get("result")) > 0:
|
||||
audit_results.append(True)
|
||||
@@ -436,7 +427,7 @@ def chat(title: str, content: str) -> Optional[bool]:
|
||||
print("chat 服务启动")
|
||||
data = "payload=" + json.dumps({"text": title + "\n" + content})
|
||||
url = push_config.get("CHAT_URL") + push_config.get("CHAT_TOKEN")
|
||||
response = requests.post(url, data=data, timeout=REQUEST_TIMEOUT)
|
||||
response = requests.post(url, data=data)
|
||||
|
||||
if response.status_code == 200:
|
||||
audit_results.append(True)
|
||||
@@ -470,9 +461,7 @@ def pushplus_bot(title: str, content: str) -> Optional[bool]:
|
||||
}
|
||||
body = json.dumps(data).encode(encoding="utf-8")
|
||||
headers = {"Content-Type": "application/json"}
|
||||
response = requests.post(
|
||||
url=url, data=body, headers=headers, timeout=REQUEST_TIMEOUT
|
||||
).json()
|
||||
response = requests.post(url=url, data=body, headers=headers).json()
|
||||
|
||||
code = response["code"]
|
||||
if code == 200:
|
||||
@@ -488,9 +477,7 @@ def pushplus_bot(title: str, content: str) -> Optional[bool]:
|
||||
else:
|
||||
url_old = "http://pushplus.hxtrip.com/send"
|
||||
headers["Accept"] = "application/json"
|
||||
response = requests.post(
|
||||
url=url_old, data=body, headers=headers, timeout=REQUEST_TIMEOUT
|
||||
).json()
|
||||
response = requests.post(url=url_old, data=body, headers=headers).json()
|
||||
|
||||
if response["code"] == 200:
|
||||
audit_results.append(True)
|
||||
@@ -526,9 +513,7 @@ def weplus_bot(title: str, content: str) -> Optional[bool]:
|
||||
}
|
||||
body = json.dumps(data).encode(encoding="utf-8")
|
||||
headers = {"Content-Type": "application/json"}
|
||||
response = requests.post(
|
||||
url=url, data=body, headers=headers, timeout=REQUEST_TIMEOUT
|
||||
).json()
|
||||
response = requests.post(url=url, data=body, headers=headers).json()
|
||||
|
||||
if response["code"] == 200:
|
||||
audit_results.append(True)
|
||||
@@ -550,9 +535,7 @@ def qmsg_bot(title: str, content: str) -> Optional[bool]:
|
||||
|
||||
url = f'https://qmsg.zendee.cn/{push_config.get("QMSG_TYPE")}/{push_config.get("QMSG_KEY")}'
|
||||
payload = {"msg": f'{title}\n\n{content.replace("----", "-")}'.encode("utf-8")}
|
||||
response = requests.post(
|
||||
url=url, params=payload, timeout=REQUEST_TIMEOUT
|
||||
).json()
|
||||
response = requests.post(url=url, params=payload).json()
|
||||
|
||||
if response["code"] == 0:
|
||||
audit_results.append(True)
|
||||
@@ -616,7 +599,7 @@ class WeCom:
|
||||
"corpid": self.CORPID,
|
||||
"corpsecret": self.CORPSECRET,
|
||||
}
|
||||
req = requests.post(url, params=values, timeout=REQUEST_TIMEOUT)
|
||||
req = requests.post(url, params=values)
|
||||
data = json.loads(req.text)
|
||||
return data["access_token"]
|
||||
|
||||
@@ -632,9 +615,7 @@ class WeCom:
|
||||
"safe": "0",
|
||||
}
|
||||
send_msges = bytes(json.dumps(send_values), "utf-8")
|
||||
respone = requests.post(
|
||||
send_url, send_msges, timeout=REQUEST_TIMEOUT
|
||||
)
|
||||
respone = requests.post(send_url, send_msges)
|
||||
respone = respone.json()
|
||||
return respone["errmsg"]
|
||||
|
||||
@@ -660,9 +641,7 @@ class WeCom:
|
||||
},
|
||||
}
|
||||
send_msges = bytes(json.dumps(send_values), "utf-8")
|
||||
respone = requests.post(
|
||||
send_url, send_msges, timeout=REQUEST_TIMEOUT
|
||||
)
|
||||
respone = requests.post(send_url, send_msges)
|
||||
respone = respone.json()
|
||||
return respone["errmsg"]
|
||||
|
||||
@@ -684,7 +663,7 @@ def wecom_bot(title: str, content: str) -> Optional[bool]:
|
||||
headers = {"Content-Type": "application/json;charset=utf-8"}
|
||||
data = {"msgtype": "text", "text": {"content": f"{title}\n\n{content}"}}
|
||||
response = requests.post(
|
||||
url=url, data=json.dumps(data), headers=headers, timeout=REQUEST_TIMEOUT
|
||||
url=url, data=json.dumps(data), headers=headers, timeout=15
|
||||
).json()
|
||||
|
||||
if response["errcode"] == 0:
|
||||
@@ -732,11 +711,7 @@ def telegram_bot(title: str, content: str) -> Optional[bool]:
|
||||
)
|
||||
proxies = {"http": proxyStr, "https": proxyStr}
|
||||
response = requests.post(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=payload,
|
||||
proxies=proxies,
|
||||
timeout=REQUEST_TIMEOUT,
|
||||
url=url, headers=headers, params=payload, proxies=proxies
|
||||
).json()
|
||||
|
||||
if response["ok"]:
|
||||
@@ -777,9 +752,7 @@ def aibotk(title: str, content: str) -> Optional[bool]:
|
||||
}
|
||||
body = json.dumps(data).encode(encoding="utf-8")
|
||||
headers = {"Content-Type": "application/json"}
|
||||
response = requests.post(
|
||||
url=url, data=body, headers=headers, timeout=REQUEST_TIMEOUT
|
||||
).json()
|
||||
response = requests.post(url=url, data=body, headers=headers).json()
|
||||
print(response)
|
||||
if response["code"] == 0:
|
||||
audit_results.append(True)
|
||||
@@ -863,7 +836,7 @@ def pushme(title: str, content: str) -> Optional[bool]:
|
||||
"date": push_config.get("date") if push_config.get("date") else "",
|
||||
"type": push_config.get("type") if push_config.get("type") else "",
|
||||
}
|
||||
response = requests.post(url, data=data, timeout=REQUEST_TIMEOUT)
|
||||
response = requests.post(url, data=data)
|
||||
|
||||
if response.status_code == 200 and response.text == "success":
|
||||
audit_results.append(True)
|
||||
@@ -910,9 +883,7 @@ def chronocat(title: str, content: str) -> Optional[bool]:
|
||||
}
|
||||
],
|
||||
}
|
||||
response = requests.post(
|
||||
url, headers=headers, data=json.dumps(data), timeout=REQUEST_TIMEOUT
|
||||
)
|
||||
response = requests.post(url, headers=headers, data=json.dumps(data))
|
||||
if response.status_code == 200:
|
||||
if chat_type == 1:
|
||||
audit_results.append(True)
|
||||
@@ -965,9 +936,7 @@ def ntfy(title: str, content: str) -> Optional[bool]:
|
||||
headers['Actions'] = encode_rfc2047(push_config.get("NTFY_ACTIONS"))
|
||||
|
||||
url = push_config.get("NTFY_URL") + "/" + push_config.get("NTFY_TOPIC")
|
||||
response = requests.post(
|
||||
url, data=data, headers=headers, timeout=REQUEST_TIMEOUT
|
||||
)
|
||||
response = requests.post(url, data=data, headers=headers)
|
||||
if response.status_code == 200: # 使用 response.status_code 进行检查
|
||||
audit_results.append(True)
|
||||
print("Ntfy 推送成功!")
|
||||
@@ -1026,9 +995,7 @@ def wxpusher_bot(title: str, content: str) -> Optional[bool]:
|
||||
}
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
response = requests.post(
|
||||
url=url, json=data, headers=headers, timeout=REQUEST_TIMEOUT
|
||||
).json()
|
||||
response = requests.post(url=url, json=data, headers=headers).json()
|
||||
|
||||
if response.get("code") == 1000:
|
||||
audit_results.append(True)
|
||||
@@ -1079,9 +1046,7 @@ def wxpusher_spt(title: str, content: str) -> Optional[bool]:
|
||||
data["sptList"] = spts
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
response = requests.post(
|
||||
url=url, json=data, headers=headers, timeout=REQUEST_TIMEOUT
|
||||
).json()
|
||||
response = requests.post(url=url, json=data, headers=headers).json()
|
||||
|
||||
if response.get("code") == 1000:
|
||||
audit_results.append(True)
|
||||
@@ -1115,9 +1080,7 @@ def wpush(title: str, content: str) -> Optional[bool]:
|
||||
data["topic_code"] = push_config.get("WPUSH_TOPIC_CODE")
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
response = requests.post(
|
||||
url=url, json=data, headers=headers, timeout=REQUEST_TIMEOUT
|
||||
).json()
|
||||
response = requests.post(url=url, json=data, headers=headers, timeout=15).json()
|
||||
|
||||
if response.get("code") == 0:
|
||||
audit_results.append(True)
|
||||
@@ -1158,9 +1121,7 @@ def openilink(title: str, content: str) -> Optional[bool]:
|
||||
if push_config.get("OPENILINK_CONTEXT_TOKEN"):
|
||||
data["context_token"] = push_config.get("OPENILINK_CONTEXT_TOKEN")
|
||||
|
||||
response = requests.post(
|
||||
url=url, json=data, headers=headers, timeout=REQUEST_TIMEOUT
|
||||
).json()
|
||||
response = requests.post(url=url, json=data, headers=headers).json()
|
||||
|
||||
if response.get("ok"):
|
||||
audit_results.append(True)
|
||||
@@ -1254,7 +1215,7 @@ def custom_notify(title: str, content: str) -> Optional[bool]:
|
||||
"$title", urllib.parse.quote_plus(title)
|
||||
).replace("$content", urllib.parse.quote_plus(content))
|
||||
response = requests.request(
|
||||
method=WEBHOOK_METHOD, url=formatted_url, headers=headers, timeout=REQUEST_TIMEOUT, data=body
|
||||
method=WEBHOOK_METHOD, url=formatted_url, headers=headers, timeout=15, data=body
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
@@ -1272,7 +1233,7 @@ def one() -> str:
|
||||
:return:
|
||||
"""
|
||||
url = "https://v1.hitokoto.cn/"
|
||||
res = requests.get(url, timeout=REQUEST_TIMEOUT).json()
|
||||
res = requests.get(url).json()
|
||||
return res["hitokoto"] + " ----" + res["from"]
|
||||
|
||||
|
||||
@@ -1413,20 +1374,11 @@ def send(title: str, content: str, ignore_default_config: bool = False, **kwargs
|
||||
notify_function = add_notify_function()
|
||||
results = []
|
||||
ts = [
|
||||
threading.Thread(
|
||||
target=audited_notification,
|
||||
args=(mode, title, content, results),
|
||||
name=mode.__name__,
|
||||
daemon=True,
|
||||
)
|
||||
threading.Thread(target=audited_notification, args=(mode, title, content, results), name=mode.__name__)
|
||||
for mode in notify_function
|
||||
]
|
||||
[t.start() for t in ts]
|
||||
for t in ts:
|
||||
t.join(timeout=NOTIFY_JOIN_TIMEOUT)
|
||||
if t.is_alive():
|
||||
results.append((t.name, False))
|
||||
print(f"{t.name} 推送超时(>{NOTIFY_JOIN_TIMEOUT}s),已跳过,任务继续结束")
|
||||
[t.join() for t in ts]
|
||||
report_notifications(results, title, content)
|
||||
|
||||
|
||||
|
||||
@@ -508,7 +508,6 @@
|
||||
"脚本(只读)": "Scripts (read only)",
|
||||
"自动删除任务": "Automatically Delete Tasks",
|
||||
"自动拆分": "Auto Split",
|
||||
"自动换行": "Word wrap",
|
||||
"自动添加任务": "Automatically Add Tasks",
|
||||
"自动生成": "Auto-generated",
|
||||
"自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成": "Custom log folder name to distinguish logs from different tasks. Leave blank to auto-generate",
|
||||
|
||||
@@ -508,7 +508,6 @@
|
||||
"脚本(只读)": "脚本(只读)",
|
||||
"自动删除任务": "自动删除任务",
|
||||
"自动拆分": "自动拆分",
|
||||
"自动换行": "自动换行",
|
||||
"自动添加任务": "自动添加任务",
|
||||
"自动生成": "自动生成",
|
||||
"自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成": "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成",
|
||||
|
||||
@@ -6,7 +6,7 @@ import React, {
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { Button, message, Modal, Space, Switch, TreeSelect } from 'antd';
|
||||
import { Button, message, Modal, TreeSelect } from 'antd';
|
||||
import config from '@/utils/config';
|
||||
import { PageContainer } from '@ant-design/pro-layout';
|
||||
import { request } from '@/utils/http';
|
||||
@@ -15,7 +15,6 @@ import CodeMirror from '@uiw/react-codemirror';
|
||||
import { useOutletContext } from '@umijs/max';
|
||||
import { SharedContext } from '@/layouts';
|
||||
import { langs } from '@uiw/codemirror-extensions-langs';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { getEditorMode } from '@/utils';
|
||||
|
||||
@@ -29,11 +28,6 @@ const Config = () => {
|
||||
const editorRef = useRef<any>(null);
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
const [language, setLanguage] = useState<string>('shell');
|
||||
const [wordWrap, setWordWrap] = useState(
|
||||
() =>
|
||||
(localStorage.getItem('qinglong_editor_word_wrap') ??
|
||||
localStorage.getItem('qinglong_config_word_wrap')) === 'true',
|
||||
);
|
||||
|
||||
const getConfig = (name: string) => {
|
||||
request
|
||||
@@ -110,20 +104,6 @@ const Config = () => {
|
||||
defaultValue="config.sh"
|
||||
onSelect={onSelect}
|
||||
/>,
|
||||
<Space key="word-wrap" size={8}>
|
||||
<span>{intl.get('自动换行')}</span>
|
||||
<Switch
|
||||
aria-label={intl.get('自动换行')}
|
||||
checked={wordWrap}
|
||||
onChange={(checked) => {
|
||||
setWordWrap(checked);
|
||||
localStorage.setItem(
|
||||
'qinglong_editor_word_wrap',
|
||||
String(checked),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Space>,
|
||||
<Button
|
||||
key="1"
|
||||
loading={confirmLoading}
|
||||
@@ -141,10 +121,7 @@ const Config = () => {
|
||||
<CodeMirror
|
||||
value={value}
|
||||
theme={theme.includes('dark') ? 'dark' : 'light'}
|
||||
extensions={[
|
||||
langs.shell(),
|
||||
...(wordWrap ? [EditorView.lineWrapping] : []),
|
||||
]}
|
||||
extensions={[langs.shell()]}
|
||||
onChange={(value) => {
|
||||
setValue(value);
|
||||
}}
|
||||
@@ -159,8 +136,7 @@ const Config = () => {
|
||||
lineNumbersMinChars: 3,
|
||||
folding: false,
|
||||
glyphMargin: false,
|
||||
accessibilitySupport: 'off',
|
||||
wordWrap: wordWrap ? 'on' : 'off',
|
||||
accessibilitySupport: 'off'
|
||||
}}
|
||||
onMount={(editor) => {
|
||||
editorRef.current = editor;
|
||||
|
||||
@@ -6,16 +6,7 @@ import React, {
|
||||
useCallback,
|
||||
useReducer,
|
||||
} from 'react';
|
||||
import {
|
||||
Drawer,
|
||||
Button,
|
||||
Tabs,
|
||||
Badge,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
TreeSelect,
|
||||
} from 'antd';
|
||||
import { Drawer, Button, Tabs, Badge, Select, TreeSelect } from 'antd';
|
||||
import { request } from '@/utils/http';
|
||||
import config from '@/utils/config';
|
||||
import SplitPane from 'react-split-pane';
|
||||
@@ -33,15 +24,11 @@ const EditModal = ({
|
||||
treeData,
|
||||
currentNode,
|
||||
content,
|
||||
wordWrap,
|
||||
onWordWrapChange,
|
||||
handleCancel,
|
||||
}: {
|
||||
treeData?: any;
|
||||
content?: string;
|
||||
currentNode: any;
|
||||
wordWrap: boolean;
|
||||
onWordWrapChange: (checked: boolean) => void;
|
||||
handleCancel: () => void;
|
||||
}) => {
|
||||
const [value, setValue] = useState('');
|
||||
@@ -232,16 +219,6 @@ const EditModal = ({
|
||||
</>
|
||||
}
|
||||
width={'100%'}
|
||||
extra={
|
||||
<Space size={8}>
|
||||
<span>{intl.get('自动换行')}</span>
|
||||
<Switch
|
||||
aria-label={intl.get('自动换行')}
|
||||
checked={wordWrap}
|
||||
onChange={onWordWrapChange}
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
headerStyle={{ padding: '11px 24px' }}
|
||||
onClose={cancel}
|
||||
open={true}
|
||||
@@ -264,7 +241,6 @@ const EditModal = ({
|
||||
lineNumbersMinChars: 3,
|
||||
glyphMargin: false,
|
||||
accessibilitySupport: 'off',
|
||||
wordWrap: wordWrap ? 'on' : 'off',
|
||||
}}
|
||||
onMount={(editor) => {
|
||||
editorRef.current = editor;
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
PlusOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { PageContainer } from '@ant-design/pro-layout';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import Editor from '@monaco-editor/react';
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import { history, useOutletContext } from '@umijs/max';
|
||||
@@ -27,8 +26,6 @@ import {
|
||||
MenuProps,
|
||||
message,
|
||||
Modal,
|
||||
Space,
|
||||
Switch,
|
||||
Tooltip,
|
||||
Tree,
|
||||
TreeSelect,
|
||||
@@ -73,11 +70,6 @@ const Script = () => {
|
||||
const [showMonaco, setShowMonaco] = useState(true);
|
||||
const [historyVisible, setHistoryVisible] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [wordWrap, setWordWrap] = useState(
|
||||
() =>
|
||||
(localStorage.getItem('qinglong_editor_word_wrap') ??
|
||||
localStorage.getItem('qinglong_config_word_wrap')) === 'true',
|
||||
);
|
||||
const savingRef = useRef(false);
|
||||
const savedContentRef = useRef('');
|
||||
const currentKeyRef = useRef(currentNode?.key);
|
||||
@@ -552,11 +544,6 @@ const Script = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const changeWordWrap = (checked: boolean) => {
|
||||
setWordWrap(checked);
|
||||
localStorage.setItem('qinglong_editor_word_wrap', String(checked));
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer
|
||||
className="ql-container-wrapper log-wrapper"
|
||||
@@ -580,14 +567,6 @@ const Script = () => {
|
||||
}
|
||||
loading={loading}
|
||||
extra={[
|
||||
<Space key="word-wrap" size={8}>
|
||||
<span>{intl.get('自动换行')}</span>
|
||||
<Switch
|
||||
aria-label={intl.get('自动换行')}
|
||||
checked={wordWrap}
|
||||
onChange={changeWordWrap}
|
||||
/>
|
||||
</Space>,
|
||||
...(currentNode?.type === 'file' && showMonaco
|
||||
? [
|
||||
<Button
|
||||
@@ -755,7 +734,6 @@ const Script = () => {
|
||||
lineNumbersMinChars: 3,
|
||||
glyphMargin: false,
|
||||
accessibilitySupport: 'off',
|
||||
wordWrap: wordWrap ? 'on' : 'off',
|
||||
}}
|
||||
onMount={(editor) => {
|
||||
editorRef.current = editor;
|
||||
@@ -769,10 +747,7 @@ const Script = () => {
|
||||
{isPhone && (
|
||||
<CodeMirror
|
||||
value={value}
|
||||
extensions={[
|
||||
...getCodeMirrorExtensions(mode),
|
||||
...(wordWrap ? [EditorView.lineWrapping] : []),
|
||||
]}
|
||||
extensions={getCodeMirrorExtensions(mode)}
|
||||
theme={theme.includes('dark') ? 'dark' : 'light'}
|
||||
readOnly={!isEditing || saving}
|
||||
onChange={(value) => {
|
||||
@@ -785,8 +760,6 @@ const Script = () => {
|
||||
treeData={data}
|
||||
currentNode={currentNode}
|
||||
content={value}
|
||||
wordWrap={wordWrap}
|
||||
onWordWrapChange={changeWordWrap}
|
||||
handleCancel={() => {
|
||||
setIsLogModalVisible(false);
|
||||
}}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
const test = require('node:test'),
|
||||
assert = require('node:assert/strict'),
|
||||
fs = require('node:fs'),
|
||||
path = require('node:path'),
|
||||
{ spawnSync } = require('node:child_process');
|
||||
|
||||
const notifyFile = path.resolve(
|
||||
process.env.NOTIFY_PY_PATH || 'sample/notify.py',
|
||||
);
|
||||
const source = fs.readFileSync(notifyFile, 'utf8');
|
||||
|
||||
function httpCalls(text) {
|
||||
const calls = [];
|
||||
const pattern = /requests\.(?:get|post|request)\s*\(/g;
|
||||
let match;
|
||||
while ((match = pattern.exec(text))) {
|
||||
let depth = 0;
|
||||
let end = match.index + match[0].length - 1;
|
||||
for (; end < text.length; end++) {
|
||||
if (text[end] === '(') depth++;
|
||||
else if (text[end] === ')' && --depth === 0) break;
|
||||
}
|
||||
calls.push(text.slice(match.index, end + 1));
|
||||
}
|
||||
return calls;
|
||||
}
|
||||
|
||||
test('sample/notify.py sends every HTTP request with a timeout', () => {
|
||||
const calls = httpCalls(source);
|
||||
assert.ok(
|
||||
calls.length >= 27,
|
||||
`expected at least 27 HTTP calls, got ${calls.length}`,
|
||||
);
|
||||
const missing = calls.filter((call) => !/\btimeout\s*=/.test(call));
|
||||
assert.deepEqual(missing, []);
|
||||
});
|
||||
|
||||
test('notify.send returns after a bounded wait when a channel hangs', () => {
|
||||
const script = `
|
||||
import os, sys, time, types
|
||||
requests = types.ModuleType("requests")
|
||||
requests.get = requests.post = requests.request = lambda *a, **k: None
|
||||
sys.modules["requests"] = requests
|
||||
sys.path.insert(0, ${JSON.stringify(path.dirname(notifyFile))})
|
||||
os.environ.pop("QL_DIR", None)
|
||||
import notify
|
||||
notify.NOTIFY_JOIN_TIMEOUT = 1
|
||||
notify.push_config = {key: "" for key in notify.push_config}
|
||||
notify.push_config["HITOKOTO"] = False
|
||||
notify.push_config["CONSOLE"] = True
|
||||
def hang(*args, **kwargs):
|
||||
time.sleep(60)
|
||||
notify.console = hang
|
||||
start = time.monotonic()
|
||||
notify.send("title", "content")
|
||||
elapsed = time.monotonic() - start
|
||||
assert elapsed < 5, elapsed
|
||||
print("elapsed=%.2f" % elapsed)
|
||||
`;
|
||||
const python = process.platform === 'win32' ? 'python' : 'python3';
|
||||
const result = spawnSync(python, ['-'], {
|
||||
input: script,
|
||||
encoding: 'utf8',
|
||||
timeout: 15000,
|
||||
env: { ...process.env, QL_DIR: '' },
|
||||
});
|
||||
assert.equal(result.status, 0, result.stdout + result.stderr);
|
||||
assert.match(result.stdout, /elapsed=/);
|
||||
});
|
||||
@@ -3,7 +3,7 @@ const test = require('node:test');
|
||||
const fs = require('node:fs/promises');
|
||||
const path = require('node:path');
|
||||
const os = require('node:os');
|
||||
const { Sequelize, DataTypes, QueryTypes } = require('sequelize');
|
||||
const { Sequelize, QueryTypes } = require('sequelize');
|
||||
const { migrateSchema } = require('../../back/shared/schemaMigrations');
|
||||
const load = require('../helpers/load-security-module.cjs');
|
||||
|
||||
@@ -46,39 +46,6 @@ test('legacy database upgrades without losing rows and repeated migration is ide
|
||||
assert.equal(applied.length, 15);
|
||||
});
|
||||
|
||||
test('migration repairs malformed environment labels before ORM reads', async (t) => {
|
||||
const database = await createLegacy();
|
||||
t.after(() => database.close());
|
||||
await migrateSchema(database);
|
||||
for (const [id, labels] of [
|
||||
[1, ''],
|
||||
[2, null],
|
||||
[3, '{invalid'],
|
||||
[4, '{}'],
|
||||
[5, '["keep"]'],
|
||||
]) {
|
||||
await database.query('INSERT INTO "Envs" (id, name, labels) VALUES (?, ?, ?)', {
|
||||
replacements: [id, `env-${id}`, labels],
|
||||
});
|
||||
}
|
||||
|
||||
const EnvModel = database.define('Env', {
|
||||
name: DataTypes.STRING,
|
||||
labels: DataTypes.JSON,
|
||||
}, { timestamps: false });
|
||||
await assert.rejects(EnvModel.findAll(), /Unexpected end of JSON input/);
|
||||
await migrateSchema(database);
|
||||
const rows = await EnvModel.findAll({ order: [['id', 'ASC']] });
|
||||
assert.deepEqual(rows.map((row) => row.labels), [[], [], [], [], ['keep']]);
|
||||
await migrateSchema(database);
|
||||
const repaired = await database.query('SELECT id, labels FROM "Envs" ORDER BY id', {
|
||||
type: QueryTypes.SELECT,
|
||||
});
|
||||
assert.deepEqual(repaired.map(({ labels }) => labels), [
|
||||
'[]', '[]', '[]', '[]', '["keep"]',
|
||||
]);
|
||||
});
|
||||
|
||||
test('migration failure rolls back added columns and can be retried after repair', async (t) => {
|
||||
const database = await createLegacy(':memory:', true);
|
||||
t.after(() => database.close());
|
||||
|
||||
Reference in New Issue
Block a user