Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79e2366d67 | ||
|
|
731743b618 | ||
|
|
04c4aec0d8 | ||
|
|
f63df148ad | ||
|
|
6f0be94bd6 |
@@ -6,6 +6,10 @@ Portions of this software are licensed as follows:
|
||||
|
||||
* All content that resides under the "app/proprietary/" directory of this repository,
|
||||
if that directory exists, is licensed under the license defined in "app/proprietary/LICENSE".
|
||||
* All content that resides under the "frontend/src/proprietary/" directory of this repository,
|
||||
if that directory exists, is licensed under the license defined in "frontend/src/proprietary/LICENSE".
|
||||
* All content that resides under the "frontend/src/desktop/" directory of this repository,
|
||||
if that directory exists, is licensed under the license defined in "frontend/src/desktop/LICENSE".
|
||||
* Content outside of the above mentioned directories or restrictions above is
|
||||
available under the MIT License as defined below.
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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(())
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import auditService, { AuditFilters } from '@app/services/auditService';
|
||||
/**
|
||||
* Shared hook for managing audit filters across components
|
||||
*/
|
||||
export function useAuditFilters(initialFilters: Partial<AuditFilters> = {}) {
|
||||
export function useAuditFilters(initialFilters: Partial<AuditFilters> = {}, loginEnabled: boolean = true) {
|
||||
const [eventTypes, setEventTypes] = useState<string[]>([]);
|
||||
const [users, setUsers] = useState<string[]>([]);
|
||||
const [filters, setFilters] = useState<AuditFilters>({
|
||||
@@ -30,8 +30,10 @@ export function useAuditFilters(initialFilters: Partial<AuditFilters> = {}) {
|
||||
}
|
||||
};
|
||||
|
||||
fetchMetadata();
|
||||
}, []);
|
||||
if (loginEnabled) {
|
||||
fetchMetadata();
|
||||
}
|
||||
}, [loginEnabled]);
|
||||
|
||||
const handleFilterChange = (key: keyof AuditFilters, value: any) => {
|
||||
setFilters((prev) => ({ ...prev, [key]: value }));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
Stirling PDF User License
|
||||
|
||||
Copyright (c) 2025 Stirling PDF Inc.
|
||||
|
||||
License Scope & Usage Rights
|
||||
|
||||
Production use of the Stirling PDF Software is only permitted with a valid Stirling PDF User License.
|
||||
|
||||
For purposes of this license, “the Software” refers to the Stirling PDF application and any associated documentation files
|
||||
provided by Stirling PDF Inc. You or your organization may not use the Software in production, at scale, or for business-critical
|
||||
processes unless you have agreed to, and remain in compliance with, the Stirling PDF Subscription Terms of Service
|
||||
(https://www.stirlingpdf.com/terms) or another valid agreement with Stirling PDF, and hold an active User License subscription
|
||||
covering the appropriate number of licensed users.
|
||||
|
||||
Trial and Minimal Use
|
||||
|
||||
You may use the Software without a paid subscription for the sole purposes of internal trial, evaluation, or minimal use, provided that:
|
||||
* Use is limited to the capabilities and restrictions defined by the Software itself;
|
||||
* You do not copy, distribute, sublicense, reverse-engineer, or use the Software in client-facing or commercial contexts.
|
||||
|
||||
Continued use beyond this scope requires a valid Stirling PDF User License.
|
||||
|
||||
Modifications and Derivative Works
|
||||
|
||||
You may modify the Software only for development or internal testing purposes. Any such modifications or derivative works:
|
||||
|
||||
* May not be deployed in production environments without a valid User License;
|
||||
* May not be distributed or sublicensed;
|
||||
* Remain the intellectual property of Stirling PDF and/or its licensors;
|
||||
* May only be used, copied, or exploited in accordance with the terms of a valid Stirling PDF User License subscription.
|
||||
|
||||
Prohibited Actions
|
||||
|
||||
Unless explicitly permitted by a paid license or separate agreement, you may not:
|
||||
|
||||
* Use the Software in production environments;
|
||||
* Copy, merge, distribute, sublicense, or sell the Software;
|
||||
* Remove or alter any licensing or copyright notices;
|
||||
* Circumvent access restrictions or licensing requirements.
|
||||
|
||||
Third-Party Components
|
||||
|
||||
The Stirling PDF Software may include components subject to separate open source licenses. Such components remain governed by
|
||||
their original license terms as provided by their respective owners.
|
||||
|
||||
Disclaimer
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+26
-5
@@ -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>
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ const AuditEventsTable: React.FC<AuditEventsTableProps> = ({ loginEnabled = true
|
||||
const { filters, eventTypes, users, handleFilterChange, handleClearFilters } = useAuditFilters({
|
||||
page: 0,
|
||||
pageSize: 20,
|
||||
});
|
||||
}, loginEnabled);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchEvents = async () => {
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ const AuditExportSection: React.FC<AuditExportSectionProps> = ({ loginEnabled =
|
||||
const [exporting, setExporting] = useState(false);
|
||||
|
||||
// Use shared filters hook
|
||||
const { filters, eventTypes, users, handleFilterChange, handleClearFilters } = useAuditFilters();
|
||||
const { filters, eventTypes, users, handleFilterChange, handleClearFilters } = useAuditFilters({}, loginEnabled);
|
||||
|
||||
const handleExport = async () => {
|
||||
if (!loginEnabled) return;
|
||||
|
||||
+4
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user