From f8d34b8fadb5f9d02e4b75d3c8be60aff093bcde Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 3 Jun 2022 17:01:30 +0800 Subject: [PATCH 01/14] Small styling update to backups empty state --- .../interfaces/Database/Backups/BackupsList.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/Database/Backups/BackupsList.tsx b/studio/components/interfaces/Database/Backups/BackupsList.tsx index 9f1e79464da..92d5319f704 100644 --- a/studio/components/interfaces/Database/Backups/BackupsList.tsx +++ b/studio/components/interfaces/Database/Backups/BackupsList.tsx @@ -55,12 +55,10 @@ const BackupsList: FC = ({}) => { return (
{!sortedBackups?.length && tierId !== STRIPE_PRODUCT_IDS.FREE ? ( -
+
-
- -
-

No backups created yet. Check again tomorrow.

+ +

No backups created yet. Check again tomorrow.

) : ( From feec6ef61134defa538316938f1a132c02f5465a Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 9 Jun 2022 15:23:35 +0800 Subject: [PATCH 02/14] Implement selection interface for PITR backups --- .../Database/Backups/BackupsList.tsx | 34 +- .../PITRBackupSelection.constants.ts | 1204 +++++++++++++++++ .../PITRBackupSelection.types.ts | 8 + .../PITRBackupSelection.utils.ts | 20 + .../Backups/PITRBackupSelection/index.tsx | 229 ++++ .../components/interfaces/Database/index.ts | 2 + .../layouts/DatabaseLayout/DatabaseLayout.tsx | 6 +- .../DatabaseLayout/DatabaseMenu.utils.ts | 96 +- studio/components/ui/ConfirmationModal.tsx | 4 +- studio/components/ui/UpgradeToPro.tsx | 12 +- studio/pages/_app.tsx | 2 +- .../pages/project/[ref]/database/backups.tsx | 25 - .../project/[ref]/database/backups/pitr.tsx | 45 + .../[ref]/database/backups/scheduled.tsx | 44 + studio/stores/RootStore.ts | 26 +- studio/stores/project/ProjectBackupsStore.ts | 107 ++ .../ProjectContentStore.ts | 0 .../ProjectFunctionsStore.ts | 3 - 18 files changed, 1743 insertions(+), 124 deletions(-) create mode 100644 studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.constants.ts create mode 100644 studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.types.ts create mode 100644 studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.utils.ts create mode 100644 studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx delete mode 100644 studio/pages/project/[ref]/database/backups.tsx create mode 100644 studio/pages/project/[ref]/database/backups/pitr.tsx create mode 100644 studio/pages/project/[ref]/database/backups/scheduled.tsx create mode 100644 studio/stores/project/ProjectBackupsStore.ts rename studio/stores/{content => project}/ProjectContentStore.ts (100%) rename studio/stores/{functions => project}/ProjectFunctionsStore.ts (97%) diff --git a/studio/components/interfaces/Database/Backups/BackupsList.tsx b/studio/components/interfaces/Database/Backups/BackupsList.tsx index 92d5319f704..0e0f95b7a18 100644 --- a/studio/components/interfaces/Database/Backups/BackupsList.tsx +++ b/studio/components/interfaces/Database/Backups/BackupsList.tsx @@ -1,6 +1,6 @@ -import React, { FC, useState, useEffect } from 'react' +import { FC } from 'react' import { observer } from 'mobx-react-lite' -import { Typography, IconInfo } from '@supabase/ui' +import { IconInfo } from '@supabase/ui' import { useStore } from 'hooks' import { STRIPE_PRODUCT_IDS } from 'lib/constants' @@ -12,35 +12,13 @@ import Panel from 'components/to-be-cleaned/Panel' interface Props {} const BackupsList: FC = ({}) => { - const { ui, app } = useStore() + const { ui, backups } = useStore() const projectRef = ui.selectedProject?.ref || 'default' - const [isLoading, setIsLoading] = useState(true) - const [projectData, setProjectData] = useState([]) + if (backups.isLoading) return - useEffect(() => { - retrieveBackups() - }, []) - - const retrieveBackups = async () => { - setIsLoading(true) - const projectData = (await app.database.getBackups(projectRef)) as any - setProjectData(projectData) - setIsLoading(false) - } - - if (isLoading) return - if (projectData.error) { - return ( -
- Error loading backups -
- ) - } - - // Data Loaded - const { backups, tierId } = projectData - const sortedBackups = backups.sort((a: any, b: any) => b.id - a.id) + const { tierId } = backups.configuration + const sortedBackups = backups.list() if (tierId === STRIPE_PRODUCT_IDS.FREE) { return ( diff --git a/studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.constants.ts b/studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.constants.ts new file mode 100644 index 00000000000..c0f1f870270 --- /dev/null +++ b/studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.constants.ts @@ -0,0 +1,1204 @@ +// https://github.com/dmfilipenko/timezones.json/blob/master/timezones.json +export const ALL_TIMEZONES = [ + { + value: 'Dateline Standard Time', + abbr: 'DST', + offset: -12, + isdst: false, + text: '(UTC-12:00) International Date Line West', + utc: ['Etc/GMT+12'], + }, + { + value: 'UTC-11', + abbr: 'U', + offset: -11, + isdst: false, + text: '(UTC-11:00) Coordinated Universal Time-11', + utc: ['Etc/GMT+11', 'Pacific/Midway', 'Pacific/Niue', 'Pacific/Pago_Pago'], + }, + { + value: 'Hawaiian Standard Time', + abbr: 'HST', + offset: -10, + isdst: false, + text: '(UTC-10:00) Hawaii', + utc: [ + 'Etc/GMT+10', + 'Pacific/Honolulu', + 'Pacific/Johnston', + 'Pacific/Rarotonga', + 'Pacific/Tahiti', + ], + }, + { + value: 'Alaskan Standard Time', + abbr: 'AKDT', + offset: -8, + isdst: true, + text: '(UTC-09:00) Alaska', + utc: [ + 'America/Anchorage', + 'America/Juneau', + 'America/Nome', + 'America/Sitka', + 'America/Yakutat', + ], + }, + { + value: 'Pacific Standard Time (Mexico)', + abbr: 'PDT', + offset: -7, + isdst: true, + text: '(UTC-08:00) Baja California', + utc: ['America/Santa_Isabel'], + }, + { + value: 'Pacific Daylight Time', + abbr: 'PDT', + offset: -7, + isdst: true, + text: '(UTC-07:00) Pacific Daylight Time (US & Canada)', + utc: ['America/Los_Angeles', 'America/Tijuana', 'America/Vancouver'], + }, + { + value: 'Pacific Standard Time', + abbr: 'PST', + offset: -8, + isdst: false, + text: '(UTC-08:00) Pacific Standard Time (US & Canada)', + utc: ['America/Los_Angeles', 'America/Tijuana', 'America/Vancouver', 'PST8PDT'], + }, + { + value: 'US Mountain Standard Time', + abbr: 'UMST', + offset: -7, + isdst: false, + text: '(UTC-07:00) Arizona', + utc: [ + 'America/Creston', + 'America/Dawson', + 'America/Dawson_Creek', + 'America/Hermosillo', + 'America/Phoenix', + 'America/Whitehorse', + 'Etc/GMT+7', + ], + }, + { + value: 'Mountain Standard Time (Mexico)', + abbr: 'MDT', + offset: -6, + isdst: true, + text: '(UTC-07:00) Chihuahua, La Paz, Mazatlan', + utc: ['America/Chihuahua', 'America/Mazatlan'], + }, + { + value: 'Mountain Standard Time', + abbr: 'MDT', + offset: -6, + isdst: true, + text: '(UTC-07:00) Mountain Time (US & Canada)', + utc: [ + 'America/Boise', + 'America/Cambridge_Bay', + 'America/Denver', + 'America/Edmonton', + 'America/Inuvik', + 'America/Ojinaga', + 'America/Yellowknife', + 'MST7MDT', + ], + }, + { + value: 'Central America Standard Time', + abbr: 'CAST', + offset: -6, + isdst: false, + text: '(UTC-06:00) Central America', + utc: [ + 'America/Belize', + 'America/Costa_Rica', + 'America/El_Salvador', + 'America/Guatemala', + 'America/Managua', + 'America/Tegucigalpa', + 'Etc/GMT+6', + 'Pacific/Galapagos', + ], + }, + { + value: 'Central Standard Time', + abbr: 'CDT', + offset: -5, + isdst: true, + text: '(UTC-06:00) Central Time (US & Canada)', + utc: [ + 'America/Chicago', + 'America/Indiana/Knox', + 'America/Indiana/Tell_City', + 'America/Matamoros', + 'America/Menominee', + 'America/North_Dakota/Beulah', + 'America/North_Dakota/Center', + 'America/North_Dakota/New_Salem', + 'America/Rainy_River', + 'America/Rankin_Inlet', + 'America/Resolute', + 'America/Winnipeg', + 'CST6CDT', + ], + }, + { + value: 'Central Standard Time (Mexico)', + abbr: 'CDT', + offset: -5, + isdst: true, + text: '(UTC-06:00) Guadalajara, Mexico City, Monterrey', + utc: [ + 'America/Bahia_Banderas', + 'America/Cancun', + 'America/Merida', + 'America/Mexico_City', + 'America/Monterrey', + ], + }, + { + value: 'Canada Central Standard Time', + abbr: 'CCST', + offset: -6, + isdst: false, + text: '(UTC-06:00) Saskatchewan', + utc: ['America/Regina', 'America/Swift_Current'], + }, + { + value: 'SA Pacific Standard Time', + abbr: 'SPST', + offset: -5, + isdst: false, + text: '(UTC-05:00) Bogota, Lima, Quito', + utc: [ + 'America/Bogota', + 'America/Cayman', + 'America/Coral_Harbour', + 'America/Eirunepe', + 'America/Guayaquil', + 'America/Jamaica', + 'America/Lima', + 'America/Panama', + 'America/Rio_Branco', + 'Etc/GMT+5', + ], + }, + { + value: 'Eastern Standard Time', + abbr: 'EST', + offset: -5, + isdst: false, + text: '(UTC-05:00) Eastern Time (US & Canada)', + utc: [ + 'America/Detroit', + 'America/Havana', + 'America/Indiana/Petersburg', + 'America/Indiana/Vincennes', + 'America/Indiana/Winamac', + 'America/Iqaluit', + 'America/Kentucky/Monticello', + 'America/Louisville', + 'America/Montreal', + 'America/Nassau', + 'America/New_York', + 'America/Nipigon', + 'America/Pangnirtung', + 'America/Port-au-Prince', + 'America/Thunder_Bay', + 'America/Toronto', + ], + }, + { + value: 'Eastern Daylight Time', + abbr: 'EDT', + offset: -4, + isdst: true, + text: '(UTC-04:00) Eastern Daylight Time (US & Canada)', + utc: [ + 'America/Detroit', + 'America/Havana', + 'America/Indiana/Petersburg', + 'America/Indiana/Vincennes', + 'America/Indiana/Winamac', + 'America/Iqaluit', + 'America/Kentucky/Monticello', + 'America/Louisville', + 'America/Montreal', + 'America/Nassau', + 'America/New_York', + 'America/Nipigon', + 'America/Pangnirtung', + 'America/Port-au-Prince', + 'America/Thunder_Bay', + 'America/Toronto', + ], + }, + { + value: 'US Eastern Standard Time', + abbr: 'UEDT', + offset: -5, + isdst: false, + text: '(UTC-05:00) Indiana (East)', + utc: ['America/Indiana/Marengo', 'America/Indiana/Vevay', 'America/Indianapolis'], + }, + { + value: 'Venezuela Standard Time', + abbr: 'VST', + offset: -4.5, + isdst: false, + text: '(UTC-04:30) Caracas', + utc: ['America/Caracas'], + }, + { + value: 'Paraguay Standard Time', + abbr: 'PYT', + offset: -4, + isdst: false, + text: '(UTC-04:00) Asuncion', + utc: ['America/Asuncion'], + }, + { + value: 'Atlantic Standard Time', + abbr: 'ADT', + offset: -3, + isdst: true, + text: '(UTC-04:00) Atlantic Time (Canada)', + utc: [ + 'America/Glace_Bay', + 'America/Goose_Bay', + 'America/Halifax', + 'America/Moncton', + 'America/Thule', + 'Atlantic/Bermuda', + ], + }, + { + value: 'Central Brazilian Standard Time', + abbr: 'CBST', + offset: -4, + isdst: false, + text: '(UTC-04:00) Cuiaba', + utc: ['America/Campo_Grande', 'America/Cuiaba'], + }, + { + value: 'SA Western Standard Time', + abbr: 'SWST', + offset: -4, + isdst: false, + text: '(UTC-04:00) Georgetown, La Paz, Manaus, San Juan', + utc: [ + 'America/Anguilla', + 'America/Antigua', + 'America/Aruba', + 'America/Barbados', + 'America/Blanc-Sablon', + 'America/Boa_Vista', + 'America/Curacao', + 'America/Dominica', + 'America/Grand_Turk', + 'America/Grenada', + 'America/Guadeloupe', + 'America/Guyana', + 'America/Kralendijk', + 'America/La_Paz', + 'America/Lower_Princes', + 'America/Manaus', + 'America/Marigot', + 'America/Martinique', + 'America/Montserrat', + 'America/Port_of_Spain', + 'America/Porto_Velho', + 'America/Puerto_Rico', + 'America/Santo_Domingo', + 'America/St_Barthelemy', + 'America/St_Kitts', + 'America/St_Lucia', + 'America/St_Thomas', + 'America/St_Vincent', + 'America/Tortola', + 'Etc/GMT+4', + ], + }, + { + value: 'Pacific SA Standard Time', + abbr: 'PSST', + offset: -4, + isdst: false, + text: '(UTC-04:00) Santiago', + utc: ['America/Santiago', 'Antarctica/Palmer'], + }, + { + value: 'Newfoundland Standard Time', + abbr: 'NDT', + offset: -2.5, + isdst: true, + text: '(UTC-03:30) Newfoundland', + utc: ['America/St_Johns'], + }, + { + value: 'E. South America Standard Time', + abbr: 'ESAST', + offset: -3, + isdst: false, + text: '(UTC-03:00) Brasilia', + utc: ['America/Sao_Paulo'], + }, + { + value: 'Argentina Standard Time', + abbr: 'AST', + offset: -3, + isdst: false, + text: '(UTC-03:00) Buenos Aires', + utc: [ + 'America/Argentina/La_Rioja', + 'America/Argentina/Rio_Gallegos', + 'America/Argentina/Salta', + 'America/Argentina/San_Juan', + 'America/Argentina/San_Luis', + 'America/Argentina/Tucuman', + 'America/Argentina/Ushuaia', + 'America/Buenos_Aires', + 'America/Catamarca', + 'America/Cordoba', + 'America/Jujuy', + 'America/Mendoza', + ], + }, + { + value: 'SA Eastern Standard Time', + abbr: 'SEST', + offset: -3, + isdst: false, + text: '(UTC-03:00) Cayenne, Fortaleza', + utc: [ + 'America/Araguaina', + 'America/Belem', + 'America/Cayenne', + 'America/Fortaleza', + 'America/Maceio', + 'America/Paramaribo', + 'America/Recife', + 'America/Santarem', + 'Antarctica/Rothera', + 'Atlantic/Stanley', + 'Etc/GMT+3', + ], + }, + { + value: 'Greenland Standard Time', + abbr: 'GDT', + offset: -3, + isdst: true, + text: '(UTC-03:00) Greenland', + utc: ['America/Godthab'], + }, + { + value: 'Montevideo Standard Time', + abbr: 'MST', + offset: -3, + isdst: false, + text: '(UTC-03:00) Montevideo', + utc: ['America/Montevideo'], + }, + { + value: 'Bahia Standard Time', + abbr: 'BST', + offset: -3, + isdst: false, + text: '(UTC-03:00) Salvador', + utc: ['America/Bahia'], + }, + { + value: 'UTC-02', + abbr: 'U', + offset: -2, + isdst: false, + text: '(UTC-02:00) Coordinated Universal Time-02', + utc: ['America/Noronha', 'Atlantic/South_Georgia', 'Etc/GMT+2'], + }, + { + value: 'Mid-Atlantic Standard Time', + abbr: 'MDT', + offset: -1, + isdst: true, + text: '(UTC-02:00) Mid-Atlantic - Old', + utc: [], + }, + { + value: 'Azores Standard Time', + abbr: 'ADT', + offset: 0, + isdst: true, + text: '(UTC-01:00) Azores', + utc: ['America/Scoresbysund', 'Atlantic/Azores'], + }, + { + value: 'Cape Verde Standard Time', + abbr: 'CVST', + offset: -1, + isdst: false, + text: '(UTC-01:00) Cape Verde Is.', + utc: ['Atlantic/Cape_Verde', 'Etc/GMT+1'], + }, + { + value: 'Morocco Standard Time', + abbr: 'MDT', + offset: 1, + isdst: true, + text: '(UTC) Casablanca', + utc: ['Africa/Casablanca', 'Africa/El_Aaiun'], + }, + { + value: 'UTC', + abbr: 'UTC', + offset: 0, + isdst: false, + text: '(UTC) Coordinated Universal Time', + utc: ['America/Danmarkshavn', 'Etc/GMT'], + }, + { + value: 'GMT Standard Time', + abbr: 'GMT', + offset: 0, + isdst: false, + text: '(UTC) Edinburgh, London', + utc: ['Europe/Isle_of_Man', 'Europe/Guernsey', 'Europe/Jersey', 'Europe/London'], + }, + { + value: 'British Summer Time', + abbr: 'BST', + offset: 1, + isdst: true, + text: '(UTC+01:00) Edinburgh, London', + utc: ['Europe/Isle_of_Man', 'Europe/Guernsey', 'Europe/Jersey', 'Europe/London'], + }, + { + value: 'GMT Standard Time', + abbr: 'GDT', + offset: 1, + isdst: true, + text: '(UTC) Dublin, Lisbon', + utc: [ + 'Atlantic/Canary', + 'Atlantic/Faeroe', + 'Atlantic/Madeira', + 'Europe/Dublin', + 'Europe/Lisbon', + ], + }, + { + value: 'Greenwich Standard Time', + abbr: 'GST', + offset: 0, + isdst: false, + text: '(UTC) Monrovia, Reykjavik', + utc: [ + 'Africa/Abidjan', + 'Africa/Accra', + 'Africa/Bamako', + 'Africa/Banjul', + 'Africa/Bissau', + 'Africa/Conakry', + 'Africa/Dakar', + 'Africa/Freetown', + 'Africa/Lome', + 'Africa/Monrovia', + 'Africa/Nouakchott', + 'Africa/Ouagadougou', + 'Africa/Sao_Tome', + 'Atlantic/Reykjavik', + 'Atlantic/St_Helena', + ], + }, + { + value: 'W. Europe Standard Time', + abbr: 'WEDT', + offset: 2, + isdst: true, + text: '(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna', + utc: [ + 'Arctic/Longyearbyen', + 'Europe/Amsterdam', + 'Europe/Andorra', + 'Europe/Berlin', + 'Europe/Busingen', + 'Europe/Gibraltar', + 'Europe/Luxembourg', + 'Europe/Malta', + 'Europe/Monaco', + 'Europe/Oslo', + 'Europe/Rome', + 'Europe/San_Marino', + 'Europe/Stockholm', + 'Europe/Vaduz', + 'Europe/Vatican', + 'Europe/Vienna', + 'Europe/Zurich', + ], + }, + { + value: 'Central Europe Standard Time', + abbr: 'CEDT', + offset: 2, + isdst: true, + text: '(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague', + utc: [ + 'Europe/Belgrade', + 'Europe/Bratislava', + 'Europe/Budapest', + 'Europe/Ljubljana', + 'Europe/Podgorica', + 'Europe/Prague', + 'Europe/Tirane', + ], + }, + { + value: 'Romance Standard Time', + abbr: 'RDT', + offset: 2, + isdst: true, + text: '(UTC+01:00) Brussels, Copenhagen, Madrid, Paris', + utc: ['Africa/Ceuta', 'Europe/Brussels', 'Europe/Copenhagen', 'Europe/Madrid', 'Europe/Paris'], + }, + { + value: 'Central European Standard Time', + abbr: 'CEDT', + offset: 2, + isdst: true, + text: '(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb', + utc: ['Europe/Sarajevo', 'Europe/Skopje', 'Europe/Warsaw', 'Europe/Zagreb'], + }, + { + value: 'W. Central Africa Standard Time', + abbr: 'WCAST', + offset: 1, + isdst: false, + text: '(UTC+01:00) West Central Africa', + utc: [ + 'Africa/Algiers', + 'Africa/Bangui', + 'Africa/Brazzaville', + 'Africa/Douala', + 'Africa/Kinshasa', + 'Africa/Lagos', + 'Africa/Libreville', + 'Africa/Luanda', + 'Africa/Malabo', + 'Africa/Ndjamena', + 'Africa/Niamey', + 'Africa/Porto-Novo', + 'Africa/Tunis', + 'Etc/GMT-1', + ], + }, + { + value: 'Namibia Standard Time', + abbr: 'NST', + offset: 1, + isdst: false, + text: '(UTC+01:00) Windhoek', + utc: ['Africa/Windhoek'], + }, + { + value: 'GTB Standard Time', + abbr: 'GDT', + offset: 3, + isdst: true, + text: '(UTC+02:00) Athens, Bucharest', + utc: ['Asia/Nicosia', 'Europe/Athens', 'Europe/Bucharest', 'Europe/Chisinau'], + }, + { + value: 'Middle East Standard Time', + abbr: 'MEDT', + offset: 3, + isdst: true, + text: '(UTC+02:00) Beirut', + utc: ['Asia/Beirut'], + }, + { + value: 'Egypt Standard Time', + abbr: 'EST', + offset: 2, + isdst: false, + text: '(UTC+02:00) Cairo', + utc: ['Africa/Cairo'], + }, + { + value: 'Syria Standard Time', + abbr: 'SDT', + offset: 3, + isdst: true, + text: '(UTC+02:00) Damascus', + utc: ['Asia/Damascus'], + }, + { + value: 'E. Europe Standard Time', + abbr: 'EEDT', + offset: 3, + isdst: true, + text: '(UTC+02:00) E. Europe', + utc: [ + 'Asia/Nicosia', + 'Europe/Athens', + 'Europe/Bucharest', + 'Europe/Chisinau', + 'Europe/Helsinki', + 'Europe/Kiev', + 'Europe/Mariehamn', + 'Europe/Nicosia', + 'Europe/Riga', + 'Europe/Sofia', + 'Europe/Tallinn', + 'Europe/Uzhgorod', + 'Europe/Vilnius', + 'Europe/Zaporozhye', + ], + }, + { + value: 'South Africa Standard Time', + abbr: 'SAST', + offset: 2, + isdst: false, + text: '(UTC+02:00) Harare, Pretoria', + utc: [ + 'Africa/Blantyre', + 'Africa/Bujumbura', + 'Africa/Gaborone', + 'Africa/Harare', + 'Africa/Johannesburg', + 'Africa/Kigali', + 'Africa/Lubumbashi', + 'Africa/Lusaka', + 'Africa/Maputo', + 'Africa/Maseru', + 'Africa/Mbabane', + 'Etc/GMT-2', + ], + }, + { + value: 'FLE Standard Time', + abbr: 'FDT', + offset: 3, + isdst: true, + text: '(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius', + utc: [ + 'Europe/Helsinki', + 'Europe/Kiev', + 'Europe/Mariehamn', + 'Europe/Riga', + 'Europe/Sofia', + 'Europe/Tallinn', + 'Europe/Uzhgorod', + 'Europe/Vilnius', + 'Europe/Zaporozhye', + ], + }, + { + value: 'Turkey Standard Time', + abbr: 'TDT', + offset: 3, + isdst: false, + text: '(UTC+03:00) Istanbul', + utc: ['Europe/Istanbul'], + }, + { + value: 'Israel Standard Time', + abbr: 'JDT', + offset: 3, + isdst: true, + text: '(UTC+02:00) Jerusalem', + utc: ['Asia/Jerusalem'], + }, + { + value: 'Libya Standard Time', + abbr: 'LST', + offset: 2, + isdst: false, + text: '(UTC+02:00) Tripoli', + utc: ['Africa/Tripoli'], + }, + { + value: 'Jordan Standard Time', + abbr: 'JST', + offset: 3, + isdst: false, + text: '(UTC+03:00) Amman', + utc: ['Asia/Amman'], + }, + { + value: 'Arabic Standard Time', + abbr: 'AST', + offset: 3, + isdst: false, + text: '(UTC+03:00) Baghdad', + utc: ['Asia/Baghdad'], + }, + { + value: 'Kaliningrad Standard Time', + abbr: 'KST', + offset: 3, + isdst: false, + text: '(UTC+02:00) Kaliningrad', + utc: ['Europe/Kaliningrad'], + }, + { + value: 'Arab Standard Time', + abbr: 'AST', + offset: 3, + isdst: false, + text: '(UTC+03:00) Kuwait, Riyadh', + utc: ['Asia/Aden', 'Asia/Bahrain', 'Asia/Kuwait', 'Asia/Qatar', 'Asia/Riyadh'], + }, + { + value: 'E. Africa Standard Time', + abbr: 'EAST', + offset: 3, + isdst: false, + text: '(UTC+03:00) Nairobi', + utc: [ + 'Africa/Addis_Ababa', + 'Africa/Asmera', + 'Africa/Dar_es_Salaam', + 'Africa/Djibouti', + 'Africa/Juba', + 'Africa/Kampala', + 'Africa/Khartoum', + 'Africa/Mogadishu', + 'Africa/Nairobi', + 'Antarctica/Syowa', + 'Etc/GMT-3', + 'Indian/Antananarivo', + 'Indian/Comoro', + 'Indian/Mayotte', + ], + }, + { + value: 'Moscow Standard Time', + abbr: 'MSK', + offset: 3, + isdst: false, + text: '(UTC+03:00) Moscow, St. Petersburg, Volgograd, Minsk', + utc: ['Europe/Kirov', 'Europe/Moscow', 'Europe/Simferopol', 'Europe/Volgograd', 'Europe/Minsk'], + }, + { + value: 'Samara Time', + abbr: 'SAMT', + offset: 4, + isdst: false, + text: '(UTC+04:00) Samara, Ulyanovsk, Saratov', + utc: ['Europe/Astrakhan', 'Europe/Samara', 'Europe/Ulyanovsk'], + }, + { + value: 'Iran Standard Time', + abbr: 'IDT', + offset: 4.5, + isdst: true, + text: '(UTC+03:30) Tehran', + utc: ['Asia/Tehran'], + }, + { + value: 'Arabian Standard Time', + abbr: 'AST', + offset: 4, + isdst: false, + text: '(UTC+04:00) Abu Dhabi, Muscat', + utc: ['Asia/Dubai', 'Asia/Muscat', 'Etc/GMT-4'], + }, + { + value: 'Azerbaijan Standard Time', + abbr: 'ADT', + offset: 5, + isdst: true, + text: '(UTC+04:00) Baku', + utc: ['Asia/Baku'], + }, + { + value: 'Mauritius Standard Time', + abbr: 'MST', + offset: 4, + isdst: false, + text: '(UTC+04:00) Port Louis', + utc: ['Indian/Mahe', 'Indian/Mauritius', 'Indian/Reunion'], + }, + { + value: 'Georgian Standard Time', + abbr: 'GET', + offset: 4, + isdst: false, + text: '(UTC+04:00) Tbilisi', + utc: ['Asia/Tbilisi'], + }, + { + value: 'Caucasus Standard Time', + abbr: 'CST', + offset: 4, + isdst: false, + text: '(UTC+04:00) Yerevan', + utc: ['Asia/Yerevan'], + }, + { + value: 'Afghanistan Standard Time', + abbr: 'AST', + offset: 4.5, + isdst: false, + text: '(UTC+04:30) Kabul', + utc: ['Asia/Kabul'], + }, + { + value: 'West Asia Standard Time', + abbr: 'WAST', + offset: 5, + isdst: false, + text: '(UTC+05:00) Ashgabat, Tashkent', + utc: [ + 'Antarctica/Mawson', + 'Asia/Aqtau', + 'Asia/Aqtobe', + 'Asia/Ashgabat', + 'Asia/Dushanbe', + 'Asia/Oral', + 'Asia/Samarkand', + 'Asia/Tashkent', + 'Etc/GMT-5', + 'Indian/Kerguelen', + 'Indian/Maldives', + ], + }, + { + value: 'Yekaterinburg Time', + abbr: 'YEKT', + offset: 5, + isdst: false, + text: '(UTC+05:00) Yekaterinburg', + utc: ['Asia/Yekaterinburg'], + }, + { + value: 'Pakistan Standard Time', + abbr: 'PKT', + offset: 5, + isdst: false, + text: '(UTC+05:00) Islamabad, Karachi', + utc: ['Asia/Karachi'], + }, + { + value: 'India Standard Time', + abbr: 'IST', + offset: 5.5, + isdst: false, + text: '(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi', + utc: ['Asia/Kolkata', 'Asia/Calcutta'], + }, + { + value: 'Sri Lanka Standard Time', + abbr: 'SLST', + offset: 5.5, + isdst: false, + text: '(UTC+05:30) Sri Jayawardenepura', + utc: ['Asia/Colombo'], + }, + { + value: 'Nepal Standard Time', + abbr: 'NST', + offset: 5.75, + isdst: false, + text: '(UTC+05:45) Kathmandu', + utc: ['Asia/Kathmandu'], + }, + { + value: 'Central Asia Standard Time', + abbr: 'CAST', + offset: 6, + isdst: false, + text: '(UTC+06:00) Nur-Sultan (Astana)', + utc: [ + 'Antarctica/Vostok', + 'Asia/Almaty', + 'Asia/Bishkek', + 'Asia/Qyzylorda', + 'Asia/Urumqi', + 'Etc/GMT-6', + 'Indian/Chagos', + ], + }, + { + value: 'Bangladesh Standard Time', + abbr: 'BST', + offset: 6, + isdst: false, + text: '(UTC+06:00) Dhaka', + utc: ['Asia/Dhaka', 'Asia/Thimphu'], + }, + { + value: 'Myanmar Standard Time', + abbr: 'MST', + offset: 6.5, + isdst: false, + text: '(UTC+06:30) Yangon (Rangoon)', + utc: ['Asia/Rangoon', 'Indian/Cocos'], + }, + { + value: 'SE Asia Standard Time', + abbr: 'SAST', + offset: 7, + isdst: false, + text: '(UTC+07:00) Bangkok, Hanoi, Jakarta', + utc: [ + 'Antarctica/Davis', + 'Asia/Bangkok', + 'Asia/Hovd', + 'Asia/Jakarta', + 'Asia/Phnom_Penh', + 'Asia/Pontianak', + 'Asia/Saigon', + 'Asia/Vientiane', + 'Etc/GMT-7', + 'Indian/Christmas', + ], + }, + { + value: 'N. Central Asia Standard Time', + abbr: 'NCAST', + offset: 7, + isdst: false, + text: '(UTC+07:00) Novosibirsk', + utc: ['Asia/Novokuznetsk', 'Asia/Novosibirsk', 'Asia/Omsk'], + }, + { + value: 'China Standard Time', + abbr: 'CST', + offset: 8, + isdst: false, + text: '(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi', + utc: ['Asia/Hong_Kong', 'Asia/Macau', 'Asia/Shanghai'], + }, + { + value: 'North Asia Standard Time', + abbr: 'NAST', + offset: 8, + isdst: false, + text: '(UTC+08:00) Krasnoyarsk', + utc: ['Asia/Krasnoyarsk'], + }, + { + value: 'Singapore Standard Time', + abbr: 'MPST', + offset: 8, + isdst: false, + text: '(UTC+08:00) Kuala Lumpur, Singapore', + utc: [ + 'Asia/Brunei', + 'Asia/Kuala_Lumpur', + 'Asia/Kuching', + 'Asia/Makassar', + 'Asia/Manila', + 'Asia/Singapore', + 'Etc/GMT-8', + ], + }, + { + value: 'W. Australia Standard Time', + abbr: 'WAST', + offset: 8, + isdst: false, + text: '(UTC+08:00) Perth', + utc: ['Antarctica/Casey', 'Australia/Perth'], + }, + { + value: 'Taipei Standard Time', + abbr: 'TST', + offset: 8, + isdst: false, + text: '(UTC+08:00) Taipei', + utc: ['Asia/Taipei'], + }, + { + value: 'Ulaanbaatar Standard Time', + abbr: 'UST', + offset: 8, + isdst: false, + text: '(UTC+08:00) Ulaanbaatar', + utc: ['Asia/Choibalsan', 'Asia/Ulaanbaatar'], + }, + { + value: 'North Asia East Standard Time', + abbr: 'NAEST', + offset: 8, + isdst: false, + text: '(UTC+08:00) Irkutsk', + utc: ['Asia/Irkutsk'], + }, + { + value: 'Japan Standard Time', + abbr: 'JST', + offset: 9, + isdst: false, + text: '(UTC+09:00) Osaka, Sapporo, Tokyo', + utc: ['Asia/Dili', 'Asia/Jayapura', 'Asia/Tokyo', 'Etc/GMT-9', 'Pacific/Palau'], + }, + { + value: 'Korea Standard Time', + abbr: 'KST', + offset: 9, + isdst: false, + text: '(UTC+09:00) Seoul', + utc: ['Asia/Pyongyang', 'Asia/Seoul'], + }, + { + value: 'Cen. Australia Standard Time', + abbr: 'CAST', + offset: 9.5, + isdst: false, + text: '(UTC+09:30) Adelaide', + utc: ['Australia/Adelaide', 'Australia/Broken_Hill'], + }, + { + value: 'AUS Central Standard Time', + abbr: 'ACST', + offset: 9.5, + isdst: false, + text: '(UTC+09:30) Darwin', + utc: ['Australia/Darwin'], + }, + { + value: 'E. Australia Standard Time', + abbr: 'EAST', + offset: 10, + isdst: false, + text: '(UTC+10:00) Brisbane', + utc: ['Australia/Brisbane', 'Australia/Lindeman'], + }, + { + value: 'AUS Eastern Standard Time', + abbr: 'AEST', + offset: 10, + isdst: false, + text: '(UTC+10:00) Canberra, Melbourne, Sydney', + utc: ['Australia/Melbourne', 'Australia/Sydney'], + }, + { + value: 'West Pacific Standard Time', + abbr: 'WPST', + offset: 10, + isdst: false, + text: '(UTC+10:00) Guam, Port Moresby', + utc: [ + 'Antarctica/DumontDUrville', + 'Etc/GMT-10', + 'Pacific/Guam', + 'Pacific/Port_Moresby', + 'Pacific/Saipan', + 'Pacific/Truk', + ], + }, + { + value: 'Tasmania Standard Time', + abbr: 'TST', + offset: 10, + isdst: false, + text: '(UTC+10:00) Hobart', + utc: ['Australia/Currie', 'Australia/Hobart'], + }, + { + value: 'Yakutsk Standard Time', + abbr: 'YST', + offset: 9, + isdst: false, + text: '(UTC+09:00) Yakutsk', + utc: ['Asia/Chita', 'Asia/Khandyga', 'Asia/Yakutsk'], + }, + { + value: 'Central Pacific Standard Time', + abbr: 'CPST', + offset: 11, + isdst: false, + text: '(UTC+11:00) Solomon Is., New Caledonia', + utc: [ + 'Antarctica/Macquarie', + 'Etc/GMT-11', + 'Pacific/Efate', + 'Pacific/Guadalcanal', + 'Pacific/Kosrae', + 'Pacific/Noumea', + 'Pacific/Ponape', + ], + }, + { + value: 'Vladivostok Standard Time', + abbr: 'VST', + offset: 11, + isdst: false, + text: '(UTC+11:00) Vladivostok', + utc: ['Asia/Sakhalin', 'Asia/Ust-Nera', 'Asia/Vladivostok'], + }, + { + value: 'New Zealand Standard Time', + abbr: 'NZST', + offset: 12, + isdst: false, + text: '(UTC+12:00) Auckland, Wellington', + utc: ['Antarctica/McMurdo', 'Pacific/Auckland'], + }, + { + value: 'UTC+12', + abbr: 'U', + offset: 12, + isdst: false, + text: '(UTC+12:00) Coordinated Universal Time+12', + utc: [ + 'Etc/GMT-12', + 'Pacific/Funafuti', + 'Pacific/Kwajalein', + 'Pacific/Majuro', + 'Pacific/Nauru', + 'Pacific/Tarawa', + 'Pacific/Wake', + 'Pacific/Wallis', + ], + }, + { + value: 'Fiji Standard Time', + abbr: 'FST', + offset: 12, + isdst: false, + text: '(UTC+12:00) Fiji', + utc: ['Pacific/Fiji'], + }, + { + value: 'Magadan Standard Time', + abbr: 'MST', + offset: 12, + isdst: false, + text: '(UTC+12:00) Magadan', + utc: ['Asia/Anadyr', 'Asia/Kamchatka', 'Asia/Magadan', 'Asia/Srednekolymsk'], + }, + { + value: 'Kamchatka Standard Time', + abbr: 'KDT', + offset: 13, + isdst: true, + text: '(UTC+12:00) Petropavlovsk-Kamchatsky - Old', + utc: ['Asia/Kamchatka'], + }, + { + value: 'Tonga Standard Time', + abbr: 'TST', + offset: 13, + isdst: false, + text: "(UTC+13:00) Nuku'alofa", + utc: ['Etc/GMT-13', 'Pacific/Enderbury', 'Pacific/Fakaofo', 'Pacific/Tongatapu'], + }, + { + value: 'Samoa Standard Time', + abbr: 'SST', + offset: 13, + isdst: false, + text: '(UTC+13:00) Samoa', + utc: ['Pacific/Apia'], + }, +] diff --git a/studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.types.ts b/studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.types.ts new file mode 100644 index 00000000000..cd86f6ea2fc --- /dev/null +++ b/studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.types.ts @@ -0,0 +1,8 @@ +export interface Timezone { + value: string + abbr: string + offset: number + isdst: boolean + text: string + utc: string[] +} diff --git a/studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.utils.ts b/studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.utils.ts new file mode 100644 index 00000000000..52b72b7a15f --- /dev/null +++ b/studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.utils.ts @@ -0,0 +1,20 @@ +import dayjs from 'dayjs' +import { ALL_TIMEZONES } from './PITRBackupSelection.constants' +import { Timezone } from './PITRBackupSelection.types' + +export const getClientTimezone = () => { + const defaultTz = dayjs.tz.guess() + const timezone = ALL_TIMEZONES.find((option) => { + if (option.utc.includes(defaultTz)) return option + else return undefined + }) + return timezone +} + +export const getTimezoneOffsetText = (timezone: Timezone) => { + return timezone.text.split(')')[0].slice(1) +} + +export const getTimezoneOffset = (timezone: Timezone) => { + return timezone.text.split(')')[0].slice(4) +} diff --git a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx new file mode 100644 index 00000000000..a5ff8d82c03 --- /dev/null +++ b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx @@ -0,0 +1,229 @@ +import dayjs from 'dayjs' +import { FC, FormEvent, useState } from 'react' +import { IconInfo, IconSearch, Input, Listbox, Popover, Button, Modal } from '@supabase/ui' + +import { useStore, useFlag } from 'hooks' +import UpgradeToPro from 'components/ui/UpgradeToPro' +import ConfirmationModal from 'components/ui/ConfirmationModal' +import { ALL_TIMEZONES } from './PITRBackupSelection.constants' +import { + getClientTimezone, + getTimezoneOffset, + getTimezoneOffsetText, +} from './PITRBackupSelection.utils' + +interface Props {} + +const PITRBackupSelection: FC = () => { + const { ui, backups } = useStore() + const isAvailable = useFlag('pitrBackups') + + const [searchString, setSearchString] = useState('') + const [selectedTimezone, setSelectedTimezone] = useState(getClientTimezone()) + const [showConfirmation, setShowConfirmation] = useState(false) + + const [errors, setErrors] = useState() + const [recoveryPoint, setRecoveryPoint] = useState() + + // Mock new properties, to delete after BE is fully ready + const configuration = { + ...backups.configuration, + region: 'ap-southeast-1', + walg_enabled: true, + physicalBackupData: { + earliestPhysicalBackupDateUnix: 1654054628338, + latestPhysicalBackupDateUnix: 1654065771390, + }, + } + + const projectRef = ui.selectedProject?.ref ?? 'default' + const hasPhysicalBackups = + configuration.physicalBackupData.earliestPhysicalBackupDateUnix !== null && + configuration.physicalBackupData.latestPhysicalBackupDateUnix !== null + + if (!isAvailable) { + return ( +
+

Coming soon

+
+ ) + } else if (!configuration.walg_enabled) { + // Using this check as opposed to checking price tier to allow enabling PITR for our own internal projects + return ( + + ) + } else if (!hasPhysicalBackups) { + return ( +
+
+ +

No backups created yet. Check again tomorrow.

+
+
+ ) + } + + const timezoneOptions = + searchString.length > 0 + ? ALL_TIMEZONES.map((option) => option.text).filter((option) => + option.toLowerCase().includes(searchString.toLowerCase()) + ) + : ALL_TIMEZONES.map((option) => option.text) + + const earliestAvailableBackup = dayjs( + configuration.physicalBackupData.earliestPhysicalBackupDateUnix + ) + .tz(selectedTimezone.utc[0]) + .format('YYYY-MM-DDTHH:mm:ss') + + const latestAvailableBackup = dayjs(configuration.physicalBackupData.latestPhysicalBackupDateUnix) + .tz(selectedTimezone.utc[0]) + .format('YYYY-MM-DDTHH:mm:ss') + + const onSubmit = (e: FormEvent) => { + e.preventDefault() + + if (!recoveryPoint) { + setErrors({ recoveryPoint: 'Please enter a date to recovery your project from' }) + } else { + setErrors(undefined) + setShowConfirmation(true) + } + } + + const onConfirmRestore = async () => { + // To unix milliseconds + const timezoneOffset = getTimezoneOffset(selectedTimezone) + const recoveryPointWithTimezone = `${recoveryPoint}${timezoneOffset}` + const recoveryTimeTargetUnix = dayjs(recoveryPointWithTimezone).valueOf() + console.log('Confirm restore', { recoveryTimeTargetUnix, region: configuration.region }) + } + + return ( + <> +
+
+
+
+

Select timezone

+
+ { + const selectedTimezone = ALL_TIMEZONES.find((option) => option.text === text) + setSelectedTimezone(selectedTimezone) + }} + onBlur={() => setSearchString('')} + > +
+ + ) => + setSearchString(e.currentTarget.value) + } + /> +
+ {/* Whitespace to shift listbox options down for searchfield */} +
+ {timezoneOptions.map((option) => { + return ( + +
{option}
+
+ ) + })} + {timezoneOptions.length === 0 && ( + + No timezones found + + )} + +
+
+ + + + { + setErrors(undefined) + setRecoveryPoint(e.target.value) + }} + /> + +
+ +
+
+
+ + +
+
+

