This commit is contained in:
@@ -15,8 +15,13 @@ interface CreateResponse {
|
||||
publicKey?: string | null;
|
||||
}
|
||||
|
||||
async function fetchConnections(): Promise<{ list: SshConnection[]; sshDisabled: boolean }> {
|
||||
const res = await fetch('/api/ssh/connections', { credentials: 'include' });
|
||||
async function fetchConnections(spaceId?: string): Promise<{ list: SshConnection[]; sshDisabled: boolean }> {
|
||||
// spaceId 指定時はそのスペースの接続だけを一覧(バックエンドが可視性を検証)。
|
||||
// 未指定なら従来どおりユーザー所有+global を返す。
|
||||
const url = spaceId
|
||||
? `/api/ssh/connections?spaceId=${encodeURIComponent(spaceId)}`
|
||||
: '/api/ssh/connections';
|
||||
const res = await fetch(url, { credentials: 'include' });
|
||||
if (res.status === 404) {
|
||||
return { list: [], sshDisabled: true };
|
||||
}
|
||||
@@ -25,12 +30,14 @@ async function fetchConnections(): Promise<{ list: SshConnection[]; sshDisabled:
|
||||
return { list: data.connections ?? [], sshDisabled: false };
|
||||
}
|
||||
|
||||
async function apiCreate(body: Record<string, unknown>): Promise<CreateResponse> {
|
||||
async function apiCreate(body: Record<string, unknown>, spaceId?: string): Promise<CreateResponse> {
|
||||
// spaceId 指定時は space_id を載せてそのスペース所属の接続として作成する。
|
||||
const payload = spaceId ? { ...body, space_id: spaceId } : body;
|
||||
const res = await fetch('/api/ssh/connections', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const txt = await res.text();
|
||||
@@ -121,15 +128,17 @@ function parseApiError(rawText: string, status: number): string {
|
||||
interface SshConnectionsPanelProps {
|
||||
/** Render personal+globals (user mode) or only globals via admin endpoints. */
|
||||
scope?: 'user';
|
||||
/** スペース内で表示する場合の space id。指定時は一覧/作成がそのスペースに紐づく。 */
|
||||
spaceId?: string;
|
||||
showToast?: (msg: string, variant?: 'success' | 'error') => void;
|
||||
}
|
||||
|
||||
export function SshConnectionsPanel({ showToast }: SshConnectionsPanelProps = {}) {
|
||||
export function SshConnectionsPanel({ spaceId, showToast }: SshConnectionsPanelProps = {}) {
|
||||
const { t } = useTranslation('userfolder');
|
||||
const qc = useQueryClient();
|
||||
const { data, isLoading, error } = useQuery({
|
||||
queryKey: ['ssh', 'connections'],
|
||||
queryFn: fetchConnections,
|
||||
queryKey: ['ssh', 'connections', spaceId ?? null],
|
||||
queryFn: () => fetchConnections(spaceId),
|
||||
staleTime: 15_000,
|
||||
});
|
||||
|
||||
@@ -142,10 +151,13 @@ export function SshConnectionsPanel({ showToast }: SshConnectionsPanelProps = {}
|
||||
freshlyGenerated: boolean;
|
||||
} | null>(null);
|
||||
|
||||
const invalidateConnections = () =>
|
||||
qc.invalidateQueries({ queryKey: ['ssh', 'connections', spaceId ?? null] });
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: apiCreate,
|
||||
mutationFn: (body: Record<string, unknown>) => apiCreate(body, spaceId),
|
||||
onSuccess: (resp) => {
|
||||
qc.invalidateQueries({ queryKey: ['ssh', 'connections'] });
|
||||
invalidateConnections();
|
||||
setCreating(false);
|
||||
showToast?.(t('ssh.toast.created'), 'success');
|
||||
// If the server returned a public key (always for keypairSource=generate;
|
||||
@@ -179,7 +191,7 @@ export function SshConnectionsPanel({ showToast }: SshConnectionsPanelProps = {}
|
||||
const patchMutation = useMutation({
|
||||
mutationFn: ({ id, body }: { id: string; body: Record<string, unknown> }) => apiPatch(id, body),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['ssh', 'connections'] });
|
||||
invalidateConnections();
|
||||
setEditingId(null);
|
||||
showToast?.(t('ssh.toast.updated'), 'success');
|
||||
},
|
||||
@@ -187,7 +199,7 @@ export function SshConnectionsPanel({ showToast }: SshConnectionsPanelProps = {}
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: apiDelete,
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['ssh', 'connections'] });
|
||||
invalidateConnections();
|
||||
showToast?.(t('ssh.toast.deleted'), 'success');
|
||||
},
|
||||
onError: (e) => {
|
||||
@@ -197,7 +209,7 @@ export function SshConnectionsPanel({ showToast }: SshConnectionsPanelProps = {}
|
||||
const testMutation = useMutation({
|
||||
mutationFn: apiTest,
|
||||
onSuccess: (response, id) => {
|
||||
qc.invalidateQueries({ queryKey: ['ssh', 'connections'] });
|
||||
invalidateConnections();
|
||||
// Surface result. pass = already verified; first_observe/mismatch = needs confirm.
|
||||
if (response.verdict === 'pass') {
|
||||
showToast?.(t('ssh.toast.hostKeyMatch', { fingerprint: response.fingerprint.slice(0, 20) }), 'success');
|
||||
@@ -235,7 +247,7 @@ export function SshConnectionsPanel({ showToast }: SshConnectionsPanelProps = {}
|
||||
const globals = (data?.list ?? []).filter(c => c.ownerId === null);
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-y-auto">
|
||||
<div className="h-full overflow-y-auto" data-testid={spaceId ? 'space-ssh-panel' : undefined}>
|
||||
<div className="max-w-3xl mx-auto px-6 py-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
@@ -275,7 +287,10 @@ export function SshConnectionsPanel({ showToast }: SshConnectionsPanelProps = {}
|
||||
{t('ssh.ownEmpty')}
|
||||
</div>
|
||||
)}
|
||||
<ul className="divide-y divide-hairline mb-6">
|
||||
<ul
|
||||
className="divide-y divide-hairline mb-6"
|
||||
data-testid={spaceId ? 'space-ssh-list' : undefined}
|
||||
>
|
||||
{owned.map(c => (
|
||||
<ConnectionRow
|
||||
key={c.id}
|
||||
@@ -385,7 +400,7 @@ function ConnectionRow(props: ConnectionRowProps) {
|
||||
const disabled = c.disabledByAdmin || !c.enabled;
|
||||
|
||||
return (
|
||||
<li className="py-3">
|
||||
<li className="py-3" data-connection-id={c.id}>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
|
||||
Reference in New Issue
Block a user