Deletes old project
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { openModal, closeModal } from 'svelte-modals';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import { user } from '$lib/stores/user';
|
||||
import type { userProfile } from '$lib/stores/user';
|
||||
import { page } from '$app/stores';
|
||||
import { notifications } from '$lib/components/toasts/notifications';
|
||||
import InputPassword from '$lib/components/InputPassword.svelte';
|
||||
import SettingsCard from '$lib/components/SettingsCard.svelte';
|
||||
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
||||
import EditUser from './EditUser.svelte';
|
||||
import Spinner from '$lib/components/Spinner.svelte';
|
||||
import Delete from '~icons/tabler/trash';
|
||||
import AddUser from '~icons/tabler/user-plus';
|
||||
import Edit from '~icons/tabler/pencil';
|
||||
import Admin from '~icons/tabler/key';
|
||||
import Users from '~icons/tabler/users';
|
||||
import Warning from '~icons/tabler/alert-triangle';
|
||||
import Cancel from '~icons/tabler/x';
|
||||
import Check from '~icons/tabler/check';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
type userSetting = {
|
||||
username: string;
|
||||
password: string;
|
||||
admin: boolean;
|
||||
};
|
||||
|
||||
type SecuritySettings = {
|
||||
jwt_secret: string;
|
||||
users: userSetting[];
|
||||
};
|
||||
|
||||
let securitySettings: SecuritySettings;
|
||||
|
||||
async function getSecuritySettings() {
|
||||
try {
|
||||
const response = await fetch('/api/securitySettings', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
securitySettings = await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
async function postSecuritySettings(data: SecuritySettings) {
|
||||
try {
|
||||
const response = await fetch('/api/securitySettings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
securitySettings = await response.json();
|
||||
if (response.status == 200) {
|
||||
if (await validateUser($user)) {
|
||||
notifications.success('Security settings updated.', 3000);
|
||||
}
|
||||
} else {
|
||||
notifications.error('User not authorized.', 3000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
async function validateUser(userdata: userProfile) {
|
||||
try {
|
||||
const response = await fetch('/api/verifyAuthorization', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + userdata.bearer_token,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
if (response.status !== 200) {
|
||||
user.invalidate();
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function confirmDelete(index: number) {
|
||||
openModal(ConfirmDialog, {
|
||||
title: 'Confirm Delete User',
|
||||
message:
|
||||
'Are you sure you want to delete the user "' +
|
||||
securitySettings.users[index].username +
|
||||
'"?',
|
||||
labels: {
|
||||
cancel: { label: 'Abort', icon: Cancel },
|
||||
confirm: { label: 'Yes', icon: Check }
|
||||
},
|
||||
onConfirm: () => {
|
||||
securitySettings.users.splice(index, 1);
|
||||
securitySettings = securitySettings;
|
||||
closeModal();
|
||||
postSecuritySettings(securitySettings);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleEdit(index: number) {
|
||||
openModal(EditUser, {
|
||||
title: 'Edit User',
|
||||
user: { ...securitySettings.users[index] }, // Shallow Copy
|
||||
onSaveUser: (editedUser: userSetting) => {
|
||||
securitySettings.users[index] = editedUser;
|
||||
closeModal();
|
||||
postSecuritySettings(securitySettings);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleNewUser() {
|
||||
openModal(EditUser, {
|
||||
title: 'Add User',
|
||||
onSaveUser: (newUser: userSetting) => {
|
||||
securitySettings.users = [...securitySettings.users, newUser];
|
||||
closeModal();
|
||||
postSecuritySettings(securitySettings);
|
||||
}
|
||||
});
|
||||
//
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $user.admin}
|
||||
<div
|
||||
class="mx-0 my-1 flex flex-col space-y-4
|
||||
sm:mx-8 sm:my-8"
|
||||
>
|
||||
<SettingsCard collapsible={false}>
|
||||
<Users slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
||||
<span slot="title">Manage Users</span>
|
||||
{#await getSecuritySettings()}
|
||||
<Spinner />
|
||||
{:then nothing}
|
||||
<div class="relative w-full overflow-visible">
|
||||
<button
|
||||
class="btn btn-primary text-primary-content btn-md absolute -top-14 right-0"
|
||||
on:click={handleNewUser}
|
||||
>
|
||||
<AddUser class="h-6 w-6" /></button
|
||||
>
|
||||
|
||||
<div class="overflow-x-auto" transition:slide|local={{ duration: 300, easing: cubicOut }}>
|
||||
<table class="table w-full table-auto">
|
||||
<thead>
|
||||
<tr class="font-bold">
|
||||
<th align="left">Username</th>
|
||||
<th align="center">Admin</th>
|
||||
<th align="right" class="pr-8">Edit</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each securitySettings.users as user, index}
|
||||
<tr>
|
||||
<td align="left">{user.username}</td>
|
||||
<td align="center">
|
||||
{#if user.admin}
|
||||
<Admin class="text-secondary" />
|
||||
{/if}
|
||||
</td>
|
||||
<td align="right">
|
||||
<span class="my-auto inline-flex flex-row space-x-2">
|
||||
<button
|
||||
class="btn btn-ghost btn-circle btn-xs"
|
||||
on:click={() => handleEdit(index)}
|
||||
>
|
||||
<Edit class="h-6 w-6" /></button
|
||||
>
|
||||
<button
|
||||
class="btn btn-ghost btn-circle btn-xs"
|
||||
on:click={() => confirmDelete(index)}
|
||||
>
|
||||
<Delete class="text-error h-6 w-6" />
|
||||
</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider mb-0" />
|
||||
|
||||
<span class="pb-2 text-xl font-medium">Security Settings</span>
|
||||
<div class="alert alert-warning shadow-lg">
|
||||
<Warning class="h-6 w-6 flex-shrink-0" />
|
||||
<span
|
||||
>The JWT secret is used to sign authentication tokens. If you modify the JWT Secret, all
|
||||
users will be signed out.</span
|
||||
>
|
||||
</div>
|
||||
<label class="label" for="secret">
|
||||
<span class="label-text text-md">JWT Secret</span>
|
||||
</label>
|
||||
<InputPassword bind:value={securitySettings.jwt_secret} id="secret" />
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button class="btn btn-primary" on:click={() => postSecuritySettings(securitySettings)}
|
||||
>Apply Settings</button
|
||||
>
|
||||
</div>
|
||||
{/await}
|
||||
</SettingsCard>
|
||||
</div>
|
||||
{:else}
|
||||
{goto('/')}
|
||||
{/if}
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async () => {
|
||||
return {
|
||||
title: 'Users'
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
@@ -0,0 +1,102 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { closeModal } from 'svelte-modals';
|
||||
import { fly } from 'svelte/transition';
|
||||
import InputPassword from '$lib/components/InputPassword.svelte';
|
||||
import Cancel from '~icons/tabler/x';
|
||||
import Save from '~icons/tabler/device-floppy';
|
||||
|
||||
// provided by <Modals />
|
||||
export let isOpen: boolean;
|
||||
|
||||
export let title: string;
|
||||
export let onSaveUser: any; // Callback on Save
|
||||
export let user = {
|
||||
username: '',
|
||||
password: '',
|
||||
admin: false
|
||||
};
|
||||
|
||||
let errorUsername = false;
|
||||
|
||||
let usernameEditable = false;
|
||||
|
||||
onMount(() => {
|
||||
if (user.username == '') {
|
||||
usernameEditable = true;
|
||||
}
|
||||
});
|
||||
|
||||
function handleSave() {
|
||||
// Validate if username is within range
|
||||
if (user.username.length < 3 || user.username.length > 32) {
|
||||
errorUsername = true;
|
||||
} else {
|
||||
errorUsername = false;
|
||||
// Callback on saving
|
||||
onSaveUser(user);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if isOpen}
|
||||
<div
|
||||
role="dialog"
|
||||
class="pointer-events-none fixed inset-0 z-50 flex items-center justify-center overflow-y-auto"
|
||||
transition:fly={{ y: 50 }}
|
||||
on:introstart
|
||||
on:outroend
|
||||
>
|
||||
<div
|
||||
class="rounded-box bg-base-100 pointer-events-auto flex min-w-fit max-w-md flex-col justify-between p-4 shadow-lg md:w-[28rem]"
|
||||
>
|
||||
<h2 class="text-base-content text-start text-2xl font-bold">{title}</h2>
|
||||
<div class="divider my-2" />
|
||||
<form
|
||||
class="form-control text-base-content mb-1 w-full"
|
||||
on:submit|preventDefault={handleSave}
|
||||
novalidate
|
||||
>
|
||||
<label class="label" for="username">
|
||||
<span class="label-text text-md">Username</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
min="3"
|
||||
max="32"
|
||||
class="input input-bordered invalid:border-error w-full invalid:border-2"
|
||||
bind:value={user.username}
|
||||
id="username"
|
||||
disabled={!usernameEditable}
|
||||
/>
|
||||
<label for="username" class="label"
|
||||
><span class="label-text-alt text-error {errorUsername ? '' : 'hidden'}"
|
||||
>Username must be between 3 and 32 characters long</span
|
||||
></label
|
||||
>
|
||||
<label class="label" for="pwd">
|
||||
<span class="label-text text-md">Password</span>
|
||||
</label>
|
||||
<InputPassword bind:value={user.password} id="pwd" />
|
||||
<label class="label my-auto cursor-pointer justify-start gap-4">
|
||||
<input type="checkbox" bind:checked={user.admin} class="checkbox checkbox-primary" />
|
||||
<span class="">Is Admin?</span>
|
||||
</label>
|
||||
<div class="divider my-2" />
|
||||
<div class="flex justify-end gap-2">
|
||||
<button
|
||||
class="btn btn-neutral text-neutral-content inline-flex items-center"
|
||||
on:click={closeModal}
|
||||
type="button"
|
||||
>
|
||||
<Cancel class="mr-2 h-5 w-5" /><span>Cancel</span></button
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary text-primary-content inline-flex items-center"
|
||||
type="submit"><Save class="mr-2 h-5 w-5" /><span>Save</span></button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user