mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge pull request #2775 from appwrite/fix-dat-1012
This commit is contained in:
@@ -5,7 +5,6 @@
|
||||
import { isCloud, isSelfHosted } from '$lib/system';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import type { BackupArchive, BackupRestoration } from '$lib/sdk/backups';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -13,10 +12,11 @@
|
||||
import { getProjectId } from '$lib/helpers/project';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
import { type Models } from '@appwrite.io/console';
|
||||
|
||||
const backupRestoreItems: {
|
||||
archives: Map<string, BackupArchive>;
|
||||
restorations: Map<string, BackupRestoration>;
|
||||
archives: Map<string, Models.BackupArchive>;
|
||||
restorations: Map<string, Models.BackupRestoration>;
|
||||
} = {
|
||||
archives: new Map(),
|
||||
restorations: new Map()
|
||||
@@ -117,7 +117,7 @@
|
||||
if (which === 'restorations') lastDatabaseRestorationId = null;
|
||||
}
|
||||
|
||||
function backupName(item: BackupArchive | BackupRestoration, key: string) {
|
||||
function backupName(item: Models.BackupArchive | Models.BackupRestoration, key: string) {
|
||||
const column = key === 'archives' ? '$createdAt' : 'startedAt';
|
||||
|
||||
return toLocaleDate(item[column]);
|
||||
|
||||
@@ -1,332 +0,0 @@
|
||||
import { AppwriteException, Client, type Payload } from '@appwrite.io/console';
|
||||
|
||||
export type BackupPolicyList = {
|
||||
total: number;
|
||||
policies: BackupPolicy[];
|
||||
};
|
||||
|
||||
export type BackupArchiveList = {
|
||||
total: number;
|
||||
archives: BackupArchive[];
|
||||
};
|
||||
|
||||
export type BackupRestorationList = {
|
||||
total: number;
|
||||
restorations: BackupRestoration[];
|
||||
};
|
||||
|
||||
export type BackupPolicy = {
|
||||
$id: string;
|
||||
name: string;
|
||||
$createdAt: string;
|
||||
$updatedAt: string;
|
||||
services: string[];
|
||||
resources: string[];
|
||||
resourceId?: string;
|
||||
resourceType?: string;
|
||||
retention: number;
|
||||
schedule: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type BackupArchive = {
|
||||
$id: string;
|
||||
$createdAt: string;
|
||||
$updatedAt: string;
|
||||
policyId: string;
|
||||
size: number;
|
||||
status: string;
|
||||
startedAt: string;
|
||||
migrationId: string;
|
||||
services: string[];
|
||||
resources: string[];
|
||||
resourceId?: string;
|
||||
resourceType?: string;
|
||||
};
|
||||
|
||||
export type BackupRestoration = {
|
||||
$id: string;
|
||||
$createdAt: string;
|
||||
$updatedAt: string;
|
||||
archiveId: string;
|
||||
policyId: string;
|
||||
status: string;
|
||||
startedAt: string;
|
||||
migrationId: string;
|
||||
services: string[];
|
||||
resources: string[];
|
||||
options: string;
|
||||
};
|
||||
|
||||
export class Backups {
|
||||
client: Client;
|
||||
|
||||
constructor(client: Client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
async createArchive(services: string[], resourceId?: string): Promise<BackupArchive> {
|
||||
if (typeof services === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "services"');
|
||||
}
|
||||
const apiPath = '/backups/archives';
|
||||
const payload: Payload = {};
|
||||
if (typeof services !== 'undefined') {
|
||||
payload['services'] = services;
|
||||
}
|
||||
if (typeof resourceId !== 'undefined') {
|
||||
payload['resourceId'] = resourceId;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('post', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async deleteArchive(archiveId: string): Promise<object> {
|
||||
if (typeof archiveId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "archiveId"');
|
||||
}
|
||||
const apiPath = '/backups/archives/{archiveId}'.replace('{archiveId}', archiveId);
|
||||
const payload: Payload = {};
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('delete', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async listArchives(queries?: string[]): Promise<BackupArchiveList> {
|
||||
const apiPath = '/backups/archives';
|
||||
const payload: Payload = {};
|
||||
if (typeof queries !== 'undefined') {
|
||||
payload['queries'] = queries;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('get', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async getArchive(archiveId: string): Promise<BackupArchive> {
|
||||
if (typeof archiveId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "archiveId"');
|
||||
}
|
||||
const apiPath = '/backups/archives/{archiveId}'.replace('{archiveId}', archiveId);
|
||||
const payload: Payload = {};
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('get', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async listPolicies(queries?: string[]): Promise<BackupPolicyList> {
|
||||
const apiPath = '/backups/policies';
|
||||
const payload: Payload = {};
|
||||
if (typeof queries !== 'undefined') {
|
||||
payload['queries'] = queries;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('get', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async createPolicy(
|
||||
policyId: string,
|
||||
services: string[],
|
||||
retention: number,
|
||||
schedule: string,
|
||||
name?: string,
|
||||
resourceId?: string,
|
||||
enabled?: boolean
|
||||
): Promise<BackupPolicy> {
|
||||
if (typeof policyId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "policyId"');
|
||||
}
|
||||
if (typeof services === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "services"');
|
||||
}
|
||||
if (typeof retention === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "retention"');
|
||||
}
|
||||
if (typeof schedule === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "schedule"');
|
||||
}
|
||||
const apiPath = '/backups/policies';
|
||||
const payload: Payload = {};
|
||||
if (typeof policyId !== 'undefined') {
|
||||
payload['policyId'] = policyId;
|
||||
}
|
||||
if (typeof name !== 'undefined') {
|
||||
payload['name'] = name;
|
||||
}
|
||||
if (typeof services !== 'undefined') {
|
||||
payload['services'] = services;
|
||||
}
|
||||
if (typeof resourceId !== 'undefined') {
|
||||
payload['resourceId'] = resourceId;
|
||||
}
|
||||
if (typeof enabled !== 'undefined') {
|
||||
payload['enabled'] = enabled;
|
||||
}
|
||||
if (typeof retention !== 'undefined') {
|
||||
payload['retention'] = retention;
|
||||
}
|
||||
if (typeof schedule !== 'undefined') {
|
||||
payload['schedule'] = schedule;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('post', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async getPolicy(policyId: string): Promise<BackupPolicy> {
|
||||
if (typeof policyId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "policyId"');
|
||||
}
|
||||
const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId);
|
||||
const payload: Payload = {};
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('get', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async updatePolicy(
|
||||
policyId: string,
|
||||
name?: string,
|
||||
retention?: number,
|
||||
schedule?: string,
|
||||
enabled?: boolean
|
||||
): Promise<BackupPolicy> {
|
||||
if (typeof policyId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "policyId"');
|
||||
}
|
||||
const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId);
|
||||
const payload: Payload = {};
|
||||
if (typeof name !== 'undefined') {
|
||||
payload['name'] = name;
|
||||
}
|
||||
if (typeof retention !== 'undefined') {
|
||||
payload['retention'] = retention;
|
||||
}
|
||||
if (typeof schedule !== 'undefined') {
|
||||
payload['schedule'] = schedule;
|
||||
}
|
||||
if (typeof enabled !== 'undefined') {
|
||||
payload['enabled'] = enabled;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('patch', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async deletePolicy(policyId: string): Promise<object> {
|
||||
if (typeof policyId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "policyId"');
|
||||
}
|
||||
const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId);
|
||||
const payload: Payload = {};
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('delete', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async createRestoration(
|
||||
archiveId: string,
|
||||
services: string[],
|
||||
newResourceId?: string,
|
||||
newResourceName?: string
|
||||
): Promise<BackupRestoration> {
|
||||
if (typeof archiveId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "archiveId"');
|
||||
}
|
||||
if (typeof services === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "services"');
|
||||
}
|
||||
const apiPath = '/backups/restoration';
|
||||
const payload: Payload = {};
|
||||
if (typeof archiveId !== 'undefined') {
|
||||
payload['archiveId'] = archiveId;
|
||||
}
|
||||
if (typeof services !== 'undefined') {
|
||||
payload['services'] = services;
|
||||
}
|
||||
if (typeof newResourceId !== 'undefined') {
|
||||
payload['newResourceId'] = newResourceId;
|
||||
}
|
||||
if (typeof newResourceName !== 'undefined') {
|
||||
payload['newResourceName'] = newResourceName;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('post', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async listRestorations(queries?: string[]): Promise<BackupRestorationList> {
|
||||
const apiPath = '/backups/restorations';
|
||||
const payload: Payload = {};
|
||||
if (typeof queries !== 'undefined') {
|
||||
payload['queries'] = queries;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('get', uri, apiHeaders, payload);
|
||||
}
|
||||
|
||||
async getRestoration(restorationId: string): Promise<BackupArchive> {
|
||||
if (typeof restorationId === 'undefined') {
|
||||
throw new AppwriteException('Missing required parameter: "restorationId"');
|
||||
}
|
||||
const apiPath = '/backups/restorations/{restorationId}'.replace(
|
||||
'{restorationId}',
|
||||
restorationId
|
||||
);
|
||||
const payload: Payload = {};
|
||||
const uri = new URL(this.client.config.endpoint + apiPath);
|
||||
|
||||
const apiHeaders: { [header: string]: string } = {
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
return await this.client.call('get', uri, apiHeaders, payload);
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,9 @@ export async function checkForDatabaseBackupPolicies(
|
||||
|
||||
if (isCloud && backupsEnabled) {
|
||||
try {
|
||||
const policies = await sdk
|
||||
.forProject(region, projectId)
|
||||
.backups.listPolicies([Query.limit(1), Query.equal('resourceId', database.$id)]);
|
||||
const policies = await sdk.forProject(region, projectId).backups.listPolicies({
|
||||
queries: [Query.limit(1), Query.equal('resourceId', database.$id)]
|
||||
});
|
||||
|
||||
total = policies.total;
|
||||
} catch (e) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
Account,
|
||||
Assistant,
|
||||
Avatars,
|
||||
Backups,
|
||||
Client,
|
||||
Console,
|
||||
Functions,
|
||||
@@ -26,7 +27,6 @@ import {
|
||||
Organizations
|
||||
} from '@appwrite.io/console';
|
||||
import { Billing } from '../sdk/billing';
|
||||
import { Backups } from '../sdk/backups';
|
||||
import { Sources } from '$lib/sdk/sources';
|
||||
import {
|
||||
REGION_FRA,
|
||||
|
||||
@@ -77,7 +77,11 @@
|
||||
{#if data.view === 'grid'}
|
||||
<Grid {data} bind:showCreate />
|
||||
{:else}
|
||||
<Table {data} />
|
||||
<Table
|
||||
tables={data.tables}
|
||||
policies={data.policies}
|
||||
databases={data.databases}
|
||||
lastBackups={data.lastBackups} />
|
||||
{/if}
|
||||
|
||||
<PaginationWithLimit
|
||||
|
||||
@@ -4,7 +4,6 @@ import { sdk } from '$lib/stores/sdk';
|
||||
import { type Models, Query } from '@appwrite.io/console';
|
||||
import { timeFromNow } from '$lib/helpers/date';
|
||||
import type { PageLoad, RouteParams } from './$types';
|
||||
import type { BackupPolicy } from '$lib/sdk/backups';
|
||||
import { isSelfHosted } from '$lib/system';
|
||||
import { isCloud } from '$lib/system';
|
||||
import type { Plan } from '$lib/sdk/billing';
|
||||
@@ -71,7 +70,8 @@ async function fetchDatabasesAndBackups(
|
||||
})
|
||||
);
|
||||
|
||||
let lastBackups: Record<string, string>, policies: Record<string, BackupPolicy[]>;
|
||||
let lastBackups: Record<string, string> = {};
|
||||
let policies: Record<string, Models.BackupPolicy[]> = {};
|
||||
|
||||
if (isCloud && backupsEnabled) {
|
||||
[policies, lastBackups] = await Promise.all([
|
||||
@@ -86,19 +86,21 @@ async function fetchDatabasesAndBackups(
|
||||
async function fetchPolicies(databases: Models.DatabaseList, params: RouteParams) {
|
||||
if (isSelfHosted) return {};
|
||||
|
||||
const databasePolicies: Record<string, BackupPolicy[]> = {};
|
||||
const databasePolicies: Record<string, Models.BackupPolicy[]> = {};
|
||||
|
||||
await Promise.all(
|
||||
databases.databases.map(async (database) => {
|
||||
try {
|
||||
const { policies } = await sdk
|
||||
.forProject(params.region, params.project)
|
||||
.backups.listPolicies([
|
||||
// TODO: are all needed!?
|
||||
// Query.limit(3),
|
||||
Query.equal('resourceType', 'database'),
|
||||
Query.equal('resourceId', database.$id)
|
||||
]);
|
||||
.backups.listPolicies({
|
||||
queries: [
|
||||
// TODO: are all needed!?
|
||||
// Query.limit(3),
|
||||
Query.equal('resourceType', 'database'),
|
||||
Query.equal('resourceId', database.$id)
|
||||
]
|
||||
});
|
||||
|
||||
if (policies.length > 0) {
|
||||
databasePolicies[database.$id] = policies;
|
||||
@@ -122,12 +124,14 @@ async function fetchLastBackups(databases: Models.DatabaseList, params: RoutePar
|
||||
try {
|
||||
const { archives } = await sdk
|
||||
.forProject(params.region, params.project)
|
||||
.backups.listArchives([
|
||||
Query.limit(1),
|
||||
Query.orderDesc('$createdAt'),
|
||||
Query.equal('resourceType', 'database'),
|
||||
Query.equal('resourceId', database.$id)
|
||||
]);
|
||||
.backups.listArchives({
|
||||
queries: [
|
||||
Query.limit(1),
|
||||
Query.orderDesc('$createdAt'),
|
||||
Query.equal('resourceType', 'database'),
|
||||
Query.equal('resourceId', database.$id)
|
||||
]
|
||||
});
|
||||
|
||||
if (archives.length > 0) {
|
||||
lastBackups[database.$id] = timeFromNow(archives[0].$createdAt);
|
||||
|
||||
@@ -61,16 +61,14 @@
|
||||
const totalPoliciesPromise = totalPolicies.map((policy) => {
|
||||
cronExpression(policy);
|
||||
|
||||
return sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.backups.createPolicy(
|
||||
ID.unique(),
|
||||
['databases'],
|
||||
policy.retained,
|
||||
policy.schedule,
|
||||
policy.label,
|
||||
resourceId
|
||||
);
|
||||
return sdk.forProject(page.params.region, page.params.project).backups.createPolicy({
|
||||
policyId: ID.unique(),
|
||||
services: ['databases'],
|
||||
retention: policy.retained,
|
||||
schedule: policy.schedule,
|
||||
name: policy.label,
|
||||
resourceId
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(totalPoliciesPromise);
|
||||
|
||||
+12
-13
@@ -66,9 +66,10 @@
|
||||
|
||||
const createManualBackup = async () => {
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.backups.createArchive(['databases'], data.database.$id);
|
||||
await sdk.forProject(page.params.region, page.params.project).backups.createArchive({
|
||||
services: ['databases'],
|
||||
resourceId: data.database.$id
|
||||
});
|
||||
await invalidate(Dependencies.BACKUPS);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
@@ -119,16 +120,14 @@
|
||||
const totalPoliciesPromise = totalPolicies.map((policy) => {
|
||||
cronExpression(policy);
|
||||
|
||||
return sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.backups.createPolicy(
|
||||
ID.unique(),
|
||||
['databases'],
|
||||
policy.retained,
|
||||
policy.schedule,
|
||||
policy.label,
|
||||
data.database.$id
|
||||
);
|
||||
return sdk.forProject(page.params.region, page.params.project).backups.createPolicy({
|
||||
policyId: ID.unique(),
|
||||
services: ['databases'],
|
||||
retention: policy.retained,
|
||||
schedule: policy.schedule,
|
||||
name: policy.label,
|
||||
resourceId: data.database.$id
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
+14
-14
@@ -2,8 +2,8 @@ import { getLimit, getPage, getView, pageToOffset, View } from '$lib/helpers/loa
|
||||
import { Dependencies, PAGE_LIMIT } from '$lib/constants';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import type { BackupArchive, BackupArchiveList, BackupPolicyList } from '$lib/sdk/backups';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { type Models } from '@appwrite.io/console';
|
||||
|
||||
export const load = async ({ params, url, route, depends, parent }) => {
|
||||
depends(Dependencies.BACKUPS);
|
||||
@@ -13,8 +13,8 @@ export const load = async ({ params, url, route, depends, parent }) => {
|
||||
const view = getView(url, route, View.Grid);
|
||||
const offset = pageToOffset(page, limit);
|
||||
|
||||
let backups: BackupArchiveList = { total: 0, archives: [] };
|
||||
let policies: BackupPolicyList = { total: 0, policies: [] };
|
||||
let backups: Models.BackupArchiveList = { total: 0, archives: [] };
|
||||
let policies: Models.BackupPolicyList = { total: 0, policies: [] };
|
||||
|
||||
// already loaded by parent.
|
||||
const { currentPlan } = await parent();
|
||||
@@ -23,23 +23,23 @@ export const load = async ({ params, url, route, depends, parent }) => {
|
||||
if (isCloud && backupsEnabled) {
|
||||
try {
|
||||
[backups, policies] = await Promise.all([
|
||||
sdk
|
||||
.forProject(params.region, params.project)
|
||||
.backups.listArchives([
|
||||
sdk.forProject(params.region, params.project).backups.listArchives({
|
||||
queries: [
|
||||
Query.limit(limit),
|
||||
Query.offset(offset),
|
||||
Query.orderDesc('$createdAt'),
|
||||
Query.equal('resourceType', 'database'),
|
||||
Query.equal('resourceId', params.database)
|
||||
]),
|
||||
]
|
||||
}),
|
||||
|
||||
sdk
|
||||
.forProject(params.region, params.project)
|
||||
.backups.listPolicies([
|
||||
sdk.forProject(params.region, params.project).backups.listPolicies({
|
||||
queries: [
|
||||
Query.orderDesc('$createdAt'),
|
||||
Query.equal('resourceType', 'database'),
|
||||
Query.equal('resourceId', params.database)
|
||||
])
|
||||
]
|
||||
})
|
||||
]);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
@@ -59,17 +59,17 @@ export const load = async ({ params, url, route, depends, parent }) => {
|
||||
};
|
||||
};
|
||||
|
||||
const groupArchivesByPolicy = (archives: BackupArchive[]) => {
|
||||
const groupArchivesByPolicy = (archives: Models.BackupArchive[]) => {
|
||||
return archives.reduce((acc, archive) => {
|
||||
if (!acc.has(archive.policyId)) {
|
||||
acc.set(archive.policyId, []);
|
||||
}
|
||||
acc.get(archive.policyId)!.push(archive);
|
||||
return acc;
|
||||
}, new Map<string, BackupArchive[]>());
|
||||
}, new Map<string, Models.BackupArchive[]>());
|
||||
};
|
||||
|
||||
const getLatestBackupForPolicies = (policyIdMap: Map<string, BackupArchive[]>) => {
|
||||
const getLatestBackupForPolicies = (policyIdMap: Map<string, Models.BackupArchive[]>) => {
|
||||
const latestBackups = new Map<string, string | null>();
|
||||
for (const [policyId, archives] of policyIdMap) {
|
||||
const latestBackup = archives.sort(
|
||||
|
||||
+6
-6
@@ -11,7 +11,6 @@
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import EmptyDark from '$lib/images/backups/backups-dark.png';
|
||||
import EmptyLight from '$lib/images/backups/backups-light.png';
|
||||
import type { BackupPolicy, BackupPolicyList } from '$lib/sdk/backups';
|
||||
import { backupFrequencies } from '$lib/helpers/backups';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
import {
|
||||
@@ -27,21 +26,22 @@
|
||||
import { Confirm } from '$lib/components/index.js';
|
||||
import Ellipse from './components/Ellipse.svelte';
|
||||
import { page } from '$app/state';
|
||||
import { type Models } from '@appwrite.io/console';
|
||||
|
||||
let showDelete = false;
|
||||
let selectedPolicy: BackupPolicy = null;
|
||||
let selectedPolicy: Models.BackupPolicy = null;
|
||||
|
||||
let showEveryPolicy = false;
|
||||
|
||||
export let showCreatePolicy = false;
|
||||
export let policies: BackupPolicyList;
|
||||
export let policies: Models.BackupPolicyList;
|
||||
export let lastBackupDates: Record<string, string>;
|
||||
|
||||
async function deletePolicy() {
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.backups.deletePolicy(selectedPolicy.$id);
|
||||
await sdk.forProject(page.params.region, page.params.project).backups.deletePolicy({
|
||||
policyId: selectedPolicy.$id
|
||||
});
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Backup policy has been deleted'
|
||||
|
||||
+15
-14
@@ -15,10 +15,9 @@
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { calculateSize } from '$lib/helpers/sizeConvertion';
|
||||
import { ID } from '@appwrite.io/console';
|
||||
import { ID, type Models } from '@appwrite.io/console';
|
||||
import { columns } from './store';
|
||||
import { database } from '../store';
|
||||
import type { BackupArchive, BackupPolicy } from '$lib/sdk/backups';
|
||||
import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { copy } from '$lib/helpers/copy';
|
||||
import { LabelCard } from '$lib/components/index.js';
|
||||
@@ -53,7 +52,7 @@
|
||||
} = $props();
|
||||
|
||||
let showDelete = $state(false);
|
||||
let selectedBackup: BackupArchive | null = $state(null);
|
||||
let selectedBackup: Models.BackupArchive | null = $state(null);
|
||||
|
||||
let showDropdown = [];
|
||||
|
||||
@@ -87,15 +86,15 @@
|
||||
);
|
||||
});
|
||||
|
||||
function getPolicyDetails(policyId: string | null): BackupPolicy | null {
|
||||
function getPolicyDetails(policyId: string | null): Models.BackupPolicy | null {
|
||||
return data.policies.policies.find((policy) => policy.$id === policyId);
|
||||
}
|
||||
|
||||
function getCleanBackupName(backup: BackupArchive): string {
|
||||
function getCleanBackupName(backup: Models.BackupArchive): string {
|
||||
return toLocaleDateTime(backup.$createdAt).replaceAll(',', '');
|
||||
}
|
||||
|
||||
function getBackupStatus(backup: BackupArchive) {
|
||||
function getBackupStatus(backup: Models.BackupArchive) {
|
||||
switch (backup.status) {
|
||||
case 'pending':
|
||||
return 'pending';
|
||||
@@ -115,7 +114,7 @@
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.backups.deleteArchive(archiveId);
|
||||
.backups.deleteArchive({ archiveId });
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
@@ -135,7 +134,9 @@
|
||||
|
||||
async function deleteBackups(batchDelete: DeleteOperation): Promise<DeleteOperationState> {
|
||||
const result = await batchDelete((archiveId) =>
|
||||
sdk.forProject(page.params.region, page.params.project).backups.deleteArchive(archiveId)
|
||||
sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.backups.deleteArchive({ archiveId })
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -160,12 +161,12 @@
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.backups.createRestoration(
|
||||
selectedBackup.$id,
|
||||
['databases'],
|
||||
newDatabaseInfo.id ?? ID.unique(),
|
||||
newDatabaseInfo.name
|
||||
);
|
||||
.backups.createRestoration({
|
||||
archiveId: selectedBackup.$id,
|
||||
services: ['databases'],
|
||||
newResourceId: newDatabaseInfo.id ?? ID.unique(),
|
||||
newResourceName: newDatabaseInfo.name
|
||||
});
|
||||
await invalidate(Dependencies.BACKUPS);
|
||||
|
||||
addNotification({
|
||||
|
||||
@@ -6,10 +6,19 @@
|
||||
import { columns } from './store';
|
||||
import { IconExclamation } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Layout, Tooltip, Table, Icon } from '@appwrite.io/pink-svelte';
|
||||
import type { BackupPolicy } from '$lib/sdk/backups';
|
||||
import { type Models } from '@appwrite.io/console';
|
||||
|
||||
export let data;
|
||||
const tables = data.tables;
|
||||
let {
|
||||
tables,
|
||||
policies,
|
||||
databases,
|
||||
lastBackups
|
||||
}: {
|
||||
tables: Record<string, string>;
|
||||
databases: Models.DatabaseList;
|
||||
lastBackups: Record<string, string>;
|
||||
policies: Record<string, Models.BackupPolicy[]>;
|
||||
} = $props();
|
||||
|
||||
function getPolicyDescription(cron: string): string {
|
||||
const [minute, hour, dayOfMonth, , dayOfWeek] = cron.split(' ');
|
||||
@@ -19,6 +28,10 @@
|
||||
if (minute !== '*' && hour === '*') return 'Hourly';
|
||||
if (hour !== '*') return 'Daily';
|
||||
}
|
||||
|
||||
function getPoliciesDescription(policies: Models.BackupPolicy[] | null): string {
|
||||
return policies?.map((policy) => getPolicyDescription(policy.schedule)).join(', ') ?? '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<Table.Root columns={$columns} let:root>
|
||||
@@ -27,7 +40,7 @@
|
||||
<Table.Header.Cell column={id} {root}>{title}</Table.Header.Cell>
|
||||
{/each}
|
||||
</svelte:fragment>
|
||||
{#each data.databases.databases as database (database.$id)}
|
||||
{#each databases.databases as database (database.$id)}
|
||||
<!-- takes directly to the spreadsheet -->
|
||||
{@const tableId = tables[database?.$id] ?? null}
|
||||
{@const tableHref = tableId ? `/table-${tableId}` : ''}
|
||||
@@ -45,18 +58,16 @@
|
||||
{:else if column.id === 'name'}
|
||||
{database.name}
|
||||
{:else if column.id === 'backup'}
|
||||
{@const policies = data.policies?.[database.$id] ?? null}
|
||||
{@const lastBackup = data.lastBackups?.[database.$id] ?? null}
|
||||
{@const description = policies
|
||||
?.map((policy: BackupPolicy) => getPolicyDescription(policy.schedule))
|
||||
.join(', ')}
|
||||
{@const backupPolicies = policies?.[database.$id] ?? null}
|
||||
{@const lastBackup = lastBackups?.[database.$id] ?? null}
|
||||
{@const description = getPoliciesDescription(backupPolicies)}
|
||||
|
||||
<Tooltip
|
||||
placement="bottom"
|
||||
disabled={!policies || !lastBackup}
|
||||
disabled={!backupPolicies || !lastBackup}
|
||||
maxWidth="fit-content">
|
||||
<span class="u-trim">
|
||||
{#if !policies}
|
||||
{#if !backupPolicies}
|
||||
<Layout.Stack direction="row" gap="xxs" alignItems="center">
|
||||
<Icon
|
||||
icon={IconExclamation}
|
||||
|
||||
Reference in New Issue
Block a user