Compare commits

...
Author SHA1 Message Date
Anthony Stirling 7e2de51e51 fix desktop package #5058 2025-11-28 21:03:19 +00:00
James Brunton 731743b618 Don't block desktop app on backend starting up (#5041)
# Description of Changes
Start bundled backend instantly on startup of app and don't wait on it
being fully up to spawn app. This is techincally wasteful curently on
self-hosted mode where everything runs remotely, but in the future we'll
probably route simple operations to the local machine regardless of
connection, and it stops unnecessary waiting in the offline mode.
2025-11-27 15:54:35 +00:00
ConnorYoh 04c4aec0d8 Disable admin plan section when no login (#5039)
Admin plan section matches other admin sections

<img width="1013" height="629" alt="image"
src="https://github.com/user-attachments/assets/39e9fad7-461c-491d-99cb-4b140292f2f4"
/>
<img width="730" height="595" alt="image"
src="https://github.com/user-attachments/assets/b26354d2-5401-40b3-8ca7-3b48b26b644e"
/>
2025-11-27 13:57:04 +00:00
8 changed files with 52 additions and 34 deletions
@@ -398,10 +398,6 @@ pub async fn start_backend(
e
})?;
// Wait for the backend to start
println!("⏳ Waiting for backend startup...");
tokio::time::sleep(std::time::Duration::from_millis(10000)).await;
// Reset the starting flag since startup is complete
reset_starting_flag();
add_log("✅ Backend startup sequence completed, starting flag cleared".to_string());
+12 -1
View File
@@ -66,7 +66,7 @@ pub fn run() {
// Emit a generic notification that files were added (frontend will re-read storage)
let _ = app.emit("files-changed", ());
}))
.setup(|_app| {
.setup(|app| {
add_log("🚀 Tauri app setup started".to_string());
// Process command line arguments on first launch
@@ -78,6 +78,17 @@ pub fn run() {
}
}
// Start backend immediately, non-blocking
let app_handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
add_log("🚀 Starting bundled backend in background".to_string());
let connection_state = app_handle.state::<AppConnectionState>();
if let Err(e) = commands::backend::start_backend(app_handle.clone(), connection_state).await {
add_log(format!("⚠️ Backend start failed: {}", e));
}
});
add_log("🔍 DEBUG: Setup completed".to_string());
Ok(())
})
+1
View File
@@ -3,6 +3,7 @@ Version=1.0
Type=Application
Name=Stirling-PDF
Comment=Locally hosted web application that allows you to perform various operations on PDF files
Exec=/usr/bin/stirling-pdf
Icon={{icon}}
Terminal=false
MimeType=application/pdf;
@@ -31,10 +31,10 @@ export function AppProviders({ children }: { children: ReactNode }) {
}
}, [setupComplete, isFirstLaunch, connectionMode]);
// Only start bundled backend if in SaaS mode (local backend) and setup is complete
// Self-hosted mode connects to remote server so doesn't need local backend
const shouldStartBackend = setupComplete && !isFirstLaunch && connectionMode === 'saas';
useBackendInitializer(shouldStartBackend);
// Initialize monitoring for bundled backend (already started in Rust)
// This sets up port detection and health checks
const shouldMonitorBackend = setupComplete && !isFirstLaunch && connectionMode === 'saas';
useBackendInitializer(shouldMonitorBackend);
// Show setup wizard on first launch
if (isFirstLaunch && !setupComplete) {
@@ -51,23 +51,7 @@ export function AppProviders({ children }: { children: ReactNode }) {
}}
>
<SetupWizard
onComplete={async () => {
// Wait for backend to become healthy before reloading
// This prevents reloading mid-startup which would interrupt the backend
const maxWaitTime = 60000; // 60 seconds max
const checkInterval = 1000; // Check every second
const startTime = Date.now();
while (Date.now() - startTime < maxWaitTime) {
if (tauriBackendService.isBackendHealthy()) {
window.location.reload();
return;
}
await new Promise(resolve => setTimeout(resolve, checkInterval));
}
// If we timeout, reload anyway
console.warn('[AppProviders] Backend health check timeout, reloading anyway...');
onComplete={() => {
window.location.reload();
}}
/>
@@ -61,7 +61,7 @@ export const SetupWizard: React.FC<SetupWizardProps> = ({ onComplete }) => {
}
await connectionModeService.switchToSaaS(serverConfig.url);
await tauriBackendService.startBackend();
tauriBackendService.startBackend().catch(console.error);
onComplete();
} catch (err) {
console.error('SaaS login failed:', err);
@@ -13,9 +13,12 @@ import { InfoBanner } from '@app/components/shared/InfoBanner';
import { useLicenseAlert } from '@app/hooks/useLicenseAlert';
import { isSupabaseConfigured } from '@app/services/supabaseClient';
import { getPreferredCurrency, setCachedCurrency } from '@app/utils/currencyDetection';
import { useLoginRequired } from '@app/hooks/useLoginRequired';
import LoginRequiredBanner from '@core/components/shared/config/LoginRequiredBanner';
const AdminPlanSection: React.FC = () => {
const { t, i18n } = useTranslation();
const { loginEnabled, validateLoginEnabled } = useLoginRequired();
const { openCheckout } = useCheckout();
const { licenseInfo, refetchLicense } = useLicense();
const [currency, setCurrency] = useState<string>(() => {
@@ -39,6 +42,11 @@ const AdminPlanSection: React.FC = () => {
}, [error]);
const handleSaveLicense = async () => {
// Block save if login is disabled
if (!validateLoginEnabled()) {
return;
}
try {
setSavingLicense(true);
// Allow empty string to clear/remove license
@@ -86,6 +94,11 @@ const AdminPlanSection: React.FC = () => {
];
const handleManageClick = useCallback(async () => {
// Block access if login is disabled
if (!validateLoginEnabled()) {
return;
}
try {
// Only allow PRO or ENTERPRISE licenses to access billing portal
if (!licenseInfo?.licenseType || licenseInfo.licenseType === 'NORMAL') {
@@ -112,7 +125,7 @@ const AdminPlanSection: React.FC = () => {
body: error.message || 'Please try again or contact support.',
});
}
}, [licenseInfo, t]);
}, [licenseInfo, t, validateLoginEnabled]);
const handleCurrencyChange = useCallback((newCurrency: string) => {
setCurrency(newCurrency);
@@ -122,6 +135,11 @@ const AdminPlanSection: React.FC = () => {
const handleUpgradeClick = useCallback(
(planGroup: PlanTierGroup) => {
// Block access if login is disabled
if (!validateLoginEnabled()) {
return;
}
// Only allow upgrades for server and enterprise tiers
if (planGroup.tier === 'free') {
return;
@@ -151,7 +169,7 @@ const AdminPlanSection: React.FC = () => {
},
});
},
[openCheckout, currency, refetch, licenseInfo, t]
[openCheckout, currency, refetch, licenseInfo, t, validateLoginEnabled]
);
const shouldShowLicenseWarning = licenseAlert.active && licenseAlert.audience === 'admin';
@@ -200,7 +218,9 @@ const AdminPlanSection: React.FC = () => {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
{shouldShowLicenseWarning && (
<LoginRequiredBanner show={!loginEnabled} />
{shouldShowLicenseWarning && (
<InfoBanner
icon="warning-rounded"
tone="warning"
@@ -231,6 +251,7 @@ const AdminPlanSection: React.FC = () => {
currency={currency}
onCurrencyChange={handleCurrencyChange}
currencyOptions={currencyOptions}
loginEnabled={loginEnabled}
/>
<Divider />
@@ -288,11 +309,11 @@ const AdminPlanSection: React.FC = () => {
onChange={(e) => setLicenseKeyInput(e.target.value)}
placeholder={licenseInfo?.licenseKey || '00000000-0000-0000-0000-000000000000'}
type="password"
disabled={savingLicense}
disabled={!loginEnabled || savingLicense}
/>
<Group justify="flex-end">
<Button onClick={handleSaveLicense} loading={savingLicense} size="sm">
<Button onClick={handleSaveLicense} loading={savingLicense} size="sm" disabled={!loginEnabled}>
{t('admin.settings.save', 'Save Changes')}
</Button>
</Group>
@@ -15,6 +15,7 @@ interface AvailablePlansSectionProps {
currency?: string;
onCurrencyChange?: (currency: string) => void;
currencyOptions?: { value: string; label: string }[];
loginEnabled?: boolean;
}
const AvailablePlansSection: React.FC<AvailablePlansSectionProps> = ({
@@ -25,6 +26,7 @@ const AvailablePlansSection: React.FC<AvailablePlansSectionProps> = ({
currency,
onCurrencyChange,
currencyOptions,
loginEnabled = true,
}) => {
const { t } = useTranslation();
const [showComparison, setShowComparison] = useState(false);
@@ -91,6 +93,7 @@ const AvailablePlansSection: React.FC<AvailablePlansSectionProps> = ({
clearable={false}
w={300}
comboboxProps={{ withinPortal: true, zIndex: Z_INDEX_OVER_CONFIG_MODAL }}
disabled={!loginEnabled}
/>
)}
</Group>
@@ -113,6 +116,7 @@ const AvailablePlansSection: React.FC<AvailablePlansSectionProps> = ({
currentTier={currentTier}
onUpgradeClick={onUpgradeClick}
onManageClick={onManageClick}
loginEnabled={loginEnabled}
/>
))}
</div>
@@ -15,9 +15,10 @@ interface PlanCardProps {
currentTier?: 'free' | 'server' | 'enterprise' | null;
onUpgradeClick: (planGroup: PlanTierGroup) => void;
onManageClick?: () => void;
loginEnabled?: boolean;
}
const PlanCard: React.FC<PlanCardProps> = ({ planGroup, isCurrentTier, isDowngrade, currentLicenseInfo, currentTier, onUpgradeClick, onManageClick }) => {
const PlanCard: React.FC<PlanCardProps> = ({ planGroup, isCurrentTier, isDowngrade, currentLicenseInfo, currentTier, onUpgradeClick, onManageClick, loginEnabled = true }) => {
const { t } = useTranslation();
// Render Free plan
@@ -174,7 +175,7 @@ const PlanCard: React.FC<PlanCardProps> = ({ planGroup, isCurrentTier, isDowngra
variant={isCurrentTier ? 'filled' : isDowngrade ? 'filled' : isEnterpriseBlockedForFree ? 'light' : 'filled'}
fullWidth
onClick={() => isCurrentTier && onManageClick ? onManageClick() : onUpgradeClick(planGroup)}
disabled={isDowngrade || isEnterpriseBlockedForFree}
disabled={!loginEnabled || isDowngrade || isEnterpriseBlockedForFree}
>
{isCurrentTier
? t('plan.manage', 'Manage')