Skip to content

Commit f658431

Browse files
feat(frontend): call out a catch-all that only repeats the final fallback
A catch-all routed to the proxy with nothing enabled below it shadows only the automatic tail, which already ends in the same proxy fallback. Saying it matches everything is true but unhelpful there — the rule buys nothing and costs the automatic IP check, so name that instead.
1 parent 28a893f commit f658431

11 files changed

Lines changed: 61 additions & 3 deletions

File tree

frontend/src/features/settings/__tests__/isCatchAllRule.test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it } from "vitest";
22
import type { RoutingRule } from "../../../generated/bindings";
3-
import { isCatchAllRule } from "../helpers";
3+
import { isCatchAllRule, isRedundantCatchAll } from "../helpers";
44

55
const rule = (patch: Partial<RoutingRule>): RoutingRule => ({
66
id: "r1",
@@ -47,3 +47,31 @@ describe("isCatchAllRule", () => {
4747
expect(isCatchAllRule(rule({ port: "0-abc" }))).toBe(false);
4848
});
4949
});
50+
51+
describe("isRedundantCatchAll", () => {
52+
const catchAll = (patch: Partial<RoutingRule> = {}) => rule({ port: "0-65535", ...patch });
53+
54+
it("flags a trailing catch-all that only repeats the final proxy fallback", () => {
55+
expect(isRedundantCatchAll([rule({ ip: ["geoip:ru"] }), catchAll()], 1)).toBe(true);
56+
});
57+
58+
it("ignores one that still shadows an enabled rule below", () => {
59+
expect(isRedundantCatchAll([catchAll(), rule({ ip: ["geoip:ru"] })], 0)).toBe(false);
60+
});
61+
62+
it("looks past disabled rules below", () => {
63+
expect(isRedundantCatchAll([catchAll(), rule({ ip: ["geoip:ru"], enabled: false })], 0)).toBe(
64+
true,
65+
);
66+
});
67+
68+
it("ignores a catch-all routed anywhere but the proxy", () => {
69+
expect(isRedundantCatchAll([catchAll({ outboundTag: "direct" })], 0)).toBe(false);
70+
expect(isRedundantCatchAll([catchAll({ outboundTag: "block" })], 0)).toBe(false);
71+
});
72+
73+
it("ignores an index that points at no rule", () => {
74+
expect(isRedundantCatchAll([catchAll()], -1)).toBe(false);
75+
expect(isRedundantCatchAll([], 0)).toBe(false);
76+
});
77+
});

frontend/src/features/settings/helpers.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ export function isCatchAllRule(rule: RoutingRule): boolean {
9494
return portCoversEveryPort(rule.port);
9595
}
9696

