feat: initial public release (MAESTRO)

This commit is contained in:
oss-sync
2026-06-03 05:08:00 +00:00
commit f5c7666f6b
823 changed files with 184150 additions and 0 deletions
@@ -0,0 +1,167 @@
import { useState } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import {
createBrowserSessionProfile, startBrowserSessionLogin,
saveBrowserSession, cancelBrowserSession,
type BrowserSessionProfile,
} from '../../api';
import { usePictureInPicture } from '../../lib/usePictureInPicture.js';
import { PipButton } from '../browser/PipButton.js';
type Phase = 'form' | 'logging-in' | 'saving' | 'done' | 'error';
interface Props {
existingProfile?: BrowserSessionProfile | null;
onClose: () => void;
}
export function AddBrowserSessionDialog({ existingProfile, onClose }: Props) {
const qc = useQueryClient();
const [phase, setPhase] = useState<Phase>('form');
const [label, setLabel] = useState(existingProfile?.label ?? '');
const [startUrl, setStartUrl] = useState(existingProfile?.startUrl ?? '');
const [loggedInSelector, setLoggedInSelector] = useState(existingProfile?.loggedInSelector ?? '');
const [loginUrl, setLoginUrl] = useState(existingProfile?.loginUrlPatterns?.[0] ?? '');
const [profileId, setProfileId] = useState<number | null>(existingProfile?.id ?? null);
const [sessionId, setSessionId] = useState<string | null>(null);
const [novncPath, setNovncPath] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const pip = usePictureInPicture(novncPath, label ? `noVNC — ログイン: ${label}` : 'noVNC — ログイン');
async function startLogin() {
setError(null);
try {
let pid = profileId;
if (!pid) {
const created = await createBrowserSessionProfile({
label,
startUrl,
matchPatterns: [],
storageOrigins: [new URL(startUrl).origin],
loggedInSelector: loggedInSelector || undefined,
loginUrlPatterns: loginUrl ? [loginUrl] : [],
});
pid = created.id;
setProfileId(pid);
}
const r = await startBrowserSessionLogin(pid);
setSessionId(r.sessionId);
setNovncPath(r.novncPath);
setPhase('logging-in');
} catch (e) {
setError((e as Error).message);
setPhase('error');
}
}
async function saveNow() {
if (!profileId || !sessionId) return;
setPhase('saving');
try {
await saveBrowserSession(profileId, sessionId);
qc.invalidateQueries({ queryKey: ['browser-session-profiles'] });
setPhase('done');
setTimeout(onClose, 800);
} catch (e) {
setError((e as Error).message);
setPhase('error');
}
}
async function cancel() {
if (profileId && sessionId) await cancelBrowserSession(profileId, sessionId).catch(() => {});
onClose();
}
// The remote noVNC content is rendered at the Xvfb native resolution
// (1280x720, see src/engine/browser-session.ts). At the form-phase 640px
// dialog width the iframe scales down to ~50%, which feels cramped while
// the user is actually logging in. Expand the dialog to ~1320x860 once we
// enter the login phase so the iframe can show 1:1.
const inLogin = phase === 'logging-in';
const dialogSize = inLogin
? 'w-[1320px] h-[860px] max-w-[95vw] max-h-[95vh]'
: 'w-[640px] max-w-[95vw] max-h-[90vh]';
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40">
<div className={`bg-white rounded-lg shadow-xl ${dialogSize} overflow-hidden flex flex-col`}>
<div className="px-4 py-3 border-b border-hairline flex items-center justify-between">
<h3 className="text-sm font-semibold text-slate-800">
{existingProfile ? `再ログイン: ${existingProfile.label}` : 'ブラウザセッションを追加'}
</h3>
<button onClick={cancel} className="text-slate-400 hover:text-slate-700 text-lg leading-none">×</button>
</div>
{phase === 'form' && (
<div className="p-4 space-y-3">
<div>
<label className="block text-xs text-slate-700 mb-1"></label>
<input value={label} onChange={e => setLabel(e.target.value)}
disabled={!!existingProfile}
placeholder="My Twitter"
className="w-full h-8 px-2 text-xs border border-hairline rounded-md disabled:bg-slate-50 disabled:text-slate-500" />
</div>
<div>
<label className="block text-xs text-slate-700 mb-1"> URL</label>
<input value={startUrl} onChange={e => setStartUrl(e.target.value)}
placeholder="https://twitter.com/home"
className="w-full h-8 px-2 text-xs border border-hairline rounded-md" />
</div>
<div>
<label className="block text-xs text-slate-700 mb-1"></label>
<input value={loggedInSelector} onChange={e => setLoggedInSelector(e.target.value)}
placeholder='[data-testid="primaryColumn"]'
className="w-full h-8 px-2 text-xs border border-hairline rounded-md" />
</div>
<div>
<label className="block text-xs text-slate-700 mb-1"> URL </label>
<input value={loginUrl} onChange={e => setLoginUrl(e.target.value)}
placeholder="https://twitter.com/i/flow/login**"
className="w-full h-8 px-2 text-xs border border-hairline rounded-md" />
</div>
{error && <div className="text-xs text-rose-600">{error}</div>}
<div className="flex justify-end gap-2 pt-2">
<button onClick={cancel} className="text-xs px-3 py-1.5 rounded-md hover:bg-surface"></button>
<button disabled={!label || !startUrl} onClick={startLogin}
className="text-xs px-3 py-1.5 rounded-md bg-accent text-accent-fg hover:bg-accent-deep disabled:bg-slate-300">
</button>
</div>
</div>
)}
{phase === 'logging-in' && novncPath && (
<div className="flex-1 flex flex-col min-h-0">
<div className="flex-1 min-h-[420px] bg-black">
{pip.isOpen ? (
<div className="w-full h-full flex items-center justify-center text-xs text-slate-300">
PiP
</div>
) : (
<iframe src={novncPath} title="login" className="w-full h-full" allow="clipboard-read; clipboard-write" />
)}
</div>
<div className="px-4 py-3 border-t border-hairline flex items-center justify-between text-xs">
<span className="text-slate-600"></span>
<div className="flex gap-2 items-center">
<PipButton pip={pip} />
<button onClick={cancel} className="px-3 py-1.5 rounded-md hover:bg-surface"></button>
<button onClick={saveNow} className="px-3 py-1.5 rounded-md bg-accent text-accent-fg hover:bg-accent-deep"></button>
</div>
</div>
</div>
)}
{phase === 'saving' && <div className="p-6 text-center text-xs text-slate-500"></div>}
{phase === 'done' && <div className="p-6 text-center text-xs text-emerald-600"></div>}
{phase === 'error' && (
<div className="p-6 space-y-3 text-center">
<div className="text-xs text-rose-600">{error}</div>
<button onClick={cancel} className="px-3 py-1.5 rounded-md hover:bg-surface text-xs"></button>
</div>
)}
</div>
</div>
);
}