mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	feat(web) styling server stats page (#866)
This commit is contained in:
		| @@ -48,7 +48,7 @@ html::-webkit-scrollbar-thumb:hover { | ||||
| body { | ||||
| 	/* min-height: 100vh; */ | ||||
| 	margin: 0; | ||||
| 	background-color: #f6f8fe; | ||||
| 	/* background-color: #f6f8fe; */ | ||||
| 	color: #5f6368; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -11,23 +11,25 @@ | ||||
| 	const dispatch = createEventDispatcher(); | ||||
| </script> | ||||
|  | ||||
| <div class="flex border p-6 rounded-2xl bg-white"> | ||||
| <div class="flex border-b pb-5"> | ||||
| 	<div class="w-[70%]"> | ||||
| 		<h1 class="font-medium text-immich-primary">{title}</h1> | ||||
| 		<p class="text-sm mt-1 font-medium">{subtitle}</p> | ||||
| 		<h1 class="text-immich-primary text-sm">{title.toUpperCase()}</h1> | ||||
| 		<p class="text-sm mt-1">{subtitle}</p> | ||||
| 		<p class="text-sm"> | ||||
| 			<slot /> | ||||
| 		</p> | ||||
| 		<table class="text-left w-full mt-4"> | ||||
| 		<table class="text-left w-full mt-5"> | ||||
| 			<!-- table header --> | ||||
| 			<thead class="border rounded-md mb-2 bg-gray-50 flex text-immich-primary w-full h-12"> | ||||
| 			<thead | ||||
| 				class="border rounded-md mb-2 bg-immich-primary/10 flex text-immich-primary w-full h-12" | ||||
| 			> | ||||
| 				<tr class="flex w-full place-items-center"> | ||||
| 					<th class="text-center w-1/3 font-medium text-sm">Status</th> | ||||
| 					<th class="text-center w-1/3 font-medium text-sm">Active</th> | ||||
| 					<th class="text-center w-1/3 font-medium text-sm">Waiting</th> | ||||
| 				</tr> | ||||
| 			</thead> | ||||
| 			<tbody class="overflow-y-auto rounded-md w-full max-h-[320px] block border"> | ||||
| 			<tbody class="overflow-y-auto rounded-md w-full max-h-[320px] block border bg-white"> | ||||
| 				<tr class="text-center flex place-items-center w-full h-[40px]"> | ||||
| 					<td class="text-sm px-2 w-1/3 text-ellipsis">{jobStatus ? 'Active' : 'Idle'}</td> | ||||
| 					<td class="text-sm px-2 w-1/3 text-ellipsis">{activeJobCount}</td> | ||||
| @@ -39,7 +41,7 @@ | ||||
| 	<div class="w-[30%] flex place-items-center place-content-end"> | ||||
| 		<button | ||||
| 			on:click={() => dispatch('click')} | ||||
| 			class="border px-6 py-3 text-sm bg-gray-50 font-medium rounded-2xl hover:bg-immich-primary/10 transition-all hover:cursor-pointer disabled:cursor-not-allowed" | ||||
| 			class="px-6 py-3 text-sm bg-immich-primary font-medium rounded-2xl hover:bg-immich-primary/50 transition-all hover:cursor-pointer disabled:cursor-not-allowed shadow-sm text-immich-bg" | ||||
| 			disabled={jobStatus} | ||||
| 		> | ||||
| 			{#if jobStatus} | ||||
|   | ||||
| @@ -106,7 +106,7 @@ | ||||
| 	}; | ||||
| </script> | ||||
|  | ||||
| <div class="flex flex-col gap-6"> | ||||
| <div class="flex flex-col gap-10"> | ||||
| 	<JobTile | ||||
| 		title={'Generate thumbnails'} | ||||
| 		subtitle={'Regenerate missing thumbnail (JPEG, WEBP)'} | ||||
|   | ||||
| @@ -1,5 +1,10 @@ | ||||
| <script lang="ts"> | ||||
| 	import { ServerStatsResponseDto, UserResponseDto } from '@api'; | ||||
| 	import CameraIris from 'svelte-material-icons/CameraIris.svelte'; | ||||
| 	import PlayCircle from 'svelte-material-icons/PlayCircle.svelte'; | ||||
| 	import FileImageOutline from 'svelte-material-icons/FileImageOutline.svelte'; | ||||
| 	import Memory from 'svelte-material-icons/Memory.svelte'; | ||||
| 	import StatsCard from './stats-card.svelte'; | ||||
| 	export let stats: ServerStatsResponseDto; | ||||
| 	export let allUsers: Array<UserResponseDto>; | ||||
| 
 | ||||
| @@ -10,24 +15,27 @@ | ||||
| 		}); | ||||
| 		return name; | ||||
| 	}; | ||||
| 
 | ||||
