import { useContext, useState } from 'react' import { getSettings, setSettings, temporaryDisableBlocking } from '../../api/settings' import { Confirm } from '../../ui/Confirm' import { Menu } from '../../ui/Menu' import { Button } from '../../ui/Button' import { Tooltip } from '../../ui/Tooltip' import { nodeArg, useHubWrite } from '../../lib/hub-write' import { HubAll } from '../../lib/hub-all' import type { AlertType } from '../../ui/Alert' import { noticeFromFailure } from '../../lib/notice' /* The Dashboard's "Blocking" menu. Upstream puts it in the header of "Top Blocked Domains" (`btnDashboardBlockingOptions`, index.html) and here it was missing: turning blocking off for a while from the Dashboard is one of the things most often done in this console (the typical case is a page not loading because of a list), and it forced a trip to Settings › Blocking to find it. Two behavioural details copied as they are: - The state is asked for ON OPENING the menu, not when drawing the screen (`main.js:2429`): between the two the setting may have changed from another tab, and showing "Enable Blocking" with blocking already on is worse than taking 100 ms. - Either "Enable" or "Disable" comes out, never both. While it is not known, neither. The confirmation and alert texts are upstream's literals (`main.js:2448-2496`). What changes is the way of asking: there it is a browser `confirm()` and here the console's dialog. */ /** Upstream's eight durations, with their exact labels. */ const DURATIONS: { minutes: number; text: string }[] = [ { minutes: 1, text: 'Disable Blocking For 1 Minute' }, { minutes: 2, text: 'Disable Blocking For 2 Minutes' }, { minutes: 5, text: 'Disable Blocking For 5 Minutes' }, { minutes: 10, text: 'Disable Blocking For 10 Minutes' }, { minutes: 15, text: 'Disable Blocking For 15 Minutes' }, { minutes: 30, text: 'Disable Blocking For 30 Minutes' }, { minutes: 60, text: 'Disable Blocking For 1 Hour' }, { minutes: 180, text: 'Disable Blocking For 3 Hours' }, ] interface Pending { title: string text: string label: string variant: 'primary' | 'danger' perform: () => Promise } export function BlockingMenu({ token, onNotice, text = 'Blocking', onChanged, node = '', allowed = true, }: { token: string | null /** Behind a hub: the server the change is made on (empty or "cluster": none chosen, so it is switched off). */ node?: string /** Behind a hub: what the role may do. */ allowed?: boolean onNotice: (a: { type: AlertType; title: string; text: string }) => void /** The trigger text. The Dashboard keeps upstream's "Blocking". */ text?: string /** After a successful change, for a caller that draws the state. */ onChanged?: () => void }) { const [active, setActive] = useState(null) const [pending, setPending] = useState(null) const [busy, setBusy] = useState(false) /* Behind a hub with "All servers" chosen: the choice opens the flow that makes it on the servers picked next. */ const all = useContext(HubAll) async function loadState() { setActive(null) const s = await getSettings(token, ...nodeArg(node)) if (s != null) setActive(s.enableBlocking) } function flip(turnOn: boolean): Pending { return { title: turnOn ? 'Enable Blocking' : 'Disable Blocking', text: `Are you sure you want to ${turnOn ? 'enable' : 'disable'} blocking?`, label: turnOn ? 'Enable' : 'Disable', variant: turnOn ? 'primary' : 'danger', perform: async () => { const r = await setSettings(token, { enableBlocking: String(turnOn), ...(node ? { node } : {}) }) // The same text as the other thirty-six: it was the only one that said // a bare "Session expired." for this very condition. if (r.kind !== 'ok') throw new Error(noticeFromFailure(r).text) setActive(turnOn) onNotice({ type: 'success', title: turnOn ? 'Blocking Enabled!' : 'Blocking Disabled!', text: `Blocking was ${turnOn ? 'enabled' : 'disabled'} successfully.`, }) }, } } function forAWhile(minutes: number): Pending { return { title: 'Temporarily Disable Blocking', text: `Are you sure to temporarily disable blocking for ${minutes} minute(s)?`, label: 'Disable', variant: 'danger', perform: async () => { const till = await temporaryDisableBlocking(token, String(minutes), ...nodeArg(node)) if (till == null) throw new Error('The request failed.') setActive(false) onNotice({ type: 'success', title: 'Blocking Disabled!', text: `Blocking was successfully disabled temporarily for ${minutes} minute(s).`, }) }, } } async function confirm() { if (pending == null) return setBusy(true) try { await pending.perform() onChanged?.() setPending(null) } catch (e) { onNotice({ type: 'danger', title: 'Error!', text: (e as Error).message }) setPending(null) } finally { setBusy(false) } } /* Behind a hub, only on a chosen server the hub can write to; the tooltip says why not. */ const write = useHubWrite(node, allowed) if (all !== null) { return ( {(close) => ( <> {DURATIONS.map((p) => ( ))} )} ) } if (!write.allowed) { return ( ) } return ( <> void loadState()}> {(close) => ( <> {active === false && ( )} {active === true && ( )} {DURATIONS.map((p) => ( ))} )} setPending(null)} onConfirm={() => void confirm()} /> ) }