mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 02:27:08 -04:00 
			
		
		
		
	chore(web) Add automatic server stats refetching (#1271)
This commit is contained in:
		
							parent
							
								
									af2eac52a8
								
							
						
					
					
						commit
						5999af6c78
					
				| @ -19,7 +19,6 @@ | |||||||
| 			allJobsStatus = data; | 			allJobsStatus = data; | ||||||
| 		}, 1000); | 		}, 1000); | ||||||
| 	}); | 	}); | ||||||
| 	1; |  | ||||||
| 
 | 
 | ||||||
| 	onDestroy(() => { | 	onDestroy(() => { | ||||||
| 		clearInterval(setIntervalHandler); | 		clearInterval(setIntervalHandler); | ||||||
|  | |||||||
| @ -1,13 +1,32 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import { ServerStatsResponseDto, UserResponseDto } from '@api'; | 	import { api, ServerStatsResponseDto, UserResponseDto } from '@api'; | ||||||
| 	import CameraIris from 'svelte-material-icons/CameraIris.svelte'; | 	import CameraIris from 'svelte-material-icons/CameraIris.svelte'; | ||||||
| 	import PlayCircle from 'svelte-material-icons/PlayCircle.svelte'; | 	import PlayCircle from 'svelte-material-icons/PlayCircle.svelte'; | ||||||
| 	import Memory from 'svelte-material-icons/Memory.svelte'; | 	import Memory from 'svelte-material-icons/Memory.svelte'; | ||||||
| 	import StatsCard from './stats-card.svelte'; | 	import StatsCard from './stats-card.svelte'; | ||||||
| 	import { getBytesWithUnit, asByteUnitString } from '../../../utils/byte-units'; | 	import { getBytesWithUnit, asByteUnitString } from '../../../utils/byte-units'; | ||||||
| 	export let stats: ServerStatsResponseDto; | 	import { onMount, onDestroy } from 'svelte'; | ||||||
|  | 	import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; | ||||||
|  | 
 | ||||||
| 	export let allUsers: Array<UserResponseDto>; | 	export let allUsers: Array<UserResponseDto>; | ||||||
| 
 | 
 | ||||||
|  | 	let stats: ServerStatsResponseDto; | ||||||
|  | 	let setIntervalHandler: NodeJS.Timer; | ||||||
|  | 
 | ||||||
|  | 	onMount(async () => { | ||||||
|  | 		const { data } = await api.serverInfoApi.getStats(); | ||||||
|  | 		stats = data; | ||||||
|  | 
 | ||||||
|  | 		setIntervalHandler = setInterval(async () => { | ||||||
|  | 			const { data } = await api.serverInfoApi.getStats(); | ||||||
|  | 			stats = data; | ||||||
|  | 		}, 5000); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	onDestroy(() => { | ||||||
|  | 		clearInterval(setIntervalHandler); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
| 	const getFullName = (userId: string) => { | 	const getFullName = (userId: string) => { | ||||||
| 		let name = 'Admin'; // since we do not have admin user in allUsers | 		let name = 'Admin'; // since we do not have admin user in allUsers | ||||||
| 		allUsers.forEach((user) => { | 		allUsers.forEach((user) => { | ||||||
| @ -16,7 +35,8 @@ | |||||||
| 		return name; | 		return name; | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	$: [spaceUsage, spaceUnit] = getBytesWithUnit(stats.usageRaw); | 	// Stats are unavailable if data is not loaded yet | ||||||
|  | 	$: [spaceUsage, spaceUnit] = getBytesWithUnit(stats ? stats.usageRaw : 0); | ||||||
| 
 | 
 | ||||||
| 	const locale = navigator.language; | 	const locale = navigator.language; | ||||||
| </script> | </script> | ||||||
| @ -26,9 +46,14 @@ | |||||||
| 		<p class="text-sm dark:text-immich-dark-fg">TOTAL USAGE</p> | 		<p class="text-sm dark:text-immich-dark-fg">TOTAL USAGE</p> | ||||||
| 
 | 
 | ||||||
| 		<div class="flex mt-5 justify-between"> | 		<div class="flex mt-5 justify-between"> | ||||||
| 			<StatsCard logo={CameraIris} title={'PHOTOS'} value={stats.photos.toString()} /> | 			<StatsCard logo={CameraIris} title={'PHOTOS'} value={stats && stats.photos.toString()} /> | ||||||
| 			<StatsCard logo={PlayCircle} title={'VIDEOS'} value={stats.videos.toString()} /> | 			<StatsCard logo={PlayCircle} title={'VIDEOS'} value={stats && stats.videos.toString()} /> | ||||||
| 			<StatsCard logo={Memory} title={'STORAGE'} value={spaceUsage.toString()} unit={spaceUnit} /> | 			<StatsCard | ||||||
|  | 				logo={Memory} | ||||||
|  | 				title={'STORAGE'} | ||||||
|  | 				value={stats && spaceUsage.toString()} | ||||||
|  | 				unit={spaceUnit} | ||||||
|  | 			/> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| 
 | 
 | ||||||
| @ -48,6 +73,7 @@ | |||||||
| 			<tbody | 			<tbody | ||||||
| 				class="overflow-y-auto rounded-md w-full max-h-[320px] block border dark:border-immich-dark-gray dark:text-immich-dark-fg" | 				class="overflow-y-auto rounded-md w-full max-h-[320px] block border dark:border-immich-dark-gray dark:text-immich-dark-fg" | ||||||
| 			> | 			> | ||||||
|  | 				{#if stats} | ||||||
| 					{#each stats.usageByUser as user, i} | 					{#each stats.usageByUser as user, i} | ||||||
| 						<tr | 						<tr | ||||||
| 							class={`text-center flex place-items-center w-full h-[50px] ${ | 							class={`text-center flex place-items-center w-full h-[50px] ${ | ||||||
| @ -62,6 +88,15 @@ | |||||||
| 							<td class="text-sm px-2 w-1/4 text-ellipsis">{asByteUnitString(user.usageRaw)}</td> | 							<td class="text-sm px-2 w-1/4 text-ellipsis">{asByteUnitString(user.usageRaw)}</td> | ||||||
| 						</tr> | 						</tr> | ||||||
| 					{/each} | 					{/each} | ||||||
|  | 				{:else} | ||||||
|  | 					<tr | ||||||
|  | 						class="text-center flex place-items-center w-full h-[50px] bg-immich-gray dark:bg-immich-dark-gray/75" | ||||||
|  | 					> | ||||||
|  | 						<td class="w-full flex justify-center"> | ||||||
|  | 							<LoadingSpinner /> | ||||||
|  | 						</td> | ||||||
|  | 					</tr> | ||||||
|  | 				{/if} | ||||||
| 			</tbody> | 			</tbody> | ||||||
| 		</table> | 		</table> | ||||||
| 	</div> | 	</div> | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|  | 	import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; | ||||||
|  | 
 | ||||||
| 	// eslint-disable-next-line @typescript-eslint/no-explicit-any | 	// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||||
| 	export let logo: any; | 	export let logo: any; | ||||||
| 	export let title: string; | 	export let title: string; | ||||||
| @ -6,8 +8,12 @@ | |||||||
| 	export let unit: string | undefined = undefined; | 	export let unit: string | undefined = undefined; | ||||||
| 
 | 
 | ||||||
| 	$: zeros = () => { | 	$: zeros = () => { | ||||||
| 		let result = ''; | 		if (!value) { | ||||||
|  | 			return ''; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		const maxLength = 13; | 		const maxLength = 13; | ||||||
|  | 		let result = ''; | ||||||
| 		const valueLength = parseInt(value).toString().length; | 		const valueLength = parseInt(value).toString().length; | ||||||
| 		const zeroLength = maxLength - valueLength; | 		const zeroLength = maxLength - valueLength; | ||||||
| 		for (let i = 0; i < zeroLength; i++) { | 		for (let i = 0; i < zeroLength; i++) { | ||||||
| @ -26,9 +32,15 @@ | |||||||
| 	</div> | 	</div> | ||||||
| 
 | 
 | ||||||
| 	<div class="relative text-center font-mono font-semibold text-2xl"> | 	<div class="relative text-center font-mono font-semibold text-2xl"> | ||||||
|  | 		{#if value !== undefined} | ||||||
| 			<span class="text-[#DCDADA] dark:text-[#525252]">{zeros()}</span><span | 			<span class="text-[#DCDADA] dark:text-[#525252]">{zeros()}</span><span | ||||||
| 				class="text-immich-primary dark:text-immich-dark-primary">{parseInt(value)}</span | 				class="text-immich-primary dark:text-immich-dark-primary">{parseInt(value)}</span | ||||||
| 			> | 			> | ||||||
|  | 		{:else} | ||||||
|  | 			<div class="flex justify-end pr-2"> | ||||||
|  | 				<LoadingSpinner /> | ||||||
|  | 			</div> | ||||||
|  | 		{/if} | ||||||
| 		{#if unit} | 		{#if unit} | ||||||
| 			<span class="absolute -top-5 right-2 text-base font-light text-gray-400">{unit}</span> | 			<span class="absolute -top-5 right-2 text-base font-light text-gray-400">{unit}</span> | ||||||
| 		{/if} | 		{/if} | ||||||
|  | |||||||
| @ -13,5 +13,5 @@ export const load: PageServerLoad = async ({ parent }) => { | |||||||
| 
 | 
 | ||||||
| 	const { data: allUsers } = await serverApi.userApi.getAllUsers(false); | 	const { data: allUsers } = await serverApi.userApi.getAllUsers(false); | ||||||
| 
 | 
 | ||||||
| 	return { user, allUsers }; | 	return { allUsers }; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,29 +1,12 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| 	import ServerStatsPanel from '$lib/components/admin-page/server-stats/server-stats-panel.svelte'; | 	import ServerStatsPanel from '$lib/components/admin-page/server-stats/server-stats-panel.svelte'; | ||||||
| 	import { api, ServerStatsResponseDto } from '@api'; |  | ||||||
| 	import { onMount } from 'svelte'; |  | ||||||
| 	import { page } from '$app/stores'; | 	import { page } from '$app/stores'; | ||||||
| 
 |  | ||||||
| 	let serverStat: ServerStatsResponseDto; |  | ||||||
| 
 |  | ||||||
| 	onMount(() => { |  | ||||||
| 		getServerStats(); |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	const getServerStats = async () => { |  | ||||||
| 		try { |  | ||||||
| 			const res = await api.serverInfoApi.getStats(); |  | ||||||
| 			serverStat = res.data; |  | ||||||
| 		} catch (e) { |  | ||||||
| 			console.log(e); |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <svelte:head> | <svelte:head> | ||||||
| 	<title>Server Status - Immich</title> | 	<title>Server Status - Immich</title> | ||||||
| </svelte:head> | </svelte:head> | ||||||
| 
 | 
 | ||||||
| {#if $page.data.allUsers && serverStat} | {#if $page.data.allUsers} | ||||||
| 	<ServerStatsPanel stats={serverStat} allUsers={$page.data.allUsers} /> | 	<ServerStatsPanel allUsers={$page.data.allUsers} /> | ||||||
| {/if} | {/if} | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user