updated protected files visualization

This commit is contained in:
Gani Georgiev 2023-04-17 13:28:41 +03:00
parent 5eb54c7a3d
commit 1b8776926e
7 changed files with 54 additions and 36 deletions

View File

@ -16,6 +16,8 @@
- Refreshed the OAuth2 Admin UI. - 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 ## v0.14.5

View File

@ -2,7 +2,6 @@
import ApiClient from "@/utils/ApiClient"; import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper"; import CommonHelper from "@/utils/CommonHelper";
import PreviewPopup from "@/components/base/PreviewPopup.svelte"; import PreviewPopup from "@/components/base/PreviewPopup.svelte";
import { privateFilesCollectionsCache } from "@/stores/collections";
export let record = null; export let record = null;
export let filename = ""; export let filename = "";
@ -14,16 +13,7 @@
let token = ""; let token = "";
let isLoadingToken = false; let isLoadingToken = false;
$: withToken = loadFileToken();
typeof $privateFilesCollectionsCache[record?.collectionId] !== "undefined"
? $privateFilesCollectionsCache[record?.collectionId]
: true;
$: if (withToken) {
loadFileToken();
} else {
token = "";
}
$: type = CommonHelper.getFileType(filename); $: type = CommonHelper.getFileType(filename);
@ -39,7 +29,7 @@
isLoadingToken = true; isLoadingToken = true;
try { try {
token = await ApiClient.getAdminFileToken(); token = await ApiClient.getAdminFileToken(record.collectionId);
} catch (err) { } catch (err) {
console.warn("File token failure:", err); console.warn("File token failure:", err);
} }

View File