| 	$: spaceUnit = stats.usage.slice(stats.usage.length - 2, stats.usage.length); | ||||
| 	$: spaceUsage = stats.usage.slice(0, stats.usage.length - 2); | ||||
| </script> | ||||
| 
 | ||||
| <div class="flex flex-col gap-6"> | ||||
| 	<div class="border p-6 rounded-2xl bg-white text-center"> | ||||
| 		<h1 class="font-medium text-immich-primary">Server Usage</h1> | ||||
| 		<div class="flex flex-row gap-6 mt-4 font-medium"> | ||||
| 			<p class="grow">Photos: {stats.photos}</p> | ||||
| 			<p class="grow">Videos: {stats.videos}</p> | ||||
| 			<p class="grow">Objects: {stats.objects}</p> | ||||
| 			<p class="grow">Size: {stats.usage}</p> | ||||
| <div class="flex flex-col gap-5"> | ||||
| 	<div> | ||||
| 		<p class="text-sm">TOTAL USAGE</p> | ||||
| 
 | ||||
| 		<div class="flex mt-5 justify-between"> | ||||
| 			<StatsCard logo={CameraIris} title={'PHOTOS'} value={stats.photos.toString()} /> | ||||
| 			<StatsCard logo={PlayCircle} title={'VIDEOS'} value={stats.videos.toString()} /> | ||||
| 			<StatsCard logo={FileImageOutline} title={'OBJECTS'} value={stats.objects.toString()} /> | ||||
| 			<StatsCard logo={Memory} title={'STORAGE'} value={spaceUsage} unit={spaceUnit} /> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<div class="border p-6 rounded-2xl bg-white"> | ||||
| 		<h1 class="font-medium text-immich-primary">Usage by User</h1> | ||||
| 		<table class="text-left w-full mt-4"> | ||||
| 			<!-- table header --> | ||||
| 			<thead class="border rounded-md mb-2 bg-gray-50 flex text-immich-primary w-full h-12"> | ||||
| 	<div> | ||||
| 		<p class="text-sm">USER USAGE DETAIL</p> | ||||
| 		<table class="text-left w-full mt-5"> | ||||
| 			<thead class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full h-12"> | ||||
| 				<tr class="flex w-full place-items-center"> | ||||
| 					<th class="text-center w-1/5 font-medium text-sm">User</th> | ||||
| 					<th class="text-center w-1/5 font-medium text-sm">Photos</th> | ||||
| @@ -37,8 +45,12 @@ | ||||
| 				</tr> | ||||
| 			</thead> | ||||
| 			<tbody class="overflow-y-auto rounded-md w-full max-h-[320px] block border"> | ||||
| 				{#each stats.usageByUser as user} | ||||
| 					<tr class="text-center flex place-items-center w-full h-[40px]"> | ||||
| 				{#each stats.usageByUser as user, i} | ||||
| 					<tr | ||||
| 						class={`text-center flex place-items-center w-full h-[50px] ${ | ||||
| 							i % 2 == 0 ? 'bg-immich-gray' : 'bg-immich-bg' | ||||
| 						}`} | ||||
| 					> | ||||
| 						<td class="text-sm px-2 w-1/5 text-ellipsis">{getFullName(user.userId)}</td> | ||||
| 						<td class="text-sm px-2 w-1/5 text-ellipsis">{user.photos}</td> | ||||
| 						<td class="text-sm px-2 w-1/5 text-ellipsis">{user.videos}</td> | ||||
| @@ -0,0 +1,33 @@ | ||||
| <script lang="ts"> | ||||
| 	export let logo: any; | ||||
| 	export let title: string; | ||||
| 	export let value: string; | ||||
| 	export let unit: string | undefined = undefined; | ||||
|  | ||||
| 	$: zeros = () => { | ||||
| 		let result = ''; | ||||
| 		const maxLength = 9; | ||||
| 		const valueLength = parseInt(value).toString().length; | ||||
| 		const zeroLength = maxLength - valueLength; | ||||
| 		for (let i = 0; i < zeroLength; i++) { | ||||
| 			result += '0'; | ||||
| 		} | ||||
| 		return result; | ||||
| 	}; | ||||
| </script> | ||||
|  | ||||
| <div class="w-[180px] h-[140px] bg-immich-gray rounded-3xl p-5 flex flex-col justify-between"> | ||||
| 	<div class="flex place-items-center gap-4"> | ||||
| 		<svelte:component this={logo} size="40" color={'#4250af'} /> | ||||
| 		<p class="text-immich-primary">{title}</p> | ||||
| 	</div> | ||||
|  | ||||
| 	<div class="relative text-center font-mono font-semibold text-2xl"> | ||||
| 		<span class="text-[#DCDADA]">{zeros()}</span><span class="text-immich-primary" | ||||
| 			>{parseInt(value)}</span | ||||
| 		> | ||||
| 		{#if unit} | ||||
| 			<span class="absolute -top-5 right-2 text-base font-light text-gray-400">{unit}</span> | ||||
| 		{/if} | ||||
| 	</div> | ||||
| </div> | ||||
| @@ -8,8 +8,8 @@ | ||||
| 	const dispatch = createEventDispatcher(); | ||||
| </script> | ||||
|  | ||||
| <table class="text-left w-full my-4"> | ||||
| 	<thead class="border rounded-md mb-2 bg-gray-50 flex text-immich-primary w-full h-12 "> | ||||
| <table class="text-left w-full my-5"> | ||||
| 	<thead class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full h-12 "> | ||||
| 		<tr class="flex w-full place-items-center"> | ||||
| 			<th class="text-center w-1/4 font-medium text-sm">Email</th> | ||||
| 			<th class="text-center w-1/4 font-medium text-sm">First name</th> | ||||
| @@ -20,7 +20,7 @@ | ||||
| 	<tbody class="overflow-y-auto rounded-md w-full max-h-[320px] block border"> | ||||
| 		{#each allUsers as user, i} | ||||
| 			<tr | ||||
| 				class={`text-center flex place-items-center w-full border-b h-[80px] ${ | ||||
| 				class={`text-center flex place-items-center w-full h-[80px] ${ | ||||
| 					i % 2 == 0 ? 'bg-gray-100' : 'bg-immich-bg' | ||||
| 				}`} | ||||
| 			> | ||||
|   | ||||
| @@ -15,7 +15,7 @@ | ||||
| 	import type { PageData } from './$types'; | ||||
| 	import { api, ServerStatsResponseDto, UserResponseDto } from '@api'; | ||||
| 	import JobsPanel from '$lib/components/admin-page/jobs/jobs-panel.svelte'; | ||||
| 	import ServerStats from '$lib/components/admin-page/server-stats.svelte'; | ||||
| 	import ServerStatsPanel from '$lib/components/admin-page/server-stats/server-stats-panel.svelte'; | ||||
|  | ||||
| 	let selectedAction: AdminSideBarSelection = AdminSideBarSelection.USER_MANAGEMENT; | ||||
|  | ||||
| @@ -33,7 +33,7 @@ | ||||
| 	}; | ||||
|  | ||||
| 	onMount(() => { | ||||
| 		selectedAction = AdminSideBarSelection.USER_MANAGEMENT; | ||||
| 		selectedAction = AdminSideBarSelection.JOBS; | ||||
| 		getServerStats(); | ||||
| 	}); | ||||
|  | ||||
| @@ -146,13 +146,13 @@ | ||||
| 		</div> | ||||
| 	</section> | ||||
| 	<section class="overflow-y-auto relative"> | ||||
| 		<div id="setting-title" class="pt-10 fixed w-full z-50 bg-immich-bg"> | ||||
| 		<div id="setting-title" class="pt-10 fixed w-full z-50"> | ||||
| 			<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"> | ||||
| 			<section class="w-[800px] pt-5"> | ||||
| 				{#if selectedAction === AdminSideBarSelection.USER_MANAGEMENT} | ||||
| 					<UserManagement | ||||
| 						allUsers={data.allUsers} | ||||
| @@ -164,7 +164,7 @@ | ||||
| 					<JobsPanel /> | ||||
| 				{/if} | ||||
| 				{#if selectedAction === AdminSideBarSelection.STATS && serverStat} | ||||
| 					<ServerStats stats={serverStat} allUsers={data.allUsers} /> | ||||
| 					<ServerStatsPanel stats={serverStat} allUsers={data.allUsers} /> | ||||
| 				{/if} | ||||
| 			</section> | ||||
| 		</section> | ||||
|   | ||||
| @@ -4,9 +4,9 @@ module.exports = { | ||||
| 		extend: { | ||||
| 			colors: { | ||||
| 				'immich-primary': '#4250af', | ||||
| 				'immich-bg': '#f6f8fe', | ||||
| 				'immich-fg': 'black' | ||||
|  | ||||
| 				'immich-bg': 'white', | ||||
| 				'immich-fg': 'black', | ||||
| 				'immich-gray': '#F6F6F4' | ||||
| 				// 'immich-bg': '#121212', | ||||
| 				// 'immich-fg': '#D0D0D0', | ||||
| 			}, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user