mirror of
https://github.com/immich-app/immich.git
synced 2024-11-27 09:21:05 +02:00
Edit user on the web (#458)
* Added dispatch event for edit user * Fixed import location * solve merge conflict * Fixed issue not admin user can access admin page * Implemented edit user and password reset
This commit is contained in:
parent
5b7236f6ad
commit
4b9187928c
3
Makefile
3
Makefile
@ -1,6 +1,9 @@
|
||||
dev:
|
||||
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||
|
||||
dev-new:
|
||||
rm -rf ./server/dist && docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||
|
||||
dev-update:
|
||||
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||
|
||||
|
@ -6,85 +6,86 @@
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
font-family: 'Work Sans', sans-serif;
|
||||
/* --immich-icon-button-hover-color: #d3d3d3; */
|
||||
font-family: 'Work Sans', sans-serif;
|
||||
/* --immich-icon-button-hover-color: #d3d3d3; */
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
html::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 16px;
|
||||
background: #f1f1f1;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
html::-webkit-scrollbar-thumb {
|
||||
background: rgba(85, 86, 87, 0.408);
|
||||
border-radius: 16px;
|
||||
background: rgba(85, 86, 87, 0.408);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
/* Handle on hover */
|
||||
html::-webkit-scrollbar-thumb:hover {
|
||||
background: #4250afad;
|
||||
border-radius: 16px;
|
||||
background: #4250afad;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
body {
|
||||
/* min-height: 100vh; */
|
||||
margin: 0;
|
||||
background-color: #f6f8fe;
|
||||
color: #5f6368;
|
||||
/* min-height: 100vh; */
|
||||
margin: 0;
|
||||
background-color: #f6f8fe;
|
||||
color: #5f6368;
|
||||
}
|
||||
|
||||
input:focus-visible {
|
||||
outline-offset: 0px !important;
|
||||
outline: none !important;
|
||||
outline-offset: 0px !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.immich-form-input {
|
||||
@apply bg-slate-100 p-2 rounded-md focus:border-immich-primary text-sm;
|
||||
}
|
||||
.immich-form-input {
|
||||
@apply bg-slate-100 p-2 rounded-md focus:border-immich-primary text-sm ;
|
||||
}
|
||||
|
||||
.immich-form-label {
|
||||
@apply font-medium text-sm text-gray-500;
|
||||
}
|
||||
.immich-form-label {
|
||||
@apply font-medium text-sm text-gray-500;
|
||||
}
|
||||
|
||||
.immich-btn-primary {
|
||||
@apply bg-immich-primary text-gray-100 border rounded-xl py-2 px-4 transition-all duration-150 hover:bg-immich-primary hover:shadow-lg text-sm font-medium;
|
||||
}
|
||||
.immich-btn-primary {
|
||||
@apply bg-immich-primary text-gray-100 border rounded-xl py-2 px-4 transition-all duration-150 hover:bg-immich-primary hover:shadow-lg text-sm font-medium;
|
||||
}
|
||||
|
||||
.immich-text-button {
|
||||
@apply flex place-items-center place-content-center gap-2 hover:bg-immich-primary/5 p-2 rounded-lg font-medium;
|
||||
}
|
||||
.immich-text-button {
|
||||
@apply flex place-items-center place-content-center gap-2 hover:bg-immich-primary/5 p-2 rounded-lg font-medium;
|
||||
}
|
||||
|
||||
/* width */
|
||||
.immich-scrollbar::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
/* width */
|
||||
.immich-scrollbar::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
.immich-scrollbar::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 16px;
|
||||
}
|
||||
/* Track */
|
||||
.immich-scrollbar::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
.immich-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: rgba(85, 86, 87, 0.408);
|
||||
border-radius: 16px;
|
||||
}
|
||||
/* Handle */
|
||||
.immich-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: rgba(85, 86, 87, 0.408);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
/* Handle on hover */
|
||||
.immich-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: #4250afad;
|
||||
border-radius: 16px;
|
||||
}
|
||||
/* Handle on hover */
|
||||
.immich-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: #4250afad;
|
||||
border-radius: 16px;
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,9 @@
|
||||
<td class="text-sm px-4 w-1/4 text-ellipsis">{user.lastName}</td>
|
||||
<td class="text-sm px-4 w-1/4 text-ellipsis"
|
||||
><button
|
||||
on:click={() => {
|
||||
dispatch('edit-user', { user });
|
||||
}}
|
||||
class="bg-immich-primary text-gray-100 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
|
||||
><PencilOutline size="20" /></button
|
||||
></td
|
||||
@ -40,4 +43,4 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<button on:click={() => dispatch('createUser')} class="immich-btn-primary">Create user</button>
|
||||
<button on:click={() => dispatch('create-user')} class="immich-btn-primary">Create user</button>
|
||||
|
@ -1,122 +1,122 @@
|
||||
<script lang="ts">
|
||||
import { api } from '@api';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { api } from '@api';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
let error: string;
|
||||
let success: string;
|
||||
let error: string;
|
||||
let success: string;
|
||||
|
||||
let password: string = '';
|
||||
let confirmPassowrd: string = '';
|
||||
let password = '';
|
||||
let confirmPassowrd = '';
|
||||
|
||||
let canCreateUser = false;
|
||||
let canCreateUser = false;
|
||||
|
||||
$: {
|
||||
if (password !== confirmPassowrd && confirmPassowrd.length > 0) {
|
||||
error = 'Password does not match';
|
||||
canCreateUser = false;
|
||||
} else {
|
||||
error = '';
|
||||
canCreateUser = true;
|
||||
}
|
||||
}
|
||||
const dispatch = createEventDispatcher();
|
||||
$: {
|
||||
if (password !== confirmPassowrd && confirmPassowrd.length > 0) {
|
||||
error = 'Password does not match';
|
||||
canCreateUser = false;
|
||||
} else {
|
||||
error = '';
|
||||
canCreateUser = true;
|
||||
}
|
||||
}
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
async function registerUser(event: SubmitEvent) {
|
||||
console.log('registerUser');
|
||||
if (canCreateUser) {
|
||||
error = '';
|
||||
async function registerUser(event: SubmitEvent) {
|
||||
if (canCreateUser) {
|
||||
error = '';
|
||||
|
||||
const formElement = event.target as HTMLFormElement;
|
||||
const formElement = event.target as HTMLFormElement;
|
||||
|
||||
const form = new FormData(formElement);
|
||||
const form = new FormData(formElement);
|
||||
|
||||
const email = form.get('email');
|
||||
const password = form.get('password');
|
||||
const firstName = form.get('firstName');
|
||||
const lastName = form.get('lastName');
|
||||
const email = form.get('email');
|
||||
const password = form.get('password');
|
||||
const firstName = form.get('firstName');
|
||||
const lastName = form.get('lastName');
|
||||
|
||||
const { status } = await api.userApi.createUser({
|
||||
email: String(email),
|
||||
password: String(password),
|
||||
firstName: String(firstName),
|
||||
lastName: String(lastName)
|
||||
});
|
||||
const {status} = await api.userApi.createUser({
|
||||
email: String(email),
|
||||
password: String(password),
|
||||
firstName: String(firstName),
|
||||
lastName: String(lastName)
|
||||
});
|
||||
|
||||
if (status === 201) {
|
||||
success = 'New user created';
|
||||
if (status === 201) {
|
||||
success = 'New user created';
|
||||
|
||||
dispatch('user-created');
|
||||
return;
|
||||
} else {
|
||||
error = 'Error create user account';
|
||||
}
|
||||
}
|
||||
}
|
||||
dispatch('user-created');
|
||||
return;
|
||||
} else {
|
||||
error = 'Error create user account';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="border bg-white p-4 shadow-sm w-[500px] rounded-md py-8">
|
||||
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
|
||||
<img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo" />
|
||||
<h1 class="text-2xl text-immich-primary font-medium">Create new user</h1>
|
||||
<p class="text-sm border rounded-md p-4 font-mono text-gray-600">
|
||||
Please provide your user with the password, they will have to change it on their first sign
|
||||
in.
|
||||
</p>
|
||||
</div>
|
||||
<div class="border bg-white p-4 shadow-sm w-[500px] rounded-3xl py-8">
|
||||
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
|
||||
<img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo"/>
|
||||
<h1 class="text-2xl text-immich-primary font-medium">Create new user</h1>
|
||||
<p class="text-sm border rounded-md p-4 font-mono text-gray-600">
|
||||
Please provide your user with the password, they will have to change it on their first sign
|
||||
in.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form on:submit|preventDefault={registerUser} autocomplete="off">
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="email">Email</label>
|
||||
<input class="immich-form-input" id="email" name="email" type="email" required />
|
||||
</div>
|
||||
<form on:submit|preventDefault={registerUser} autocomplete="off">
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="email">Email</label>
|
||||
<input class="immich-form-input" id="email" name="email" type="email" required/>
|
||||
</div>
|
||||
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="password">Password</label>
|
||||
<input
|
||||
class="immich-form-input"
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
bind:value={password}
|
||||
/>
|
||||
</div>
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="password">Password</label>
|
||||
<input
|
||||
class="immich-form-input"
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
bind:value={password}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="confirmPassword">Confirm Password</label>
|
||||
<input
|
||||
class="immich-form-input"
|
||||
id="confirmPassword"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
bind:value={confirmPassowrd}
|
||||
/>
|
||||
</div>
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="confirmPassword">Confirm Password</label>
|
||||
<input
|
||||
class="immich-form-input"
|
||||
id="confirmPassword"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
bind:value={confirmPassowrd}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="firstName">First Name</label>
|
||||
<input class="immich-form-input" id="firstName" name="firstName" type="text" required />
|
||||
</div>
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="firstName">First Name</label>
|
||||
<input class="immich-form-input" id="firstName" name="firstName" type="text" required/>
|
||||
</div>
|
||||
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="lastName">Last Name</label>
|
||||
<input class="immich-form-input" id="lastName" name="lastName" type="text" required />
|
||||
</div>
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="lastName">Last Name</label>
|
||||
<input class="immich-form-input" id="lastName" name="lastName" type="text" required/>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<p class="text-red-400 ml-4 text-sm">{error}</p>
|
||||
{/if}
|
||||
{#if error}
|
||||
<p class="text-red-400 ml-4 text-sm">{error}</p>
|
||||
{/if}
|
||||
|
||||
{#if success}
|
||||
<p class="text-immich-primary ml-4 text-sm">{success}</p>
|
||||
{/if}
|
||||
<div class="flex w-full">
|
||||
<button
|
||||
type="submit"
|
||||
class="m-4 p-2 bg-immich-primary hover:bg-immich-primary/75 px-6 py-4 text-white rounded-md shadow-md w-full"
|
||||
>Create</button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
{#if success}
|
||||
<p class="text-immich-primary ml-4 text-sm">{success}</p>
|
||||
{/if}
|
||||
<div class="flex w-full">
|
||||
<button
|
||||
type="submit"
|
||||
class="m-4 bg-immich-primary hover:bg-immich-primary/75 px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
|
||||
>Create
|
||||
</button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
103
web/src/lib/components/forms/edit-user-form.svelte
Normal file
103
web/src/lib/components/forms/edit-user-form.svelte
Normal file
@ -0,0 +1,103 @@
|
||||
<script lang="ts">
|
||||
import { api, UserResponseDto } from '@api';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import AccountEditOutline from 'svelte-material-icons/AccountEditOutline.svelte';
|
||||
|
||||
export let user: UserResponseDto;
|
||||
|
||||
let error: string;
|
||||
let success: string;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const editUser = async (event: SubmitEvent) => {
|
||||
|
||||
const formElement = event.target as HTMLFormElement;
|
||||
const form = new FormData(formElement);
|
||||
|
||||
const firstName = form.get('firstName');
|
||||
const lastName = form.get('lastName');
|
||||
|
||||
|
||||
const {status} = await api.userApi.updateUser({
|
||||
id: user.id,
|
||||
firstName: firstName.toString(),
|
||||
lastName: lastName.toString()
|
||||
}).catch(e => console.log("Error updating user ", e));
|
||||
|
||||
if (status == 200) {
|
||||
dispatch('edit-success');
|
||||
}
|
||||
}
|
||||
|
||||
const resetPassword = async () => {
|
||||
const defaultPassword = 'password'
|
||||
|
||||
const {status} = await api.userApi.updateUser({
|
||||
id: user.id,
|
||||
password: defaultPassword,
|
||||
shouldChangePassword: true,
|
||||
|
||||
}).catch(e => console.log("Error updating user ", e));
|
||||
|
||||
if (status == 200) {
|
||||
dispatch('reset-password-success');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="border bg-white p-4 shadow-sm w-[500px] rounded-3xl py-8">
|
||||
<div class="flex flex-col place-items-center place-content-center gap-4 px-4">
|
||||
<!-- <img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo"/>-->
|
||||
<AccountEditOutline size="4em" color="#4250affe"/>
|
||||
<h1 class="text-2xl text-immich-primary font-medium">Edit user</h1>
|
||||
</div>
|
||||
|
||||
<form on:submit|preventDefault={editUser} autocomplete="off">
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="email">Email
|
||||
(cannot change)</label>
|
||||
<input class="immich-form-input disabled:bg-gray-200 hover:cursor-not-allowed"
|
||||
id="email" name="email"
|
||||
type="email" disabled
|
||||
bind:value={user.email}/>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="firstName">First Name</label>
|
||||
<input class="immich-form-input" id="firstName" name="firstName" type="text" required
|
||||
bind:value={user.firstName}/>
|
||||
</div>
|
||||
|
||||
<div class="m-4 flex flex-col gap-2">
|
||||
<label class="immich-form-label" for="lastName">Last Name</label>
|
||||
<input class="immich-form-input" id="lastName" name="lastName" type="text" required
|
||||
bind:value={user.lastName}/>
|
||||
</div>
|
||||
|
||||
|
||||
{#if error}
|
||||
<p class="text-red-400 ml-4 text-sm">{error}</p>
|
||||
{/if}
|
||||
|
||||
{#if success}
|
||||
<p class="text-immich-primary ml-4 text-sm">{success}</p>
|
||||
{/if}
|
||||
<div class="flex w-full px-4 gap-4 mt-8">
|
||||
<button on:click={resetPassword}
|
||||
class="flex-1 transition-colors bg-[#F9DEDC] hover:bg-red-50 text-[#410E0B] px-6 py-3 rounded-full w-full font-medium"
|
||||
|
||||
>Reset password
|
||||
</button
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="flex-1 transition-colors bg-immich-primary hover:bg-immich-primary/75 px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
|
||||
>Confirm
|
||||
</button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
@ -1,114 +1,181 @@
|
||||
<script context="module" lang="ts">
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
import { api, UserResponseDto } from '@api';
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
import { api, UserResponseDto } from '@api';
|
||||
import { browser } from '$app/env';
|
||||
|
||||
export const load: Load = async ({ fetch, session }) => {
|
||||
if (!browser && !session.user) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/auth/login'
|
||||
};
|
||||
}
|
||||
export const load: Load = async ({fetch, session}) => {
|
||||
if (!browser && !session.user) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/auth/login'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const [user, allUsers] = await Promise.all([
|
||||
fetch('/data/user/get-my-user-info').then((r) => r.json()),
|
||||
fetch('/data/user/get-all-users?isAll=false').then((r) => r.json())
|
||||
]);
|
||||
try {
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
props: {
|
||||
user: user,
|
||||
allUsers: allUsers
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/auth/login'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const user: UserResponseDto = await fetch('/data/user/get-my-user-info').then((r) => r.json());
|
||||
const allUsers: UserResponseDto[] = await fetch<UserResponseDto[]>('/data/user/get-all-users?isAll=false').then((r) => r.json());
|
||||
|
||||
if (!user.isAdmin) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/photos'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
props: {
|
||||
user: user,
|
||||
allUsers: allUsers
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/auth/login'
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import type { ImmichUser } from '$lib/models/immich-user';
|
||||
import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection';
|
||||
import SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.svelte';
|
||||
import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
|
||||
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
|
||||
import UserManagement from '$lib/components/admin-page/user-management.svelte';
|
||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||
import CreateUserForm from '$lib/components/forms/create-user-form.svelte';
|
||||
import StatusBox from '$lib/components/shared-components/status-box.svelte';
|
||||
import { browser } from '$app/env';
|
||||
import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection';
|
||||
import SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.svelte';
|
||||
import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte';
|
||||
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
|
||||
import UserManagement from '$lib/components/admin-page/user-management.svelte';
|
||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||
import CreateUserForm from '$lib/components/forms/create-user-form.svelte';
|
||||
import EditUserForm from '$lib/components/forms/edit-user-form.svelte';
|
||||
import StatusBox from '$lib/components/shared-components/status-box.svelte';
|
||||
|
||||
let selectedAction: AdminSideBarSelection = AdminSideBarSelection.USER_MANAGEMENT;
|
||||
|
||||
export let user: ImmichUser;
|
||||
export let allUsers: UserResponseDto[];
|
||||
let selectedAction: AdminSideBarSelection = AdminSideBarSelection.USER_MANAGEMENT;
|
||||
|
||||
let shouldShowCreateUserForm: boolean;
|
||||
export let user: UserResponseDto;
|
||||
export let allUsers: UserResponseDto[];
|
||||
|
||||
const onButtonClicked = (buttonType: CustomEvent) => {
|
||||
selectedAction = buttonType.detail['actionType'] as AdminSideBarSelection;
|
||||
};
|
||||
let editUser: UserResponseDto;
|
||||
|
||||
onMount(() => {
|
||||
selectedAction = AdminSideBarSelection.USER_MANAGEMENT;
|
||||
});
|
||||
let shouldShowEditUserForm = false;
|
||||
let shouldShowCreateUserForm = false;
|
||||
let shouldShowInfoPanel = false;
|
||||
|
||||
const onUserCreated = async () => {
|
||||
const { data } = await api.userApi.getAllUsers(false);
|
||||
allUsers = data;
|
||||
const onButtonClicked = (buttonType: CustomEvent) => {
|
||||
selectedAction = buttonType.detail['actionType'] as AdminSideBarSelection;
|
||||
};
|
||||
|
||||
shouldShowCreateUserForm = false;
|
||||
};
|
||||
onMount(() => {
|
||||
selectedAction = AdminSideBarSelection.USER_MANAGEMENT;
|
||||
});
|
||||
|
||||
const onUserCreated = async () => {
|
||||
const {data} = await api.userApi.getAllUsers(false);
|
||||
allUsers = data;
|
||||
shouldShowCreateUserForm = false;
|
||||
};
|
||||
|
||||
const editUserHandler = async (event: CustomEvent) => {
|
||||
const {user} = event.detail;
|
||||
editUser = user;
|
||||
shouldShowEditUserForm = true;
|
||||
};
|
||||
|
||||
const onEditUserSuccess = async () => {
|
||||
const {data} = await api.userApi.getAllUsers(false);
|
||||
allUsers = data;
|
||||
shouldShowEditUserForm = false;
|
||||
}
|
||||
|
||||
const onEditPasswordSuccess = async () => {
|
||||
const {data} = await api.userApi.getAllUsers(false);
|
||||
allUsers = data;
|
||||
shouldShowEditUserForm = false;
|
||||
shouldShowInfoPanel = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Administration - Immich</title>
|
||||
<title>Administration - Immich</title>
|
||||
</svelte:head>
|
||||
|
||||
<NavigationBar {user} />
|
||||
<NavigationBar {user}/>
|
||||
|
||||
{#if shouldShowCreateUserForm}
|
||||
<FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}>
|
||||
<div>
|
||||
<CreateUserForm on:user-created={onUserCreated} />
|
||||
</div>
|
||||
</FullScreenModal>
|
||||
<FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}>
|
||||
<CreateUserForm on:user-created={onUserCreated}/>
|
||||
</FullScreenModal>
|
||||
{/if}
|
||||
|
||||
{#if shouldShowEditUserForm}
|
||||
<FullScreenModal on:clickOutside={() => (shouldShowEditUserForm = false)}>
|
||||
<EditUserForm user={editUser} on:edit-success={onEditUserSuccess}
|
||||
on:reset-password-success={onEditPasswordSuccess}/>
|
||||
</FullScreenModal>
|
||||
{/if}
|
||||
|
||||
{#if shouldShowInfoPanel}
|
||||
<FullScreenModal on:clickOutside={() => (shouldShowInfoPanel = false)}>
|
||||
|
||||
<div class="border bg-white shadow-sm w-[500px] rounded-3xl p-8 text-sm">
|
||||
<h1 class="font-bold text-immich-primary text-lg mb-4">Password reset success</h1>
|
||||
|
||||
<p>
|
||||
The user's password has been reset to the default <code
|
||||
class="font-bold bg-gray-200 px-2 py-1 rounded-md text-immich-primary">password</code>
|
||||
<br>
|
||||
Please inform the user, and they will need to change the password at the next log-on.
|
||||
</p>
|
||||
|
||||
<div class="flex w-full">
|
||||
<button
|
||||
on:click={() => shouldShowInfoPanel = false}
|
||||
class="mt-6 bg-immich-primary hover:bg-immich-primary/75 px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
|
||||
>Done
|
||||
</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</FullScreenModal>
|
||||
{/if}
|
||||
|
||||
|
||||
<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen">
|
||||
<section id="admin-sidebar" class="pt-8 pr-6 flex flex-col">
|
||||
<SideBarButton
|
||||
title="User"
|
||||
logo={AccountMultipleOutline}
|
||||
actionType={AdminSideBarSelection.USER_MANAGEMENT}
|
||||
isSelected={selectedAction === AdminSideBarSelection.USER_MANAGEMENT}
|
||||
on:selected={onButtonClicked}
|
||||
/>
|
||||
<section id="admin-sidebar" class="pt-8 pr-6 flex flex-col">
|
||||
<SideBarButton
|
||||
title="User"
|
||||
logo={AccountMultipleOutline}
|
||||
actionType={AdminSideBarSelection.USER_MANAGEMENT}
|
||||
isSelected={selectedAction === AdminSideBarSelection.USER_MANAGEMENT}
|
||||
on:selected={onButtonClicked}
|
||||
/>
|
||||
|
||||
<div class="mb-6 mt-auto">
|
||||
<StatusBox />
|
||||
</div>
|
||||
</section>
|
||||
<section class="overflow-y-auto relative">
|
||||
<div id="setting-title" class="pt-10 fixed w-full z-50 bg-immich-bg">
|
||||
<h1 class="text-lg ml-8 mb-4 text-immich-primary font-medium">{selectedAction}</h1>
|
||||
<hr />
|
||||
</div>
|
||||
<div class="mb-6 mt-auto">
|
||||
<StatusBox/>
|
||||
</div>
|
||||
</section>
|
||||
<section class="overflow-y-auto relative">
|
||||
<div id="setting-title" class="pt-10 fixed w-full z-50 bg-immich-bg">
|
||||
<h1 class="text-lg ml-8 mb-4 text-immich-primary font-medium">{selectedAction}</h1>
|
||||
<hr/>
|
||||
</div>
|
||||
|
||||
|
||||
<section id="setting-content" class="relative pt-[85px] flex place-content-center">
|
||||
<section class="w-[800px] pt-4">
|
||||
{#if selectedAction === AdminSideBarSelection.USER_MANAGEMENT}
|
||||
<UserManagement
|
||||
{allUsers}
|
||||
on:create-user={() => (shouldShowCreateUserForm = true)}
|
||||
on:edit-user={editUserHandler}
|
||||
/>
|
||||
{/if}
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="setting-content" class="relative pt-[85px] flex place-content-center">
|
||||
<section class="w-[800px] pt-4">
|
||||
{#if selectedAction === AdminSideBarSelection.USER_MANAGEMENT}
|
||||
<UserManagement {allUsers} on:createUser={() => (shouldShowCreateUserForm = true)} />
|
||||
{/if}
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
@ -1,56 +1,58 @@
|
||||
<script context="module" lang="ts">
|
||||
export const prerender = false;
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
import { api } from '@api';
|
||||
export const prerender = false;
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
import { api } from '@api';
|
||||
import { browser } from '$app/env';
|
||||
|
||||
export const load: Load = async () => {
|
||||
if (browser) {
|
||||
try {
|
||||
const { data: user } = await api.userApi.getMyUserInfo();
|
||||
export const load: Load = async () => {
|
||||
if (browser) {
|
||||
try {
|
||||
const {data: user} = await api.userApi.getMyUserInfo();
|
||||
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/photos'
|
||||
};
|
||||
} catch (e) {}
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/photos'
|
||||
};
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
const { data } = await api.userApi.getUserCount();
|
||||
const {data} = await api.userApi.getUserCount();
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
props: {
|
||||
isAdminUserExist: data.userCount == 0 ? false : true
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
return {
|
||||
status: 200,
|
||||
props: {
|
||||
isAdminUserExist: data.userCount != 0
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/env';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
export let isAdminUserExist: boolean;
|
||||
export let isAdminUserExist: boolean;
|
||||
|
||||
async function onGettingStartedClicked() {
|
||||
isAdminUserExist ? goto('/auth/login') : goto('/auth/register');
|
||||
}
|
||||
async function onGettingStartedClicked() {
|
||||
isAdminUserExist ? await goto('/auth/login') : await goto('/auth/register');
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Welcome 🎉 - Immich</title>
|
||||
<meta name="description" content="Immich Web Interface" />
|
||||
<title>Welcome 🎉 - Immich</title>
|
||||
<meta name="description" content="Immich Web Interface"/>
|
||||
</svelte:head>
|
||||
|
||||
<section class="h-screen w-screen flex place-items-center place-content-center">
|
||||
<div class="flex flex-col place-items-center gap-8 text-center max-w-[350px]">
|
||||
<div class="flex place-items-center place-content-center ">
|
||||
<img class="text-center" src="immich-logo.svg" height="200" width="200" alt="immich-logo" />
|
||||
</div>
|
||||
<h1 class="text-4xl text-immich-primary font-bold font-immich-title">Welcome to IMMICH Web</h1>
|
||||
<button
|
||||
class="border px-4 py-2 rounded-md bg-immich-primary hover:bg-immich-primary/75 text-white font-bold w-[200px]"
|
||||
on:click={onGettingStartedClicked}>Getting Started</button
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-col place-items-center gap-8 text-center max-w-[350px]">
|
||||
<div class="flex place-items-center place-content-center ">
|
||||
<img class="text-center" src="immich-logo.svg" height="200" width="200" alt="immich-logo"/>
|
||||
</div>
|
||||
<h1 class="text-4xl text-immich-primary font-bold font-immich-title">Welcome to IMMICH Web</h1>
|
||||
<button
|
||||
class="border px-4 py-2 rounded-md bg-immich-primary hover:bg-immich-primary/75 text-white font-bold w-[200px]"
|
||||
on:click={onGettingStartedClicked}>Getting Started
|
||||
</button
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -1,119 +1,120 @@
|
||||
<script context="module" lang="ts">
|
||||
export const prerender = false;
|
||||
export const prerender = false;
|
||||
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
import { AlbumResponseDto, api, UserResponseDto } from '@api';
|
||||
import type { Load } from '@sveltejs/kit';
|
||||
import { AlbumResponseDto, api, UserResponseDto } from '@api';
|
||||
import { browser } from '$app/env';
|
||||
|
||||
export const load: Load = async ({ fetch, session }) => {
|
||||
if (!browser && !session.user) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/auth/login'
|
||||
};
|
||||
}
|
||||
export const load: Load = async ({fetch, session}) => {
|
||||
if (!browser && !session.user) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/auth/login'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const [user, sharedAlbums] = await Promise.all([
|
||||
fetch('/data/user/get-my-user-info').then((r) => r.json()),
|
||||
fetch('/data/album/get-all-albums?isShared=true').then((r) => r.json())
|
||||
]);
|
||||
try {
|
||||
const [user, sharedAlbums] = await Promise.all([
|
||||
fetch('/data/user/get-my-user-info').then((r) => r.json()),
|
||||
fetch('/data/album/get-all-albums?isShared=true').then((r) => r.json())
|
||||
]);
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
props: {
|
||||
user: user,
|
||||
sharedAlbums: sharedAlbums
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/auth/login'
|
||||
};
|
||||
}
|
||||
};
|
||||
return {
|
||||
status: 200,
|
||||
props: {
|
||||
user: user,
|
||||
sharedAlbums: sharedAlbums
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
status: 302,
|
||||
redirect: '/auth/login'
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
|
||||
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
|
||||
import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
|
||||
import SharedAlbumListTile from '$lib/components/sharing-page/shared-album-list-tile.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/env';
|
||||
import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte';
|
||||
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
|
||||
import PlusBoxOutline from 'svelte-material-icons/PlusBoxOutline.svelte';
|
||||
import SharedAlbumListTile from '$lib/components/sharing-page/shared-album-list-tile.svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
export let user: UserResponseDto;
|
||||
export let sharedAlbums: AlbumResponseDto[];
|
||||
export let user: UserResponseDto;
|
||||
export let sharedAlbums: AlbumResponseDto[];
|
||||
|
||||
const createSharedAlbum = async () => {
|
||||
try {
|
||||
const { data: newAlbum } = await api.albumApi.createAlbum({
|
||||
albumName: 'Untitled'
|
||||
});
|
||||
const createSharedAlbum = async () => {
|
||||
try {
|
||||
const {data: newAlbum} = await api.albumApi.createAlbum({
|
||||
albumName: 'Untitled'
|
||||
});
|
||||
|
||||
goto('/albums/' + newAlbum.id);
|
||||
} catch (e) {
|
||||
console.log('Error [createAlbum] ', e);
|
||||
}
|
||||
};
|
||||
goto('/albums/' + newAlbum.id);
|
||||
} catch (e) {
|
||||
console.log('Error [createAlbum] ', e);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Albums - Immich</title>
|
||||
<title>Albums - Immich</title>
|
||||
</svelte:head>
|
||||
|
||||
<section>
|
||||
<NavigationBar {user} on:uploadClicked={() => {}} />
|
||||
<NavigationBar {user} on:uploadClicked={() => {}}/>
|
||||
</section>
|
||||
|
||||
<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg">
|
||||
<SideBar />
|
||||
<SideBar/>
|
||||
|
||||
<section class="overflow-y-auto relative">
|
||||
<section id="album-content" class="relative pt-8 pl-4 mb-12 bg-immich-bg">
|
||||
<!-- Main Section -->
|
||||
<div class="px-4 flex justify-between place-items-center">
|
||||
<div>
|
||||
<p class="font-medium">Sharing</p>
|
||||
</div>
|
||||
<section class="overflow-y-auto relative">
|
||||
<section id="album-content" class="relative pt-8 pl-4 mb-12 bg-immich-bg">
|
||||
<!-- Main Section -->
|
||||
<div class="px-4 flex justify-between place-items-center">
|
||||
<div>
|
||||
<p class="font-medium">Sharing</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
on:click={createSharedAlbum}
|
||||
class="flex place-items-center gap-1 text-sm hover:bg-immich-primary/5 p-2 rounded-lg font-medium hover:text-gray-700"
|
||||
>
|
||||
<div>
|
||||
<button
|
||||
on:click={createSharedAlbum}
|
||||
class="flex place-items-center gap-1 text-sm hover:bg-immich-primary/5 p-2 rounded-lg font-medium hover:text-gray-700"
|
||||
>
|
||||
<span>
|
||||
<PlusBoxOutline size="18" />
|
||||
<PlusBoxOutline size="18"/>
|
||||
</span>
|
||||
<p>Create shared album</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p>Create shared album</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-4">
|
||||
<hr />
|
||||
</div>
|
||||
<div class="my-4">
|
||||
<hr/>
|
||||
</div>
|
||||
|
||||
<!-- Share Album List -->
|
||||
<div class="w-full flex flex-col place-items-center">
|
||||
{#each sharedAlbums as album}
|
||||
<a sveltekit:prefetch href={`albums/${album.id}`}>
|
||||
<SharedAlbumListTile {album} {user} /></a
|
||||
>
|
||||
{/each}
|
||||
</div>
|
||||
<!-- Share Album List -->
|
||||
<div class="w-full flex flex-col place-items-center">
|
||||
{#each sharedAlbums as album}
|
||||
<a sveltekit:prefetch href={`albums/${album.id}`}>
|
||||
<SharedAlbumListTile {album} {user}/>
|
||||
</a
|
||||
>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Empty List -->
|
||||
{#if sharedAlbums.length === 0}
|
||||
<div
|
||||
class="border p-5 w-[50%] m-auto mt-10 bg-gray-50 rounded-3xl flex flex-col place-content-center place-items-center"
|
||||
>
|
||||
<img src="/empty-2.svg" alt="Empty shared album" width="500" />
|
||||
<p class="text-center text-immich-text-gray-500">
|
||||
Create a shared album to share photos and videos with people in your network
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
</section>
|
||||
<!-- Empty List -->
|
||||
{#if sharedAlbums.length === 0}
|
||||
<div
|
||||
class="border p-5 w-[50%] m-auto mt-10 bg-gray-50 rounded-3xl flex flex-col place-content-center place-items-center"
|
||||
>
|
||||
<img src="/empty-2.svg" alt="Empty shared album" width="500"/>
|
||||
<p class="text-center text-immich-text-gray-500">
|
||||
Create a shared album to share photos and videos with people in your network
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
Loading…
Reference in New Issue
Block a user