+ Are you sure you want to restore your database from: +

+

+ {dayjs(recoveryPoint).format('DD MMM YYYY, HH:mm:ss')} ( + {getTimezoneOffsetText(selectedTimezone)})? +

+
+

+ This will destroy any new data written since this backup was made. +

+
+ + } + buttonLabel="Restore" + buttonLoadingLabel="Restoring" + onSelectCancel={() => setShowConfirmation(false)} + onSelectConfirm={onConfirmRestore} + /> + + ) +} + +export default PITRBackupSelection diff --git a/studio/components/interfaces/Database/index.ts b/studio/components/interfaces/Database/index.ts index 60141982fab..2bd42e99171 100644 --- a/studio/components/interfaces/Database/index.ts +++ b/studio/components/interfaces/Database/index.ts @@ -10,6 +10,7 @@ import PublicationsList from './Publications/PublicationsList' import PublicationsTables from './Publications/PublicationsTables' import BackupsList from './Backups/BackupsList' +import PITRBackupSelection from './Backups/PITRBackupSelection' import ConnectionPooling from './Pooling/ConnectionPooling' @@ -28,6 +29,7 @@ export { PublicationsList, PublicationsTables, BackupsList, + PITRBackupSelection, ConnectionPooling, CreateTrigger, DeleteTrigger, diff --git a/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx b/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx index 1725558e1ba..cf59cef8af7 100644 --- a/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx +++ b/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx @@ -14,7 +14,7 @@ interface Props { } const DatabaseLayout: FC = ({ title, children }) => { - const { meta, ui } = useStore() + const { meta, ui, backups } = useStore() const { isInitialized, isLoading, error } = meta.tables const project = ui.selectedProject @@ -33,9 +33,13 @@ const DatabaseLayout: FC = ({ title, children }) => { meta.triggers.load() meta.extensions.load() meta.publications.load() + + backups.load() } }, [ui.selectedProject]) + // Optimization required: load logic should be at the page level + // e.g backups page is waiting for meta.tables to load finish when it doesnt even need that data useEffect(() => { if (!isLoading && !loaded) { setLoaded(true) diff --git a/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts b/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts index 33e7ea21753..a37952f3a32 100644 --- a/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts +++ b/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts @@ -29,63 +29,63 @@ export const generateDatabaseMenu = (project?: Project): ProductMenuGroup[] => { { name: 'Backups', key: 'backups', - url: `/project/${ref}/database/backups`, + url: `/project/${ref}/database/backups/scheduled`, items: [], }, ], }, ...(IS_PLATFORM ? [ - { - title: 'Logs', - items: [ - { - name: 'API logs', - key: 'api-logs', - url: `/project/${ref}/database/api-logs`, - items: [], - }, - { - name: 'Postgres logs', - key: 'postgres-logs', - url: `/project/${ref}/database/postgres-logs`, - items: [], - }, - ], - }, - ] + { + title: 'Logs', + items: [ + { + name: 'API logs', + key: 'api-logs', + url: `/project/${ref}/database/api-logs`, + items: [], + }, + { + name: 'Postgres logs', + key: 'postgres-logs', + url: `/project/${ref}/database/postgres-logs`, + items: [], + }, + ], + }, + ] : []), ...(IS_PLATFORM ? [ - { - title: 'Alpha Preview', - isPreview: true, - items: [ - { - name: 'Triggers', - key: 'triggers', - url: `/project/${ref}/database/triggers`, - items: [], - }, - { - name: 'Functions', - key: 'functions', - url: `/project/${ref}/database/functions`, - items: [], - }, - ...(showHooksRoute - ? [ - { - name: 'Function Hooks', - key: 'hooks', - url: `/project/${ref}/database/hooks`, - items: [], - }, - ] - : []), - ], - }, - ] + { + title: 'Alpha Preview', + isPreview: true, + items: [ + { + name: 'Triggers', + key: 'triggers', + url: `/project/${ref}/database/triggers`, + items: [], + }, + { + name: 'Functions', + key: 'functions', + url: `/project/${ref}/database/functions`, + items: [], + }, + ...(showHooksRoute + ? [ + { + name: 'Function Hooks', + key: 'hooks', + url: `/project/${ref}/database/hooks`, + items: [], + }, + ] + : []), + ], + }, + ] : []), ] } diff --git a/studio/components/ui/ConfirmationModal.tsx b/studio/components/ui/ConfirmationModal.tsx index a99758f8101..338cd93e702 100644 --- a/studio/components/ui/ConfirmationModal.tsx +++ b/studio/components/ui/ConfirmationModal.tsx @@ -48,8 +48,8 @@ const ConfirmationModal: FC = ({ size={size} onCancel={onSelectCancel} customFooter={ -
- - -
- - } - > - -

