mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-03-13 02:18:16 +01:00
Can remove license without rebooting backend
This commit is contained in:
@@ -83,7 +83,8 @@ public class AdminLicenseController {
|
||||
@RequestBody Map<String, String> request) {
|
||||
String licenseKey = request.get("licenseKey");
|
||||
|
||||
if (licenseKey == null || licenseKey.trim().isEmpty()) {
|
||||
// Reject null but allow empty string to clear license
|
||||
if (licenseKey == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(Map.of("success", false, "error", "License key is required"));
|
||||
}
|
||||
@@ -97,6 +98,7 @@ public class AdminLicenseController {
|
||||
applicationProperties.getPremium().setEnabled(true);
|
||||
|
||||
// Use existing LicenseKeyChecker to update and validate license
|
||||
// Empty string will be evaluated as NORMAL license (free tier)
|
||||
licenseKeyChecker.updateLicenseKey(licenseKey.trim());
|
||||
|
||||
// Get current license status
|
||||
@@ -106,6 +108,12 @@ public class AdminLicenseController {
|
||||
if (license != License.NORMAL) {
|
||||
GeneralUtils.saveKeyToSettings("premium.enabled", true);
|
||||
// Enable premium features
|
||||
|
||||
// Save maxUsers from license metadata
|
||||
Integer maxUsers = applicationProperties.getPremium().getMaxUsers();
|
||||
if (maxUsers != null) {
|
||||
GeneralUtils.saveKeyToSettings("premium.maxUsers", maxUsers);
|
||||
}
|
||||
} else {
|
||||
GeneralUtils.saveKeyToSettings("premium.enabled", false);
|
||||
log.info("License key is not valid for premium features: type={}", license.name());
|
||||
|
||||
@@ -2,50 +2,27 @@ import React, { useState, useCallback, useEffect } from 'react';
|
||||
import { Divider, Loader, Alert, Select, Group, Text, Collapse, Button, TextInput, Stack, Paper } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePlans } from '@app/hooks/usePlans';
|
||||
import { PlanTierGroup } from '@app/services/licenseService';
|
||||
import licenseService, { PlanTierGroup } from '@app/services/licenseService';
|
||||
import { useCheckout } from '@app/contexts/CheckoutContext';
|
||||
import { useLicense } from '@app/contexts/LicenseContext';
|
||||
import AvailablePlansSection from '@app/components/shared/config/configSections/plan/AvailablePlansSection';
|
||||
import StaticPlanSection from '@app/components/shared/config/configSections/plan/StaticPlanSection';
|
||||
import { useAppConfig } from '@app/contexts/AppConfigContext';
|
||||
import { alert } from '@app/components/toast';
|
||||
import LocalIcon from '@app/components/shared/LocalIcon';
|
||||
import RestartConfirmationModal from '@app/components/shared/config/RestartConfirmationModal';
|
||||
import { useRestartServer } from '@app/components/shared/config/useRestartServer';
|
||||
import { useAdminSettings } from '@app/hooks/useAdminSettings';
|
||||
import PendingBadge from '@app/components/shared/config/PendingBadge';
|
||||
import { Z_INDEX_OVER_CONFIG_MODAL } from '@app/styles/zIndex';
|
||||
import { ManageBillingButton } from '@app/components/shared/ManageBillingButton';
|
||||
|
||||
interface PremiumSettingsData {
|
||||
key?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
const AdminPlanSection: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { config } = useAppConfig();
|
||||
const { openCheckout } = useCheckout();
|
||||
const { licenseInfo } = useLicense();
|
||||
const { licenseInfo, refetchLicense } = useLicense();
|
||||
const [currency, setCurrency] = useState<string>('gbp');
|
||||
const [useStaticVersion, setUseStaticVersion] = useState(false);
|
||||
const [showLicenseKey, setShowLicenseKey] = useState(false);
|
||||
const [licenseKeyInput, setLicenseKeyInput] = useState<string>('');
|
||||
const [savingLicense, setSavingLicense] = useState(false);
|
||||
const { plans, loading, error, refetch } = usePlans(currency);
|
||||
|
||||
// Premium/License key management
|
||||
const { restartModalOpened, showRestartModal, closeRestartModal, restartServer } = useRestartServer();
|
||||
const {
|
||||
settings: premiumSettings,
|
||||
setSettings: setPremiumSettings,
|
||||
loading: premiumLoading,
|
||||
saving: premiumSaving,
|
||||
fetchSettings: fetchPremiumSettings,
|
||||
saveSettings: savePremiumSettings,
|
||||
isFieldPending,
|
||||
} = useAdminSettings<PremiumSettingsData>({
|
||||
sectionName: 'premium',
|
||||
});
|
||||
|
||||
// Check if we should use static version
|
||||
useEffect(() => {
|
||||
// Check if Stripe is configured
|
||||
@@ -53,21 +30,42 @@ const AdminPlanSection: React.FC = () => {
|
||||
if (!stripeKey || error) {
|
||||
setUseStaticVersion(true);
|
||||
}
|
||||
|
||||
// Fetch premium settings
|
||||
fetchPremiumSettings();
|
||||
}, [error, config, fetchPremiumSettings]);
|
||||
}, [error]);
|
||||
|
||||
const handleSaveLicense = async () => {
|
||||
try {
|
||||
await savePremiumSettings();
|
||||
showRestartModal();
|
||||
} catch (_error) {
|
||||
setSavingLicense(true);
|
||||
// Allow empty string to clear/remove license
|
||||
const response = await licenseService.saveLicenseKey(licenseKeyInput.trim());
|
||||
|
||||
if (response.success) {
|
||||
// Refresh license context to update all components
|
||||
await refetchLicense();
|
||||
|
||||
alert({
|
||||
alertType: 'success',
|
||||
title: t('admin.settings.premium.key.success', 'License Key Saved'),
|
||||
body: t('admin.settings.premium.key.successMessage', 'Your license key has been activated successfully. No restart required.'),
|
||||
});
|
||||
|
||||
// Clear input
|
||||
setLicenseKeyInput('');
|
||||
} else {
|
||||
alert({
|
||||
alertType: 'error',
|
||||
title: t('admin.error', 'Error'),
|
||||
body: response.error || t('admin.settings.saveError', 'Failed to save license key'),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to save license key:', error);
|
||||
alert({
|
||||
alertType: 'error',
|
||||
title: t('admin.error', 'Error'),
|
||||
body: t('admin.settings.saveError', 'Failed to save settings'),
|
||||
body: t('admin.settings.saveError', 'Failed to save license key'),
|
||||
});
|
||||
} finally {
|
||||
setSavingLicense(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -190,46 +188,50 @@ const AdminPlanSection: React.FC = () => {
|
||||
</Text>
|
||||
</Alert>
|
||||
|
||||
{premiumLoading ? (
|
||||
<Stack align="center" justify="center" h={100}>
|
||||
<Loader size="md" />
|
||||
</Stack>
|
||||
) : (
|
||||
<Paper withBorder p="md" radius="md">
|
||||
<Stack gap="md">
|
||||
<div>
|
||||
<TextInput
|
||||
label={
|
||||
<Group gap="xs">
|
||||
<span>{t('admin.settings.premium.key.label', 'License Key')}</span>
|
||||
<PendingBadge show={isFieldPending('key')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.premium.key.description', 'Enter your premium or enterprise license key. Premium features will be automatically enabled when a key is provided.')}
|
||||
value={premiumSettings.key || ''}
|
||||
onChange={(e) => setPremiumSettings({ ...premiumSettings, key: e.target.value })}
|
||||
placeholder="00000000-0000-0000-0000-000000000000"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Group justify="flex-end">
|
||||
<Button onClick={handleSaveLicense} loading={premiumSaving} size="sm">
|
||||
{t('admin.settings.save', 'Save Changes')}
|
||||
</Button>
|
||||
</Group>
|
||||
{/* Severe warning if license already exists */}
|
||||
{licenseInfo?.licenseKey && (
|
||||
<Alert
|
||||
variant="light"
|
||||
color="red"
|
||||
icon={<LocalIcon icon="warning-rounded" width="1rem" height="1rem" />}
|
||||
title={t('admin.settings.premium.key.overwriteWarning.title', '⚠️ Warning: Existing License Detected')}
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Text size="sm" fw={600}>
|
||||
{t('admin.settings.premium.key.overwriteWarning.line1', 'Overwriting your current license key cannot be undone.')}
|
||||
</Text>
|
||||
<Text size="sm">
|
||||
{t('admin.settings.premium.key.overwriteWarning.line2', 'Your previous license will be permanently lost unless you have backed it up elsewhere.')}
|
||||
</Text>
|
||||
<Text size="sm" fw={500}>
|
||||
{t('admin.settings.premium.key.overwriteWarning.line3', 'Important: Keep license keys private and secure. Never share them publicly.')}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Paper withBorder p="md" radius="md">
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label={t('admin.settings.premium.key.label', 'License Key')}
|
||||
description={t('admin.settings.premium.key.description', 'Enter your premium or enterprise license key. Premium features will be automatically enabled when a key is provided.')}
|
||||
value={licenseKeyInput}
|
||||
onChange={(e) => setLicenseKeyInput(e.target.value)}
|
||||
placeholder="00000000-0000-0000-0000-000000000000"
|
||||
type="password"
|
||||
disabled={savingLicense}
|
||||
/>
|
||||
|
||||
<Group justify="flex-end">
|
||||
<Button onClick={handleSaveLicense} loading={savingLicense} size="sm">
|
||||
{t('admin.settings.save', 'Save Changes')}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Stack>
|
||||
</Collapse>
|
||||
</div>
|
||||
|
||||
{/* Restart Confirmation Modal */}
|
||||
<RestartConfirmationModal
|
||||
opened={restartModalOpened}
|
||||
onClose={closeRestartModal}
|
||||
onRestart={restartServer}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user