mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-17 13:52:14 +01:00
Headless windows installer (#5664)
This commit is contained in:
@@ -9,6 +9,8 @@ import { DESKTOP_DEFAULT_APP_CONFIG } from '@app/config/defaultAppConfig';
|
||||
import { connectionModeService } from '@app/services/connectionModeService';
|
||||
import { tauriBackendService } from '@app/services/tauriBackendService';
|
||||
import { authService } from '@app/services/authService';
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
import { isTauri } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* Desktop application providers
|
||||
@@ -21,7 +23,6 @@ export function AppProviders({ children }: { children: ReactNode }) {
|
||||
const [connectionMode, setConnectionMode] = useState<'saas' | 'selfhosted' | null>(null);
|
||||
const [authChecked, setAuthChecked] = useState(false);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
|
||||
// Load connection mode on mount
|
||||
useEffect(() => {
|
||||
void connectionModeService.getCurrentMode().then(setConnectionMode);
|
||||
@@ -51,6 +52,42 @@ export function AppProviders({ children }: { children: ReactNode }) {
|
||||
const shouldMonitorBackend = setupComplete && !isFirstLaunch && connectionMode === 'saas';
|
||||
useBackendInitializer(shouldMonitorBackend);
|
||||
|
||||
useEffect(() => {
|
||||
if (!authChecked) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isTauri()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentWindow = getCurrentWindow();
|
||||
currentWindow
|
||||
.show()
|
||||
.then(() => currentWindow.unminimize().catch(() => {}))
|
||||
.then(() => currentWindow.setFocus().catch(() => {}))
|
||||
.then(() => currentWindow.requestUserAttention(1).catch(() => {}))
|
||||
.catch(() => {});
|
||||
}, [authChecked]);
|
||||
|
||||
if (!authChecked) {
|
||||
return (
|
||||
<ProprietaryAppProviders
|
||||
appConfigRetryOptions={{
|
||||
maxRetries: 5,
|
||||
initialDelay: 1000,
|
||||
}}
|
||||
appConfigProviderProps={{
|
||||
initialConfig: DESKTOP_DEFAULT_APP_CONFIG,
|
||||
bootstrapMode: 'non-blocking',
|
||||
autoFetch: false,
|
||||
}}
|
||||
>
|
||||
<div style={{ minHeight: '100vh' }} />
|
||||
</ProprietaryAppProviders>
|
||||
);
|
||||
}
|
||||
|
||||
// Show setup wizard on first launch
|
||||
if (isFirstLaunch && !setupComplete) {
|
||||
return (
|
||||
|
||||
@@ -31,11 +31,13 @@ export const ConnectionSettings: React.FC = () => {
|
||||
setLoading(true);
|
||||
await authService.logout();
|
||||
|
||||
// Switch to SaaS mode
|
||||
await connectionModeService.switchToSaaS(STIRLING_SAAS_URL);
|
||||
if (!config?.lock_connection_mode) {
|
||||
// Switch to SaaS mode
|
||||
await connectionModeService.switchToSaaS(STIRLING_SAAS_URL);
|
||||
|
||||
// Reset setup completion to force login screen on reload
|
||||
await connectionModeService.resetSetupCompletion();
|
||||
// Reset setup completion to force login screen on reload
|
||||
await connectionModeService.resetSetupCompletion();
|
||||
}
|
||||
|
||||
// Reload config
|
||||
const newConfig = await connectionModeService.getCurrentConfig();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DesktopAuthLayout } from '@app/components/SetupWizard/DesktopAuthLayout';
|
||||
import { SaaSLoginScreen } from '@app/components/SetupWizard/SaaSLoginScreen';
|
||||
@@ -10,7 +10,6 @@ import { AuthServiceError, authService, UserInfo } from '@app/services/authServi
|
||||
import { tauriBackendService } from '@app/services/tauriBackendService';
|
||||
import { STIRLING_SAAS_URL } from '@app/constants/connection';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { useEffect } from 'react';
|
||||
import '@app/routes/authShared/auth.css';
|
||||
|
||||
enum SetupStep {
|
||||
@@ -32,6 +31,7 @@ export const SetupWizard: React.FC<SetupWizardProps> = ({ onComplete }) => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [selfHostedMfaCode, setSelfHostedMfaCode] = useState('');
|
||||
const [selfHostedMfaRequired, setSelfHostedMfaRequired] = useState(false);
|
||||
const [lockConnectionMode, setLockConnectionMode] = useState(false);
|
||||
|
||||
const handleSaaSLogin = async (username: string, password: string) => {
|
||||
if (!serverConfig) {
|
||||
@@ -82,6 +82,9 @@ export const SetupWizard: React.FC<SetupWizardProps> = ({ onComplete }) => {
|
||||
};
|
||||
|
||||
const handleSelfHostedClick = () => {
|
||||
if (lockConnectionMode) {
|
||||
return;
|
||||
}
|
||||
setError(null);
|
||||
setActiveStep(SetupStep.ServerSelection);
|
||||
};
|
||||
@@ -259,6 +262,9 @@ export const SetupWizard: React.FC<SetupWizardProps> = ({ onComplete }) => {
|
||||
}, [onComplete, serverConfig?.url]);
|
||||
|
||||
const handleBack = () => {
|
||||
if (lockConnectionMode) {
|
||||
return;
|
||||
}
|
||||
setError(null);
|
||||
if (activeStep === SetupStep.SelfHostedLogin) {
|
||||
setSelfHostedMfaCode('');
|
||||
@@ -272,10 +278,23 @@ export const SetupWizard: React.FC<SetupWizardProps> = ({ onComplete }) => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadConfig = async () => {
|
||||
const currentConfig = await connectionModeService.getCurrentConfig();
|
||||
if (currentConfig.lock_connection_mode && currentConfig.server_config?.url) {
|
||||
setLockConnectionMode(true);
|
||||
setServerConfig(currentConfig.server_config);
|
||||
setActiveStep(SetupStep.SelfHostedLogin);
|
||||
}
|
||||
};
|
||||
|
||||
void loadConfig();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DesktopAuthLayout>
|
||||
{/* Step Content */}
|
||||
{activeStep === SetupStep.SaaSLogin && (
|
||||
{!lockConnectionMode && activeStep === SetupStep.SaaSLogin && (
|
||||
<SaaSLoginScreen
|
||||
serverUrl={serverConfig?.url || STIRLING_SAAS_URL}
|
||||
onLogin={handleSaaSLogin}
|
||||
@@ -287,7 +306,7 @@ export const SetupWizard: React.FC<SetupWizardProps> = ({ onComplete }) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeStep === SetupStep.SaaSSignup && (
|
||||
{!lockConnectionMode && activeStep === SetupStep.SaaSSignup && (
|
||||
<SaaSSignupScreen
|
||||
loading={loading}
|
||||
error={error}
|
||||
@@ -296,7 +315,7 @@ export const SetupWizard: React.FC<SetupWizardProps> = ({ onComplete }) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeStep === SetupStep.ServerSelection && (
|
||||
{!lockConnectionMode && activeStep === SetupStep.ServerSelection && (
|
||||
<ServerSelectionScreen
|
||||
onSelect={handleServerSelection}
|
||||
loading={loading}
|
||||
@@ -319,7 +338,7 @@ export const SetupWizard: React.FC<SetupWizardProps> = ({ onComplete }) => {
|
||||
)}
|
||||
|
||||
{/* Back Button */}
|
||||
{activeStep > SetupStep.SaaSLogin && !loading && (
|
||||
{!lockConnectionMode && activeStep > SetupStep.SaaSLogin && !loading && (
|
||||
<div className="navigation-link-container" style={{ marginTop: '1.5rem' }}>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -16,8 +16,11 @@ export function useAccountLogout() {
|
||||
try {
|
||||
await signOut();
|
||||
|
||||
await connectionModeService.switchToSaaS(STIRLING_SAAS_URL);
|
||||
await connectionModeService.resetSetupCompletion().catch(() => {});
|
||||
const currentConfig = await connectionModeService.getCurrentConfig();
|
||||
if (!currentConfig.lock_connection_mode) {
|
||||
await connectionModeService.switchToSaaS(STIRLING_SAAS_URL);
|
||||
await connectionModeService.resetSetupCompletion().catch(() => {});
|
||||
}
|
||||
|
||||
window.history.replaceState({}, '', '/');
|
||||
window.location.reload();
|
||||
|
||||
@@ -107,6 +107,9 @@ export function setupApiInterceptors(client: AxiosInstance): void {
|
||||
|
||||
// Handle 401 Unauthorized - try to refresh token
|
||||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||
if (typeof window !== 'undefined') {
|
||||
console.warn('[apiClientSetup] 401 on path:', window.location.pathname, 'url:', originalRequest.url);
|
||||
}
|
||||
if (originalRequest.skipAuthRedirect) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface ServerConfig {
|
||||
export interface ConnectionConfig {
|
||||
mode: ConnectionMode;
|
||||
server_config: ServerConfig | null;
|
||||
lock_connection_mode: boolean;
|
||||
}
|
||||
|
||||
export interface DiagnosticResult {
|
||||
@@ -50,7 +51,7 @@ export class ConnectionModeService {
|
||||
if (!this.configLoadedOnce) {
|
||||
await this.loadConfig();
|
||||
}
|
||||
return this.currentConfig || { mode: 'saas', server_config: null };
|
||||
return this.currentConfig || { mode: 'saas', server_config: null, lock_connection_mode: false };
|
||||
}
|
||||
|
||||
async getCurrentMode(): Promise<ConnectionMode> {
|
||||
@@ -84,12 +85,16 @@ export class ConnectionModeService {
|
||||
} catch (error) {
|
||||
console.error('Failed to load connection config:', error);
|
||||
// Default to SaaS mode on error
|
||||
this.currentConfig = { mode: 'saas', server_config: null };
|
||||
this.currentConfig = { mode: 'saas', server_config: null, lock_connection_mode: false };
|
||||
this.configLoadedOnce = true;
|
||||
}
|
||||
}
|
||||
|
||||
async switchToSaaS(saasServerUrl: string): Promise<void> {
|
||||
if (this.currentConfig?.lock_connection_mode) {
|
||||
throw new Error('Connection mode is locked by provisioning');
|
||||
}
|
||||
|
||||
console.log('Switching to SaaS mode');
|
||||
|
||||
const serverConfig: ServerConfig = { url: saasServerUrl };
|
||||
@@ -99,7 +104,7 @@ export class ConnectionModeService {
|
||||
serverConfig,
|
||||
});
|
||||
|
||||
this.currentConfig = { mode: 'saas', server_config: serverConfig };
|
||||
this.currentConfig = { mode: 'saas', server_config: serverConfig, lock_connection_mode: this.currentConfig?.lock_connection_mode ?? false };
|
||||
this.notifyListeners();
|
||||
|
||||
console.log('Switched to SaaS mode successfully');
|
||||
@@ -113,7 +118,7 @@ export class ConnectionModeService {
|
||||
serverConfig,
|
||||
});
|
||||
|
||||
this.currentConfig = { mode: 'selfhosted', server_config: serverConfig };
|
||||
this.currentConfig = { mode: 'selfhosted', server_config: serverConfig, lock_connection_mode: this.currentConfig?.lock_connection_mode ?? false };
|
||||
this.notifyListeners();
|
||||
|
||||
console.log('Switched to self-hosted mode successfully');
|
||||
@@ -901,6 +906,9 @@ export class ConnectionModeService {
|
||||
}
|
||||
|
||||
async resetSetupCompletion(): Promise<void> {
|
||||
if (this.currentConfig?.lock_connection_mode) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await invoke('reset_setup_completion');
|
||||
console.log('Setup completion flag reset successfully');
|
||||
|
||||
@@ -171,6 +171,12 @@ export function ServerExperienceProvider({ children }: { children: ReactNode })
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
if (!config.appVersion) {
|
||||
return;
|
||||
}
|
||||
if (loginEnabled && !isAuthenticated) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldUseAdminData = (config.enableLogin ?? true) && config.isAdmin;
|
||||
// Use WAU estimate for no-login scenarios OR for login non-admin users
|
||||
@@ -353,4 +359,3 @@ export function useServerExperienceContext() {
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user