mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-08-17 08:00:34 +02:00
improve settings UI
Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
let autoPruneOption = { enabled: false, companies: [] };
|
||||
let autoPruneEnabled = false;
|
||||
let autoPruneError = '';
|
||||
let isSavingAutoPrune = false;
|
||||
|
||||
// backup
|
||||
let isBackupModalVisible = false;
|
||||
@@ -74,10 +75,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function setAutoPruneValue(enabled) {
|
||||
// save the auto-prune setting, only applied when the user clicks Save
|
||||
async function saveAutoPrune() {
|
||||
autoPruneError = '';
|
||||
isSavingAutoPrune = true;
|
||||
// read-modify-write: preserve per-company entries
|
||||
const updated = { ...autoPruneOption, enabled };
|
||||
const updated = { ...autoPruneOption, enabled: autoPruneEnabled };
|
||||
try {
|
||||
const res = await api.option.setAutoPrune(updated);
|
||||
if (!res.success) {
|
||||
@@ -85,11 +88,12 @@
|
||||
return;
|
||||
}
|
||||
autoPruneOption = updated;
|
||||
autoPruneEnabled = enabled;
|
||||
addToast('Auto-prune setting saved', 'Success');
|
||||
} catch (e) {
|
||||
autoPruneError = 'Failed to save auto-prune setting';
|
||||
console.error('failed to set auto-prune setting', e);
|
||||
} finally {
|
||||
isSavingAutoPrune = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,16 +323,21 @@
|
||||
checked={autoPruneEnabled}
|
||||
label="Enabled"
|
||||
description="Orphaned recipients are deleted automatically each hour"
|
||||
on:change={() => setAutoPruneValue(true)}
|
||||
on:change={() => (autoPruneEnabled = true)}
|
||||
/>
|
||||
<RadioOption
|
||||
checked={!autoPruneEnabled}
|
||||
label="Disabled"
|
||||
description="Orphaned recipients are kept until manually deleted"
|
||||
on:change={() => setAutoPruneValue(false)}
|
||||
on:change={() => (autoPruneEnabled = false)}
|
||||
/>
|
||||
</div>
|
||||
<FormError message={autoPruneError} />
|
||||
<div class="mt-2 flex justify-end">
|
||||
<FormButton size={'medium'} isSubmitting={isSavingAutoPrune} on:click={saveAutoPrune}
|
||||
>Save Changes</FormButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
</div>
|
||||
|
||||
@@ -5,14 +5,18 @@
|
||||
import SettingsCard from '$lib/components/SettingsCard.svelte';
|
||||
import SettingsLoading from '$lib/components/SettingsLoading.svelte';
|
||||
import Form from '$lib/components/Form.svelte';
|
||||
import FormButton from '$lib/components/FormButton.svelte';
|
||||
import FormError from '$lib/components/FormError.svelte';
|
||||
import TextField from '$lib/components/TextField.svelte';
|
||||
import TextFieldSelect from '$lib/components/TextFieldSelect.svelte';
|
||||
|
||||
let loaded = false;
|
||||
let scimError = '';
|
||||
let isSaving = false;
|
||||
|
||||
// SCIM provisioning: single global domain that serves the SCIM endpoints
|
||||
let scimDomain = '';
|
||||
let scimDomainOptions = [{ value: '', label: '— Disabled —' }];
|
||||
let scimDomainOptions = [{ value: '', label: '- Disabled -' }];
|
||||
// retention window (days) before a SCIM-disabled recipient is pruned
|
||||
let scimRetentionDays = 30;
|
||||
|
||||
@@ -36,24 +40,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function setScimRetention() {
|
||||
// save both SCIM settings together, like the other settings panels, so a
|
||||
// change is only applied when the user explicitly clicks Save
|
||||
async function saveScim() {
|
||||
scimError = '';
|
||||
const days = parseInt(scimRetentionDays, 10);
|
||||
if (isNaN(days) || days < 0) {
|
||||
scimError = 'Retention days must be zero or positive';
|
||||
return;
|
||||
}
|
||||
isSaving = true;
|
||||
try {
|
||||
const days = parseInt(scimRetentionDays, 10);
|
||||
if (isNaN(days) || days < 0) {
|
||||
addToast('Retention days must be zero or positive', 'Error');
|
||||
const domainRes = await api.option.setScimDomain(scimDomain);
|
||||
if (!domainRes.success) {
|
||||
scimError = domainRes.error || 'Failed to update SCIM domain';
|
||||
await refreshScimDomain();
|
||||
return;
|
||||
}
|
||||
const retentionRes = await api.option.setScimRetentionDays(days);
|
||||
if (!retentionRes.success) {
|
||||
scimError = retentionRes.error || 'Failed to update SCIM retention';
|
||||
await refreshScimRetention();
|
||||
return;
|
||||
}
|
||||
const res = await api.option.setScimRetentionDays(days);
|
||||
if (res.success) {
|
||||
addToast('SCIM retention updated', 'Success');
|
||||
} else {
|
||||
addToast(res.error || 'Failed to update SCIM retention', 'Error');
|
||||
await refreshScimRetention();
|
||||
}
|
||||
addToast('SCIM settings updated', 'Success');
|
||||
} catch (e) {
|
||||
addToast('Failed to update SCIM retention', 'Error');
|
||||
console.error(e);
|
||||
scimError = 'Failed to update SCIM settings';
|
||||
console.error('failed to update SCIM settings', e);
|
||||
} finally {
|
||||
isSaving = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +85,7 @@
|
||||
if (domains.success) {
|
||||
const names = (domains.data.rows || []).map((d) => d.name);
|
||||
scimDomainOptions = [
|
||||
{ value: '', label: '— Disabled —' },
|
||||
{ value: '', label: '- Disabled -' },
|
||||
...names.map((n) => ({ value: n, label: n }))
|
||||
];
|
||||
}
|
||||
@@ -79,20 +94,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function setScimDomain() {
|
||||
try {
|
||||
const res = await api.option.setScimDomain(scimDomain);
|
||||
if (res.success) {
|
||||
addToast(scimDomain ? 'SCIM domain updated' : 'SCIM serving disabled', 'Success');
|
||||
} else {
|
||||
addToast(res.error || 'Failed to update SCIM domain', 'Error');
|
||||
await refreshScimDomain();
|
||||
}
|
||||
} catch (e) {
|
||||
addToast('Failed to update SCIM domain', 'Error');
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !loaded}
|
||||
@@ -104,11 +105,10 @@
|
||||
Global domain that serves SCIM provisioning. Must be publicly reachable on 443 with a valid
|
||||
certificate; prefer a dedicated domain not used for campaigns.
|
||||
</p>
|
||||
<Form>
|
||||
<Form on:submit={saveScim} fullWidth>
|
||||
<TextFieldSelect
|
||||
id="scimDomain"
|
||||
bind:value={scimDomain}
|
||||
onSelect={setScimDomain}
|
||||
options={scimDomainOptions}>SCIM domain</TextFieldSelect
|
||||
>
|
||||
<TextField
|
||||
@@ -117,14 +117,17 @@
|
||||
min="0"
|
||||
width="small"
|
||||
bind:value={scimRetentionDays}
|
||||
onBlur={setScimRetention}
|
||||
toolTipText="Days a disabled recipient is kept before removal. 0 removes on the next prune."
|
||||
>Retention (days)</TextField
|
||||
>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-300 text-sm transition-colors duration-200">
|
||||
How long deprovisioned recipients are kept (disabled) before being permanently removed.
|
||||
</p>
|
||||
<FormError message={scimError} />
|
||||
<div class="mt-6 flex justify-end">
|
||||
<FormButton size={'medium'} isSubmitting={isSaving}>Save Changes</FormButton>
|
||||
</div>
|
||||
</Form>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-300 text-sm transition-colors duration-200">
|
||||
How long deprovisioned recipients are kept (disabled) before being permanently removed.
|
||||
</p>
|
||||
</SettingsCard>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
import SettingsCard from '$lib/components/SettingsCard.svelte';
|
||||
import SettingsLoading from '$lib/components/SettingsLoading.svelte';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import Alert from '$lib/components/Alert.svelte';
|
||||
import Form from '$lib/components/Form.svelte';
|
||||
import FormButton from '$lib/components/FormButton.svelte';
|
||||
import FormError from '$lib/components/FormError.svelte';
|
||||
import TextFieldSelect from '$lib/components/TextFieldSelect.svelte';
|
||||
|
||||
const logLevels = ['debug', 'info', 'warn', 'error'];
|
||||
@@ -16,12 +19,15 @@
|
||||
let loaded = false;
|
||||
let logLevel = '';
|
||||
let dbLogLevel = '';
|
||||
let logError = '';
|
||||
let isSavingLog = false;
|
||||
|
||||
let version = '';
|
||||
let updateAvailable = false;
|
||||
let isCheckingUpdate = false;
|
||||
|
||||
let isWipingBrowserCache = false;
|
||||
let isWipeAlertVisible = false;
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
@@ -47,14 +53,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function setLogLevel() {
|
||||
// save both log levels together, only applied when the user clicks Save
|
||||
async function saveLogLevel() {
|
||||
logError = '';
|
||||
isSavingLog = true;
|
||||
try {
|
||||
const res = await api.log.setLevel(logLevel, dbLogLevel);
|
||||
if (!res.success) {
|
||||
console.error(res);
|
||||
logError = res.error || 'Failed to update logging';
|
||||
return;
|
||||
}
|
||||
addToast('Logging updated', 'Success');
|
||||
} catch (err) {
|
||||
logError = 'Failed to update logging';
|
||||
console.error(err);
|
||||
} finally {
|
||||
isSavingLog = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,11 +123,13 @@
|
||||
const response = await api.reportTemplate.wipeBrowserCache();
|
||||
if (response.success) {
|
||||
addToast('Browser cache wiped', 'Success');
|
||||
} else {
|
||||
addToast(response.error || 'Failed to wipe browser cache', 'Error');
|
||||
return { success: true };
|
||||
}
|
||||
addToast(response.error || 'Failed to wipe browser cache', 'Error');
|
||||
return { success: false };
|
||||
} catch (e) {
|
||||
addToast('Failed to wipe browser cache', 'Error');
|
||||
return { success: false };
|
||||
} finally {
|
||||
isWipingBrowserCache = false;
|
||||
}
|
||||
@@ -125,12 +141,11 @@
|
||||
{:else}
|
||||
<div class="flex flex-wrap gap-6">
|
||||
<SettingsCard title="Logging">
|
||||
<Form>
|
||||
<Form on:submit={saveLogLevel} fullWidth>
|
||||
<TextFieldSelect
|
||||
id="appLogLevel"
|
||||
required
|
||||
bind:value={logLevel}
|
||||
onSelect={setLogLevel}
|
||||
options={logLevels}>Application log level</TextFieldSelect
|
||||
>
|
||||
|
||||
@@ -138,9 +153,12 @@
|
||||
id="dbLogLevel"
|
||||
required
|
||||
bind:value={dbLogLevel}
|
||||
onSelect={setLogLevel}
|
||||
options={dbLogLevels}>Database log level</TextFieldSelect
|
||||
>
|
||||
<FormError message={logError} />
|
||||
<div class="mt-6 flex justify-end">
|
||||
<FormButton size={'medium'} isSubmitting={isSavingLog}>Save Changes</FormButton>
|
||||
</div>
|
||||
</Form>
|
||||
</SettingsCard>
|
||||
|
||||
@@ -154,7 +172,7 @@
|
||||
size={'large'}
|
||||
backgroundColor="bg-red-600"
|
||||
disabled={isWipingBrowserCache}
|
||||
on:click={onWipeBrowserCache}
|
||||
on:click={() => (isWipeAlertVisible = true)}
|
||||
>
|
||||
{isWipingBrowserCache ? 'Wiping...' : 'Wipe Browser Cache'}
|
||||
</Button>
|
||||
@@ -203,3 +221,15 @@
|
||||
</SettingsCard>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Alert
|
||||
headline="Wipe browser cache"
|
||||
bind:visible={isWipeAlertVisible}
|
||||
onConfirm={onWipeBrowserCache}
|
||||
ok="Wipe cache"
|
||||
>
|
||||
<p>
|
||||
Chromium will be deleted and downloaded again on the next PDF report or remote browser session,
|
||||
which can take a few minutes.
|
||||
</p>
|
||||
</Alert>
|
||||
|
||||
Reference in New Issue
Block a user