sync: update from private repo (dfadcd5f)
CI / build-and-test (push) Has been cancelled

This commit is contained in:
oss-sync
2026-06-23 06:38:48 +00:00
parent 6a2f2cc736
commit 29ccaf1e92
377 changed files with 31028 additions and 8994 deletions
@@ -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">