From fcc7436965939f7d0cb3efac7fb9fb810445d865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sat, 29 Aug 2026 00:28:24 +0100 Subject: [PATCH] Administration is emptied, and starts again with a Database block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit the address-and-token form is gone from the screen and ConnectionPanel.tsx with it. leaving an unreferenced component behind would have been worse than deleting it: it is in the history, and a file nothing renders rots without anyone noticing. in its place, Administration is a LIST OF BLOCKS rather than one form. it will hold several unrelated settings, and giving each a titled block now means the second one moves nothing — a single form would have to be broken up the first time anything joined it. the first block is Database, and it is empty on purpose. it says "nothing to set yet" rather than rendering nothing, because a section that draws blank reads as one somebody abandoned. what goes in it is not decided, and inventing fields to fill the space would be deciding it in the wrong place. WHAT THIS COSTS, plainly: there is now no way to configure this plugin from the screen at all. the server half is untouched — `/_config` still stores a URL and a token and the proxy in api/router.ts still reads them — but nothing can set them. that is consistent with the gate being hardcoded false rather than a second breakage: both are the same unfinished change, and neither is worth half-fixing while the shape of the connection is being replaced. Co-Authored-By: Claude Opus 5 --- manifest.ts | 2 +- web/AdministrationPanel.tsx | 55 +++++++++ web/ConnectionPanel.tsx | 240 ------------------------------------ web/DetailPanel.tsx | 4 +- 4 files changed, 58 insertions(+), 243 deletions(-) create mode 100644 web/AdministrationPanel.tsx delete mode 100644 web/ConnectionPanel.tsx diff --git a/manifest.ts b/manifest.ts index 19020c7..0d997a6 100644 --- a/manifest.ts +++ b/manifest.ts @@ -35,7 +35,7 @@ import type { PluginManifest } from '@@/plugins/manifest'; */ export const manifest: PluginManifest = { publisher: 'officerdev', - version: '2026.08.29.04', + version: '2026.08.29.05', platform: '>=1.0.0', label: 'Fofos', diff --git a/web/AdministrationPanel.tsx b/web/AdministrationPanel.tsx new file mode 100644 index 0000000..9ae1c2f --- /dev/null +++ b/web/AdministrationPanel.tsx @@ -0,0 +1,55 @@ +import { Database } from 'lucide-react'; + +/** + * Administration — this plugin's own machinery, not the client's site. + * + * ── It is a list of blocks, and it starts with one ── + * + * Not one form. Administration will hold several unrelated settings, and giving each its own titled + * block from the start means adding the second one moves nothing — as against a single form that has + * to be broken up the first time anything joins it. + * + * ── What used to be here ── + * + * An address and a token: where fofos is, over HTTP. That is gone. It has NOT been replaced yet, so + * nothing can currently configure this plugin — which is why `useReady` stays hardcoded false and + * every other section stays locked. The two are the same change and neither is finished. + * + * The server half is untouched: `/_config` still stores a URL and a token, and the proxy in + * `api/router.ts` still reads them. Only the way in from the screen is gone. + */ + +/** One titled block. The shape every Administration setting gets, so the second one costs nothing. */ +const Block = ({ + icon: Icon, + title, + hint, + children, +}: { + icon: typeof Database; + title: string; + hint: string; + children?: React.ReactNode; +}) => ( +
+
+

+ + {title} +

+

{hint}

+
+ {children} +
+); + +export const AdministrationPanel = () => ( +
+ + {/* Deliberately empty. The block exists so the shape is settled and so the screen says what is + coming rather than looking like a section nobody finished — but nothing is decided about what + goes in it, and guessing at fields would be a decision made in the wrong place. */} +

Nothing to set yet.

+
+
+); diff --git a/web/ConnectionPanel.tsx b/web/ConnectionPanel.tsx deleted file mode 100644 index 53cc12f..0000000 --- a/web/ConnectionPanel.tsx +++ /dev/null @@ -1,240 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; -import { CheckCircle2, Loader2, Plug, ShieldAlert, Trash2, TriangleAlert } from 'lucide-react'; -import { useClient } from 'hooks/useClient'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { readableError } from './errors'; -import { useFofosChanged } from './channel'; - -/** - * Where fofos is, and the token to reach it. Both the first-run setup and the permanent settings. - * - * One component for both — the same choice `plugins/proxy-manager/web/ConnectionPanel.tsx` makes, and - * for the same reason: a connection edited months later goes through exactly the code path that - * stored the first one, so there is no second path to rot. - * - * ── Owner only, and it says so rather than failing oddly ── - * - * The manifest marks `/_config` `ownerOnly`, so every request from this component 403s for anybody - * else — including the test button, deliberately, because that request carries the token in its body. - * A member who reaches this screen should read a sentence explaining that, not a raw refusal. - * - * ── The token is never loaded ── - * - * `GET /_config` returns `hasToken`, never the token. So the field starts empty even when one is - * stored, and an empty field on save means "keep the stored one" — which is what lets a typo in the - * URL be fixed without going to find the credential again. - */ - -type ConnectionView = { - configured: boolean; - url: string | null; - hasToken: boolean; - sections: string[] | null; - lastProbeAt: string | null; - lastProbeOk: boolean | null; -}; - -type TestResult = { - reachable: boolean; - authenticated: boolean; - sections?: string[]; - error?: string; -}; - -export const ConnectionPanel = () => { - const client = useClient(); - const clientRef = useRef(client); - clientRef.current = client; - - const [view, setView] = useState(null); - const [url, setUrl] = useState(''); - const [token, setToken] = useState(''); - - const [busy, setBusy] = useState(false); - const [result, setResult] = useState(null); - const [error, setError] = useState(null); - const [forbidden, setForbidden] = useState(false); - - const [, bump] = useFofosChanged(); - - const load = useCallback(async () => { - try { - const current = await clientRef.current.get('/fofos-admin/_config'); - setView(current); - setUrl(current.url ?? ''); - } catch (failure) { - // A 403 here is not a fault — it is a member looking at an owner-only screen, and it needs a - // different sentence from "that did not work". - if ((failure as { status?: number })?.status === 403) setForbidden(true); - else setError(readableError(failure)); - setView({ configured: false, url: null, hasToken: false, sections: null, lastProbeAt: null, lastProbeOk: null }); - } - }, []); - - useEffect(() => { - void load(); - }, [load]); - - const test = async () => { - setBusy(true); - setError(null); - try { - // Sent only when typed. Omitted, the server tests what is STORED — which is how "is my - // connection still good?" is answered without retyping a secret. - const body: Record = {}; - if (url.trim()) body.url = url.trim(); - if (token.trim()) body.token = token.trim(); - - setResult(await clientRef.current.post('/fofos-admin/_config/test', body)); - } catch (failure) { - setError(readableError(failure)); - } finally { - setBusy(false); - } - }; - - const save = async () => { - setBusy(true); - setError(null); - try { - const body: Record = { url: url.trim() }; - if (token.trim()) body.token = token.trim(); - - const saved = await clientRef.current.put( - '/fofos-admin/_config', - body, - ); - setView(saved); - setResult(saved.probe ?? null); - // Cleared on success: it is stored now, and keeping it on screen serves nobody. - setToken(''); - // Everything else re-reads. Saving a connection is the one action that changes what every other - // panel can see. - bump(); - } catch (failure) { - setError(readableError(failure)); - } finally { - setBusy(false); - } - }; - - const forget = async () => { - setBusy(true); - try { - setView(await clientRef.current.delete('/fofos-admin/_config')); - setUrl(''); - setToken(''); - setResult(null); - bump(); - } catch (failure) { - setError(readableError(failure)); - } finally { - setBusy(false); - } - }; - - if (forbidden) { - return ( -
- -

Only the owner can change this connection.

-

- The token stored here can rewrite the whole public site, so setting it belongs to whoever owns the - server. You can still edit content. -

-
- ); - } - - if (!view) { - return
Loading…
; - } - - return ( -
-
-

- - The fofos server -

-

- Where the site lives, and a token to edit it. The token is fofos's FOFOS_API_TOKEN. -

-
- - - - - -
- - - {view.configured && ( - - )} -
- - {error &&

{error}

} - - {result && ( -
- {result.authenticated ? ( - - ) : ( - - )} - - {result.authenticated - ? `Connected. ${result.sections?.length ?? 0} sections available.` - : (result.error ?? 'That did not work.')} - -
- )} - - {view.configured && !result && ( -

- {view.lastProbeOk === null - ? 'Saved, and not yet checked.' - : view.lastProbeOk - ? `Last checked ${view.lastProbeAt ? new Date(view.lastProbeAt).toLocaleString() : ''} — working.` - : 'The last check failed. Press Test to try again.'} -

- )} -
- ); -}; diff --git a/web/DetailPanel.tsx b/web/DetailPanel.tsx index 8d29f4f..6fc712d 100644 --- a/web/DetailPanel.tsx +++ b/web/DetailPanel.tsx @@ -1,6 +1,6 @@ import { useSearchParams } from 'react-router'; import { DEFAULT_SECTION, isSectionKey, sectionFor, type SectionKey } from './sections'; -import { ConnectionPanel } from './ConnectionPanel'; +import { AdministrationPanel } from './AdministrationPanel'; import { MessagesPanel } from './MessagesPanel'; import { PlannedSection } from './PlannedSection'; import { useReady } from './useReady'; @@ -38,7 +38,7 @@ import { useReady } from './useReady'; */ const PANELS: Partial React.JSX.Element>> = { messages: MessagesPanel, - administration: ConnectionPanel, + administration: AdministrationPanel, }; export const DetailPanel = () => {