{message}

-
- - ); -}; - -function removeElement() { - const target = document.getElementById('supabase-ui-confirm-alert'); - if (target) { - unmountComponentAtNode(target); - target?.parentNode?.removeChild(target); - } -} - -function createElement(props: ConfirmModalProps) { - let divTarget = document.getElementById('supabase-ui-confirm-alert'); - if (divTarget) { - render(, divTarget); - } else { - divTarget = document.createElement('div'); - divTarget.id = 'supabase-ui-confirm-alert'; - document.body.appendChild(divTarget); - render(, divTarget); - } -} - -export function showConfirmAlert(props: ConfirmModalProps) { - createElement(props); -} diff --git a/studio/components/grid/components/common/index.ts b/studio/components/grid/components/common/index.ts index ad34f221222..9ac92bc11a2 100644 --- a/studio/components/grid/components/common/index.ts +++ b/studio/components/grid/components/common/index.ts @@ -1,8 +1,7 @@ -export * from './BlockKeys'; -export * from './ConfirmAlert'; -export * from './DropdownControl'; -export * from './MonacoEditor'; -export * from './NullValue'; -export * from './Hooks'; -export * from './Shortcuts'; -export * from './EmptyValue'; +export * from './BlockKeys' +export * from './DropdownControl' +export * from './MonacoEditor' +export * from './NullValue' +export * from './Hooks' +export * from './Shortcuts' +export * from './EmptyValue' diff --git a/studio/components/grid/components/header/Header.tsx b/studio/components/grid/components/header/Header.tsx index 502a6b2c2d7..2ad4c625b2c 100644 --- a/studio/components/grid/components/header/Header.tsx +++ b/studio/components/grid/components/header/Header.tsx @@ -1,13 +1,13 @@ import * as React from 'react' import { useDispatch, useTrackedState } from '../../store' -import { Button, Divider, IconDownload, IconPlus, IconX, IconTrash, Typography } from '@supabase/ui' +import { Button, Divider, IconDownload, IconPlus, IconX, IconTrash } from '@supabase/ui' import { saveAs } from 'file-saver' import FilterDropdown from './filter' import SortPopover from './sort' import StatusLabel from './StatusLabel' import RefreshButton from './RefreshButton' import { exportRowsToCsv } from '../../utils' -import { showConfirmAlert } from '../common' +import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' type HeaderProps = { onAddColumn?: () => void @@ -91,7 +91,7 @@ const RowHeader: React.FC = ({}) => { const { selectedRows, rows: allRows, editable } = state const onRowsDelete = () => { - showConfirmAlert({ + confirmAlert({ title: 'Confirm to delete', message: 'Are you sure you want to delete the selected rows? This action cannot be undone.', onConfirm: async () => { diff --git a/studio/components/grid/components/menu/RowContextMenu.tsx b/studio/components/grid/components/menu/RowContextMenu.tsx index 0db4c8749c0..2ae413641fc 100644 --- a/studio/components/grid/components/menu/RowContextMenu.tsx +++ b/studio/components/grid/components/menu/RowContextMenu.tsx @@ -1,71 +1,69 @@ -import * as React from 'react'; -import { Menu, Item, ItemParams, PredicateParams } from 'react-contexify'; -import { IconTrash, IconClipboard, IconEdit } from '@supabase/ui'; -import { useDispatch, useTrackedState } from '../../store'; -import { formatClipboardValue, copyToClipboard } from '../../utils'; -import { showConfirmAlert } from '../common'; +import * as React from 'react' +import { Menu, Item, ItemParams, PredicateParams } from 'react-contexify' +import { IconTrash, IconClipboard, IconEdit } from '@supabase/ui' +import { useDispatch, useTrackedState } from '../../store' +import { formatClipboardValue, copyToClipboard } from '../../utils' +import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' -export const ROW_CONTEXT_MENU_ID = 'row-context-menu-id'; +export const ROW_CONTEXT_MENU_ID = 'row-context-menu-id' -type RowContextMenuProps = {}; +type RowContextMenuProps = {} const RowContextMenu: React.FC = ({}) => { - const state = useTrackedState(); - const dispatch = useDispatch(); + const state = useTrackedState() + const dispatch = useDispatch() function onDeleteRow(p: ItemParams) { - showConfirmAlert({ + confirmAlert({ title: 'Confirm to delete', - message: - 'Are you sure you want to delete this row? This action cannot be undone.', + message: 'Are you sure you want to delete this row? This action cannot be undone.', onConfirm: async () => { - const { props } = p; - const { rowIdx } = props; - const row = state.rows[rowIdx]; - const { error } = state.rowService!.delete([row]); + const { props } = p + const { rowIdx } = props + const row = state.rows[rowIdx] + const { error } = state.rowService!.delete([row]) if (error) { - if (state.onError) state.onError(error); + if (state.onError) state.onError(error) } else { - dispatch({ type: 'REMOVE_ROWS', payload: { rowIdxs: [row.idx] } }); + dispatch({ type: 'REMOVE_ROWS', payload: { rowIdxs: [row.idx] } }) dispatch({ type: 'SELECTED_ROWS_CHANGE', payload: { selectedRows: new Set() }, - }); + }) } }, - }); + }) } function onEditRowClick(p: ItemParams) { - const { props } = p; - const { rowIdx } = props; - const row = state.rows[rowIdx]; - if (state.onEditRow) state.onEditRow(row); + const { props } = p + const { rowIdx } = props + const row = state.rows[rowIdx] + if (state.onEditRow) state.onEditRow(row) } function isItemHidden({ data }: PredicateParams) { - if (data === 'edit') return state.onEditRow == undefined; - if (data === 'delete') return !state.editable; - return false; + if (data === 'edit') return state.onEditRow == undefined + if (data === 'delete') return !state.editable + return false } function onCopyCellContent(p: ItemParams) { - const { props } = p; + const { props } = p if (!state.selectedCellPosition || !props) { - return; + return } - const { rowIdx } = props; - const row = state.rows[rowIdx]; + const { rowIdx } = props + const row = state.rows[rowIdx] - const columnKey = - state.gridColumns[state.selectedCellPosition?.idx as number].key; + const columnKey = state.gridColumns[state.selectedCellPosition?.idx as number].key - const value = row[columnKey]; - const text = formatClipboardValue(value); + const value = row[columnKey] + const text = formatClipboardValue(value) - copyToClipboard(text); + copyToClipboard(text) } return ( @@ -85,6 +83,6 @@ const RowContextMenu: React.FC = ({}) => { - ); -}; -export default RowContextMenu; + ) +} +export default RowContextMenu diff --git a/studio/components/grid/components/menu/RowMenu.tsx b/studio/components/grid/components/menu/RowMenu.tsx index 36437a43fd8..bff5b03eaab 100644 --- a/studio/components/grid/components/menu/RowMenu.tsx +++ b/studio/components/grid/components/menu/RowMenu.tsx @@ -3,7 +3,7 @@ import * as React from 'react' import { useDispatch, useTrackedState } from '../../store' import { exportRowsToCsv } from '../../utils' import { saveAs } from 'file-saver' -import { showConfirmAlert } from '../common' +import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' type RowMenuProps = {} @@ -13,7 +13,7 @@ const RowMenu: React.FC = ({}) => { const { selectedRows, rows: allRows, editable } = state function onRowsDelete() { - showConfirmAlert({ + confirmAlert({ title: 'Confirm to delete', message: 'Are you sure you want to delete the selected rows? This action cannot be undone.', onConfirm: async () => { diff --git a/studio/components/interfaces/Database/Backups/BackupItem.tsx b/studio/components/interfaces/Database/Backups/BackupItem.tsx index a2f1a7ff794..ae530eb0972 100644 --- a/studio/components/interfaces/Database/Backups/BackupItem.tsx +++ b/studio/components/interfaces/Database/Backups/BackupItem.tsx @@ -5,7 +5,7 @@ import { observer } from 'mobx-react-lite' import { Badge, Button, IconDownload } from '@supabase/ui' import { useStore } from 'hooks' -import { API_URL } from 'lib/constants' +import { API_URL, PROJECT_STATUS } from 'lib/constants' import { post } from 'lib/common/fetch' import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' @@ -17,17 +17,26 @@ interface Props { const BackupItem: FC = ({ projectRef, backup, index }) => { const router = useRouter() - const { ui } = useStore() + const { app, ui } = useStore() const [isDownloading, setDownloading] = useState(false) const [isRestoring, setRestoring] = useState(false) + const projectId = ui.selectedProject?.id ?? -1 + async function restore(backup: any) { setRestoring(true) try { post(`${API_URL}/database/${projectRef}/backups/restore`, backup).then(() => { setTimeout(() => { - router.push('/project/[id]', `/project/${projectRef}`) + app.onProjectStatusUpdated(projectId, PROJECT_STATUS.RESTORING) + ui.setNotification({ + category: 'success', + message: `Restoring database back to ${dayjs(backup.inserted_at).format( + 'DD MMM YYYY HH:mm:ss' + )}`, + }) + router.push(`/project/${projectRef}`) }, 3000) }) } catch (error) { @@ -46,7 +55,7 @@ const BackupItem: FC = ({ projectRef, backup, index }) => { const res = await post(`${API_URL}/database/${projectRef}/backups/download`, backup) const { fileUrl } = await res - // triger browser download by create,trigger and remove tempLink + // Trigger browser download by create,trigger and remove tempLink const tempLink = document.createElement('a') tempLink.href = fileUrl document.body.appendChild(tempLink) @@ -101,18 +110,16 @@ const BackupItem: FC = ({ projectRef, backup, index }) => { } return ( - <> -
-

- {dayjs(backup.inserted_at).format('DD MMM YYYY HH:mm:ss')} -

-
{generateSideButtons(backup)}
-
- +
+

+ {dayjs(backup.inserted_at).format('DD MMM YYYY HH:mm:ss')} +

+
{generateSideButtons(backup)}
+
) } diff --git a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx index ff0c3c3a59e..9848d44c07c 100644 --- a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx +++ b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx @@ -3,6 +3,8 @@ import { FC, FormEvent, useState } from 'react' import { useRouter } from 'next/router' import { IconSearch, Input, Listbox, Popover, Button, Modal } from '@supabase/ui' +import { post } from 'lib/common/fetch' +import { API_URL, PROJECT_STATUS } from 'lib/constants' import { useStore, useFlag } from 'hooks' import Loading from 'components/ui/Loading' import UpgradeToPro from 'components/ui/UpgradeToPro' @@ -16,14 +18,12 @@ import { getTimezoneOffsetText, convertTimeStringtoUnixMs, } from './PITRBackupSelection.utils' -import { post } from 'lib/common/fetch' -import { API_URL } from 'lib/constants' interface Props {} const PITRBackupSelection: FC = () => { const router = useRouter() - const { ui, backups } = useStore() + const { app, ui, backups } = useStore() const isAvailable = useFlag('pitrBackups') if (!isAvailable) { @@ -42,6 +42,7 @@ const PITRBackupSelection: FC = () => { const [recoveryPoint, setRecoveryPoint] = useState() const { configuration } = backups + const projectId = ui.selectedProject?.id ?? -1 const projectRef = ui.selectedProject?.ref ?? 'default' if (backups.isLoading) { @@ -123,6 +124,7 @@ const PITRBackupSelection: FC = () => { }).then(() => { setTimeout(() => { setShowConfirmation(false) + app.onProjectStatusUpdated(projectId, PROJECT_STATUS.RESTORING) ui.setNotification({ category: 'success', message: `Restoring database back to ${dayjs(recoveryPoint).format( @@ -130,7 +132,7 @@ const PITRBackupSelection: FC = () => { )} ( ${getTimezoneOffsetText(selectedTimezone)})`, }) - router.push('/project/[id]', `/project/${projectRef}`) + router.push(`/project/${projectRef}`) }, 3000) }) } catch (error) { diff --git a/studio/components/ui/Error.tsx b/studio/components/ui/Error.tsx index 3d650b6924a..575c1504196 100644 --- a/studio/components/ui/Error.tsx +++ b/studio/components/ui/Error.tsx @@ -8,13 +8,13 @@ export default function EmptyPageState({ error }: any) { }, []) return ( -
-
+
+
Something went wrong 🤕 - +

Sorry about that, please try again later or feel free to reach out to us if the problem persists. - +

@@ -28,9 +28,9 @@ export default function EmptyPageState({ error }: any) {
- +

Error: [{error?.code}] {error?.message} - +

) } From d964e2ed8c8014bd2bd8be6c4cba760fd114ea72 Mon Sep 17 00:00:00 2001 From: dragarcia Date: Tue, 21 Jun 2022 14:01:49 +0800 Subject: [PATCH 08/14] standardise to unix in seconds --- .../PITRBackupSelection/PITRBackupSelection.utils.ts | 4 ++-- .../Database/Backups/PITRBackupSelection/index.tsx | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.utils.ts b/studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.utils.ts index 8fbce2583e1..545340e5e5d 100644 --- a/studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.utils.ts +++ b/studio/components/interfaces/Database/Backups/PITRBackupSelection/PITRBackupSelection.utils.ts @@ -19,9 +19,9 @@ export const getTimezoneOffset = (timezone: Timezone) => { return timezone.text.split(')')[0].slice(4) } -export const convertTimeStringtoUnixMs = (datetime: string, timezone: Timezone) => { +export const convertTimeStringtoUnixS = (datetime: string, timezone: Timezone) => { // Datetime in the format of YYYY-MM-DDTHH:mm:ss (from datetime-local input) const timezoneOffset = getTimezoneOffset(timezone) const datetimestringWithTimezone = `${datetime}${timezoneOffset}` - return dayjs(datetimestringWithTimezone).valueOf() + return dayjs(datetimestringWithTimezone).unix() } diff --git a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx index 9848d44c07c..663cbf5ee46 100644 --- a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx +++ b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx @@ -16,7 +16,7 @@ import { ALL_TIMEZONES } from './PITRBackupSelection.constants' import { getClientTimezone, getTimezoneOffsetText, - convertTimeStringtoUnixMs, + convertTimeStringtoUnixS, } from './PITRBackupSelection.utils' interface Props {} @@ -75,13 +75,13 @@ const PITRBackupSelection: FC = () => { ) : ALL_TIMEZONES.map((option) => option.text) - const earliestAvailableBackup = dayjs( + const earliestAvailableBackup = dayjs.unix( configuration.physicalBackupData.earliestPhysicalBackupDateUnix ) .tz(selectedTimezone.utc[0]) .format('YYYY-MM-DDTHH:mm:ss') - const latestAvailableBackup = dayjs(configuration.physicalBackupData.latestPhysicalBackupDateUnix) + const latestAvailableBackup = dayjs.unix(configuration.physicalBackupData.latestPhysicalBackupDateUnix) .tz(selectedTimezone.utc[0]) .format('YYYY-MM-DDTHH:mm:ss') @@ -92,7 +92,7 @@ const PITRBackupSelection: FC = () => { if (!recoveryPoint) { setErrors({ recoveryPoint: 'Please enter a date to recovery your project from' }) } else { - const recoveryTimeTargetUnix = convertTimeStringtoUnixMs(recoveryPoint, selectedTimezone) + const recoveryTimeTargetUnix = convertTimeStringtoUnixS(recoveryPoint, selectedTimezone) const { earliestPhysicalBackupDateUnix, latestPhysicalBackupDateUnix } = configuration.physicalBackupData @@ -115,7 +115,7 @@ const PITRBackupSelection: FC = () => { if (!recoveryPoint) return // To unix milliseconds - const recoveryTimeTargetUnix = convertTimeStringtoUnixMs(recoveryPoint, selectedTimezone) + const recoveryTimeTargetUnix = convertTimeStringtoUnixS(recoveryPoint, selectedTimezone) try { post(`${API_URL}/database/${projectRef}/backups/pitr`, { From 29f1fadca86f3a9f120a7459f9a364a4ea461351 Mon Sep 17 00:00:00 2001 From: dragarcia Date: Tue, 21 Jun 2022 14:26:44 +0800 Subject: [PATCH 09/14] additional condition for hasPhysicalBackups --- .../interfaces/Database/Backups/PITRBackupSelection/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx index 663cbf5ee46..dc11391b578 100644 --- a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx +++ b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx @@ -53,7 +53,8 @@ const PITRBackupSelection: FC = () => { const hasPhysicalBackups = configuration.physicalBackupData.earliestPhysicalBackupDateUnix !== null && - configuration.physicalBackupData.latestPhysicalBackupDateUnix !== null + configuration.physicalBackupData.latestPhysicalBackupDateUnix !== null && + configuration.physicalBackupData.earliestPhysicalBackupDateUnix !== configuration.physicalBackupData.latestPhysicalBackupDateUnix if (!configuration.walg_enabled) { // Using this check as opposed to checking price tier to allow enabling PITR for our own internal projects From 38c0f02dadc190dddbc2e937f027b28492a13cc7 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 22 Jun 2022 15:45:47 +0800 Subject: [PATCH 10/14] Ensure that APi error handling works in pitr --- .../Backups/PITRBackupSelection/index.tsx | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx index dc11391b578..7ba8b9b0be4 100644 --- a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx +++ b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx @@ -54,7 +54,8 @@ const PITRBackupSelection: FC = () => { const hasPhysicalBackups = configuration.physicalBackupData.earliestPhysicalBackupDateUnix !== null && configuration.physicalBackupData.latestPhysicalBackupDateUnix !== null && - configuration.physicalBackupData.earliestPhysicalBackupDateUnix !== configuration.physicalBackupData.latestPhysicalBackupDateUnix + configuration.physicalBackupData.earliestPhysicalBackupDateUnix !== + configuration.physicalBackupData.latestPhysicalBackupDateUnix if (!configuration.walg_enabled) { // Using this check as opposed to checking price tier to allow enabling PITR for our own internal projects @@ -76,13 +77,13 @@ const PITRBackupSelection: FC = () => { ) : ALL_TIMEZONES.map((option) => option.text) - const earliestAvailableBackup = dayjs.unix( - configuration.physicalBackupData.earliestPhysicalBackupDateUnix - ) + const earliestAvailableBackup = dayjs + .unix(configuration.physicalBackupData.earliestPhysicalBackupDateUnix) .tz(selectedTimezone.utc[0]) .format('YYYY-MM-DDTHH:mm:ss') - const latestAvailableBackup = dayjs.unix(configuration.physicalBackupData.latestPhysicalBackupDateUnix) + const latestAvailableBackup = dayjs + .unix(configuration.physicalBackupData.latestPhysicalBackupDateUnix) .tz(selectedTimezone.utc[0]) .format('YYYY-MM-DDTHH:mm:ss') @@ -118,30 +119,31 @@ const PITRBackupSelection: FC = () => { // To unix milliseconds const recoveryTimeTargetUnix = convertTimeStringtoUnixS(recoveryPoint, selectedTimezone) - try { - post(`${API_URL}/database/${projectRef}/backups/pitr`, { - recovery_time_target_unix: recoveryTimeTargetUnix, - region: configuration.region, - }).then(() => { - setTimeout(() => { - setShowConfirmation(false) - app.onProjectStatusUpdated(projectId, PROJECT_STATUS.RESTORING) - ui.setNotification({ - category: 'success', - message: `Restoring database back to ${dayjs(recoveryPoint).format( - 'DD MMM YYYY, HH:mm:ss' - )} ( - ${getTimezoneOffsetText(selectedTimezone)})`, - }) - router.push(`/project/${projectRef}`) - }, 3000) - }) - } catch (error) { + const { error } = await post(`${API_URL}/database/${projectRef}/backups/pitr`, { + recovery_time_target_unix: recoveryTimeTargetUnix, + region: configuration.region, + }) + + if (error) { ui.setNotification({ error, category: 'error', message: `Point in time recovery for project failed`, }) + setShowConfirmation(false) + } else { + setTimeout(() => { + setShowConfirmation(false) + app.onProjectStatusUpdated(projectId, PROJECT_STATUS.RESTORING) + ui.setNotification({ + category: 'success', + message: `Restoring database back to ${dayjs(recoveryPoint).format( + 'DD MMM YYYY, HH:mm:ss' + )} ( + ${getTimezoneOffsetText(selectedTimezone)})`, + }) + router.push(`/project/${projectRef}`) + }, 3000) } } From 8d55a26a88126e94e626bd0384aa7884eda6c698 Mon Sep 17 00:00:00 2001 From: dragarcia Date: Wed, 22 Jun 2022 16:43:39 +0800 Subject: [PATCH 11/14] no longer pass region to pitr api call --- .../interfaces/Database/Backups/PITRBackupSelection/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx index 7ba8b9b0be4..8b0165c8555 100644 --- a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx +++ b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx @@ -121,7 +121,6 @@ const PITRBackupSelection: FC = () => { const { error } = await post(`${API_URL}/database/${projectRef}/backups/pitr`, { recovery_time_target_unix: recoveryTimeTargetUnix, - region: configuration.region, }) if (error) { From 78b79eeec49a32651d1f696a2424bc3283890639 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 24 Jun 2022 15:50:14 +0800 Subject: [PATCH 12/14] Small spelling fix --- .../Backups/PITRBackupSelection/index.tsx | 174 ++++++++++-------- 1 file changed, 96 insertions(+), 78 deletions(-) diff --git a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx index 7ba8b9b0be4..c69a94555a1 100644 --- a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx +++ b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx @@ -92,7 +92,7 @@ const PITRBackupSelection: FC = () => { // Validation if (!recoveryPoint) { - setErrors({ recoveryPoint: 'Please enter a date to recovery your project from' }) + setErrors({ recoveryPoint: 'Please enter a date to recover your project from' }) } else { const recoveryTimeTargetUnix = convertTimeStringtoUnixS(recoveryPoint, selectedTimezone) const { earliestPhysicalBackupDateUnix, latestPhysicalBackupDateUnix } = @@ -116,9 +116,7 @@ const PITRBackupSelection: FC = () => { const onConfirmRestore = async () => { if (!recoveryPoint) return - // To unix milliseconds const recoveryTimeTargetUnix = convertTimeStringtoUnixS(recoveryPoint, selectedTimezone) - const { error } = await post(`${API_URL}/database/${projectRef}/backups/pitr`, { recovery_time_target_unix: recoveryTimeTargetUnix, region: configuration.region, @@ -152,86 +150,106 @@ const PITRBackupSelection: FC = () => {
-
-

Select timezone

-
- { - const selectedTimezone = ALL_TIMEZONES.find((option) => option.text === text) - setSelectedTimezone(selectedTimezone) - }} - onBlur={() => setSearchString('')} - > -
+
+

Select timezone

+
+ { + const selectedTimezone = ALL_TIMEZONES.find((option) => option.text === text) + setSelectedTimezone(selectedTimezone) + }} + onBlur={() => setSearchString('')} > - - ) => - setSearchString(e.currentTarget.value) - } - /> -
- {/* Whitespace to shift listbox options down for searchfield */} -
- {timezoneOptions.map((option) => { - return ( - -
{option}
+
+ + ) => + setSearchString(e.currentTarget.value) + } + /> +
+ {/* Whitespace to shift listbox options down for searchfield */} +
+ {timezoneOptions.map((option) => { + return ( + +
{option}
+
+ ) + })} + {timezoneOptions.length === 0 && ( + + No timezones found - ) - })} - {timezoneOptions.length === 0 && ( - - No timezones found - - )} - + )} + +
+
+
+
+

Earliest point of recovery

+
+
+ +
+
+
+
+

Latest point of recovery

+
+
+ +
- - - { - setErrors(undefined) - setRecoveryPoint(e.target.value) - }} - /> +
+
+

