disallow to disable guard during focus
docker / image (push) Failing after 24s
lint / ruff (push) Failing after 28s
tests / backend (push) Successful in 37s
tests / desktop (push) Successful in 22s
tests / android-core (push) Successful in 49s
tests / web (push) Successful in 1m20s
android / apk (push) Successful in 8m49s

This commit is contained in:
timka123 committed 2026-10-28 09:50:58 +03:00
1 parent 157a113c39
commit d280101bce
21 files changed
+481 -43

No files matched your search

+4 -2
View File
@@ -88,7 +88,10 @@ export interface BlockerPolicy {
reason: string;
mode: string;
enabled: boolean;
strict: boolean;
/** The guard is on and an interval is live, so it may not be switched off. Not the same as `active`: scheduled mode outside its window is inactive and still locked. */
locked: boolean;
/** Why it is locked, in the server's words — `""` when it is not. */
lock_reason: string;
redirect_ip: string;
domains: string[];
apps: string[];
@@ -209,7 +212,6 @@ export interface AppSettings {
blocked_apps: string[];
kill_blocked_apps: boolean;
schedule: { label: string; start: string; end: string; days: number[] }[];
strict: boolean;
redirect_ip: string;
app_poll_seconds: number;
notify_on_block: boolean;
+23 -9
View File
@@ -6,6 +6,10 @@
* are answered by the desktop app's local DNS stub for the zones it can route.
* The stats show what is really in force (`domains` is empty while blocking is
* off) and every banner says plainly what could not be enforced.
*
* The switch is one-way while you work: the server refuses to turn the guard off
* while an interval is running or paused, so `policy.locked` greys it — turning
* it *on* is never refused.
*/
import { useEffect, useMemo, useState } from "react";
import {
@@ -97,6 +101,8 @@ export default function BlockerPage({ onToast }: { onToast: (message: string) =>
[unexpandable],
);
const badExpansions = policy.data?.invalid_expansions ?? [];
/** The server has already folded "and the guard is on" in, so an off guard stays switchable on. */
const locked = policy.data?.locked ?? false;
const top = useMemo(() => events.data?.by_target.slice(0, 8) ?? [], [events.data]);
return (
@@ -106,15 +112,23 @@ export default function BlockerPage({ onToast }: { onToast: (message: string) =>
title={policy.data?.active ? "Blocking is on" : "Blocking is off"}
caption="Rules live here; the desktop app enforces them on your machine."
actions={
<FormControlLabel
control={
<Switch
checked={blocker?.enabled ?? false}
onChange={(event) => void patch({ enabled: event.target.checked })}
/>
}
label="Enabled"
/>
<Stack alignItems="flex-end">
<FormControlLabel
control={
<Switch
checked={blocker?.enabled ?? false}
disabled={locked}
onChange={(event) => void patch({ enabled: event.target.checked })}
/>
}
label="Enabled"
/>
{locked && (
<Typography variant="caption" color="text.secondary">
{policy.data?.lock_reason}
</Typography>
)}
</Stack>
}
/>