feat(FN-2517): add remote access controls across dashboard and TUI
- Add project-scoped remote settings and control APIs for provider activation, tunnel lifecycle, token generation, URL, and QR retrieval - Extend Settings modal with a dedicated Remote Access section, provider forms, status/actions, and coverage in SettingsModal tests - Wire dashboard TUI settings state to remote configuration/status and add interactive remote shortcuts for start/stop/token/url/qr actions - Document remote-access behavior in architecture, CLI, and settings references and include a patch changeset for @runfusion/fusion
This commit is contained in:
@@ -403,6 +403,94 @@ export function updateSettings(settings: Partial<Settings>, projectId?: string):
|
||||
});
|
||||
}
|
||||
|
||||
export interface RemoteSettings {
|
||||
remoteEnabled: boolean;
|
||||
remoteActiveProvider: "tailscale" | "cloudflare" | null;
|
||||
remoteTailscaleEnabled: boolean;
|
||||
remoteTailscaleHostname: string;
|
||||
remoteTailscaleTargetPort: number;
|
||||
remoteTailscaleAcceptRoutes: boolean;
|
||||
remoteCloudflareEnabled: boolean;
|
||||
remoteCloudflareTunnelName: string;
|
||||
remoteCloudflareTunnelToken: string | null;
|
||||
remoteCloudflareIngressUrl: string;
|
||||
remotePersistentToken: string | null;
|
||||
remoteShortLivedEnabled: boolean;
|
||||
remoteShortLivedTtlMs: number;
|
||||
remoteShortLivedMaxTtlMs: number;
|
||||
remoteRememberLastRunning: boolean;
|
||||
remoteWasRunningOnShutdown: boolean;
|
||||
remoteLastStartedProvider: "tailscale" | "cloudflare" | null;
|
||||
}
|
||||
|
||||
export interface RemoteStatus {
|
||||
provider: "tailscale" | "cloudflare" | null;
|
||||
state: "stopped" | "starting" | "running" | "error";
|
||||
url: string | null;
|
||||
lastError: string | null;
|
||||
}
|
||||
|
||||
export function fetchRemoteSettings(projectId?: string): Promise<{ settings: RemoteSettings }> {
|
||||
return api<{ settings: RemoteSettings }>(withProjectId("/remote/settings", projectId));
|
||||
}
|
||||
|
||||
export function updateRemoteSettings(
|
||||
settings: Partial<RemoteSettings>,
|
||||
projectId?: string,
|
||||
): Promise<{ settings: RemoteSettings }> {
|
||||
return api<{ settings: RemoteSettings }>(withProjectId("/remote/settings", projectId), {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchRemoteStatus(projectId?: string): Promise<RemoteStatus> {
|
||||
return api<RemoteStatus>(withProjectId("/remote/status", projectId));
|
||||
}
|
||||
|
||||
export function activateRemoteProvider(provider: "tailscale" | "cloudflare", projectId?: string): Promise<{ activeProvider: "tailscale" | "cloudflare" }> {
|
||||
return api<{ activeProvider: "tailscale" | "cloudflare" }>(withProjectId("/remote/provider/activate", projectId), {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ provider }),
|
||||
});
|
||||
}
|
||||
|
||||
export function startRemoteTunnel(projectId?: string): Promise<{ state: "starting" | "running"; provider: string }> {
|
||||
return api<{ state: "starting" | "running"; provider: string }>(withProjectId("/remote/tunnel/start", projectId), {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
export function stopRemoteTunnel(projectId?: string): Promise<{ state: "stopped"; provider: string | null }> {
|
||||
return api<{ state: "stopped"; provider: string | null }>(withProjectId("/remote/tunnel/stop", projectId), {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
export function regenerateRemotePersistentToken(projectId?: string): Promise<{ token: string; maskedToken: string }> {
|
||||
return api<{ token: string; maskedToken: string }>(withProjectId("/remote/token/persistent/regenerate", projectId), {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
export function generateShortLivedRemoteToken(ttlMs: number, projectId?: string): Promise<{ token: string; expiresAt: string; ttlMs: number }> {
|
||||
return api<{ token: string; expiresAt: string; ttlMs: number }>(withProjectId("/remote/token/short-lived/generate", projectId), {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ ttlMs }),
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchRemoteUrl(projectId?: string): Promise<{ url: string; tokenType: "persistent" | "short-lived"; expiresAt: string | null }> {
|
||||
return api<{ url: string; tokenType: "persistent" | "short-lived"; expiresAt: string | null }>(withProjectId("/remote/url", projectId));
|
||||
}
|
||||
|
||||
export function fetchRemoteQr(
|
||||
format: "text" | "image/svg" = "text",
|
||||
projectId?: string,
|
||||
): Promise<{ url: string; expiresAt: string | null; format: "text" | "image/svg"; data?: string }> {
|
||||
return api<{ url: string; expiresAt: string | null; format: "text" | "image/svg"; data?: string }>(withProjectId(`/remote/qr?format=${encodeURIComponent(format)}`, projectId));
|
||||
}
|
||||
|
||||
export function fetchMemory(projectId?: string): Promise<{ content: string }> {
|
||||
return api<{ content: string }>(withProjectId("/memory", projectId));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user