make the connection settings reachable at /gitea/connection
The form appeared only when nothing was configured, so once a token was saved there was no way back to it — a member who mistyped theirs, or anyone rotating one, had nowhere to go. On a single-user machine that is an annoyance; on a company one it is the ordinary case, because every member has a token of their own to set. Uses the route pair the platform already generates for every plugin, so this needed no platform change. A plug icon beside the open-in-new-tab button, as a real Link rather than a button, because it is navigation and should be cmd-clickable. Saving returns to the frame — otherwise you are left looking at the form you just submitted, which reads as nothing having happened. The two halves were already right: the panel shows only the token field when isOwner is false, and getResolvedServiceCredentials gives a member the owner's URL with their OWN secret, never the owner's.
This commit is contained in:
+17
-4
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router';
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
import { useClient } from 'hooks/useClient';
|
import { useClient } from 'hooks/useClient';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -34,6 +35,7 @@ type Props = { state: ConnectionState; onSaved: () => void };
|
|||||||
|
|
||||||
export const GiteaConnection = ({ state, onSaved }: Props) => {
|
export const GiteaConnection = ({ state, onSaved }: Props) => {
|
||||||
const { put } = useClient();
|
const { put } = useClient();
|
||||||
|
const navigate = useNavigate();
|
||||||
// Loopback is the right default and worth putting in front of someone: the sidecar fetches
|
// Loopback is the right default and worth putting in front of someone: the sidecar fetches
|
||||||
// SERVER-SIDE, so `localhost` means the machine Officer runs on. It needs no TLS and never leaves the
|
// SERVER-SIDE, so `localhost` means the machine Officer runs on. It needs no TLS and never leaves the
|
||||||
// box — where the public URL would take the same traffic out and back.
|
// box — where the public URL would take the same traffic out and back.
|
||||||
@@ -48,6 +50,9 @@ export const GiteaConnection = ({ state, onSaved }: Props) => {
|
|||||||
try {
|
try {
|
||||||
await put('/gitea/_config', state.isOwner === false ? { token } : { url, token });
|
await put('/gitea/_config', state.isOwner === false ? { token } : { url, token });
|
||||||
onSaved();
|
onSaved();
|
||||||
|
// Back to the frame. Saving from `/gitea/connection` otherwise leaves you looking at the form you
|
||||||
|
// just submitted, which reads as nothing having happened.
|
||||||
|
navigate('/gitea');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'could not save');
|
setError(err instanceof Error ? err.message : 'could not save');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -91,10 +96,18 @@ export const GiteaConnection = ({ state, onSaved }: Props) => {
|
|||||||
|
|
||||||
{error && <p className="text-xs text-destructive">{error}</p>}
|
{error && <p className="text-xs text-destructive">{error}</p>}
|
||||||
|
|
||||||
<Button onClick={save} disabled={busy || ((!state.configured || state.unreadable) && !token)}>
|
<div className="flex gap-2">
|
||||||
{busy ? <Loader2 className="mr-2 h-3.5 w-3.5 animate-spin" /> : null}
|
<Button onClick={save} disabled={busy || ((!state.configured || state.unreadable) && !token)}>
|
||||||
{state.configured ? 'Save' : 'Connect'}
|
{busy ? <Loader2 className="mr-2 h-3.5 w-3.5 animate-spin" /> : null}
|
||||||
</Button>
|
{state.configured ? 'Save' : 'Connect'}
|
||||||
|
</Button>
|
||||||
|
{/* Only once there is something to go back TO. */}
|
||||||
|
{state.configured && !state.unreadable ? (
|
||||||
|
<Button variant="outline" onClick={() => navigate('/gitea')} disabled={busy}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+25
-11
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { ExternalLink, Loader2, TriangleAlert } from 'lucide-react';
|
import { Link, useParams } from 'react-router';
|
||||||
|
import { ExternalLink, Loader2, Plug, TriangleAlert } from 'lucide-react';
|
||||||
import { useClient } from 'hooks/useClient';
|
import { useClient } from 'hooks/useClient';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { GiteaConnection, type ConnectionState } from './GiteaConnection';
|
import { GiteaConnection, type ConnectionState } from './GiteaConnection';
|
||||||
@@ -25,6 +26,11 @@ import { GiteaConnection, type ConnectionState } from './GiteaConnection';
|
|||||||
const FRAME_SRC = '/api/gitea/_web/';
|
const FRAME_SRC = '/api/gitea/_web/';
|
||||||
|
|
||||||
export const GiteaFrame = () => {
|
export const GiteaFrame = () => {
|
||||||
|
// `/gitea/connection` renders the settings instead of the frame. The plugin route pair gives this for
|
||||||
|
// free — `<route>` and `<route>/:section` are generated for every plugin — and it is the only way to
|
||||||
|
// change a token once one is set: the form used to appear ONLY when nothing was configured, so a
|
||||||
|
// member who mistyped theirs, or anyone rotating one, had nowhere to go.
|
||||||
|
const { section } = useParams();
|
||||||
const { get, post } = useClient();
|
const { get, post } = useClient();
|
||||||
const [state, setState] = useState<'opening' | 'ready' | 'failed'>('opening');
|
const [state, setState] = useState<'opening' | 'ready' | 'failed'>('opening');
|
||||||
const [connection, setConnection] = useState<ConnectionState | null>(null);
|
const [connection, setConnection] = useState<ConnectionState | null>(null);
|
||||||
@@ -83,7 +89,8 @@ export const GiteaFrame = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connection && !connection.configured) {
|
// Unconfigured goes straight to the form; `/gitea/connection` opens it deliberately.
|
||||||
|
if (connection && (!connection.configured || section === 'connection')) {
|
||||||
return <GiteaConnection state={connection} onSaved={() => setAttempt((n) => n + 1)} />;
|
return <GiteaConnection state={connection} onSaved={() => setAttempt((n) => n + 1)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,15 +99,22 @@ export const GiteaFrame = () => {
|
|||||||
{/* The escape hatch, and it is not an admission of defeat: the whole point of proxying an app that
|
{/* The escape hatch, and it is not an admission of defeat: the whole point of proxying an app that
|
||||||
already has a UI is that its own UI stays available. Anything the frame handles badly, this
|
already has a UI is that its own UI stays available. Anything the frame handles badly, this
|
||||||
opens properly. */}
|
opens properly. */}
|
||||||
<Button
|
<div className="absolute right-3 top-3 z-10 flex gap-1.5 opacity-60 transition-opacity hover:opacity-100">
|
||||||
size="sm"
|
{/* A real link, not a button: it is navigation, and it should be cmd-clickable like any other. */}
|
||||||
variant="outline"
|
<Button size="sm" variant="outline" asChild title="Connection settings">
|
||||||
className="absolute right-3 top-3 z-10 opacity-60 hover:opacity-100"
|
<Link to="/gitea/connection">
|
||||||
onClick={() => window.open(FRAME_SRC, '_blank', 'noopener')}
|
<Plug className="h-3.5 w-3.5" />
|
||||||
title="Open Gitea in a new tab"
|
</Link>
|
||||||
>
|
</Button>
|
||||||
<ExternalLink className="h-3.5 w-3.5" />
|
<Button
|
||||||
</Button>
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => window.open(FRAME_SRC, '_blank', 'noopener')}
|
||||||
|
title="Open Gitea in a new tab"
|
||||||
|
>
|
||||||
|
<ExternalLink className="h-3.5 w-3.5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<iframe
|
<iframe
|
||||||
src={FRAME_SRC}
|
src={FRAME_SRC}
|
||||||
title="Gitea"
|
title="Gitea"
|
||||||
|
|||||||
Reference in New Issue
Block a user