update: remove unnecessary responsibility from createPolicy.

This commit is contained in:
ItzNotABug
2024-09-23 14:02:43 +05:30
parent 0885a4bb09
commit 9ca27ba9da
4 changed files with 229 additions and 445 deletions
-3
View File
@@ -1,5 +1,3 @@
import { ID } from '@appwrite.io/console';
export type UserBackupPolicy = {
id?: string;
label: string;
@@ -17,7 +15,6 @@ export type UserBackupPolicy = {
export const cronExpression = (policy: UserBackupPolicy) => {
const now = new Date();
if (!policy.id) policy.id = ID.unique();
if (policy.plainTextFrequency === 'hourly') {
const utcMinute = now.getUTCMinutes();
@@ -24,7 +24,7 @@
let showCustomId = false;
let showPlanUpgradeAlert = true;
const createPolicies = async () => {
const createPolicies = async (resourceId: string) => {
if (!totalPolicies.length) return;
const totalPoliciesPromise = totalPolicies.map((policy) => {
@@ -36,7 +36,7 @@
policy.retained,
policy.schedule,
policy.label,
id
resourceId
);
});
@@ -45,8 +45,9 @@
const create = async () => {
try {
const database = await sdk.forProject.databases.create(id ? id : ID.unique(), name);
await createPolicies();
const databaseId = id ? id : ID.unique();
const database = await sdk.forProject.databases.create(databaseId, name);
await createPolicies(databaseId);
showCreate = false;
dispatch('created', database);
@@ -85,7 +86,7 @@
{#if !showCustomId}
<div>
<Pill button on:click={() => (showCustomId = !showCustomId)}
><span class="icon-pencil" aria-hidden="true" /><span class="text">
><span class="icon-pencil" aria-hidden="true" /><span class="text">
Database ID
</span></Pill>
</div>
@@ -105,9 +106,9 @@
{/if}
{:else}
<CreatePolicy
isModal={false}
title="Backup policies"
bind:totalPolicies
isShowing={showCreate}
title="Backup policies"
subtitle="Protect your data and ensure quick recovery by adding backup policies." />
{/if}
</FormList>
@@ -21,9 +21,12 @@
import { app } from '$lib/stores/app';
import { onMount } from 'svelte';
import { feedback } from '$lib/stores/feedback';
import { cronExpression, type UserBackupPolicy } from '$lib/helpers/backups';
let showCreatePolicy = false;
let policyCreateError: string;
let showCreateManualBackup = false;
let totalPolicies: UserBackupPolicy[] = [];
let isDisabled = isSelfHosted || (isCloud && $organization?.billingPlan === BillingPlan.FREE);
export let data: PageData;
@@ -82,6 +85,46 @@
}
};
const createPolicies = async () => {
const totalPoliciesPromise = totalPolicies.map((policy) => {
cronExpression(policy);
return sdk.forProject.backups.createPolicy(
policy.id,
['databases'],
policy.retained,
policy.schedule,
policy.label,
data.database.$id
);
});
try {
await Promise.all(totalPoliciesPromise);
const message =
totalPolicies.length > 1
? `Backup policies have been created`
: `<b>${totalPolicies[0].label}</b> policy has been created`;
addNotification({
isHtml: true,
type: 'success',
message
});
invalidate(Dependencies.BACKUPS);
} catch (err) {
addNotification({
type: 'error',
message: err.message
});
} finally {
totalPolicies = [];
showCreatePolicy = false;
}
};
onMount(() => {
return sdk.forConsole.client.subscribe('console', (response) => {
if (response.events.includes('archives.*') || response.events.includes('policies.*')) {
@@ -165,7 +208,20 @@
</div>
</Container>
<CreatePolicy bind:showCreate={showCreatePolicy} />
<Modal
title="Create backup policy"
size="big"
onSubmit={createPolicies}
bind:show={showCreatePolicy}
bind:error={policyCreateError}>
<CreatePolicy bind:totalPolicies isShowing={showCreatePolicy} />
<svelte:fragment slot="footer">
<Button secondary on:click={() => (showCreatePolicy = false)}>Cancel</Button>
<Button submit disabled={!totalPolicies.length}>Create</Button>
</svelte:fragment>
</Modal>
<Modal
title="Create manual backup"
@@ -1,34 +1,15 @@
<script lang="ts">
import { Modal } from '$lib/components';
import {
Button,
FormList,
Helper,
InputCheckbox,
InputSelect,
InputText,
InputTime
} from '$lib/elements/forms';
import { Button, FormList, Helper, InputCheckbox, InputSelect, InputText, InputTime } from '$lib/elements/forms';
import { ID } from '@appwrite.io/console';
import { capitalize } from '$lib/helpers/string';
import { backupRetainingOptions, database } from '../store';
import { backupRetainingOptions } from '../store';
import { policyPricing, presetPolicies } from './store';
import { sdk } from '$lib/stores/sdk';
import { addNotification } from '$lib/stores/notifications';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import {
backupFrequencies,
backupPolicyDescription,
cronExpression,
type UserBackupPolicy
} from '$lib/helpers/backups';
export let isModal = true;
export let showCreate = false;
import { backupFrequencies, backupPolicyDescription, type UserBackupPolicy } from '$lib/helpers/backups';
export let isShowing: boolean;
export let title: string | undefined = undefined;
export let subtitle: string | undefined = undefined;
export let totalPolicies: UserBackupPolicy[] = [];
let showCustomPolicy = false;
@@ -37,9 +18,11 @@
$: totalPolicies = [
...listOfCustomPolicies,
...$presetPolicies.filter((policy) => policy.checked)
];
].map((policy) => {
if (!policy.id) policy.id = ID.unique();
return policy;
});
let error: string;
let policyInEdit = null;
let policyRetention = 30;
let selectedTime = '00:00';
@@ -49,7 +32,6 @@
let monthlyBackupFrequency = 'end';
const resetFormVariables = () => {
error = null;
policyInEdit = null;
policyRetention = 30;
selectedTime = '00:00';
@@ -58,47 +40,6 @@
monthlyBackupFrequency = 'end';
};
export const createPolicies = async () => {
const totalPoliciesPromise = totalPolicies.map((policy) => {
cronExpression(policy);
return sdk.forProject.backups.createPolicy(
policy.id,
['databases'],
policy.retained,
policy.schedule,
policy.label,
$database.$id
);
});
try {
await Promise.all(totalPoliciesPromise);
const message =
totalPolicies.length > 1
? `Backup policies have been created`
: `<b>${totalPolicies[0].label}</b> has been created`;
addNotification({
isHtml: true,
type: 'success',
message
});
showCreate = false;
resetFormVariables();
totalPolicies = [];
listOfCustomPolicies = [];
invalidate(Dependencies.BACKUPS);
} catch (err) {
addNotification({
type: 'error',
message: err.message
});
}
};
const handleSavePolicy = () => {
const userBackupPolicy = {
default: false,
@@ -118,7 +59,16 @@
};
const markPolicyChecked = (event: Event, policy: UserBackupPolicy) => {
policy.checked = (event.target as HTMLInputElement).checked;
const isChecked = (event.target as HTMLInputElement).checked;
presetPolicies.update((all) => {
return all.map((p) => {
if (p.label === policy.label) {
return { ...p, checked: isChecked };
}
return p;
});
});
};
$: customPolicyDescription = (policy: UserBackupPolicy) => {
@@ -138,7 +88,7 @@
customPolicySection?.scrollIntoView({ behavior: 'auto' });
}
$: if (!showCreate) {
$: if (isShowing) {
resetFormVariables();
showCustomPolicy = false;
listOfCustomPolicies = [];
@@ -151,53 +101,59 @@
}
</script>
{#if isModal}
<Modal
title="Create backup policy"
size="big"
bind:show={showCreate}
onSubmit={createPolicies}
bind:error>
<FormList>
<div class="u-flex-vertical u-gap-12">
<div class="grid-1-1 u-gap-12">
{#each $presetPolicies as policy, index (index)}
<div class="card policy-default-custom-card">
<div class="u-flex u-gap-8 body-text-2">
<InputCheckbox
id={index.toString()}
on:change={(event) => markPolicyChecked(event, policy)} />
<div class="u-flex-vertical u-gap-16">
{#if title || subtitle}
<div class="body-text-2">
{#if title}
<h3 class="u-bold">{title}</h3>
{/if}
<div class="u-flex-vertical u-gap-4">
<div class="u-flex u-gap-4">
{#if subtitle}
<span>{subtitle}</span>
{/if}
</div>
{/if}
<FormList>
<div class="u-flex-vertical u-gap-12">
<div class="grid-1-1 u-gap-12">
{#each $presetPolicies as policy, index (index)}
<div class="card policy-default-custom-card">
<div class="u-flex u-gap-8 body-text-2">
<InputCheckbox
id={index.toString()}
on:change={(event) => markPolicyChecked(event, policy)} />
<div class="u-flex-vertical u-gap-4">
<div class="u-flex u-gap-4">
<h3 class="u-bold">{policy.label}</h3>
<span class="inline-tag">$20.00</span>
</div>
{policy.description}
</div>
</div>
</div>
{/each}
</div>
{#if listOfCustomPolicies.length}
<div class="u-flex-vertical u-gap-8">
{#each listOfCustomPolicies as policy}
<div class="card policy-default-custom-card">
<div class="u-flex-vertical u-gap-4 body-text-2">
<div class="u-flex u-main-space-between">
<div class="u-flex u-cross-center u-gap-4">
<h3 class="u-bold">{policy.label}</h3>
<span class="inline-tag">$20.00</span>
</div>
{policy.description}
</div>
</div>
</div>
{/each}
</div>
{#if listOfCustomPolicies.length}
<div class="u-flex-vertical u-gap-8">
{#each listOfCustomPolicies as policy}
<div class="card policy-default-custom-card">
<div class="u-flex-vertical u-gap-4 body-text-2">
<div class="u-flex u-main-space-between">
<div class="u-flex u-cross-center u-gap-4">
<h3 class="u-bold">{policy.label}</h3>
<span class="inline-tag">$20.00</span>
</div>
<div class="u-flex u-gap-8">
<Button
text
noMargin
class="icon-pencil height-fit-content"
on:click={() => {
<div class="u-flex u-gap-8">
<Button
text
noMargin
class="icon-pencil height-fit-content"
on:click={() => {
policyInEdit = policy.id;
backupPolicyName = policy.label;
policyRetention = policy.retained;
@@ -214,320 +170,95 @@
];
}} />
<Button
text
noMargin
class="icon-trash height-fit-content"
on:click={() => {
<Button
text
noMargin
class="icon-trash height-fit-content"
on:click={() => {
listOfCustomPolicies = [
...listOfCustomPolicies.filter(
(p) => policy.id !== p.id
)
];
}} />
</div>
</div>
<span>{customPolicyDescription(policy)}</span>
</div>
</div>
{/each}
</div>
{/if}
{#if showCustomPolicy || policyInEdit}
<section
bind:this={customPolicySection}
class="modal is-inner-modal u-width-full-line">
<div class="modal-form">
<div class="u-flex-vertical u-gap-24">
<div class="u-flex-vertical u-gap-4">
<InputSelect
label="Frequency"
id="policyFrequency"
placeholder="Select runtime"
bind:value={policyFrequency}
options={['hourly', 'daily', 'weekly', 'monthly'].map(
(freq) => ({
value: freq,
label: freq.charAt(0).toUpperCase() + freq.slice(1)
})
)}
required />
{#if policyFrequency === 'hourly'}
<span>{formPolicyDescription()}</span>
{/if}
</div>
{#if policyFrequency !== 'hourly'}
<div class="u-flex-vertical u-gap-8">
<div class="u-flex u-gap-8 time-holder">
{#if policyFrequency === 'monthly'}
<InputSelect
id="monthly"
label="Monthly Timing"
bind:value={monthlyBackupFrequency}
placeholder="End of month (28th)"
fullWidth
options={backupFrequencies[policyFrequency]} />
{/if}
<div
class:u-margin-block-start-4={policyFrequency ===
'monthly'}>
<InputTime
id="time"
bind:value={selectedTime}
label={['daily', 'weekly'].includes(
policyFrequency
)
? 'Timing'
: ''} />
</div>
</div>
<span>{formPolicyDescription()}</span>
</div>
{/if}
<div class="u-flex-vertical u-gap-8">
<InputSelect
fullWidth
id="retention"
label="Keep for"
placeholder="3 months"
bind:value={policyRetention}
options={backupRetainingOptions} />
<span>
{#if policyRetention === 365 * 100}
Every backup created under this policy will be retained <b
>forever</b
>.
{:else}
Every backup created under this policy will be retained
for <b>
{backupRetainingOptions.find(
(option) => option.value === policyRetention
)?.label ?? '3 months'}
</b> before being automatically deleted.
{/if}
</span>
</div>
<div>
<InputText
id="name"
label="Policy name"
placeholder="{capitalize(policyFrequency)} backup"
autofocus
bind:value={backupPolicyName}
required />
{#if policyNameError}
<Helper type="warning">This field is required</Helper>
{/if}
</div>
<div class="u-main-end u-flex u-gap-8">
<Button
text
on:click={() => {
showCustomPolicy = false;
}}
>Cancel
</Button>
<Button
secondary
on:click={() => {
if (!backupPolicyName) {
policyNameError = true;
return;
}
policyNameError = false;
handleSavePolicy();
}}>
{policyInEdit ? 'Update' : 'Save'}
</Button>
</div>
</div>
</div>
</section>
{:else}
<div class="u-flex u-main-space-between u-padding-inline-4 u-width-full-line">
<button
class="custom-policy-text"
on:click={() => (showCustomPolicy = true)}
>Custom policy
</button>
<span>Total: ${totalPolicies.length * policyPricing}.00</span>
</div>
{/if}
</div>
</FormList>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (showCreate = false)}>Cancel</Button>
<Button submit disabled={!totalPolicies.length}>Create</Button>
</svelte:fragment>
</Modal>
{:else}
<div class="u-flex-vertical u-gap-16">
{#if title || subtitle}
<div class="body-text-2">
{#if title}
<h3 class="u-bold">{title}</h3>
{/if}
{#if subtitle}
<span>{subtitle}</span>
{/if}
</div>
{/if}
<FormList>
<div class="u-flex-vertical u-gap-12">
<div class="grid-1-1 u-gap-12">
{#each $presetPolicies as policy, index (index)}
<div class="card policy-default-custom-card">
<div class="u-flex u-gap-8 body-text-2">
<InputCheckbox
id={index.toString()}
on:change={(event) => markPolicyChecked(event, policy)} />
<div class="u-flex-vertical u-gap-4">
<div class="u-flex u-gap-4">
<h3 class="u-bold">{policy.label}</h3>
<span class="inline-tag">$20.00</span>
</div>
{policy.description}
</div>
<span>{customPolicyDescription(policy)}</span>
</div>
</div>
{/each}
</div>
{/if}
{#if listOfCustomPolicies.length}
<div class="u-flex-vertical u-gap-8">
{#each listOfCustomPolicies as policy}
<div class="card policy-default-custom-card">
<div class="u-flex-vertical u-gap-4 body-text-2">
<div class="u-flex u-main-space-between">
<h3 class="body-text-2 u-bold">{policy.label}</h3>
<div class="u-flex u-gap-8">
<Button
text
noMargin
class="icon-pencil height-fit-content"
on:click={() => {
policyInEdit = policy.id;
backupPolicyName = policy.label;
policyRetention = policy.retained;
selectedTime = policy.selectedTime;
policyFrequency = policy.plainTextFrequency;
monthlyBackupFrequency =
policy.monthlyBackupFrequency;
// do not show in the list can cause confusion.
listOfCustomPolicies = [
...listOfCustomPolicies.filter(
(p) => policy.id !== p.id
)
];
}} />
<Button
text
noMargin
class="icon-trash height-fit-content"
on:click={() => {
listOfCustomPolicies = [
...listOfCustomPolicies.filter(
(p) => policy.id !== p.id
)
];
}} />
</div>
</div>
<span>{customPolicyDescription(policy)}</span>
</div>
</div>
{/each}
</div>
{/if}
{#if showCustomPolicy || policyInEdit}
<section
bind:this={customPolicySection}
class="modal is-inner-modal u-width-full-line">
<div class="modal-form">
<div class="u-flex-vertical u-gap-24">
<div class="u-flex-vertical u-gap-4">
<InputSelect
label="Frequency"
id="policyFrequency"
placeholder="Select runtime"
bind:value={policyFrequency}
options={['hourly', 'daily', 'weekly', 'monthly'].map(
{#if showCustomPolicy || policyInEdit}
<section
bind:this={customPolicySection}
class="modal is-inner-modal u-width-full-line">
<div class="modal-form">
<div class="u-flex-vertical u-gap-24">
<div class="u-flex-vertical u-gap-4">
<InputSelect
label="Frequency"
id="policyFrequency"
placeholder="Select runtime"
bind:value={policyFrequency}
options={['hourly', 'daily', 'weekly', 'monthly'].map(
(freq) => ({
value: freq,
label: freq.charAt(0).toUpperCase() + freq.slice(1)
})
)}
required />
{#if policyFrequency === 'hourly'}
<span>{formPolicyDescription()}</span>
{/if}
</div>
required />
{#if policyFrequency === 'hourly'}
<span>{formPolicyDescription()}</span>
{/if}
</div>
{#if policyFrequency !== 'hourly'}
<div class="u-flex-vertical u-gap-8">
<div class="u-flex u-gap-8 time-holder">
{#if policyFrequency === 'monthly'}
<InputSelect
id="monthly"
label="Monthly Timing"
bind:value={monthlyBackupFrequency}
placeholder="End of month (28th)"
fullWidth
options={backupFrequencies[policyFrequency]} />
{/if}
{#if policyFrequency !== 'hourly'}
<div class="u-flex-vertical u-gap-8">
<div class="u-flex u-gap-8 time-holder">
{#if policyFrequency === 'monthly'}
<InputSelect
id="monthly"
label="Monthly Timing"
bind:value={monthlyBackupFrequency}
placeholder="End of month (28th)"
fullWidth
options={backupFrequencies[policyFrequency]} />
{/if}
<div
class:u-margin-block-start-4={policyFrequency ===
<div
class:u-margin-block-start-4={policyFrequency ===
'monthly'}>
<InputTime
id="time"
bind:value={selectedTime}
label={['daily', 'weekly'].includes(
<InputTime
id="time"
bind:value={selectedTime}
label={['daily', 'weekly'].includes(
policyFrequency
)
? 'Timing'
: ''} />
</div>
</div>
<span>{formPolicyDescription()}</span>
</div>
{/if}
<div class="u-flex-vertical u-gap-8">
<InputSelect
fullWidth
id="retention"
label="Keep for"
placeholder="3 months"
bind:value={policyRetention}
options={backupRetainingOptions} />
<span>
<span>{formPolicyDescription()}</span>
</div>
{/if}
<div class="u-flex-vertical u-gap-8">
<InputSelect
fullWidth
id="retention"
label="Keep for"
placeholder="3 months"
bind:value={policyRetention}
options={backupRetainingOptions} />
<span>
{#if policyRetention === 365 * 100}
Every backup created under this policy will be retained <b
>forever</b
>.
>forever</b
>.
{:else}
Every backup created under this policy will be retained
for <b>
@@ -537,34 +268,34 @@
</b> before being automatically deleted.
{/if}
</span>
</div>
</div>
<div>
<InputText
id="name"
label="Policy name"
placeholder="{capitalize(policyFrequency)} backup"
autofocus
bind:value={backupPolicyName}
required />
<div>
<InputText
id="name"
label="Policy name"
placeholder="{capitalize(policyFrequency)} backup"
autofocus
bind:value={backupPolicyName}
required />
{#if policyNameError}
<Helper type="warning">This field is required</Helper>
{/if}
</div>
{#if policyNameError}
<Helper type="warning">This field is required</Helper>
{/if}
</div>
<div class="u-main-end u-flex u-gap-8">
<Button
text
on:click={() => {
<div class="u-main-end u-flex u-gap-8">
<Button
text
on:click={() => {
showCustomPolicy = false;
}}
>Cancel
</Button>
>Cancel
</Button>
<Button
secondary
on:click={() => {
<Button
secondary
on:click={() => {
if (!backupPolicyName) {
policyNameError = true;
return;
@@ -573,26 +304,25 @@
policyNameError = false;
handleSavePolicy();
}}>
{policyInEdit ? 'Update' : 'Save'}
</Button>
</div>
{policyInEdit ? 'Update' : 'Save'}
</Button>
</div>
</div>
</section>
{:else}
<div class="u-flex u-main-space-between u-padding-inline-4 u-width-full-line">
<button
class="custom-policy-text"
on:click={() => (showCustomPolicy = true)}
>Custom policy
</button>
<span>Total: ${totalPolicies.length * policyPricing}.00</span>
</div>
{/if}
</div>
</FormList>
</div>
{/if}
</section>
{:else}
<div class="u-flex u-main-space-between u-padding-inline-4 u-width-full-line">
<button
class="custom-policy-text"
on:click={() => (showCustomPolicy = true)}
>Custom policy
</button>
<span>Total: ${totalPolicies.length * policyPricing}.00</span>
</div>
{/if}
</div>
</FormList>
</div>
<style>
.card {