import { useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import * as stylex from "@stylexjs/stylex"; import { formatTime } from "@/lib/format"; import InlineError from "@/lib/InlineError"; import { blocklistCreateMutation, blocklistDeleteMutation, blocklistUpdateMutation, blocklistsQuery, blocklistsUpdateNowMutation, } from "@/lib/queries"; import type { Blocklist, BlocklistInput } from "@/lib/types"; import ConfirmDialog from "@/ui/ConfirmDialog"; import { styles as shared } from "@/ui/styles"; import { colors } from "@/ui/tokens.stylex"; import AuthorityGate from "./AuthorityGate"; import BlocklistForm from "./BlocklistForm"; import FileModeNote from "./FileModeNote"; import QueryPanel from "./QueryPanel"; import { styles as config } from "./styles"; const SKIPPED_NOTE = "Both “Skipped” columns count lines nxdns read and did not take. Skipped regex lines are patterns nxdns accepts " + "only from you — adopt one you trust as a regex rule. Skipped unsupported lines are syntax nxdns cannot translate " + "into a DNS decision: cosmetic element hiding, browser-only modifiers. A skipped unsupported count that dwarfs the " + "domain count usually means the list is written for a browser extension, and its DNS or hosts variant will block " + "more here."; const styles = stylex.create({ name: { fontWeight: 500, }, badge: { marginLeft: "0.5rem", borderRadius: "0.25rem", backgroundColor: colors.border, paddingInline: "0.375rem", paddingBlock: "0.125rem", fontSize: "0.75rem", lineHeight: "1rem", color: colors.text, }, url: { display: "block", maxWidth: "18rem", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", }, actions: { display: "flex", gap: "0.75rem", }, dimWhenDisabled: { opacity: { default: 1, ":disabled": 0.5 }, }, }); /** * A runtime action: it re-downloads the sources the running process already * knows about, so it stays enabled under file authority and while * `/api/config/status` is still answering. * * The feedback is the action's own, and it is transient: started while the * request is in flight, the failure verbatim if it fails, and nothing on * success — the refreshed counters are the success signal, and a standing * "counters refreshed" line would claim a refetch that may itself have failed. * Durable per-source outcomes live in Diagnostics. */ function UpdateNowAction() { const queryClient = useQueryClient(); const updateNow = useMutation(blocklistsUpdateNowMutation(queryClient)); return (
Update started…
)}The shared catalogue every group draws from. A group subscribes to sources on the Groups tab.
blocklist_sources
The file declares no blocklist sources.
) : (| Name | URL | Enabled | Domains | Wildcards | Exceptions | Skipped regex | Skipped unsupported | Last updated |
|---|---|---|---|---|---|---|---|---|
| {b.name} {b.is_suggested && Suggested} | {b.url} | {String(b.enabled)} | {b.domain_count} | {b.wildcard_count} | {b.exception_count} | {b.skipped_regex_count} | {b.skipped_unsupported_count} | {b.last_updated === null ? "never" : formatTime(b.last_updated)} |
{SKIPPED_NOTE}
No blocklist sources yet. Add one below.
) : (| Name | URL | Enabled | Domains | Wildcards | Exceptions | Skipped regex | Skipped unsupported | Last updated | Actions |
|---|---|---|---|---|---|---|---|---|---|
| {b.name} {b.is_suggested && Suggested} | {b.url} | toggleEnabled(b)} {...stylex.props(shared.focusRing)} /> | {b.domain_count} | {b.wildcard_count} | {b.exception_count} | {b.skipped_regex_count} | {b.skipped_unsupported_count} | {b.last_updated === null ? "never" : formatTime(b.last_updated)} |
|
{SKIPPED_NOTE}