Files
pocketbase/ui/src/components/collections/CollectionUpdateConfirm.svelte
T

220 lines
7.6 KiB
Svelte

<script>
import { createEventDispatcher, tick } from "svelte";
import ApiClient from "@/utils/ApiClient";
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
const dispatch = createEventDispatcher();
let panel;
let oldCollection;
let newCollection;
let hideAfterSave;
let conflictingOIDCs = [];
$: isCollectionRenamed = oldCollection?.name != newCollection?.name;
$: isNewCollectionView = newCollection?.type === "view";
$: renamedFields =
newCollection?.fields?.filter(
(field) => field.id && !field._toDelete && field._originalName != field.name,
) || [];
$: deletedFields = newCollection?.fields?.filter((field) => field.id && field._toDelete) || [];
$: multipleToSingleFields =
newCollection?.fields?.filter((field) => {
const old = oldCollection?.fields?.find((f) => f.id == field.id);
if (!old) {
return false;
}
return old.maxSelect != 1 && field.maxSelect == 1;
}) || [];
$: showChanges = !isNewCollectionView || isCollectionRenamed;
export async function show(original, changed, hideAfterSaveArg = true) {
oldCollection = original;
newCollection = changed;
hideAfterSave = hideAfterSaveArg;
await detectConflictingOIDCs();
await tick();
if (
isCollectionRenamed ||
renamedFields.length ||
deletedFields.length ||
multipleToSingleFields.length ||
conflictingOIDCs.length
) {
panel?.show();
} else {
// no changes to review -> confirm directly
confirm();
}
}
export function hide() {
panel?.hide();
}
function confirm() {
hide();
dispatch("confirm", hideAfterSave);
}
const oidcProviders = ["oidc", "oidc2", "oidc3"];
async function detectConflictingOIDCs() {
conflictingOIDCs = [];
for (let name of oidcProviders) {
let oldProvider = oldCollection?.oauth2?.providers?.find((p) => p.name == name);
let newProvider = newCollection?.oauth2?.providers?.find((p) => p.name == name);
if (!oldProvider || !newProvider) {
continue;
}
let oldHost = new URL(oldProvider.authURL).host;
let newHost = new URL(newProvider.authURL).host;
if (oldHost == newHost) {
continue;
}
// check if there are existing externalAuths
if (await haveExternalAuths(name)) {
conflictingOIDCs.push({ name, oldHost, newHost });
}
}
}
async function haveExternalAuths(provider) {
try {
await ApiClient.collection("_externalAuths").getFirstListItem(
ApiClient.filter("collectionRef={:collectionId} && provider={:provider}", {
collectionId: newCollection?.id,
provider: provider,
}),
);
return true;
} catch {}
return false;
}
function getExternalAuthsFilterLink(provider) {
return `#/collections?collection=_externalAuths&filter=collectionRef%3D%22${newCollection?.id}%22+%26%26+provider%3D%22${provider}%22`;
}
</script>
<OverlayPanel bind:this={panel} class="confirm-changes-panel" popup on:hide on:show>
<svelte:fragment slot="header">
<h4>Confirm collection changes</h4>
</svelte:fragment>
{#if isCollectionRenamed || deletedFields.length || renamedFields.length}
<div class="alert alert-warning">
<div class="icon">
<i class="ri-error-warning-line" />
</div>
<div class="content txt-bold">
<p>
If any of the collection changes is part of another collection rule, filter or view query,
you'll have to update it manually!
</p>
{#if deletedFields.length}
<p>All data associated with the removed fields will be permanently deleted!</p>
{/if}
</div>
</div>
{/if}
{#if showChanges}
<h6>Changes:</h6>
<ul class="changes-list">
{#if isCollectionRenamed}
<li>
<div class="inline-flex">
Renamed collection
<strong class="txt-strikethrough txt-hint">{oldCollection?.name}</strong>
<i class="ri-arrow-right-line txt-sm" />
<strong class="txt"> {newCollection?.name}</strong>
</div>
</li>
{/if}
{#if !isNewCollectionView}
{#each multipleToSingleFields as field}
<li>
Multiple to single value conversion of field
<strong>{field.name}</strong>
<em class="txt-sm">(will keep only the last array item)</em>
</li>
{/each}
{#each renamedFields as field}
<li>
<div class="inline-flex">
Renamed field
<strong class="txt-strikethrough txt-hint">{field._originalName}</strong>
<i class="ri-arrow-right-line txt-sm" />
<strong class="txt"> {field.name}</strong>
</div>
</li>
{/each}
{#each deletedFields as field}
<li class="txt-danger">
Removed field <span class="txt-bold">{field.name}</span>
</li>
{/each}
{/if}
{#each conflictingOIDCs as oidc}
<li>
Changed <code>{oidc.name}</code> host
<div class="inline-flex m-l-5">
<strong class="txt-strikethrough txt-hint">{oidc.oldHost}</strong>
<i class="ri-arrow-right-line txt-sm" />
<strong class="txt">{oidc.newHost}</strong>
</div>
<br />
<em class="txt-hint">
If the old and new OIDC configuration is not for the same provider consider deleting
all old <code class="txt-sm">_externalAuths</code> records associated to the current
collection and provider, otherwise it may result in account linking errors.
<a href={getExternalAuthsFilterLink(oidc.name)} target="_blank">
Review existing <code class="txt-sm">_externalAuths</code> records
<i class="ri-external-link-line txt-sm"></i>
</a>.
</em>
</li>
{/each}
</ul>
{/if}
<svelte:fragment slot="footer">
<!-- svelte-ignore a11y-autofocus -->
<button autofocus type="button" class="btn btn-transparent" on:click={() => hide()}>
<span class="txt">Cancel</span>
</button>
<button type="button" class="btn btn-expanded" on:click={() => confirm()}>
<span class="txt">Confirm</span>
</button>
</svelte:fragment>
</OverlayPanel>
<style lang="scss">
.changes-list {
word-break: break-word;
line-height: var(--smLineHeight);
li {
margin-top: 10px;
margin-bottom: 10px;
}
}
</style>