351 lines
14 KiB
JavaScript
351 lines
14 KiB
JavaScript
import { toDeleteProp } from "@/base/fieldSettings";
|
|
|
|
window.app = window.app || {};
|
|
window.app.modals = window.app.modals || {};
|
|
|
|
window.app.modals.openCollectionChangesConfirmation = async function(
|
|
oldCollection,
|
|
newCollection,
|
|
yesCallback,
|
|
noCallback,
|
|
) {
|
|
const data = store({
|
|
isLoadingConflictingOIDCProviders: false,
|
|
conflictingOIDCProviders: [],
|
|
// ---
|
|
get isCollectionRenamed() {
|
|
return oldCollection?.name != newCollection?.name;
|
|
},
|
|
get isNewCollectionAuth() {
|
|
return newCollection?.type === "auth";
|
|
},
|
|
get isNewCollectionView() {
|
|
return newCollection?.type === "view";
|
|
},
|
|
get renamedFields() {
|
|
if (data.isNewCollectionView) {
|
|
return [];
|
|
}
|
|
|
|
return newCollection?.fields?.filter?.((f) => {
|
|
let oldField;
|
|
if (f.id && !f[toDeleteProp]) {
|
|
oldField = oldCollection.fields?.find?.((old) => old.id == f.id);
|
|
}
|
|
|
|
return oldField && oldField.name != f.name;
|
|
}) || [];
|
|
},
|
|
get deletedFields() {
|
|
if (data.isNewCollectionView) {
|
|
return [];
|
|
}
|
|
|
|
return newCollection?.fields?.filter?.((f) => {
|
|
return f.id && f[toDeleteProp];
|
|
}) || [];
|
|
},
|
|
get multipleToSingleFields() {
|
|
if (data.isNewCollectionView) {
|
|
return [];
|
|
}
|
|
|
|
return newCollection?.fields?.filter?.((newField) => {
|
|
const oldField = oldCollection?.fields?.find?.((f) => f.id == newField.id);
|
|
if (!oldField || typeof oldField.maxSelect == "undefined") {
|
|
return false;
|
|
}
|
|
|
|
// normalize
|
|
const oldMaxSelect = oldField.maxSelect || 1;
|
|
const newMaxSelect = newField.maxSelect || 1;
|
|
|
|
return oldMaxSelect > 1 && newMaxSelect == 1;
|
|
}) || [];
|
|
},
|
|
get changedRules() {
|
|
// for now enable only for "production"
|
|
if (window.location.protocol != "https:") {
|
|
return [];
|
|
}
|
|
|
|
const result = [];
|
|
|
|
const ruleProps = ["listRule", "viewRule"];
|
|
if (!data.isNewCollectionView) {
|
|
ruleProps.push("createRule", "updateRule", "deleteRule");
|
|
}
|
|
if (data.isNewCollectionAuth) {
|
|
ruleProps.push("manageRule", "authRule");
|
|
}
|
|
|
|
let oldRule, newRule;
|
|
for (let prop of ruleProps) {
|
|
oldRule = oldCollection?.[prop];
|
|
newRule = newCollection?.[prop];
|
|
if (oldRule === newRule) {
|
|
continue;
|
|
}
|
|
|
|
result.push({ prop, oldRule, newRule });
|
|
}
|
|
|
|
return result;
|
|
},
|
|
get needConfirmation() {
|
|
return !app.utils.isEmpty(oldCollection?.id) && (
|
|
data.isCollectionRenamed
|
|
|| data.renamedFields.length
|
|
|| data.deletedFields.length
|
|
|| data.multipleToSingleFields.length
|
|
|| data.changedRules.length
|
|
|| data.conflictingOIDCProviders.length
|
|
);
|
|
},
|
|
});
|
|
|
|
const knownOIDCProviders = ["oidc", "oidc2", "oidc3"];
|
|
|
|
async function detectConflictingOIDCProviders() {
|
|
if (app.utils.isEmpty(oldCollection?.id) || !data.isNewCollectionAuth) {
|
|
return;
|
|
}
|
|
|
|
data.isLoadingConflictingOIDCProviders = true;
|
|
|
|
try {
|
|
data.conflictingOIDCProviders = [];
|
|
|
|
for (const name of knownOIDCProviders) {
|
|
const oldProvider = oldCollection?.oauth2?.providers?.find?.((p) => p.name == name);
|
|
const newProvider = newCollection?.oauth2?.providers?.find?.((p) => p.name == name);
|
|
|
|
if (!oldProvider || !newProvider) {
|
|
continue;
|
|
}
|
|
|
|
const oldHost = new URL(oldProvider.authURL).host;
|
|
const newHost = new URL(newProvider.authURL).host;
|
|
if (oldHost == newHost) {
|
|
continue;
|
|
}
|
|
|
|
// check if there are existing externalAuths
|
|
const haveExternalAuths = await app.pb.collection("_externalAuths").getFirstListItem(
|
|
app.pb.filter("collectionRef={:collectionId} && provider={:provider}", {
|
|
collectionId: newCollection?.id,
|
|
provider: name,
|
|
}),
|
|
{
|
|
requestKey: null,
|
|
},
|
|
);
|
|
if (haveExternalAuths) {
|
|
data.conflictingOIDCProviders.push({ name, oldHost, newHost });
|
|
}
|
|
}
|
|
|
|
data.isLoadingConflictingOIDCProviders = false;
|
|
} catch (err) {
|
|
if (!err?.isAbort) {
|
|
data.isLoadingConflictingOIDCProviders = false;
|
|
app.checkApiError(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
await detectConflictingOIDCProviders();
|
|
|
|
if (!data.needConfirmation) {
|
|
return yesCallback();
|
|
}
|
|
|
|
app.modals.confirm(
|
|
t.div(
|
|
{ className: "dangerous-collection-changes-list" },
|
|
t.h5({ className: "block txt-center m-b-base" }, "Do you really want to save the collection changes?"),
|
|
// general collection warning
|
|
() => {
|
|
if (!data.isCollectionRenamed && !data.deletedFields.length && !data.renamedFields.length) {
|
|
return;
|
|
}
|
|
|
|
return t.div(
|
|
{ className: "alert warning m-b-base" },
|
|
t.p(
|
|
null,
|
|
"If the collection participate in another collection rule, filter or view query, you'll have to update it manually!",
|
|
),
|
|
() => {
|
|
if (data.deletedFields.length) {
|
|
return t.p(
|
|
null,
|
|
"All data associated with the removed fields will be permanently deleted!",
|
|
);
|
|
}
|
|
},
|
|
);
|
|
},
|
|
// renamed collection
|
|
() => {
|
|
if (!data.isCollectionRenamed) {
|
|
return;
|
|
}
|
|
|
|
return t.ul(
|
|
{ className: "collection-changes-list changes-renamed-collection" },
|
|
t.li(
|
|
{ className: "list-item" },
|
|
"Renamed collection ",
|
|
t.strong({ className: "label warning" }, oldCollection?.name),
|
|
t.i({ className: "ri-arrow-right-line txt-sm", ariaHidden: true }),
|
|
t.strong({ className: "label success" }, newCollection?.name || "N/A"),
|
|
),
|
|
);
|
|
},
|
|
// renamed fields
|
|
() => {
|
|
if (!data.renamedFields.length) {
|
|
return;
|
|
}
|
|
|
|
return t.ul(
|
|
{ className: "collection-changes-list changes-renamed-fields" },
|
|
() => {
|
|
return data.renamedFields.map((newField) => {
|
|
const oldField = oldCollection?.fields?.find?.((f) => f.id == newField.id);
|
|
return t.li(
|
|
{ className: "list-item" },
|
|
"Renamed field ",
|
|
t.strong({ className: "label warning" }, oldField?.name),
|
|
t.i({ className: "ri-arrow-right-line txt-sm", ariaHidden: true }),
|
|
t.strong({ className: "label success" }, newField.name || "N/A"),
|
|
);
|
|
});
|
|
},
|
|
);
|
|
},
|
|
// deleted fields
|
|
() => {
|
|
if (!data.deletedFields.length) {
|
|
return;
|
|
}
|
|
|
|
return t.ul(
|
|
{ className: "collection-changes-list changes-deleted-fields" },
|
|
() => {
|
|
return data.deletedFields.map((field) => {
|
|
return t.li(
|
|
{ className: "list-item" },
|
|
"Deleted field ",
|
|
t.strong({ className: "label danger" }, field.name || "N/A"),
|
|
);
|
|
});
|
|
},
|
|
);
|
|
},
|
|
// multiple->single fields
|
|
() => {
|
|
if (!data.multipleToSingleFields.length) {
|
|
return;
|
|
}
|
|
|
|
return t.ul(
|
|
{ className: "collection-changes-list changes-multiple-to-single-fields" },
|
|
() => {
|
|
return data.multipleToSingleFields.map((field) => {
|
|
return t.li(
|
|
{ className: "list-item" },
|
|
"Multiple to single value conversion of field ",
|
|
t.strong({ className: "label warning" }, field.name || field.id),
|
|
t.em({ className: "txt-sm" }, " (will keep only the last array item)"),
|
|
);
|
|
});
|
|
},
|
|
);
|
|
},
|
|
// API rule changes
|
|
() => {
|
|
if (!data.changedRules.length) {
|
|
return;
|
|
}
|
|
|
|
return t.ul(
|
|
{ className: "collection-changes-list changes-api-rules" },
|
|
() => {
|
|
return data.changedRules.map((ruleChange) => {
|
|
return t.li(
|
|
{ className: "list-item" },
|
|
t.div(
|
|
{ className: "content" },
|
|
t.span({ className: "txt" }, "Changed API rule for "),
|
|
t.code(null, ruleChange.prop),
|
|
),
|
|
t.small({ className: "txt-bold" }, "Old:"),
|
|
t.div(
|
|
{ className: "rule-content old-rule" },
|
|
ruleChange.oldRule === null
|
|
? "null (superusers only)"
|
|
: (ruleChange.oldRule || "\"\""),
|
|
),
|
|
t.small({ className: "txt-bold" }, "New:"),
|
|
t.div(
|
|
{ className: "rule-content new-rule" },
|
|
ruleChange.newRule === null
|
|
? "null (superusers only)"
|
|
: (ruleChange.newRule || "\"\""),
|
|
),
|
|
);
|
|
});
|
|
},
|
|
);
|
|
},
|
|
// Conflicting OIDC changes
|
|
() => {
|
|
if (!data.conflictingOIDCProviders.length) {
|
|
return;
|
|
}
|
|
|
|
return t.ul(
|
|
{ className: "collection-changes-list changes-api-rules" },
|
|
() => {
|
|
return data.conflictingOIDCProviders.map((oidc) => {
|
|
return t.li(
|
|
{ className: "list-item" },
|
|
"Changed OIDC ",
|
|
oidc.name,
|
|
" host ",
|
|
t.strong({ className: "label warning" }, oidc.oldHost),
|
|
t.i({ className: "ri-arrow-right-line txt-sm", ariaHidden: true }),
|
|
t.strong({ className: "label success" }, oidc.newHost),
|
|
t.br(),
|
|
t.span(
|
|
{ className: "txt-hint" },
|
|
"If the old and new OIDC configuration is not for the same provider consider deleting",
|
|
" all old _externalAuths records associated to the current collection and provider,",
|
|
" otherwise it may result in account linking errors.",
|
|
),
|
|
" ",
|
|
t.a({
|
|
rel: "noopenener noreferrer",
|
|
target: "_blank",
|
|
href: () => {
|
|
return `#/collections?collection=_externalAuths&filter=collectionRef%3D%22${newCollection?.id}%22+%26%26+provider%3D%22${oidc.name}%22`;
|
|
},
|
|
textContent: "Review existing _externalAuths records",
|
|
}),
|
|
);
|
|
});
|
|
},
|
|
);
|
|
},
|
|
),
|
|
yesCallback,
|
|
noCallback,
|
|
{
|
|
className: "collection-changes-confirm-modal",
|
|
yesButton: "Yes, save changes",
|
|
},
|
|
);
|
|
};
|