@ -92,6 +92,9 @@
initialDraft = getDraft(); initialDraft = getDraft();
if (!initialDraft || areRecordsEqual(record, initialDraft)) { if (!initialDraft || areRecordsEqual(record, initialDraft)) {
initialDraft = null; initialDraft = null;
} else {
delete initialDraft.password;
delete initialDraft.passwordConfirm;
} }
originalSerializedData = JSON.stringify(record); originalSerializedData = JSON.stringify(record);
@ -482,7 +485,7 @@
</Field> </Field>
{#if collection?.isAuth} {#if collection?.isAuth}
<AuthFields bind:record {collection} /> <AuthFields bind:record {isNew} {collection} />
{#if collection?.schema?.length} {#if collection?.schema?.length}
<hr /> <hr />

View File

@ -9,6 +9,7 @@
export let collection = new Collection(); export let collection = new Collection();
export let record = new Record(); export let record = new Record();
export let isNew = record.$isNew;
let originalUsername = record.username || null; let originalUsername = record.username || null;
@ -28,15 +29,15 @@
<div class="grid m-b-base"> <div class="grid m-b-base">
<div class="col-lg-6"> <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}> <label for={uniqueId}>
<i class={CommonHelper.getFieldTypeIcon("user")} /> <i class={CommonHelper.getFieldTypeIcon("user")} />
<span class="txt">Username</span> <span class="txt">Username</span>
</label> </label>
<input <input
type="text" type="text"
requried={!record.$isNew} requried={!isNew}
placeholder={record.$isNew ? "Leave empty to auto generate..." : originalUsername} placeholder={isNew ? "Leave empty to auto generate..." : originalUsername}
id={uniqueId} id={uniqueId}
bind:value={record.username} bind:value={record.username}
/> />
@ -69,7 +70,7 @@
<!-- svelte-ignore a11y-autofocus --> <!-- svelte-ignore a11y-autofocus -->
<input <input
type="email" type="email"
autofocus={record.$isNew} autofocus={isNew}
autocomplete="off" autocomplete="off"
id={uniqueId} id={uniqueId}
required={collection.options?.requireEmail} required={collection.options?.requireEmail}
@ -79,14 +80,14 @@
</div> </div>
<div class="col-lg-12"> <div class="col-lg-12">
{#if !record.$isNew} {#if !isNew}
<Field class="form-field form-field-toggle" name="verified" let:uniqueId> <Field class="form-field form-field-toggle" name="verified" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={changePasswordToggle} /> <input type="checkbox" id={uniqueId} bind:checked={changePasswordToggle} />
<label for={uniqueId}>Change password</label> <label for={uniqueId}>Change password</label>
</Field> </Field>
{/if} {/if}
{#if record.$isNew || changePasswordToggle} {#if isNew || changePasswordToggle}
<div class="block" transition:slide|local={{ duration: 150 }}> <div class="block" transition:slide|local={{ duration: 150 }}>
<div class="grid" class:p-t-xs={changePasswordToggle}> <div class="grid" class:p-t-xs={changePasswordToggle}>
<div class="col-sm-6"> <div class="col-sm-6">
@ -131,7 +132,7 @@
id={uniqueId} id={uniqueId}
bind:checked={record.verified} bind:checked={record.verified}
on:change|preventDefault={(e) => { on:change|preventDefault={(e) => {
if (record.$isNew) { if (isNew) {
return; // no confirmation required return; // no confirmation required
} }
confirm( confirm(

View File

@ -6,6 +6,7 @@
import Field from "@/components/base/Field.svelte"; import Field from "@/components/base/Field.svelte";
import UploadedFilePreview from "@/components/base/UploadedFilePreview.svelte"; import UploadedFilePreview from "@/components/base/UploadedFilePreview.svelte";
import RecordFileThumb from "@/components/records/RecordFileThumb.svelte"; import RecordFileThumb from "@/components/records/RecordFileThumb.svelte";
import { onMount } from "svelte";
export let record; export let record;
export let value = ""; export let value = "";
@ -16,6 +17,7 @@
let fileInput; let fileInput;
let filesListElem; let filesListElem;
let isDragOver = false; let isDragOver = false;
let fileToken = "";
// normalize uploadedFiles type // normalize uploadedFiles type
$: if (!Array.isArray(uploadedFiles)) { $: if (!Array.isArray(uploadedFiles)) {
@ -93,6 +95,10 @@
uploadedFiles = uploadedFiles; uploadedFiles = uploadedFiles;
} }
onMount(async () => {
fileToken = await ApiClient.getAdminFileToken(record.collectionId);
});
</script> </script>
<div <div
@ -129,7 +135,7 @@
<div class="content"> <div class="content">
<a <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'}" class="txt-ellipsis {isDeleted ? 'txt-strikethrough txt-hint' : 'link-primary'}"
title="Download" title="Download"
target="_blank" target="_blank"

View File

@ -2,10 +2,10 @@ import { writable } from "svelte/store";
import ApiClient from "@/utils/ApiClient"; import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper"; import CommonHelper from "@/utils/CommonHelper";
export const collections = writable([]); export const collections = writable([]);
export const activeCollection = writable({}); export const activeCollection = writable({});
export const isCollectionsLoading = writable(false); export const isCollectionsLoading = writable(false);
export const privateFilesCollectionsCache = writable({}); export const protectedFilesCollectionsCache = writable({});
export function changeActiveCollectionById(collectionId) { export function changeActiveCollectionById(collectionId) {
collections.update((list) => { collections.update((list) => {
@ -82,7 +82,7 @@ export async function loadCollections(activeId = null) {
} }
function refreshProtectedFilesCollectionsCache() { function refreshProtectedFilesCollectionsCache() {
privateFilesCollectionsCache.update((cache) => { protectedFilesCollectionsCache.update((cache) => {
collections.update((current) => { collections.update((current) => {
for (let c of current) { for (let c of current) {
cache[c.id] = !!c.schema?.find((f) => f.type == "file" && f.options?.protected); cache[c.id] = !!c.schema?.find((f) => f.type == "file" && f.options?.protected);

View File

@ -1,10 +1,13 @@
import PocketBase, { LocalAuthStore, Admin, isTokenExpired } from "pocketbase"; import PocketBase, { LocalAuthStore, Admin, isTokenExpired } from "pocketbase";
// --- // ---
import CommonHelper from "@/utils/CommonHelper"; import CommonHelper from "@/utils/CommonHelper";
import { replace } from "svelte-spa-router"; import { replace } from "svelte-spa-router";
import { addErrorToast } from "@/stores/toasts"; import { get } from "svelte/store";
import { setErrors } from "@/stores/errors"; import { addErrorToast } from "@/stores/toasts";
import { setAdmin } from "@/stores/admin"; import { setErrors } from "@/stores/errors";
import { setAdmin } from "@/stores/admin";
import { protectedFilesCollectionsCache } from "@/stores/collections";
const adminFileTokenKey = "pb_admin_file_token"; const adminFileTokenKey = "pb_admin_file_token";
@ -17,7 +20,7 @@ PocketBase.prototype.logout = function(redirect = true) {
this.authStore.clear(); this.authStore.clear();
if (redirect) { 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 {Boolean} notify Whether to add a toast notification.
* @param {String} defaultMsg Default toast notification message if the error doesn't have one. * @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) { if (!err || !(err instanceof Error) || err.isAbort) {
return; return;
} }
@ -61,15 +64,28 @@ PocketBase.prototype.errorResponseHandler = function(err, notify = true, default
// forbidden // forbidden
if (statusCode === 403) { if (statusCode === 403) {
this.cancelAllRequests(); this.cancelAllRequests();
return replace('/'); return replace("/");
} }
}; };
/** /**
* @return {Promise<String>} * @return {Promise<String>}
*/ */
PocketBase.prototype.getAdminFileToken = async function() { PocketBase.prototype.getAdminFileToken = async function(collectionId = "") {
let token = localStorage.getItem(adminFileTokenKey) || ''; 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 // request a new token only if the previous one is missing or will expire soon
if (!token || isTokenExpired(token, 15)) { if (!token || isTokenExpired(token, 15)) {