294 lines
12 KiB
JavaScript
294 lines
12 KiB
JavaScript
import { settingsSidebar } from "../settingsSidebar";
|
|
import { batchAccordion } from "./batchAccordion";
|
|
import { rateLimitAccordion, sortRules } from "./rateLimitAccordion";
|
|
import { trustedProxyAccordion } from "./trustedProxyAccordion";
|
|
|
|
export function pageApplicationSettings() {
|
|
app.store.title = "Application settings";
|
|
|
|
const data = store({
|
|
isLoading: false,
|
|
isSaving: false,
|
|
formSettings: null,
|
|
originalFormSettings: null,
|
|
get originalFormSettingsHash() {
|
|
return JSON.stringify(data.originalFormSettings);
|
|
},
|
|
get hasChanges() {
|
|
return data.originalFormSettingsHash != JSON.stringify(data.formSettings);
|
|
},
|
|
});
|
|
|
|
loadSettings();
|
|
|
|
async function loadSettings() {
|
|
data.isLoading = true;
|
|
|
|
try {
|
|
const settings = await app.pb.settings.getAll();
|
|
init(settings);
|
|
|
|
data.isLoading = false;
|
|
} catch (err) {
|
|
if (!err.isAbort) {
|
|
app.checkApiError(err);
|
|
// data.isLoading = false; don't reset in case of a server error
|
|
}
|
|
}
|
|
}
|
|
|
|
async function save() {
|
|
if (data.isSaving || !data.hasChanges) {
|
|
return;
|
|
}
|
|
|
|
data.isSaving = true;
|
|
|
|
data.formSettings.rateLimits.rules = sortRules(data.formSettings.rateLimits.rules);
|
|
|
|
try {
|
|
const redacted = app.utils.filterRedactedProps(data.formSettings);
|
|
const settings = await app.pb.settings.update(redacted);
|
|
init(settings);
|
|
|
|
app.toasts.success("Successfully saved application settings.");
|
|
} catch (err) {
|
|
app.checkApiError(err);
|
|
}
|
|
|
|
data.isSaving = false;
|
|
}
|
|
|
|
function init(settings = {}) {
|
|
// refresh local app settings
|
|
app.store.settings = JSON.parse(JSON.stringify(settings));
|
|
|
|
// load from the css style as fallback
|
|
if (!settings.meta?.accentColor) {
|
|
const cssColor = window.getComputedStyle(document.documentElement)?.getPropertyValue("--accentColor");
|
|
if (cssColor?.startsWith("#")) {
|
|
settings.meta = settings.meta || {};
|
|
settings.meta.accentColor = cssColor.toLowerCase() || "";
|
|
}
|
|
}
|
|
|
|
data.originalFormSettings = {
|
|
meta: settings.meta || {},
|
|
batch: settings.batch || {},
|
|
trustedProxy: settings.trustedProxy || { headers: [] },
|
|
rateLimits: settings.rateLimits || { rules: [] },
|
|
};
|
|
|
|
sortRules(data.originalFormSettings.rateLimits.rules);
|
|
|
|
data.formSettings = JSON.parse(JSON.stringify(data.originalFormSettings));
|
|
}
|
|
|
|
function reset() {
|
|
data.formSettings = JSON.parse(data.originalFormSettingsHash);
|
|
}
|
|
|
|
return t.div(
|
|
{
|
|
pbEvent: "pageApplicationSettings",
|
|
className: "page page-application-settings",
|
|
},
|
|
settingsSidebar(),
|
|
t.div(
|
|
{ className: "page-content full-height" },
|
|
t.header(
|
|
{ className: "page-header" },
|
|
t.nav(
|
|
{ className: "breadcrumbs" },
|
|
t.div({ className: "breadcrumb-item" }, "Settings"),
|
|
t.div({ className: "breadcrumb-item" }, "Application"),
|
|
),
|
|
),
|
|
t.div(
|
|
{ className: "wrapper m-b-base" },
|
|
() => {
|
|
if (data.isLoading) {
|
|
return t.div({ className: "block txt-center" }, t.span({ className: "loader lg" }));
|
|
}
|
|
|
|
return t.form(
|
|
{
|
|
pbEvent: "applicationSettingsForm",
|
|
className: "grid application-settings-form",
|
|
inert: () => data.isSaving,
|
|
onsubmit: (e) => {
|
|
e.preventDefault();
|
|
save();
|
|
},
|
|
},
|
|
t.div(
|
|
{ className: "col-md-5" },
|
|
t.div(
|
|
{ className: "field" },
|
|
t.label({ htmlFor: "meta.appName" }, "Application name"),
|
|
t.input({
|
|
id: "meta.appName",
|
|
name: "meta.appName",
|
|
type: "text",
|
|
required: true,
|
|
value: () => data.formSettings.meta.appName || "",
|
|
oninput: (e) => (data.formSettings.meta.appName = e.target.value),
|
|
}),
|
|
),
|
|
),
|
|
t.div(
|
|
{ className: "col-md-5" },
|
|
t.div(
|
|
{ className: "field" },
|
|
t.label({ htmlFor: "meta.appURL" }, "Application URL"),
|
|
t.input({
|
|
id: "meta.appURL",
|
|
name: "meta.appURL",
|
|
type: "url",
|
|
required: true,
|
|
value: () => data.formSettings.meta.appURL || "",
|
|
oninput: (e) => (data.formSettings.meta.appURL = e.target.value),
|
|
}),
|
|
),
|
|
),
|
|
t.div(
|
|
{ className: "col-md-2" },
|
|
// pass isSaving to ensure that it will be rerendered after save
|
|
() => accentColorField(data, data.isSaving),
|
|
),
|
|
t.div(
|
|
{ className: "col-lg-12" },
|
|
() => trustedProxyAccordion(data),
|
|
() => rateLimitAccordion(data),
|
|
() => batchAccordion(data),
|
|
),
|
|
t.div(
|
|
{ className: "col-lg-12" },
|
|
t.div(
|
|
{ className: "field" },
|
|
t.input({
|
|
id: "meta.hideControls",
|
|
name: "meta.hideControls",
|
|
type: "checkbox",
|
|
className: "switch",
|
|
checked: () => data.formSettings.meta.hideControls,
|
|
onchange: (e) => (data.formSettings.meta.hideControls = e.target.checked),
|
|
}),
|
|
t.label(
|
|
{ htmlFor: "meta.hideControls" },
|
|
t.span({ className: "txt" }, "Hide/Lock collection and record controls"),
|
|
t.i({
|
|
className: "ri-information-line link-hint",
|
|
ariaDescription: app.attrs.tooltip(
|
|
"To prevent accidental changes when in production environment, collections create and update buttons will be hidden.\nRecords update will also require an extra unlock step before save.",
|
|
),
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
t.div({ className: "col-lg-12" }, t.hr()),
|
|
t.div(
|
|
{ className: "col-lg-12" },
|
|
t.div(
|
|
{ className: "flex" },
|
|
t.div({ className: "m-r-auto" }),
|
|
t.button(
|
|
{
|
|
type: "button",
|
|
className: "btn transparent secondary",
|
|
disabled: () => data.isSaving,
|
|
hidden: () => !data.hasChanges,
|
|
onclick: reset,
|
|
},
|
|
t.span({ className: "txt" }, "Cancel"),
|
|
),
|
|
t.button(
|
|
{
|
|
className: () => `btn expanded ${data.isSaving ? "loading" : ""}`,
|
|
disabled: () => !data.hasChanges || data.isSaving,
|
|
},
|
|
t.span({ className: "txt" }, "Save changes"),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
t.footer({ className: "page-footer" }, app.components.credits()),
|
|
),
|
|
);
|
|
}
|
|
|
|
function accentColorField(pageData) {
|
|
const uniqueId = "accent_" + app.utils.randomString();
|
|
|
|
const local = store({
|
|
isTooLight: false,
|
|
});
|
|
|
|
let colorChangeTimeoutId;
|
|
let tempNoAnimationTimeoutId;
|
|
|
|
function changeAccentColor(color) {
|
|
// temporary disable animations to minimize flickering
|
|
clearTimeout(tempNoAnimationTimeoutId);
|
|
document.documentElement.style.setProperty("--animationSpeed", "0");
|
|
|
|
if (color) {
|
|
document.documentElement.style.setProperty("--accentColor", color.toLowerCase());
|
|
} else {
|
|
document.documentElement.style.removeProperty("--accentColor");
|
|
}
|
|
|
|
// restore animation
|
|
tempNoAnimationTimeoutId = setTimeout(() => {
|
|
document.documentElement.style.removeProperty("--animationSpeed");
|
|
}, 100);
|
|
}
|
|
|
|
const watchers = [
|
|
watch(() => pageData.formSettings?.meta?.accentColor, (newColor) => {
|
|
clearTimeout(colorChangeTimeoutId);
|
|
colorChangeTimeoutId = setTimeout(() => {
|
|
changeAccentColor(newColor);
|
|
}, 100);
|
|
}),
|
|
];
|
|
|
|
return t.div(
|
|
{
|
|
className: "field",
|
|
ariaDescription: app.attrs.tooltip(() => local.isTooLight ? "Invalid - color is too light" : ""),
|
|
onunmount: () => {
|
|
clearTimeout(colorChangeTimeoutId);
|
|
changeAccentColor(pageData.formSettings.meta.accentColor);
|
|
watchers.forEach((w) => w?.unwatch());
|
|
},
|
|
},
|
|
t.label(
|
|
{ htmlFor: uniqueId },
|
|
t.span({ className: "txt" }, "Accent"),
|
|
t.i({
|
|
hidden: () => !local.isTooLight,
|
|
className: "txt-warning ri-alert-line",
|
|
}),
|
|
),
|
|
app.components.colorPicker({
|
|
id: uniqueId,
|
|
name: "meta.accentColor",
|
|
predefinedColors: () => app.store.predefinedAccentColors,
|
|
value: () => pageData.formSettings.meta.accentColor,
|
|
onchange: (color) => {
|
|
// @todo consider removing the constraint once contrast-color is implemented
|
|
local.isTooLight = false;
|
|
if (!app.utils.isDarkEnoughForWhiteText(color)) {
|
|
local.isTooLight = true;
|
|
return;
|
|
}
|
|
|
|
pageData.formSettings.meta.accentColor = color;
|
|
},
|
|
}),
|
|
);
|
|
}
|