Recovery point

+

+ Select a date and time that you would like to restore your project to +

+
+
+ { + setErrors(undefined) + setRecoveryPoint(e.target.value) + }} + /> +
+
From 432f252374481079aa6a473565ead23acee43a26 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 24 Jun 2022 16:31:48 +0800 Subject: [PATCH 13/14] Small update --- studio/components/interfaces/Database/Backups/BackupsEmpty.tsx | 2 +- studio/pages/project/[ref]/database/backups/scheduled.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/studio/components/interfaces/Database/Backups/BackupsEmpty.tsx b/studio/components/interfaces/Database/Backups/BackupsEmpty.tsx index 8d89f0abec5..81f1fdb97f9 100644 --- a/studio/components/interfaces/Database/Backups/BackupsEmpty.tsx +++ b/studio/components/interfaces/Database/Backups/BackupsEmpty.tsx @@ -5,7 +5,7 @@ const BackupsEmpty = () => {
-

No backups created yet. Check again tomorrow.

+

No backups created yet - check again tomorrow.

) diff --git a/studio/pages/project/[ref]/database/backups/scheduled.tsx b/studio/pages/project/[ref]/database/backups/scheduled.tsx index e13ab35229e..2b8cfefd74d 100644 --- a/studio/pages/project/[ref]/database/backups/scheduled.tsx +++ b/studio/pages/project/[ref]/database/backups/scheduled.tsx @@ -29,7 +29,8 @@ const DatabaseScheduledBackups: NextPageWithLayout = () => {

- Projects are backed up daily and can be restored at any time. + Projects are backed up daily around midnight of your project's region and can be restored + at any time.

From 7e273c110597f96365a62b0eb88ccc065302416f Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 27 Jun 2022 13:39:52 +0800 Subject: [PATCH 14/14] Fix styling --- .../Database/Backups/PITRBackupSelection/index.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx index b4d980360b5..ead2eeca011 100644 --- a/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx +++ b/studio/components/interfaces/Database/Backups/PITRBackupSelection/index.tsx @@ -150,9 +150,9 @@ const PITRBackupSelection: FC = () => {
-
-

Select timezone

-
+
+

Select timezone

+
{ @@ -197,7 +197,7 @@ const PITRBackupSelection: FC = () => {
-
+

Earliest point of recovery

@@ -212,7 +212,7 @@ const PITRBackupSelection: FC = () => { />
-
+

Latest point of recovery

@@ -229,7 +229,7 @@ const PITRBackupSelection: FC = () => {
-
+

Recovery point