Compare commits

..

8 Commits

Author SHA1 Message Date
sagit 15e4508be4 feat(forward): support tunnel-group collapse and ordering in full mode (#243)
## Summary
- add collapsible tunnel groups in the forward page when compact mode is
disabled
- add drag-and-drop ordering for tunnel groups within each user section
in full mode
- persist group order/collapse by current login user (admins: local +
global config, normal users: local only)

## Testing
- npm run build (vite-frontend)
2026-03-02 22:25:31 +08:00
sagitchu 634c6cd620 feat(forward): add tunnel group collapse and drag sorting in full mode 2026-03-02 22:24:39 +08:00
sagit 4eaecb289b fix(config): expose forward compact mode switch in settings (#241)
## Summary
- Add `forward_compact_mode` to the `/config` settings item list so the
compact-mode switch is visible in the main settings page.
- Include `forward_compact_mode` in initial config cache keys to keep
switch state consistent on load.

## Verification
- `npm run build` (vite-frontend)
2026-03-02 21:37:10 +08:00
sagitchu 98a9e5c666 fix(config): expose forward compact mode switch in settings 2026-03-02 21:36:33 +08:00
sagit d244920dd4 feat(forward): add global compact mode with alpha8 list layout (#240)
## Summary
- Add a global forward compact mode toggle in settings, persisted via
`config` key `forward_compact_mode`.
- Make forward page read and react to this global setting in real time
through a browser event.
- In compact mode, render forward list using the 2.1.6-alpha8 style
(single grouped table / global direct card grid) while keeping non-list
interactions unchanged.

## Verification
- Installed frontend dependencies with `npm install`.
- Built frontend successfully with `npm run build`.
2026-03-02 21:11:39 +08:00
sagitchu 77e4387b35 feat(forward): add global compact mode with alpha8 list layout 2026-03-02 21:10:56 +08:00
sagit 7a40ddb1ef fix(diagnosis): tighten timeout handling and clarify timeout messaging (#233)
## Summary
- shorten per-item diagnosis command timeout from 2 minutes to 30
seconds while keeping overall request timeout at 2 minutes
- centralize timeout messages in backend constants and apply consistent
timeout fallback handling in diagnosis result assembly
- update frontend forward/tunnel diagnosis timeout copy to clearly
explain single-item and overall timeout limits

## Notes
- includes workspace tool config files under `.claude/` and `.entire/`
as part of this commit
2026-03-01 18:39:19 +08:00
sagitchu d33814e18c fix(diagnosis): tighten timeout handling and clarify timeout messaging 2026-03-01 18:37:53 +08:00
7 changed files with 1459 additions and 201 deletions
+84
View File
@@ -0,0 +1,84 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Task",
"hooks": [
{
"type": "command",
"command": "entire hooks claude-code post-task"
}
]
},
{
"matcher": "TodoWrite",
"hooks": [
{
"type": "command",
"command": "entire hooks claude-code post-todo"
}
]
}
],
"PreToolUse": [
{
"matcher": "Task",
"hooks": [
{
"type": "command",
"command": "entire hooks claude-code pre-task"
}
]
}
],
"SessionEnd": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "entire hooks claude-code session-end"
}
]
}
],
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "entire hooks claude-code session-start"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "entire hooks claude-code stop"
}
]
}
],
"UserPromptSubmit": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "entire hooks claude-code user-prompt-submit"
}
]
}
]
},
"permissions": {
"deny": [
"Read(./.entire/metadata/**)"
]
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"enabled": true,
"telemetry": false
}
@@ -110,9 +110,11 @@ func (h *Handler) buildDiagnosisStreamStartItems(workItems []diagnosisWorkItem)
const diagnosisMaxConcurrency = 8
const (
defaultNodeCommandTimeout = 6 * time.Second
diagnosisCommandTimeout = 2 * time.Minute
diagnosisRequestTimeout = 2 * time.Minute
defaultNodeCommandTimeout = 6 * time.Second
diagnosisCommandTimeout = 30 * time.Second
diagnosisRequestTimeout = 2 * time.Minute
diagnosisCommandTimeoutMsg = "诊断超时(30秒)"
diagnosisRequestTimeoutMsg = "诊断超时(2分钟)"
)
func (h *Handler) resolveForwardAccess(r *http.Request, forwardID int64) (*forwardRecord, int64, int, error) {
@@ -848,15 +850,15 @@ func resolveDiagnosisTargets(remoteAddr string) ([]diagnosisTarget, error) {
func diagnosisContextMessage(ctx context.Context) string {
if ctx == nil {
return "诊断超时(2分钟)"
return diagnosisRequestTimeoutMsg
}
switch ctx.Err() {
case context.DeadlineExceeded:
return "诊断超时(2分钟)"
return diagnosisRequestTimeoutMsg
case context.Canceled:
return "诊断已取消"
default:
return "诊断超时(2分钟)"
return diagnosisRequestTimeoutMsg
}
}
@@ -895,7 +897,7 @@ func newDiagnosisTimeoutItem(workItem diagnosisWorkItem, message string) map[str
item := newDiagnosisResultItem(workItem.fromNodeID, workItem.targetIP, targetPort, workItem.description, workItem.metadata)
item["success"] = false
if strings.TrimSpace(message) == "" {
message = "诊断超时(2分钟)"
message = diagnosisCommandTimeoutMsg
}
item["message"] = message
return item
@@ -993,7 +995,7 @@ enqueueLoop:
for i := range results {
if results[i] == nil {
results[i] = newDiagnosisTimeoutItem(workItems[i], "诊断超时(2分钟)")
results[i] = newDiagnosisTimeoutItem(workItems[i], diagnosisCommandTimeoutMsg)
}
}
return results
+7
View File
@@ -117,6 +117,12 @@ const CONFIG_ITEMS: ConfigItem[] = [
description: "用于浏览器标签页图标,上传后会自动转换为 PNG 并持久化保存",
type: "input",
},
{
key: "forward_compact_mode",
label: "转发页面精简模式",
description: "开启后,转发页面列表使用 2.1.6-alpha8 样式(全局配置)",
type: "switch",
},
{
key: "captcha_enabled",
label: "启用验证码",
@@ -167,6 +173,7 @@ const getInitialConfigs = (): Record<string, string> => {
"captcha_enabled",
"cloudflare_site_key",
"cloudflare_secret_key",
"forward_compact_mode",
"ip",
"panel_domain",
"app_logo",
File diff suppressed because it is too large Load Diff
+90
View File
@@ -6,12 +6,15 @@ import { Input } from "@/shadcn-bridge/heroui/input";
import { Button } from "@/shadcn-bridge/heroui/button";
import { Card, CardBody } from "@/shadcn-bridge/heroui/card";
import { Select, SelectItem } from "@/shadcn-bridge/heroui/select";
import { Switch } from "@/shadcn-bridge/heroui/switch";
import { reinitializeBaseURL } from "@/api/network";
import { getConfigByName, updateConfig } from "@/api";
import {
type UpdateReleaseChannel,
getUpdateReleaseChannel,
setUpdateReleaseChannel,
} from "@/utils/version-update";
import { isAdmin } from "@/utils/auth";
import {
getPanelAddresses,
savePanelAddress,
@@ -26,6 +29,8 @@ interface PanelAddress {
inx: boolean;
}
const FORWARD_COMPACT_MODE_CONFIG_KEY = "forward_compact_mode";
export const SettingsPage = () => {
const navigate = useNavigate();
const [panelAddresses, setPanelAddresses] = useState<PanelAddress[]>([]);
@@ -34,6 +39,11 @@ export const SettingsPage = () => {
const [updateChannel, setUpdateChannel] = useState<UpdateReleaseChannel>(
getUpdateReleaseChannel(),
);
const [forwardCompactMode, setForwardCompactMode] = useState(false);
const [forwardCompactModeSaving, setForwardCompactModeSaving] =
useState(false);
const admin = isAdmin();
const setPanelAddressesFunc = (newAddress: PanelAddress[]) => {
setPanelAddresses(newAddress);
@@ -86,8 +96,57 @@ export const SettingsPage = () => {
// 页面加载时获取数据
useEffect(() => {
loadPanelAddresses();
loadForwardCompactMode();
}, []);
const loadForwardCompactMode = async () => {
try {
const res = await getConfigByName(FORWARD_COMPACT_MODE_CONFIG_KEY);
const enabled =
res.code === 0 &&
typeof res.data?.value === "string" &&
res.data.value === "true";
setForwardCompactMode(enabled);
} catch {
setForwardCompactMode(false);
}
};
const handleForwardCompactModeChange = async (enabled: boolean) => {
if (!admin || forwardCompactModeSaving) {
return;
}
const previous = forwardCompactMode;
setForwardCompactMode(enabled);
setForwardCompactModeSaving(true);
try {
const response = await updateConfig(
FORWARD_COMPACT_MODE_CONFIG_KEY,
enabled ? "true" : "false",
);
if (response.code === 0) {
toast.success(`转发页面精简模式已${enabled ? "开启" : "关闭"}`);
window.dispatchEvent(
new CustomEvent("forwardCompactModeChanged", {
detail: { enabled },
}),
);
} else {
setForwardCompactMode(previous);
toast.error(response.msg || "保存精简模式失败");
}
} catch {
setForwardCompactMode(previous);
toast.error("保存精简模式失败");
} finally {
setForwardCompactModeSaving(false);
}
};
const handleUpdateChannelChange = (channel: UpdateReleaseChannel) => {
setUpdateChannel(channel);
setUpdateReleaseChannel(channel);
@@ -164,6 +223,37 @@ export const SettingsPage = () => {
</CardBody>
</Card>
<Card className="border border-gray-200 dark:border-gray-700">
<CardBody className="p-6">
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
显示设置
</h2>
<div className="rounded-lg border border-gray-200 dark:border-gray-700 px-4 py-3">
<div className="flex items-center justify-between gap-4">
<div>
<p className="text-sm font-medium text-gray-900 dark:text-white">
转发页面精简模式
</p>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
开启后,转发页面列表使用 2.1.6-alpha8 样式。
</p>
</div>
<Switch
color="primary"
isDisabled={!admin || forwardCompactModeSaving}
isSelected={forwardCompactMode}
onValueChange={handleForwardCompactModeChange}
/>
</div>
{!admin && (
<p className="mt-2 text-xs text-amber-600 dark:text-amber-400">
仅管理员可修改该全局配置。
</p>
)}
</div>
</CardBody>
</Card>
{/* 添加新地址 */}
<Card className="border border-gray-200 dark:border-gray-700">
<CardBody className="p-6">
+2 -2
View File
@@ -605,7 +605,7 @@ export default function TunnelPage() {
toast.error(streamErrorMessage);
}
if (streamResult.timedOut) {
toast.error("诊断达到2分钟超时,已返回当前结果");
toast.error("诊断超时(单条30秒 / 整体2分钟),已返回当前结果");
}
} catch {
if (abortController.signal.aborted) {
@@ -2141,7 +2141,7 @@ export default function TunnelPage() {
{diagnosisProgress.timedOut && (
<Alert
color="warning"
description="诊断已达到2分钟超时,以下为当前已完成结果。"
description="诊断超时(单条30秒 / 整体2分钟),以下为当前已完成结果。"
title="诊断超时"
variant="flat"
/>