mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-05-19 22:54:44 +02:00
Merge branch 'release/v1.3.1'
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [1.3.1] - 2025-09-21
|
||||
- Improved width of links in tables
|
||||
- Fixed asset page not showing domains
|
||||
- Fixed domain assets shown under global assets
|
||||
- Improve asset delete modal text
|
||||
- Removed asset preview icon background
|
||||
- Minor improvements to install / login UI.
|
||||
|
||||
## [1.3.0] - 2025-09-19
|
||||
- Added dark mode support and various UI improvements
|
||||
- Added manual backup functionality
|
||||
|
||||
@@ -57,7 +57,7 @@ func (r *Asset) GetAllByDomainAndContext(
|
||||
db = db.
|
||||
Joins("left join domains on domains.id = assets.domain_id").
|
||||
Select(r.joinSelectString()).
|
||||
Where("(assets.company_id = ? OR assets.company_id IS NULL) AND (domain_id = ? OR domain_id IS NULL)", companyID, domainID)
|
||||
Where("(assets.company_id = ? OR assets.company_id IS NULL) AND domain_id = ?", companyID, domainID)
|
||||
} else {
|
||||
db.Where("assets.company_id = ?", companyID)
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func (r *Asset) GetAllByGlobalContext(
|
||||
}
|
||||
var dbModels []*database.Asset
|
||||
dbRes := db.
|
||||
Where("company_id IS NULL").
|
||||
Where("company_id IS NULL AND domain_id IS NULL").
|
||||
Find(&dbModels)
|
||||
|
||||
if dbRes.Error != nil {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Hi {{.FirstName}} Welcome to The Phishing Club! We are excited to have you here. Click <a href='{{.URL}}'>here</a> to get started.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,5 @@
|
||||
/** domain asset **/
|
||||
* {
|
||||
background-color: red;
|
||||
background: url(contoso-logo.png);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/** global asset **/
|
||||
* {
|
||||
background-color: yellow;
|
||||
background: url(contoso-logo.png);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"version": "v1",
|
||||
"company": "Phishing Club",
|
||||
"key": "",
|
||||
"validUntil": "2025-12-23T15:30:22.635975262Z",
|
||||
"isValid": true,
|
||||
"signature": "lr9fmt3LqacnLzmQ3TAg5P8lQJ0R9OwNjKGa1s6nLE0=",
|
||||
"offlineActivated": true
|
||||
}
|
||||
@@ -12,9 +12,9 @@ export const immediateResponseHandler = (apiResponse) => {
|
||||
goto('/login');
|
||||
window.location.reload();
|
||||
}
|
||||
// If the user must renew their password, move them to the renew password page
|
||||
// If the user must renew their password, redirect to login
|
||||
if (apiResponse.statusCode === 400 && apiResponse.error === 'New password required') {
|
||||
goto('/login/reset-password');
|
||||
goto('/login');
|
||||
return;
|
||||
}
|
||||
return apiResponse;
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
|
||||
<button
|
||||
on:click={handleToggle}
|
||||
class="relative inline-flex items-center justify-center w-6 h-6 rounded transition-colors duration-200 focus:outline-none"
|
||||
class="relative inline-flex items-center justify-center w-10 h-10 rounded-full bg-white/10 dark:bg-gray-800/80 backdrop-blur-sm border border-gray-200/50 dark:border-gray-600/50 hover:bg-white/20 dark:hover:bg-gray-700/80 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500/50 shadow-lg"
|
||||
title={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
|
||||
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
|
||||
>
|
||||
<!-- sun icon for light mode -->
|
||||
<svg
|
||||
class="w-4 h-4 text-yellow-400 transition-all duration-200 {isDark
|
||||
class="w-5 h-5 text-yellow-500 dark:text-yellow-400 transition-all duration-300 {isDark
|
||||
? 'opacity-0 rotate-90 scale-0'
|
||||
: 'opacity-100 rotate-0 scale-100'} absolute"
|
||||
fill="currentColor"
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
<!-- moon icon for dark mode -->
|
||||
<svg
|
||||
class="w-4 h-4 text-blue-300 transition-all duration-200 {isDark
|
||||
class="w-5 h-5 text-blue-400 dark:text-blue-300 transition-all duration-300 {isDark
|
||||
? 'opacity-100 rotate-0 scale-100'
|
||||
: 'opacity-0 -rotate-90 scale-0'} absolute"
|
||||
fill="currentColor"
|
||||
@@ -44,8 +44,8 @@
|
||||
</svg>
|
||||
|
||||
<!-- invisible placeholder to maintain button size -->
|
||||
<div class="w-4 h-4 opacity-0">
|
||||
<svg class="w-4 h-4" viewBox="0 0 20 20">
|
||||
<div class="w-5 h-5 opacity-0">
|
||||
<svg class="w-5 h-5" viewBox="0 0 20 20">
|
||||
<path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<script>
|
||||
export let href;
|
||||
export let title = '';
|
||||
</script>
|
||||
|
||||
<td
|
||||
class="pl-4 font-regular text-slate-600 dark:text-gray-300 text-ellipsis whitespace-nowrap overflow-hidden pr-4 transition-colors duration-200"
|
||||
{title}
|
||||
>
|
||||
<a href={href} class="block w-full py-1">
|
||||
<slot />
|
||||
</a>
|
||||
</td>
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
{#if disabled}
|
||||
<button
|
||||
class="px py text-slate-300 dark:text-gray-500 cursor-not-allowed transition-colors duration-200"
|
||||
class="w-full px py text-slate-300 dark:text-gray-500 cursor-not-allowed text-left transition-colors duration-200"
|
||||
{disabled}
|
||||
{title}
|
||||
>
|
||||
@@ -17,7 +17,7 @@
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="px py-1 text-slate-600 dark:text-gray-300 hover:bg-red-400 dark:hover:bg-red-500 hover:text-white cursor-pointer transition-colors duration-200"
|
||||
class="w-full px py-1 text-slate-600 dark:text-gray-300 hover:bg-red-400 dark:hover:bg-red-500 hover:text-white cursor-pointer text-left transition-colors duration-200"
|
||||
on:click
|
||||
{title}
|
||||
>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
{#if disabled}
|
||||
<button
|
||||
class="px py-1 text-slate-300 dark:text-gray-500 cursor-not-allowed transition-colors duration-200"
|
||||
class="w-full px py-1 text-slate-300 dark:text-gray-500 cursor-not-allowed text-left transition-colors duration-200"
|
||||
{disabled}
|
||||
{title}
|
||||
>
|
||||
@@ -25,7 +25,7 @@
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="px py-1 text-slate-600 dark:text-gray-300 hover:bg-highlight-blue dark:hover:bg-blue-600 hover:text-white cursor-pointer transition-colors duration-200"
|
||||
class="w-full px py-1 text-slate-600 dark:text-gray-300 hover:bg-highlight-blue dark:hover:bg-blue-600 hover:text-white cursor-pointer text-left transition-colors duration-200"
|
||||
on:click
|
||||
on:keydown={handleKeydown}
|
||||
{title}
|
||||
|
||||
@@ -322,6 +322,7 @@
|
||||
}}
|
||||
{...globalButtonDisabledAttributes(apiSender, contextCompanyID)}
|
||||
title={apiSender.name}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{apiSender.name}
|
||||
</button>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
const refresh = async () => {
|
||||
try {
|
||||
isTableLoading = true;
|
||||
const res = await api.domain.getAll(tableURLParams, contextCompanyID);
|
||||
const res = await api.domain.getAllSubset(tableURLParams, contextCompanyID);
|
||||
if (!res.success) {
|
||||
throw res.error;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,8 @@
|
||||
let isDeleteAlertVisible = false;
|
||||
let deleteValues = {
|
||||
id: null,
|
||||
name: null
|
||||
name: null,
|
||||
path: null
|
||||
};
|
||||
let modalMode = null;
|
||||
let modalText = '';
|
||||
@@ -251,6 +252,7 @@
|
||||
isDeleteAlertVisible = true;
|
||||
deleteValues.id = asset.id;
|
||||
deleteValues.name = asset.name;
|
||||
deleteValues.path = asset.path;
|
||||
};
|
||||
|
||||
const onClickPreview = async (path) => {
|
||||
@@ -396,7 +398,7 @@
|
||||
<TableCell>
|
||||
{#if isImageFile(asset.path)}
|
||||
{#await getImagePreviewUrl(asset.path)}
|
||||
<div class="w-12 h-12 bg-gray-200 animate-pulse rounded"></div>
|
||||
<div class="w-12 h-12 animate-pulse rounded"></div>
|
||||
{:then imageUrl}
|
||||
{#if imageUrl}
|
||||
<button
|
||||
@@ -415,22 +417,18 @@
|
||||
</button>
|
||||
{:else}
|
||||
<div
|
||||
class="w-12 h-12 bg-gray-300 rounded flex items-center justify-center text-xs text-gray-600"
|
||||
class="w-12 h-12 rounded flex items-center justify-center text-xs text-gray-600"
|
||||
>
|
||||
No preview
|
||||
</div>
|
||||
{/if}
|
||||
{:catch}
|
||||
<div
|
||||
class="w-12 h-12 bg-red-100 rounded flex items-center justify-center text-xs text-red-600"
|
||||
>
|
||||
<div class="w-12 h-12 rounded flex items-center justify-center text-xs text-red-600">
|
||||
Error
|
||||
</div>
|
||||
{/await}
|
||||
{:else}
|
||||
<div
|
||||
class="w-12 h-12 bg-gray-100 rounded flex items-center justify-center text-xs text-gray-500"
|
||||
>
|
||||
<div class="w-12 h-12 rounded flex items-center justify-center text-xs text-gray-500">
|
||||
📄
|
||||
</div>
|
||||
{/if}
|
||||
@@ -513,7 +511,7 @@
|
||||
</FormGrid>
|
||||
</Modal>
|
||||
<DeleteAlert
|
||||
name={deleteValues.name}
|
||||
name={deleteValues.name || deleteValues.path || 'Unnamed asset'}
|
||||
onClick={() => onClickDelete(deleteValues.id)}
|
||||
bind:isVisible={isDeleteAlertVisible}
|
||||
></DeleteAlert>
|
||||
|
||||
@@ -261,6 +261,7 @@
|
||||
}}
|
||||
{...globalButtonDisabledAttributes(attachment, contextCompanyID)}
|
||||
title={attachment.name}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{attachment.name}
|
||||
</button>
|
||||
@@ -274,6 +275,7 @@
|
||||
}}
|
||||
{...globalButtonDisabledAttributes(attachment, contextCompanyID)}
|
||||
title={attachment.name}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{attachment.description}
|
||||
</button>
|
||||
@@ -287,6 +289,7 @@
|
||||
}}
|
||||
{...globalButtonDisabledAttributes(attachment, contextCompanyID)}
|
||||
title={attachment.name}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{attachment.fileName}
|
||||
</button>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import TextField from '$lib/components/TextField.svelte';
|
||||
import TableRow from '$lib/components/table/TableRow.svelte';
|
||||
import TableCell from '$lib/components/table/TableCell.svelte';
|
||||
import TableCellLink from '$lib/components/table/TableCellLink.svelte';
|
||||
import TableUpdateButton from '$lib/components/table/TableUpdateButton.svelte';
|
||||
import TableDeleteButton from '$lib/components/table/TableDeleteButton2.svelte';
|
||||
import TableCellAction from '$lib/components/table/TableCellAction.svelte';
|
||||
@@ -473,62 +474,70 @@
|
||||
on:click={() => openUpdateModal(template.id)}
|
||||
{...globalButtonDisabledAttributes(template, contextCompanyID)}
|
||||
title={template.name}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{template.name}
|
||||
</button>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{#if template.domainID}
|
||||
<a href={`/domain/?edit=${template.domainID}`}>
|
||||
<a href={`/domain/?edit=${template.domainID}`} class="block w-full py-1">
|
||||
{domainMap.byKey(template.domainID)}
|
||||
</a>
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{#if template.smtpConfigurationID}
|
||||
<a href={`/smtp-configuration/?edit=${template.smtpConfigurationID}`}>
|
||||
<a
|
||||
href={`/smtp-configuration/?edit=${template.smtpConfigurationID}`}
|
||||
class="block w-full py-1"
|
||||
>
|
||||
{smtpConfigurationMap.byKey(template.smtpConfigurationID)}
|
||||
</a>
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{#if template.apiSenderID}
|
||||
<a href={`/api-sender/?edit=${template.apiSenderID}`}>
|
||||
<a href={`/api-sender/?edit=${template.apiSenderID}`} class="block w-full py-1">
|
||||
{apiSenderMap.byKey(template.apiSenderID)}
|
||||
</a>
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{#if template.emailID}
|
||||
<a href={`/email/?edit=${template.emailID}`}>
|
||||
<a href={`/email/?edit=${template.emailID}`} class="block w-full py-1">
|
||||
{emailMap.byKey(template.emailID)}
|
||||
</a>
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{#if template.beforeLandingPageID}
|
||||
<a href={`/page/?edit=${template.beforeLandingPageID}`}>
|
||||
<a href={`/page/?edit=${template.beforeLandingPageID}`} class="block w-full py-1">
|
||||
{beforeLandingPageMap.byKey(template.beforeLandingPageID)}
|
||||
</a>
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{#if template.landingPageID}
|
||||
<a href={`/page/?edit=${template.landingPageID}`}>
|
||||
<a href={`/page/?edit=${template.landingPageID}`} class="block w-full py-1">
|
||||
{landingPageMap.byKey(template.landingPageID)}
|
||||
</a>
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{#if template.afterLandingPageID}
|
||||
<a href={`/page/?edit=${template.afterLandingPageID}`}>
|
||||
<a href={`/page/?edit=${template.afterLandingPageID}`} class="block w-full py-1">
|
||||
{afterLandingPageMap.byKey(template.afterLandingPageID)}
|
||||
</a>
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{#if template.afterLandingPageRedirectURL}
|
||||
<a href={`${template.afterLandingPageRedirectURL}`} target="_blank">
|
||||
<a
|
||||
href={`${template.afterLandingPageRedirectURL}`}
|
||||
target="_blank"
|
||||
class="block w-full py-1"
|
||||
>
|
||||
{template.afterLandingPageRedirectURL}
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import TextField from '$lib/components/TextField.svelte';
|
||||
import TableRow from '$lib/components/table/TableRow.svelte';
|
||||
import TableCell from '$lib/components/table/TableCell.svelte';
|
||||
import TableCellLink from '$lib/components/table/TableCellLink.svelte';
|
||||
import TableUpdateButton from '$lib/components/table/TableUpdateButton.svelte';
|
||||
import TableDeleteButton from '$lib/components/table/TableDeleteButton2.svelte';
|
||||
import { addToast } from '$lib/store/toast';
|
||||
@@ -994,22 +995,21 @@
|
||||
>
|
||||
{#each campaigns as campaign}
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<TableCellLink href={`/campaign/${campaign.id}`} title={campaign.name}>
|
||||
{#if campaign.isTest}
|
||||
<TestLabel />
|
||||
{/if}
|
||||
<a href={`/campaign/${campaign.id}`}>
|
||||
{campaign.name}
|
||||
</a>
|
||||
</TableCell>
|
||||
{campaign.name}
|
||||
</TableCellLink>
|
||||
<TableCell>
|
||||
{toEvent(campaign.notableEventName).name}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<a href={`/campaign-template/?edit=${campaign.templateID}`}>
|
||||
{templateMap.byKey(campaign.templateID)}
|
||||
</a>
|
||||
</TableCell>
|
||||
<TableCellLink
|
||||
href={`/campaign-template/?edit=${campaign.templateID}`}
|
||||
title={templateMap.byKey(campaign.templateID)}
|
||||
>
|
||||
{templateMap.byKey(campaign.templateID)}
|
||||
</TableCellLink>
|
||||
<TableCell value={campaign.sendStartAt} isDate isRelative />
|
||||
<TableCell value={campaign.sendEndAt} isDate isRelative />
|
||||
<TableCell value={campaign.closeAt ?? ''} isDate isRelative />
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { AppStateService } from '$lib/service/appState';
|
||||
import TableRow from '$lib/components/table/TableRow.svelte';
|
||||
import TableCell from '$lib/components/table/TableCell.svelte';
|
||||
import TableCellLink from '$lib/components/table/TableCellLink.svelte';
|
||||
import { formatWeekDays, formatTimeConstraint, timeFormat } from '$lib/utils/date.js';
|
||||
import {
|
||||
defaultPerPage,
|
||||
@@ -1362,21 +1363,21 @@
|
||||
<TableCell isDate value={event.createdAt} />
|
||||
<TableCell>
|
||||
{#if event.recipient?.firstName}
|
||||
<a href={`/recipient/${event.recipient.id}`}>
|
||||
<a href={`/recipient/${event.recipient.id}`} class="block w-full py-1">
|
||||
{event.recipient.firstName}
|
||||
</a>
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{#if event.recipient?.lastName}
|
||||
<a href={`/recipient/${event.recipient.id}`}>
|
||||
<a href={`/recipient/${event.recipient.id}`} class="block w-full py-1">
|
||||
{event.recipient.lastName}
|
||||
</a>
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{#if event.recipient?.email}
|
||||
<a href={`/recipient/${event.recipient.id}`}>
|
||||
<a href={`/recipient/${event.recipient.id}`} class="block w-full py-1">
|
||||
{event.recipient.email}
|
||||
</a>
|
||||
{/if}
|
||||
@@ -1430,18 +1431,27 @@
|
||||
<TableCell value={'anonymized'} />
|
||||
{:else}
|
||||
<TableCell>
|
||||
<button on:click={() => openEventsModal(recp.recipientID)}>
|
||||
<button
|
||||
on:click={() => openEventsModal(recp.recipientID)}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{recp.recipient.firstName}
|
||||
</button>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<button on:click={() => openEventsModal(recp.recipientID)}>
|
||||
<button
|
||||
on:click={() => openEventsModal(recp.recipientID)}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{recp.recipient.lastName}
|
||||
</button>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{#if recp?.recipient?.email}
|
||||
<button on:click={() => openEventsModal(recp.recipientID)}>
|
||||
<button
|
||||
on:click={() => openEventsModal(recp.recipientID)}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{recp.recipient.email}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -246,6 +246,7 @@
|
||||
on:click={() => {
|
||||
openUpdateModal(company.id);
|
||||
}}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{company.name}
|
||||
</button>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import CheckboxField from '$lib/components/CheckboxField.svelte';
|
||||
import TableRow from '$lib/components/table/TableRow.svelte';
|
||||
import TableCell from '$lib/components/table/TableCell.svelte';
|
||||
import TableCellLink from '$lib/components/table/TableCellLink.svelte';
|
||||
import TableUpdateButton from '$lib/components/table/TableUpdateButton.svelte';
|
||||
import TableDeleteButton from '$lib/components/table/TableDeleteButton2.svelte';
|
||||
import { addToast } from '$lib/store/toast';
|
||||
@@ -538,6 +539,7 @@
|
||||
}}
|
||||
{...globalButtonDisabledAttributes(domain, contextCompanyID)}
|
||||
title={domain.name}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{domain.name}
|
||||
</button>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import Headline from '$lib/components/Headline.svelte';
|
||||
import TextField from '$lib/components/TextField.svelte';
|
||||
import TableCell from '$lib/components/table/TableCell.svelte';
|
||||
import TableCellLink from '$lib/components/table/TableCellLink.svelte';
|
||||
import TableRow from '$lib/components/table/TableRow.svelte';
|
||||
import TableUpdateButton from '$lib/components/table/TableUpdateButton.svelte';
|
||||
import TableDeleteButton from '$lib/components/table/TableDeleteButton2.svelte';
|
||||
@@ -394,6 +395,7 @@
|
||||
}}
|
||||
{...globalButtonDisabledAttributes(email, contextCompanyID)}
|
||||
title={email.name}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{email.name}
|
||||
</button>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { API } from '$lib/api/api';
|
||||
import { AppStateService } from '$lib/service/appState';
|
||||
@@ -10,6 +11,8 @@
|
||||
import FormGrid from '$lib/components/FormGrid.svelte';
|
||||
import FormColumns from '$lib/components/FormColumns.svelte';
|
||||
import FormColumn from '$lib/components/FormColumn.svelte';
|
||||
import ThemeToggle from '$lib/components/ThemeToggle.svelte';
|
||||
import { setupTheme, setupOSThemeListener } from '$lib/theme.js';
|
||||
|
||||
// services
|
||||
const api = API.instance;
|
||||
@@ -33,6 +36,12 @@
|
||||
|
||||
// Removed edition detection - single unified installation
|
||||
|
||||
// initialize theme system
|
||||
onMount(() => {
|
||||
setupTheme();
|
||||
setupOSThemeListener();
|
||||
});
|
||||
|
||||
// if already installed or not a superadministrator redirect to the dashboard
|
||||
const user = appStateService.getUser();
|
||||
const isInstalled = appStateService.isInstalled();
|
||||
@@ -142,19 +151,16 @@
|
||||
<div
|
||||
class="inset-0 z-50 min-h-screen bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 transition-colors duration-200"
|
||||
>
|
||||
<!-- theme toggle -->
|
||||
<div class="fixed top-3 right-6 z-50">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
<div class="flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-center">
|
||||
<img src="/logo-blue.svg" class="w-48" alt="Phishing Club" />
|
||||
</div>
|
||||
<p
|
||||
class="mt-2 text-center text-sm text-gray-600 dark:text-gray-300 transition-colors duration-200"
|
||||
>
|
||||
<p class="text-center text-sm text-gray-600 dark:text-gray-300 transition-colors duration-200">
|
||||
Complete the setup to get started with Phishing Club
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="sm:mx-auto sm:max-w-2xl mt-8 bg-white dark:bg-gray-800 rounded-lg shadow-lg dark:shadow-gray-900/50 transition-colors duration-200"
|
||||
>
|
||||
<div class="sm:mx-auto sm:max-w-2xl mt-8">
|
||||
<div class="flex justify-between items-center mb-8 w-full px-4">
|
||||
{#each steps as step, index}
|
||||
<div class="flex flex-col items-center w-32">
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
import { api } from '$lib/api/apiProxy';
|
||||
import { addToast } from '$lib/store/toast';
|
||||
import { page } from '$app/stores';
|
||||
import ThemeToggle from '$lib/components/ThemeToggle.svelte';
|
||||
import { setupTheme, setupOSThemeListener, theme } from '$lib/theme.js';
|
||||
|
||||
// services
|
||||
const appState = AppStateService.instance;
|
||||
@@ -43,6 +45,10 @@
|
||||
|
||||
// hooks
|
||||
onMount(() => {
|
||||
// initialize theme system
|
||||
setupTheme();
|
||||
setupOSThemeListener();
|
||||
|
||||
// if the user is already logged in, we want to redirect to the dashboard
|
||||
if (appState.isLoggedIn()) {
|
||||
console.info('login: navigating to /dashboard');
|
||||
@@ -210,10 +216,15 @@
|
||||
<main
|
||||
class="h-screen grid-cols-1 grid md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2 bg-white dark:bg-gray-900 transition-colors duration-200"
|
||||
>
|
||||
<!-- theme toggle -->
|
||||
<div class="fixed top-3 right-6 z-50">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-center h-full">
|
||||
<img
|
||||
class="fixed center top-6 w-1/4 md:w-1/4 lg:w-1/6 xl:w-1/6 2xl:w-1/6 lg:top-6 lg:left-4 xl:top-6 xl:left-4 2xl:top-6 2xl:left-4"
|
||||
src="/logo-blue.svg"
|
||||
src={$theme === 'dark' ? '/logo-white.svg' : '/logo-blue.svg'}
|
||||
alt="phishing club logo"
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
<script>
|
||||
import { API } from '$lib/api/api.js';
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
import { UserService } from '$lib/service/user';
|
||||
import FormButton from '$lib/components/FormButton.svelte';
|
||||
import Form from '$lib/components/Form.svelte';
|
||||
import SubHeadline from '$lib/components/SubHeadline.svelte';
|
||||
import { addToast } from '$lib/store/toast';
|
||||
import FormError from '$lib/components/FormError.svelte';
|
||||
import PasswordField from '$lib/components/PasswordField.svelte';
|
||||
import HeadTitle from '$lib/components/HeadTitle.svelte';
|
||||
|
||||
// services
|
||||
const api = API.instance;
|
||||
const userService = UserService.instance;
|
||||
|
||||
// bindings
|
||||
const changePasswordFormValues = {
|
||||
currentPassword: null,
|
||||
newPassword: null,
|
||||
repeatNewPassword: null
|
||||
};
|
||||
|
||||
// local state
|
||||
let changePasswordError = '';
|
||||
|
||||
// component logic
|
||||
|
||||
// hooks
|
||||
onMount(() => {
|
||||
console.log('no implemented');
|
||||
location.href = '/login';
|
||||
});
|
||||
|
||||
// component logic
|
||||
const onSubmitChangePassword = async () => {
|
||||
changePasswordError = '';
|
||||
// check if the new password and repeated password match
|
||||
if (changePasswordFormValues.newPassword !== changePasswordFormValues.repeatNewPassword) {
|
||||
changePasswordError = 'Current and repeated password do not match.';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await api.user.changePassword(
|
||||
changePasswordFormValues.currentPassword,
|
||||
changePasswordFormValues.newPassword
|
||||
);
|
||||
if (!res.success) {
|
||||
changePasswordError = res.error;
|
||||
return;
|
||||
}
|
||||
addToast('Password changed - login required', 'Success');
|
||||
userService.clear();
|
||||
console.info('profile: changed password - navigating to login');
|
||||
goto('/login/');
|
||||
} catch (e) {
|
||||
addToast('Failed to change password', 'Error');
|
||||
console.error('failed to change password', e);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<HeadTitle title="Change password" />
|
||||
<main>
|
||||
<Form on:submit={onSubmitChangePassword}>
|
||||
<SubHeadline>Current password has expired. Set a new password</SubHeadline>
|
||||
<PasswordField bind:value={changePasswordFormValues.currentPassword}
|
||||
>Current password</PasswordField
|
||||
>
|
||||
<PasswordField bind:value={changePasswordFormValues.newPassword}>New password</PasswordField>
|
||||
<PasswordField bind:value={changePasswordFormValues.repeatNewPassword}
|
||||
>Repeat new password</PasswordField
|
||||
>
|
||||
<FormError message={changePasswordError} />
|
||||
<FormButton>Change Password</FormButton>
|
||||
</Form>
|
||||
</main>
|
||||
@@ -8,6 +8,7 @@
|
||||
import TextField from '$lib/components/TextField.svelte';
|
||||
import TableRow from '$lib/components/table/TableRow.svelte';
|
||||
import TableCell from '$lib/components/table/TableCell.svelte';
|
||||
import TableCellLink from '$lib/components/table/TableCellLink.svelte';
|
||||
import TableUpdateButton from '$lib/components/table/TableUpdateButton.svelte';
|
||||
import TableDeleteButton from '$lib/components/table/TableDeleteButton2.svelte';
|
||||
import FormError from '$lib/components/FormError.svelte';
|
||||
@@ -315,6 +316,7 @@
|
||||
}}
|
||||
{...globalButtonDisabledAttributes(page, contextCompanyID)}
|
||||
title={page.name}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{page.name}
|
||||
</button>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import TextField from '$lib/components/TextField.svelte';
|
||||
import TableRow from '$lib/components/table/TableRow.svelte';
|
||||
import TableCell from '$lib/components/table/TableCell.svelte';
|
||||
import TableCellLink from '$lib/components/table/TableCellLink.svelte';
|
||||
import TableUpdateButton from '$lib/components/table/TableUpdateButton.svelte';
|
||||
import TableDeleteButton from '$lib/components/table/TableDeleteButton2.svelte';
|
||||
import { addToast } from '$lib/store/toast';
|
||||
@@ -406,19 +407,18 @@
|
||||
>
|
||||
{#each recipients as recipient}
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<TableCellLink href={`/recipient/${recipient.id}`} title={recipient.email}>
|
||||
{#if recipient.email}
|
||||
<a href={`/recipient/${recipient.id}`}>
|
||||
{recipient.email}
|
||||
</a>
|
||||
{recipient.email}
|
||||
{/if}
|
||||
</TableCell>
|
||||
</TableCellLink>
|
||||
<TableCell>
|
||||
{#if recipient.firstName}
|
||||
<button
|
||||
on:click={() => {
|
||||
openUpdateModal(recipient.id);
|
||||
}}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{recipient.firstName}
|
||||
</button>
|
||||
@@ -430,6 +430,7 @@
|
||||
on:click={() => {
|
||||
openUpdateModal(recipient.id);
|
||||
}}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{recipient.lastName}
|
||||
</button>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import TextField from '$lib/components/TextField.svelte';
|
||||
import TableRow from '$lib/components/table/TableRow.svelte';
|
||||
import TableCell from '$lib/components/table/TableCell.svelte';
|
||||
import TableCellLink from '$lib/components/table/TableCellLink.svelte';
|
||||
import TableUpdateButton from '$lib/components/table/TableUpdateButton.svelte';
|
||||
import TableDeleteButton from '$lib/components/table/TableDeleteButton2.svelte';
|
||||
import { addToast } from '$lib/store/toast';
|
||||
@@ -232,17 +233,13 @@
|
||||
>
|
||||
{#each groups as group}
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<a href={`/recipient/group/${group.id}`}>
|
||||
{group.name}
|
||||
</a>
|
||||
</TableCell>
|
||||
<TableCellLink href={`/recipient/group/${group.id}`} title={group.name}>
|
||||
{group.name}
|
||||
</TableCellLink>
|
||||
|
||||
<TableCell>
|
||||
<a href={`/recipient/group/${group.id}`}>
|
||||
{group.recipientCount}
|
||||
</a>
|
||||
</TableCell>
|
||||
<TableCellLink href={`/recipient/group/${group.id}`} title={group.recipientCount}>
|
||||
{group.recipientCount}
|
||||
</TableCellLink>
|
||||
<TableCellEmpty />
|
||||
<TableCellAction>
|
||||
<TableDropDownEllipsis>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import SubHeadline from '$lib/components/SubHeadline.svelte';
|
||||
import TableRow from '$lib/components/table/TableRow.svelte';
|
||||
import TableCell from '$lib/components/table/TableCell.svelte';
|
||||
import TableCellLink from '$lib/components/table/TableCellLink.svelte';
|
||||
import { addToast } from '$lib/store/toast';
|
||||
import FormError from '$lib/components/FormError.svelte';
|
||||
import TableCellEmpty from '$lib/components/table/TableCellEmpty.svelte';
|
||||
@@ -391,16 +392,16 @@
|
||||
{#each recipients as recipient}
|
||||
<TableRow>
|
||||
<TableCell value={recipient.email} />
|
||||
<TableCell>
|
||||
<a href="/recipient/{recipient.id}">{recipient.firstName}</a>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<a href="/recipient/{recipient.id}">{recipient.lastName}</a>
|
||||
</TableCell>
|
||||
<TableCellLink href="/recipient/{recipient.id}" title={recipient.firstName}>
|
||||
{recipient.firstName}
|
||||
</TableCellLink>
|
||||
<TableCellLink href="/recipient/{recipient.id}" title={recipient.lastName}>
|
||||
{recipient.lastName}
|
||||
</TableCellLink>
|
||||
<TableCell value={recipient.phone} />
|
||||
<TableCell>
|
||||
<a href="/recipient/{recipient.id}">{recipient.extraIdentifier}</a>
|
||||
</TableCell>
|
||||
<TableCellLink href="/recipient/{recipient.id}" title={recipient.extraIdentifier}>
|
||||
{recipient.extraIdentifier}
|
||||
</TableCellLink>
|
||||
<TableCell value={recipient.position} />
|
||||
<TableCell value={recipient.department} />
|
||||
<TableCell value={recipient.city} />
|
||||
|
||||
@@ -441,6 +441,7 @@
|
||||
}}
|
||||
{...globalButtonDisabledAttributes(conf, contextCompanyID)}
|
||||
title={conf.name}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{conf.name}
|
||||
</button>
|
||||
|
||||
@@ -263,6 +263,7 @@
|
||||
on:click={() => {
|
||||
showEditModal(user.id);
|
||||
}}
|
||||
class="block w-full py-1 text-left"
|
||||
>
|
||||
{user.username}
|
||||
</button></TableCell
|
||||
|
||||
Reference in New Issue
Block a user