mirror of
https://github.com/pocket-id/pocket-id.git
synced 2025-12-23 17:25:22 +03:00
fix: explicitly cache images to prevent unexpected behavior
This commit is contained in:
@@ -3,6 +3,7 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/common"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
@@ -247,6 +248,8 @@ func (acc *AppConfigController) getImage(c *gin.Context, name string, imageType
|
|||||||
mimeType := utils.GetImageMimeType(imageType)
|
mimeType := utils.GetImageMimeType(imageType)
|
||||||
|
|
||||||
c.Header("Content-Type", mimeType)
|
c.Header("Content-Type", mimeType)
|
||||||
|
|
||||||
|
utils.SetCacheControlHeader(c, 15*time.Minute, 24*time.Hour)
|
||||||
c.File(imagePath)
|
c.File(imagePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
@@ -545,6 +546,8 @@ func (oc *OidcController) getClientLogoHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
utils.SetCacheControlHeader(c, 15*time.Minute, 12*time.Hour)
|
||||||
|
|
||||||
c.Header("Content-Type", mimeType)
|
c.Header("Content-Type", mimeType)
|
||||||
c.File(imagePath)
|
c.File(imagePath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,10 +250,7 @@ func (uc *UserController) getUserProfilePictureHandler(c *gin.Context) {
|
|||||||
defer picture.Close()
|
defer picture.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
_, ok := c.GetQuery("skipCache")
|
utils.SetCacheControlHeader(c, 15*time.Minute, 1*time.Hour)
|
||||||
if !ok {
|
|
||||||
c.Header("Cache-Control", "public, max-age=900")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.DataFromReader(http.StatusOK, size, "image/png", picture, nil)
|
c.DataFromReader(http.StatusOK, size, "image/png", picture, nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BearerAuth returns the value of the bearer token in the Authorization header if present
|
// BearerAuth returns the value of the bearer token in the Authorization header if present
|
||||||
@@ -16,3 +19,14 @@ func BearerAuth(r *http.Request) (string, bool) {
|
|||||||
|
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCacheControlHeader sets the Cache-Control header for the response.
|
||||||
|
func SetCacheControlHeader(ctx *gin.Context, maxAge, staleWhileRevalidate time.Duration) {
|
||||||
|
_, ok := ctx.GetQuery("skipCache")
|
||||||
|
if !ok {
|
||||||
|
maxAgeSeconds := strconv.Itoa(int(maxAge.Seconds()))
|
||||||
|
staleWhileRevalidateSeconds := strconv.Itoa(int(staleWhileRevalidate.Seconds()))
|
||||||
|
ctx.Header("Cache-Control", "public, max-age="+maxAgeSeconds+", stale-while-revalidate="+staleWhileRevalidateSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import * as Avatar from '$lib/components/ui/avatar';
|
import * as Avatar from '$lib/components/ui/avatar';
|
||||||
import Button from '$lib/components/ui/button/button.svelte';
|
import Button from '$lib/components/ui/button/button.svelte';
|
||||||
import { m } from '$lib/paraglide/messages';
|
import { m } from '$lib/paraglide/messages';
|
||||||
import { getProfilePictureUrl } from '$lib/utils/profile-picture-util';
|
import { cachedProfilePicture } from '$lib/utils/cached-image-util';
|
||||||
import { LucideLoader, LucideRefreshCw, LucideUpload } from '@lucide/svelte';
|
import { LucideLoader, LucideRefreshCw, LucideUpload } from '@lucide/svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { openConfirmDialog } from '../confirm-dialog';
|
import { openConfirmDialog } from '../confirm-dialog';
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
// The "skipCache" query will only be added to the profile picture url on client-side
|
// The "skipCache" query will only be added to the profile picture url on client-side
|
||||||
// because of that we need to set the imageDataURL after the component is mounted
|
// because of that we need to set the imageDataURL after the component is mounted
|
||||||
imageDataURL = getProfilePictureUrl(userId);
|
imageDataURL = cachedProfilePicture.getUrl(userId);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function onImageChange(e: Event) {
|
async function onImageChange(e: Event) {
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
|
|
||||||
await updateCallback(file).catch(() => {
|
await updateCallback(file).catch(() => {
|
||||||
imageDataURL = getProfilePictureUrl(userId);
|
imageDataURL = cachedProfilePicture.getUrl(userId);
|
||||||
});
|
});
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import { m } from '$lib/paraglide/messages';
|
import { m } from '$lib/paraglide/messages';
|
||||||
import WebAuthnService from '$lib/services/webauthn-service';
|
import WebAuthnService from '$lib/services/webauthn-service';
|
||||||
import userStore from '$lib/stores/user-store';
|
import userStore from '$lib/stores/user-store';
|
||||||
import { getProfilePictureUrl } from '$lib/utils/profile-picture-util';
|
import { cachedProfilePicture } from '$lib/utils/cached-image-util';
|
||||||
import { LucideLogOut, LucideUser } from '@lucide/svelte';
|
import { LucideLogOut, LucideUser } from '@lucide/svelte';
|
||||||
|
|
||||||
const webauthnService = new WebAuthnService();
|
const webauthnService = new WebAuthnService();
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
<DropdownMenu.Trigger
|
<DropdownMenu.Trigger
|
||||||
><Avatar.Root class="size-9">
|
><Avatar.Root class="size-9">
|
||||||
<Avatar.Image src={getProfilePictureUrl($userStore?.id)} />
|
<Avatar.Image src={cachedProfilePicture.getUrl($userStore!.id)} />
|
||||||
</Avatar.Root></DropdownMenu.Trigger
|
</Avatar.Root></DropdownMenu.Trigger
|
||||||
>
|
>
|
||||||
<DropdownMenu.Content class="min-w-40" align="end">
|
<DropdownMenu.Content class="min-w-40" align="end">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { m } from '$lib/paraglide/messages';
|
import { m } from '$lib/paraglide/messages';
|
||||||
|
import { cachedBackgroundImage } from '$lib/utils/cached-image-util';
|
||||||
import { cn } from '$lib/utils/style';
|
import { cn } from '$lib/utils/style';
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import { MediaQuery } from 'svelte/reactivity';
|
import { MediaQuery } from 'svelte/reactivity';
|
||||||
@@ -54,7 +55,7 @@
|
|||||||
<!-- Background image with slide animation -->
|
<!-- Background image with slide animation -->
|
||||||
<div class="{cn(animate && 'animate-slide-bg-container')} absolute top-0 right-0 bottom-0 z-0">
|
<div class="{cn(animate && 'animate-slide-bg-container')} absolute top-0 right-0 bottom-0 z-0">
|
||||||
<img
|
<img
|
||||||
src="/api/application-configuration/background-image"
|
src={cachedBackgroundImage.getUrl()}
|
||||||
class="h-screen rounded-l-[60px] object-cover {animate
|
class="h-screen rounded-l-[60px] object-cover {animate
|
||||||
? 'w-full'
|
? 'w-full'
|
||||||
: 'w-[calc(100vw-650px)]'}"
|
: 'w-[calc(100vw-650px)]'}"
|
||||||
@@ -64,7 +65,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class="flex h-screen items-center justify-center bg-[url('/api/application-configuration/background-image')] bg-cover bg-center text-center"
|
class="flex h-screen items-center justify-center bg-[url('{cachedBackgroundImage.getUrl()}')] bg-cover bg-center text-center"
|
||||||
>
|
>
|
||||||
<Card.Root class="mx-3 w-full max-w-md" style={animate ? 'animation-delay: 200ms;' : ''}>
|
<Card.Root class="mx-3 w-full max-w-md" style={animate ? 'animation-delay: 200ms;' : ''}>
|
||||||
<Card.CardContent
|
<Card.CardContent
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { m } from '$lib/paraglide/messages';
|
import { m } from '$lib/paraglide/messages';
|
||||||
|
import { cachedApplicationLogo } from '$lib/utils/cached-image-util';
|
||||||
import { mode } from 'mode-watcher';
|
import { mode } from 'mode-watcher';
|
||||||
import type { HTMLAttributes } from 'svelte/elements';
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
let { ...props }: HTMLAttributes<HTMLImageElement> = $props();
|
let { ...props }: HTMLAttributes<HTMLImageElement> = $props();
|
||||||
|
|
||||||
const isDarkMode = $derived(mode.current === 'dark');
|
const isLightMode = $derived(mode.current === 'light');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<img {...props} src="/api/application-configuration/logo?light={!isDarkMode}" alt={m.logo()} />
|
<img {...props} src={cachedApplicationLogo.getUrl(isLightMode)} alt={m.logo()} />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { AllAppConfig, AppConfigRawResponse } from '$lib/types/application-configuration';
|
import type { AllAppConfig, AppConfigRawResponse } from '$lib/types/application-configuration';
|
||||||
|
import { cachedApplicationLogo, cachedBackgroundImage } from '$lib/utils/cached-image-util';
|
||||||
import APIService from './api-service';
|
import APIService from './api-service';
|
||||||
|
|
||||||
export default class AppConfigService extends APIService {
|
export default class AppConfigService extends APIService {
|
||||||
@@ -36,6 +37,7 @@ export default class AppConfigService extends APIService {
|
|||||||
await this.api.put(`/application-configuration/logo`, formData, {
|
await this.api.put(`/application-configuration/logo`, formData, {
|
||||||
params: { light }
|
params: { light }
|
||||||
});
|
});
|
||||||
|
cachedApplicationLogo.bustCache(light);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateBackgroundImage(backgroundImage: File) {
|
async updateBackgroundImage(backgroundImage: File) {
|
||||||
@@ -43,6 +45,7 @@ export default class AppConfigService extends APIService {
|
|||||||
formData.append('file', backgroundImage!);
|
formData.append('file', backgroundImage!);
|
||||||
|
|
||||||
await this.api.put(`/application-configuration/background-image`, formData);
|
await this.api.put(`/application-configuration/background-image`, formData);
|
||||||
|
cachedBackgroundImage.bustCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendTestEmail() {
|
async sendTestEmail() {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type {
|
|||||||
OidcDeviceCodeInfo
|
OidcDeviceCodeInfo
|
||||||
} from '$lib/types/oidc.type';
|
} from '$lib/types/oidc.type';
|
||||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||||
|
import { cachedOidcClientLogo } from '$lib/utils/cached-image-util';
|
||||||
import APIService from './api-service';
|
import APIService from './api-service';
|
||||||
|
|
||||||
class OidcService extends APIService {
|
class OidcService extends APIService {
|
||||||
@@ -80,10 +81,12 @@ class OidcService extends APIService {
|
|||||||
formData.append('file', image!);
|
formData.append('file', image!);
|
||||||
|
|
||||||
await this.api.post(`/oidc/clients/${client.id}/logo`, formData);
|
await this.api.post(`/oidc/clients/${client.id}/logo`, formData);
|
||||||
|
cachedOidcClientLogo.bustCache(client.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeClientLogo(id: string) {
|
async removeClientLogo(id: string) {
|
||||||
await this.api.delete(`/oidc/clients/${id}/logo`);
|
await this.api.delete(`/oidc/clients/${id}/logo`);
|
||||||
|
cachedOidcClientLogo.bustCache(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createClientSecret(id: string) {
|
async createClientSecret(id: string) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import userStore from '$lib/stores/user-store';
|
|||||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||||
import type { UserGroup } from '$lib/types/user-group.type';
|
import type { UserGroup } from '$lib/types/user-group.type';
|
||||||
import type { User, UserCreate } from '$lib/types/user.type';
|
import type { User, UserCreate } from '$lib/types/user.type';
|
||||||
import { bustProfilePictureCache } from '$lib/utils/profile-picture-util';
|
import { cachedProfilePicture } from '$lib/utils/cached-image-util';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import APIService from './api-service';
|
import APIService from './api-service';
|
||||||
|
|
||||||
@@ -52,26 +52,26 @@ export default class UserService extends APIService {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', image!);
|
formData.append('file', image!);
|
||||||
|
|
||||||
bustProfilePictureCache(userId);
|
|
||||||
await this.api.put(`/users/${userId}/profile-picture`, formData);
|
await this.api.put(`/users/${userId}/profile-picture`, formData);
|
||||||
|
cachedProfilePicture.bustCache(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateCurrentUsersProfilePicture(image: File) {
|
async updateCurrentUsersProfilePicture(image: File) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', image!);
|
formData.append('file', image!);
|
||||||
|
|
||||||
bustProfilePictureCache(get(userStore)!.id);
|
|
||||||
await this.api.put('/users/me/profile-picture', formData);
|
await this.api.put('/users/me/profile-picture', formData);
|
||||||
|
cachedProfilePicture.bustCache(get(userStore)!.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetCurrentUserProfilePicture() {
|
async resetCurrentUserProfilePicture() {
|
||||||
bustProfilePictureCache(get(userStore)!.id);
|
|
||||||
await this.api.delete(`/users/me/profile-picture`);
|
await this.api.delete(`/users/me/profile-picture`);
|
||||||
|
cachedProfilePicture.bustCache(get(userStore)!.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetProfilePicture(userId: string) {
|
async resetProfilePicture(userId: string) {
|
||||||
bustProfilePictureCache(userId);
|
|
||||||
await this.api.delete(`/users/${userId}/profile-picture`);
|
await this.api.delete(`/users/${userId}/profile-picture`);
|
||||||
|
cachedProfilePicture.bustCache(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createOneTimeAccessToken(expiresAt: Date, userId: string) {
|
async createOneTimeAccessToken(expiresAt: Date, userId: string) {
|
||||||
|
|||||||
89
frontend/src/lib/utils/cached-image-util.ts
Normal file
89
frontend/src/lib/utils/cached-image-util.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
type SkipCacheUntil = {
|
||||||
|
[key: string]: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CachableImage = {
|
||||||
|
getUrl: (...props: any[]) => string;
|
||||||
|
bustCache: (...props: any[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cachedApplicationLogo: CachableImage = {
|
||||||
|
getUrl: (light = true) => {
|
||||||
|
let url = '/api/application-configuration/logo';
|
||||||
|
if (!light) {
|
||||||
|
url += '?light=false';
|
||||||
|
}
|
||||||
|
return getCachedImageUrl(url);
|
||||||
|
},
|
||||||
|
bustCache: (light = true) => {
|
||||||
|
let url = '/api/application-configuration/logo';
|
||||||
|
if (!light) {
|
||||||
|
url += '?light=false';
|
||||||
|
}
|
||||||
|
bustImageCache(url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cachedBackgroundImage: CachableImage = {
|
||||||
|
getUrl: () => getCachedImageUrl('/api/application-configuration/background-image'),
|
||||||
|
bustCache: () => bustImageCache('/api/application-configuration/background-image')
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cachedProfilePicture: CachableImage = {
|
||||||
|
getUrl: (userId: string) => {
|
||||||
|
const url = `/api/users/${userId}/profile-picture.png`;
|
||||||
|
return getCachedImageUrl(url);
|
||||||
|
},
|
||||||
|
bustCache: (userId: string) => {
|
||||||
|
const url = `/api/users/${userId}/profile-picture.png`;
|
||||||
|
bustImageCache(url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const cachedOidcClientLogo: CachableImage = {
|
||||||
|
getUrl: (clientId: string) => {
|
||||||
|
const url = `/api/oidc/clients/${clientId}/logo`;
|
||||||
|
return getCachedImageUrl(url);
|
||||||
|
},
|
||||||
|
bustCache: (clientId: string) => {
|
||||||
|
const url = `/api/oidc/clients/${clientId}/logo`;
|
||||||
|
bustImageCache(url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getCachedImageUrl(url: string) {
|
||||||
|
const skipCacheUntil = getSkipCacheUntil(url);
|
||||||
|
const skipCache = skipCacheUntil > Date.now();
|
||||||
|
if (skipCache) {
|
||||||
|
const skipCacheParam = new URLSearchParams();
|
||||||
|
skipCacheParam.append('skip-cache', skipCacheUntil.toString());
|
||||||
|
url += '?' + skipCacheParam.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function bustImageCache(url: string) {
|
||||||
|
const skipCacheUntil: SkipCacheUntil = JSON.parse(
|
||||||
|
localStorage.getItem('skip-cache-until') ?? '{}'
|
||||||
|
);
|
||||||
|
skipCacheUntil[hashKey(url)] = Date.now() + 1000 * 60 * 15; // 15 minutes
|
||||||
|
localStorage.setItem('skip-cache-until', JSON.stringify(skipCacheUntil));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSkipCacheUntil(url: string) {
|
||||||
|
const skipCacheUntil: SkipCacheUntil = JSON.parse(
|
||||||
|
localStorage.getItem('skip-cache-until') ?? '{}'
|
||||||
|
);
|
||||||
|
return skipCacheUntil[hashKey(url)] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hashKey(key: string): string {
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < key.length; i++) {
|
||||||
|
const char = key.charCodeAt(i);
|
||||||
|
hash = (hash << 5) - hash + char;
|
||||||
|
hash = hash & hash;
|
||||||
|
}
|
||||||
|
return Math.abs(hash).toString(36);
|
||||||
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
type SkipCacheUntil = {
|
|
||||||
[key: string]: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function getProfilePictureUrl(userId?: string) {
|
|
||||||
if (!userId) return '';
|
|
||||||
|
|
||||||
let url = `/api/users/${userId}/profile-picture.png`;
|
|
||||||
|
|
||||||
const skipCacheUntil = getSkipCacheUntil(userId);
|
|
||||||
const skipCache = skipCacheUntil > Date.now();
|
|
||||||
if (skipCache) {
|
|
||||||
const skipCacheParam = new URLSearchParams();
|
|
||||||
skipCacheParam.append('skip-cache', skipCacheUntil.toString());
|
|
||||||
url += '?' + skipCacheParam.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return url.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSkipCacheUntil(userId: string) {
|
|
||||||
const skipCacheUntil: SkipCacheUntil = JSON.parse(
|
|
||||||
localStorage.getItem('skip-cache-until') ?? '{}'
|
|
||||||
);
|
|
||||||
return skipCacheUntil[userId] ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function bustProfilePictureCache(userId: string) {
|
|
||||||
const skipCacheUntil: SkipCacheUntil = JSON.parse(
|
|
||||||
localStorage.getItem('skip-cache-until') ?? '{}'
|
|
||||||
);
|
|
||||||
skipCacheUntil[userId] = Date.now() + 1000 * 60 * 15; // 15 minutes
|
|
||||||
localStorage.setItem('skip-cache-until', JSON.stringify(skipCacheUntil));
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
import CrossAnimated from '$lib/icons/cross-animated.svelte';
|
import CrossAnimated from '$lib/icons/cross-animated.svelte';
|
||||||
import { m } from '$lib/paraglide/messages';
|
import { m } from '$lib/paraglide/messages';
|
||||||
import type { OidcClientMetaData } from '$lib/types/oidc.type';
|
import type { OidcClientMetaData } from '$lib/types/oidc.type';
|
||||||
|
import { cachedOidcClientLogo } from '$lib/utils/cached-image-util';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
success,
|
success,
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
{:else if client.hasLogo}
|
{:else if client.hasLogo}
|
||||||
<img
|
<img
|
||||||
class="size-10"
|
class="size-10"
|
||||||
src="/api/oidc/clients/{client.id}/logo"
|
src={cachedOidcClientLogo.getUrl(client.id)}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
alt={m.client_logo()}
|
alt={m.client_logo()}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Button from '$lib/components/ui/button/button.svelte';
|
import Button from '$lib/components/ui/button/button.svelte';
|
||||||
import { m } from '$lib/paraglide/messages';
|
import { m } from '$lib/paraglide/messages';
|
||||||
|
import { cachedApplicationLogo, cachedBackgroundImage } from '$lib/utils/cached-image-util';
|
||||||
import ApplicationImage from './application-image.svelte';
|
import ApplicationImage from './application-image.svelte';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -34,7 +35,7 @@
|
|||||||
imageClass="size-32"
|
imageClass="size-32"
|
||||||
label={m.light_mode_logo()}
|
label={m.light_mode_logo()}
|
||||||
bind:image={logoLight}
|
bind:image={logoLight}
|
||||||
imageURL="/api/application-configuration/logo?light=true"
|
imageURL={cachedApplicationLogo.getUrl(true)}
|
||||||
forceColorScheme="light"
|
forceColorScheme="light"
|
||||||
/>
|
/>
|
||||||
<ApplicationImage
|
<ApplicationImage
|
||||||
@@ -42,7 +43,7 @@
|
|||||||
imageClass="size-32"
|
imageClass="size-32"
|
||||||
label={m.dark_mode_logo()}
|
label={m.dark_mode_logo()}
|
||||||
bind:image={logoDark}
|
bind:image={logoDark}
|
||||||
imageURL="/api/application-configuration/logo?light=false"
|
imageURL={cachedApplicationLogo.getUrl(false)}
|
||||||
forceColorScheme="dark"
|
forceColorScheme="dark"
|
||||||
/>
|
/>
|
||||||
<ApplicationImage
|
<ApplicationImage
|
||||||
@@ -50,7 +51,7 @@
|
|||||||
imageClass="h-[350px] max-w-[500px]"
|
imageClass="h-[350px] max-w-[500px]"
|
||||||
label={m.background_image()}
|
label={m.background_image()}
|
||||||
bind:image={backgroundImage}
|
bind:image={backgroundImage}
|
||||||
imageURL="/api/application-configuration/background-image"
|
imageURL={cachedBackgroundImage.getUrl()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import Label from '$lib/components/ui/label/label.svelte';
|
import Label from '$lib/components/ui/label/label.svelte';
|
||||||
import { m } from '$lib/paraglide/messages';
|
import { m } from '$lib/paraglide/messages';
|
||||||
import type { OidcClient, OidcClientCreateWithLogo } from '$lib/types/oidc.type';
|
import type { OidcClient, OidcClientCreateWithLogo } from '$lib/types/oidc.type';
|
||||||
|
import { cachedOidcClientLogo } from '$lib/utils/cached-image-util';
|
||||||
import { preventDefault } from '$lib/utils/event-util';
|
import { preventDefault } from '$lib/utils/event-util';
|
||||||
import { createForm } from '$lib/utils/form-util';
|
import { createForm } from '$lib/utils/form-util';
|
||||||
import { cn } from '$lib/utils/style';
|
import { cn } from '$lib/utils/style';
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
let showAdvancedOptions = $state(false);
|
let showAdvancedOptions = $state(false);
|
||||||
let logo = $state<File | null | undefined>();
|
let logo = $state<File | null | undefined>();
|
||||||
let logoDataURL: string | null = $state(
|
let logoDataURL: string | null = $state(
|
||||||
existingClient?.hasLogo ? `/api/oidc/clients/${existingClient!.id}/logo` : null
|
existingClient?.hasLogo ? cachedOidcClientLogo.getUrl(existingClient!.id) : null
|
||||||
);
|
);
|
||||||
|
|
||||||
const client = {
|
const client = {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import OIDCService from '$lib/services/oidc-service';
|
import OIDCService from '$lib/services/oidc-service';
|
||||||
import type { OidcClient, OidcClientWithAllowedUserGroupsCount } from '$lib/types/oidc.type';
|
import type { OidcClient, OidcClientWithAllowedUserGroupsCount } from '$lib/types/oidc.type';
|
||||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||||
|
import { cachedOidcClientLogo } from '$lib/utils/cached-image-util';
|
||||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||||
import { LucidePencil, LucideTrash } from '@lucide/svelte';
|
import { LucidePencil, LucideTrash } from '@lucide/svelte';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
@@ -59,7 +60,7 @@
|
|||||||
{#if item.hasLogo}
|
{#if item.hasLogo}
|
||||||
<ImageBox
|
<ImageBox
|
||||||
class="min-h-8 min-w-8"
|
class="min-h-8 min-w-8"
|
||||||
src={`/api/oidc/clients/${item.id}/logo`}
|
src={cachedOidcClientLogo.getUrl(item.id)}
|
||||||
alt={m.name_logo({ name: item.name })}
|
alt={m.name_logo({ name: item.name })}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user