97+
/**
98+
* Whether the catch-all at `index` shadows nothing but the automatic tail, which
99+
* already ends in the proxy fallback — so the rule costs the IP check and buys nothing.
100+
*/
101+
export function isRedundantCatchAll(rules: RoutingRule[], index: number): boolean {
102+
if (index < 0 || rules[index]?.outboundTag !== "proxy") return false;
103+
return !rules.slice(index + 1).some((rule) => rule.enabled);
104+
}
105+
97106
export function ruleIcon(rule: RoutingRule): string {
98107
if (rule.outboundTag === "direct") return "near_me";
99108
if (rule.outboundTag === "block") return "block";

frontend/src/features/settings/sections/RoutingSection.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { RoutingRule } from "../../../generated/bindings";
1414
import { useFormatters, useT } from "../../../i18n";
1515
import type { AdvancedSettings } from "../../../lib/bridge";
1616
import { getRuntimeBridgeMode } from "../../../lib/ksu-webui";
17-
import { isCatchAllRule, ruleIcon, ruleSummary } from "../helpers";
17+
import { isCatchAllRule, isRedundantCatchAll, ruleIcon, ruleSummary } from "../helpers";
1818
import { makePresetRule, RULE_PRESETS } from "../rule-presets";
1919

2020
export function RoutingSection({
@@ -52,6 +52,7 @@ export function RoutingSection({
5252
const domainStrategy4Xray = settings.domainStrategy;
5353
const domainStrategy4Singbox = settings.domainStrategy4Singbox;
5454
const catchAllIndex = routingRules.findIndex(isCatchAllRule);
55+
const catchAllRedundant = isRedundantCatchAll(routingRules, catchAllIndex);
5556

5657
return (
5758
<>
@@ -210,7 +211,11 @@ export function RoutingSection({
210211
{ruleSummary(rule, t, formatters, profileName)}
211212
{index === catchAllIndex && (
212213
<div style={{ color: "var(--warn)", marginTop: 2 }}>
213-
{t("settings.routingCatchAll")}
214+
{t(
215+
catchAllRedundant
216+
? "settings.routingCatchAllRedundant"
217+
: "settings.routingCatchAll",
218+
)}
214219
</div>
215220
)}
216221
{catchAllIndex >= 0 && index > catchAllIndex && rule.enabled && (

frontend/src/i18n/ar.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,8 @@ const ar = {
737737
"settings.routingEmpty": "لا توجد قواعد توجيه بعد.",
738738
"settings.routingRuleDefault": "القاعدة {n}",
739739
"settings.routingCatchAll": "يطابق كل اتصال — القواعد أدناه وفحص IP التلقائي لن تُنفَّذ.",
740+
"settings.routingCatchAllRedundant":
741+
"يطابق كل اتصال. يُضاف التوجيه النهائي إلى البروكسي تلقائيًا بالفعل، لذا فإن هذه القاعدة تُلغي فحص IP التلقائي دون أي فائدة.",
740742
"settings.routingUnreachable": "لا يتم الوصول إليها أبدًا.",
741743
"settings.routingRuleDomains": plural("count", {
742744
zero: "لا نطاقات",

frontend/src/i18n/en.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,8 @@ const en = {
627627
"settings.routingRuleDefault": "Rule {n}",
628628
"settings.routingCatchAll":
629629
"Matches every connection — the rules below and the automatic IP check never run.",
630+
"settings.routingCatchAllRedundant":
631+
"Matches every connection. The proxy fallback is already appended automatically, so this rule only costs you the automatic IP check.",
630632
"settings.routingUnreachable": "Never reached.",
631633
"settings.routingRuleDomains": plural("count", {
632634
one: "# domain",

frontend/src/i18n/es.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,8 @@ const es = {
621621
"settings.routingRuleDefault": "Regla {n}",
622622
"settings.routingCatchAll":
623623
"Coincide con todas las conexiones: las reglas siguientes y la comprobación automática de IP nunca se ejecutan.",
624+
"settings.routingCatchAllRedundant":
625+
"Coincide con todas las conexiones. La salida final por el proxy ya se añade automáticamente, así que esta regla solo te cuesta la comprobación automática de IP.",
624626
"settings.routingUnreachable": "Nunca se alcanza.",
625627
"settings.routingRuleDomains": plural("count", {
626628
one: "# dominio",

frontend/src/i18n/hi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,8 @@ const hi = {
611611
"settings.routingEmpty": "अभी कोई रूटिंग नियम नहीं हैं।",
612612
"settings.routingRuleDefault": "नियम {n}",
613613
"settings.routingCatchAll": "हर कनेक्शन से मेल खाता है — नीचे के नियम और स्वचालित IP जाँच कभी नहीं चलते।",
614+
"settings.routingCatchAllRedundant":
615+
"हर कनेक्शन से मेल खाता है। प्रॉक्सी पर अंतिम फ़ॉलबैक पहले से ही अपने आप जुड़ जाता है, इसलिए यह नियम सिर्फ़ स्वचालित IP जाँच छीन लेता है।",
614616
"settings.routingUnreachable": "कभी लागू नहीं होता।",
615617
"settings.routingRuleDomains": plural("count", {
616618
one: "# डोमेन",

frontend/src/i18n/pt.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,8 @@ const pt = {
620620
"settings.routingRuleDefault": "Regra {n}",
621621
"settings.routingCatchAll":
622622
"Corresponde a todas as conexões — as regras abaixo e a verificação automática de IP nunca são executadas.",
623+
"settings.routingCatchAllRedundant":
624+
"Corresponde a todas as conexões. O encaminhamento final para o proxy já é adicionado automaticamente, portanto esta regra só lhe custa a verificação automática de IP.",
623625
"settings.routingUnreachable": "Nunca é alcançada.",
624626
"settings.routingRuleDomains": plural("count", {
625627
one: "# domínio",

frontend/src/i18n/ru.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,8 @@ const ru = {
656656
"settings.routingRuleDefault": "Правило {n}",
657657
"settings.routingCatchAll":
658658
"Перехватывает весь трафик — правила ниже и автопроверка по IP не сработают.",
659+
"settings.routingCatchAllRedundant":
660+
"Перехватывает весь трафик. Переход на прокси и так добавляется автоматически, поэтому правило лишь отключает автопроверку по IP.",
659661
"settings.routingUnreachable": "Никогда не сработает.",
660662
"settings.routingRuleDomains": plural("count", {
661663
one: "# домен",

frontend/src/i18n/vi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,8 @@ const vi = {
616616
"settings.routingRuleDefault": "Quy tắc {n}",
617617
"settings.routingCatchAll":
618618
"Khớp mọi kết nối — các quy tắc bên dưới và bước kiểm tra IP tự động sẽ không chạy.",
619+
"settings.routingCatchAllRedundant":
620+
"Khớp mọi kết nối. Bước chuyển cuối sang proxy vốn đã được thêm tự động, nên quy tắc này chỉ khiến bạn mất bước kiểm tra IP tự động.",
619621
"settings.routingUnreachable": "Không bao giờ được dùng.",
620622
"settings.routingRuleDomains": plural("count", {
621623
one: "# domain",

0 commit comments

Comments
 (0)