updated protected files visualization
This commit is contained in:
		
							parent
							
								
									5eb54c7a3d
								
							
						
					
					
						commit
						1b8776926e
					
				| 
						 | 
				
			
			@ -16,6 +16,8 @@
 | 
			
		|||
 | 
			
		||||
- Refreshed the OAuth2 Admin UI.
 | 
			
		||||
 | 
			
		||||
- Added auto "draft" to allow restoring previous record state in case of accidental reload or power outage.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## v0.14.5
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,6 @@
 | 
			
		|||
    import ApiClient from "@/utils/ApiClient";
 | 
			
		||||
    import CommonHelper from "@/utils/CommonHelper";
 | 
			
		||||
    import PreviewPopup from "@/components/base/PreviewPopup.svelte";
 | 
			
		||||
    import { privateFilesCollectionsCache } from "@/stores/collections";
 | 
			
		||||
 | 
			
		||||
    export let record = null;
 | 
			
		||||
    export let filename = "";
 | 
			
		||||
| 
						 | 
				
			
			@ -14,16 +13,7 @@
 | 
			
		|||
    let token = "";
 | 
			
		||||
    let isLoadingToken = false;
 | 
			
		||||
 | 
			
		||||
    $: withToken =
 | 
			
		||||
        typeof $privateFilesCollectionsCache[record?.collectionId] !== "undefined"
 | 
			
		||||
            ? $privateFilesCollectionsCache[record?.collectionId]
 | 
			
		||||
            : true;
 | 
			
		||||
 | 
			
		||||
    $: if (withToken) {
 | 
			
		||||
    loadFileToken();
 | 
			
		||||
    } else {
 | 
			
		||||
        token = "";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $: type = CommonHelper.getFileType(filename);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +29,7 @@
 | 
			
		|||
        isLoadingToken = true;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            token = await ApiClient.getAdminFileToken();
 | 
			
		||||
            token = await ApiClient.getAdminFileToken(record.collectionId);
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            console.warn("File token failure:", err);
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -92,6 +92,9 @@
 | 
			
		|||
        initialDraft = getDraft();
 | 
			
		||||
        if (!initialDraft || areRecordsEqual(record, initialDraft)) {
 | 
			
		||||
            initialDraft = null;
 | 
			
		||||
        } else {
 | 
			
		||||
            delete initialDraft.password;
 | 
			
		||||
            delete initialDraft.passwordConfirm;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        originalSerializedData = JSON.stringify(record);
 | 
			
		||||
| 
						 | 
				
			
			@ -482,7 +485,7 @@
 | 
			
		|||
            </Field>
 | 
			
		||||
 | 
			
		||||
            {#if collection?.isAuth}
 | 
			
		||||
                <AuthFields bind:record {collection} />
 | 
			
		||||
                <AuthFields bind:record {isNew} {collection} />
 | 
			
		||||
 | 
			
		||||
                {#if collection?.schema?.length}
 | 
			
		||||
                    <hr />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,7 @@
 | 
			
		|||
 | 
			
		||||
    export let collection = new Collection();
 | 
			
		||||
    export let record = new Record();
 | 
			
		||||
    export let isNew = record.$isNew;
 | 
			
		||||
 | 
			
		||||
    let originalUsername = record.username || null;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -28,15 +29,15 @@
 | 
			
		|||
 | 
			
		||||
<div class="grid m-b-base">
 | 
			
		||||
    <div class="col-lg-6">
 | 
			
		||||
        <Field class="form-field {!record.$isNew ? 'required' : ''}" name="username" let:uniqueId>
 | 
			
		||||
        <Field class="form-field {!isNew ? 'required' : ''}" name="username" let:uniqueId>
 | 
			
		||||
            <label for={uniqueId}>
 | 
			
		||||
                <i class={CommonHelper.getFieldTypeIcon("user")} />
 | 
			
		||||
                <span class="txt">Username</span>
 | 
			
		||||
            </label>
 | 
			
		||||
            <input
 | 
			
		||||
                type="text"
 | 
			
		||||
                requried={!record.$isNew}
 | 
			
		||||
                placeholder={record.$isNew ? "Leave empty to auto generate..." : originalUsername}
 | 
			
		||||
                requried={!isNew}
 | 
			
		||||
                placeholder={isNew ? "Leave empty to auto generate..." : originalUsername}
 | 
			
		||||
                id={uniqueId}
 | 
			
		||||
                bind:value={record.username}
 | 
			
		||||
            />
 | 
			
		||||
| 
						 | 
				
			
			@ -69,7 +70,7 @@
 | 
			
		|||
            <!-- svelte-ignore a11y-autofocus -->
 | 
			
		||||
            <input
 | 
			
		||||
                type="email"
 | 
			
		||||
                autofocus={record.$isNew}
 | 
			
		||||
                autofocus={isNew}
 | 
			
		||||
                autocomplete="off"
 | 
			
		||||
                id={uniqueId}
 | 
			
		||||
                required={collection.options?.requireEmail}
 | 
			
		||||
| 
						 | 
				
			
			@ -79,14 +80,14 @@
 | 
			
		|||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="col-lg-12">
 | 
			
		||||
        {#if !record.$isNew}
 | 
			
		||||
        {#if !isNew}
 | 
			
		||||
            <Field class="form-field form-field-toggle" name="verified" let:uniqueId>
 | 
			
		||||
                <input type="checkbox" id={uniqueId} bind:checked={changePasswordToggle} />
 | 
			
		||||
                <label for={uniqueId}>Change password</label>
 | 
			
		||||
            </Field>
 | 
			
		||||
        {/if}
 | 
			
		||||
 | 
			
		||||
        {#if record.$isNew || changePasswordToggle}
 | 
			
		||||
        {#if isNew || changePasswordToggle}
 | 
			
		||||
            <div class="block" transition:slide|local={{ duration: 150 }}>
 | 
			
		||||
                <div class="grid" class:p-t-xs={changePasswordToggle}>
 | 
			
		||||
                    <div class="col-sm-6">
 | 
			
		||||
| 
						 | 
				
			
			@ -131,7 +132,7 @@
 | 
			
		|||
                id={uniqueId}
 | 
			
		||||
                bind:checked={record.verified}
 | 
			
		||||
                on:change|preventDefault={(e) => {
 | 
			
		||||
                    if (record.$isNew) {
 | 
			
		||||
                    if (isNew) {
 | 
			
		||||
                        return; // no confirmation required
 | 
			
		||||
                    }
 | 
			
		||||
                    confirm(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@
 | 
			
		|||
    import Field from "@/components/base/Field.svelte";
 | 
			
		||||
    import UploadedFilePreview from "@/components/base/UploadedFilePreview.svelte";
 | 
			
		||||
    import RecordFileThumb from "@/components/records/RecordFileThumb.svelte";
 | 
			
		||||
    import { onMount } from "svelte";
 | 
			
		||||
 | 
			
		||||
    export let record;
 | 
			
		||||
    export let value = "";
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +17,7 @@
 | 
			
		|||
    let fileInput;
 | 
			
		||||
    let filesListElem;
 | 
			
		||||
    let isDragOver = false;
 | 
			
		||||
    let fileToken = "";
 | 
			
		||||
 | 
			
		||||
    // normalize uploadedFiles type
 | 
			
		||||
    $: if (!Array.isArray(uploadedFiles)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -93,6 +95,10 @@
 | 
			
		|||
 | 
			
		||||
        uploadedFiles = uploadedFiles;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    onMount(async () => {
 | 
			
		||||
        fileToken = await ApiClient.getAdminFileToken(record.collectionId);
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div
 | 
			
		||||
| 
						 | 
				
			
			@ -129,7 +135,7 @@
 | 
			
		|||
 | 
			
		||||
                    <div class="content">
 | 
			
		||||
                        <a
 | 
			
		||||
                            href={ApiClient.files.getUrl(record, filename)}
 | 
			
		||||
                            href={ApiClient.files.getUrl(record, filename, { token: fileToken })}
 | 
			
		||||
                            class="txt-ellipsis {isDeleted ? 'txt-strikethrough txt-hint' : 'link-primary'}"
 | 
			
		||||
                            title="Download"
 | 
			
		||||
                            target="_blank"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ import CommonHelper from "@/utils/CommonHelper";
 | 
			
		|||
export const collections                    = writable([]);
 | 
			
		||||
export const activeCollection               = writable({});
 | 
			
		||||
export const isCollectionsLoading           = writable(false);
 | 
			
		||||
export const privateFilesCollectionsCache = writable({});
 | 
			
		||||
export const protectedFilesCollectionsCache = writable({});
 | 
			
		||||
 | 
			
		||||
export function changeActiveCollectionById(collectionId) {
 | 
			
		||||
    collections.update((list) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -82,7 +82,7 @@ export async function loadCollections(activeId = null) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
function refreshProtectedFilesCollectionsCache() {
 | 
			
		||||
    privateFilesCollectionsCache.update((cache) => {
 | 
			
		||||
    protectedFilesCollectionsCache.update((cache) => {
 | 
			
		||||
        collections.update((current) => {
 | 
			
		||||
            for (let c of current) {
 | 
			
		||||
                cache[c.id] = !!c.schema?.find((f) => f.type == "file" && f.options?.protected);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,9 +2,12 @@ import PocketBase, { LocalAuthStore, Admin, isTokenExpired } from "pocketbase";
 | 
			
		|||
// ---
 | 
			
		||||
import CommonHelper                     from "@/utils/CommonHelper";
 | 
			
		||||
import { replace }                      from "svelte-spa-router";
 | 
			
		||||
import { get }                          from "svelte/store";
 | 
			
		||||
import { addErrorToast }                from "@/stores/toasts";
 | 
			
		||||
import { setErrors }                    from "@/stores/errors";
 | 
			
		||||
import { setAdmin }                     from "@/stores/admin";
 | 
			
		||||
import { protectedFilesCollectionsCache } from "@/stores/collections";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const adminFileTokenKey = "pb_admin_file_token";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +20,7 @@ PocketBase.prototype.logout = function(redirect = true) {
 | 
			
		|||
    this.authStore.clear();
 | 
			
		||||
 | 
			
		||||
    if (redirect) {
 | 
			
		||||
        replace('/login');
 | 
			
		||||
        replace("/login");
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +31,7 @@ PocketBase.prototype.logout = function(redirect = true) {
 | 
			
		|||
 * @param  {Boolean} notify     Whether to add a toast notification.
 | 
			
		||||
 * @param  {String}  defaultMsg Default toast notification message if the error doesn't have one.
 | 
			
		||||
 */
 | 
			
		||||
PocketBase.prototype.errorResponseHandler = function(err, notify = true, defaultMsg = '') {
 | 
			
		||||
PocketBase.prototype.errorResponseHandler = function(err, notify = true, defaultMsg = "") {
 | 
			
		||||
    if (!err || !(err instanceof Error) || err.isAbort) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -61,15 +64,28 @@ PocketBase.prototype.errorResponseHandler = function(err, notify = true, default
 | 
			
		|||
    // forbidden
 | 
			
		||||
    if (statusCode === 403) {
 | 
			
		||||
        this.cancelAllRequests();
 | 
			
		||||
        return replace('/');
 | 
			
		||||
        return replace("/");
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @return {Promise<String>}
 | 
			
		||||
 */
 | 
			
		||||
PocketBase.prototype.getAdminFileToken = async function() {
 | 
			
		||||
    let token = localStorage.getItem(adminFileTokenKey) || '';
 | 
			
		||||
PocketBase.prototype.getAdminFileToken = async function(collectionId = "") {
 | 
			
		||||
    let needToken = true;
 | 
			
		||||
 | 
			
		||||
    if (collectionId) {
 | 
			
		||||
        const protectedCollections = get(protectedFilesCollectionsCache);
 | 
			
		||||
        needToken = typeof protectedCollections[collectionId] !== "undefined"
 | 
			
		||||
            ? protectedCollections[collectionId]
 | 
			
		||||
            : true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!needToken) {
 | 
			
		||||
        return "";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let token = localStorage.getItem(adminFileTokenKey) || "";
 | 
			
		||||
 | 
			
		||||
    // request a new token only if the previous one is missing or will expire soon
 | 
			
		||||
    if (!token || isTokenExpired(token, 15)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue