Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GBICSGO-2277: adds trashcan button to backup dialog #416

Merged
merged 20 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e5ac9aa
GBICSGO-2277: adds trashcan button to backup dialog
KaanTolunayKilicOG Aug 16, 2024
219393d
GBICSGO-2277: fixes missing method for stub
KaanTolunayKilicOG Aug 18, 2024
367dc59
GBICSGO-2277: fmts pkg/http/rest/api_test.go
KaanTolunayKilicOG Aug 22, 2024
d458964
GBICSGO-2277: fixes description for /trashcans/{backupId}/clean_up API
KaanTolunayKilicOG Aug 22, 2024
d22b109
GBICSGO-2277: adds confirm dialog
KaanTolunayKilicOG Aug 22, 2024
9b28f6a
GBICSGO-2277: makes trashcan deletion async
KaanTolunayKilicOG Aug 28, 2024
8349703
GBICSGO-2277: implements missing method for repository mock
KaanTolunayKilicOG Aug 28, 2024
74bfaa0
GBICSGO-2277: fixes test TestCleanupTrashcansService_Noop
KaanTolunayKilicOG Aug 28, 2024
e9809a1
GBICSGO-2277: fixes MarkTrashcanCleanupStatusWithError and updates er…
KaanTolunayKilicOG Aug 28, 2024
89479b0
GBICSGO-2277: adds timestamp for schedule trashcan cleanup
KaanTolunayKilicOG Aug 28, 2024
d4e56f2
GBICSGO-2277: fixes column name for trashcan_cleanup_last_scheduled_t…
KaanTolunayKilicOG Aug 28, 2024
4ab9a86
GBICSGO-2277: fixes test for TrashcanCleanup
KaanTolunayKilicOG Aug 29, 2024
86ee881
GBICSGO-2277: fix timestamp type for trashcan_cleanup_last_scheduled_…
KaanTolunayKilicOG Aug 29, 2024
6ee82a1
GBICSGO-2277: adds status for cleanup trashcan is in progress
KaanTolunayKilicOG Aug 29, 2024
39a8436
GBICSGO-2277: adds guard for empty prefix for trashcan cleanup
KaanTolunayKilicOG Sep 3, 2024
cdbb320
GBICSGO-2277: fixes sql migration
KaanTolunayKilicOG Sep 3, 2024
5e903f2
GBICSGO-2277: adds link to cloud storage bucket
KaanTolunayKilicOG Sep 3, 2024
e416087
GBICSGO-2277: fmts func declaration
KaanTolunayKilicOG Sep 4, 2024
56e9c6e
GBICSGO-2277: fixes inner text for sink bucket a-tag
KaanTolunayKilicOG Sep 4, 2024
3cd70af
GBICSGO-2277: opens bucket sink in new tab
KaanTolunayKilicOG Sep 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func createBuilder(provider AppStartArguments) *builder.ProcessorBuilder {
processor.NewConfigRegionsProcessorFactory(),
processor.NewConfigStorageClassesProcessorFactory(),
processor.NewSourceProjectGetProcessorFactory(provider.SourceGCPProjectProvider, provider.TargetPrincipalForProjectProvider),
processor.NewTrashcanCleanUpProcessorFactory(provider.TargetPrincipalForProjectProvider, provider.SecretProvider),
)
}

Expand Down
3 changes: 3 additions & 0 deletions cron.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ cron:
- description: "check backup status"
url: /api/tasks/check_backups_status
schedule: every 15 minutes from 00:08 to 23:58
- description: "cleanup trashcans"
url: /api/tasks/cleanup_trashcans
schedule: every 60 minutes from 00:08 to 23:58
- description: "check app health status"
url: /_ah/health
schedule: every 1 minutes
90 changes: 85 additions & 5 deletions frontend/src/components/BackupViewDialog.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
<script setup lang="ts">
import ComplianceCheck from "@/components/ComplianceCheck.vue";
import PricePrediction from "@/components/PricePrediction.vue";
import {Backup, CreateRequest, DefaultService, Job, JobStatus} from "@/models/api";
import {Backup, CreateRequest, DefaultService, Job, JobStatus, TrashcanCleanupStatus} from "@/models/api";
import {BackupType} from "@/models/api/models/BackupType";
import {RestoreResponse} from "@/models/api/models/RestoreResponse";
import {useNotificationsStore} from "@/stores";
import {ref, watch} from "vue";
import Notification from "@/models/notification";
import ConfirmDialog from "@/components/common/ConfirmDialog.vue";

const props = defineProps({
id: {
type: String,
},
});

const notificationsStore = useNotificationsStore();

const emits = defineEmits(['close']);
const tab = ref();
const viewDialog = ref(false);
Expand All @@ -21,6 +25,34 @@ const listIsLoading = ref(true);
const backup = ref<Backup | undefined>(undefined);
const backupForEval = ref<CreateRequest | undefined>(undefined);
const jobItems = ref<{ job: Job; restore: RestoreResponse | undefined }[]>([]);
const cleanupTrashcanDialog = ref(false);

const confirmCleanupTrashcan = () => {
cleanupTrashcanDialog.value = true;
};

const cleanupTrashcan = () => {
if (backup.value?.id) {
DefaultService.postTrashcansCleanUp(backup.value?.id)
KaanTolunayKilicOG marked this conversation as resolved.
Show resolved Hide resolved
.then(() => {
notificationsStore.addNotification(
new Notification({
message: "Backup trashcan cleaned up",
color: "success",
}),
);
cleanupTrashcanDialog.value = false;
updateData();
})
.catch((err) => {
notificationsStore.handleError(err);
});
}
}

const cancelCleanupTrashcan = () => {
cleanupTrashcanDialog.value = false;
};

const updateData = () => {
isLoading.value = true;
Expand Down Expand Up @@ -89,7 +121,7 @@ const loadJobs = ({page, itemsPerPage}: { page: number; itemsPerPage: number; so
}) ?? [];
})
.catch((err) => {
useNotificationsStore().handleError(err);
notificationsStore.handleError(err);
})
.finally(() => {
listIsLoading.value = false;
Expand All @@ -103,7 +135,7 @@ const loadRestore = (item: { job: Job; restore: RestoreResponse | undefined }) =
item.restore = resp;
})
.catch((err) => {
useNotificationsStore().handleError(err);
notificationsStore.handleError(err);
});
};

Expand All @@ -123,6 +155,19 @@ const projectLink = (project: string) => {
return `https://console.cloud.google.com/welcome?project=${project}`;
};

const translateTrashcanCleanupStatus = (status: TrashcanCleanupStatus | undefined) => {
switch (status) {
case TrashcanCleanupStatus.NOOP:
return "No cleanup scheduled";
case TrashcanCleanupStatus.ERROR:
return "Error";
case TrashcanCleanupStatus.SCHEDULED:
return "Scheduled";
default:
return "Unknown";
}
};

watch(
() => viewDialog.value,
(value) => {
Expand All @@ -142,7 +187,7 @@ watch(
</script>

<template>
<v-dialog v-model="viewDialog" width="800">
<v-dialog v-model="viewDialog" width="800" v-if="!cleanupTrashcanDialog">
<v-card title="Backup">
<v-card-text v-if="isLoading">
<v-progress-linear indeterminate/>
Expand Down Expand Up @@ -279,7 +324,7 @@ watch(
</tr>
<tr>
<td>Sink bucket:</td>
<td>{{ backup?.sink }}</td>
<td><a :href="cloudStorageLink(backup?.sink_project ?? '', backup?.sink ?? '')" target="_blank">{{ backup?.sink }}</a></td>
</tr>
<tr>
<td>Storage region:</td>
Expand Down Expand Up @@ -336,6 +381,30 @@ watch(
<td>Updated:</td>
<td>{{ backup?.updated }}</td>
</tr>
<tr>
<td colspan="2"><h4>Trashcan Cleanup</h4></td>
</tr>
<tr>
<td>Status:</td>
<td>{{ translateTrashcanCleanupStatus(backup?.trashcan_cleanup_status) }}</td>
</tr>
<tr v-if="backup?.trashcan_cleanup_status === TrashcanCleanupStatus.ERROR">
<td>Error Message:</td>
<td>{{ backup?.trashcan_cleanup_error_message?.split(':').pop() }}</td>
</tr>
<tr v-if="backup?.trashcan_cleanup_last_scheduled_time">
<td>Scheduled:</td>
<td>{{ backup?.trashcan_cleanup_last_scheduled_time }}</td>
</tr>
<tr v-if="backup?.trashcan_cleanup_status !== TrashcanCleanupStatus.SCHEDULED">
<td>Executed:</td>
<td>
<v-btn v-bind="props" color="red" @click="confirmCleanupTrashcan()" variant="tonal">
<v-icon>mdi-delete</v-icon>
Trashcan
</v-btn>
</td>
</tr>
</tbody>
</v-table>
<v-row>
Expand Down Expand Up @@ -403,4 +472,15 @@ watch(
</template>
</v-card>
</v-dialog>
<ConfirmDialog
v-model="cleanupTrashcanDialog"
:options="{
title: 'Cleanup trashcan',
message: 'Are you sure you want to cleanup trashcan?',
color: 'red',
confirmButtonText: 'Cleanup',
cancelButtonText: 'Cancel',
}"
@confirm="cleanupTrashcan"
@cancel="cancelCleanupTrashcan"></ConfirmDialog>
</template>
100 changes: 100 additions & 0 deletions frontend/src/components/common/ConfirmDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<script setup lang="ts">

import {PropType} from "vue";

const emits = defineEmits(['confirm', 'cancel'])

const model = defineModel({default: false, required: true})

interface Options {
title?: string
message?: string
color?: string
width?: string
confirmButtonText?: string
confirmButtonColor?: string
cancelButtonText?: string
cancelButtonColor?: string
}

defineProps({
title: {
type: String,
default: '',
},
message: {
type: String,
default: '',
},
color: {
type: String,
default: 'primary',
},
width: {
type: String,
default: '500',
},
options: {
type: Object as PropType<Options>,
default: () => ({
title: 'Are you sure?',
message: '',
color: 'primary',
width: '500',
confirmButtonText: 'OK',
confirmButtonColor: 'primary',
cancelButtonText: 'Cancel',
cancelButtonColor: 'secondary',
}),
}
})

const cancel = () => {
emits('cancel')
}

const confirm = () => {
emits('confirm')
}

</script>

<template>
<v-dialog
v-model="model"
:width="width"
@keydown.esc="cancel"
>
<v-card>
<v-toolbar dark :color="options.color" dense flat>
<v-toolbar-title class="text-body-2 font-weight-bold grey--text">
{{ options.title }}
</v-toolbar-title>
</v-toolbar>
<v-card-text
v-show="!!options.message"
class="pa-4 black--text"
v-html="options.message"
></v-card-text>
<v-card-actions class="pt-3">
<v-spacer></v-spacer>
<v-btn
class="body-2 font-weight-bold"
@click.native="cancel"
:text="options.cancelButtonText"
/>
<v-btn
color="red"
class="body-2 font-weight-bold"
outlined
@click.native="confirm"
:text="options.confirmButtonText"
/>
</v-card-actions>
</v-card>
</v-dialog>
</template>

<style scoped>

</style>
2 changes: 1 addition & 1 deletion frontend/src/models/api/core/ApiError.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/models/api/core/ApiRequestOptions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/models/api/core/ApiResult.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/models/api/core/CancelablePromise.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
Expand Down Expand Up @@ -51,15 +51,15 @@ export class CancelablePromise<T> implements Promise<T> {
return;
}
this.#isResolved = true;
this.#resolve?.(value);
if (this.#resolve) this.#resolve(value);
};

const onReject = (reason?: any): void => {
if (this.#isResolved || this.#isRejected || this.#isCancelled) {
return;
}
this.#isRejected = true;
this.#reject?.(reason);
if (this.#reject) this.#reject(reason);
};

const onCancel = (cancelHandler: () => void): void => {
Expand All @@ -85,9 +85,9 @@ export class CancelablePromise<T> implements Promise<T> {
});
}

get [Symbol.toStringTag]() {
return "Cancellable Promise";
}
get [Symbol.toStringTag]() {
return "Cancellable Promise";
}

public then<TResult1 = T, TResult2 = never>(
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
Expand Down Expand Up @@ -122,7 +122,7 @@ export class CancelablePromise<T> implements Promise<T> {
}
}
this.#cancelHandlers.length = 0;
this.#reject?.(new CancelError('Request aborted'));
if (this.#reject) this.#reject(new CancelError('Request aborted'));
}

public get isCancelled(): boolean {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/models/api/core/OpenAPI.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
Expand Down
14 changes: 8 additions & 6 deletions frontend/src/models/api/core/request.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
Expand Down Expand Up @@ -137,10 +137,12 @@ export const resolve = async <T>(options: ApiRequestOptions, resolver?: T | Reso
};

export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise<Headers> => {
const token = await resolve(options, config.TOKEN);
const username = await resolve(options, config.USERNAME);
const password = await resolve(options, config.PASSWORD);
const additionalHeaders = await resolve(options, config.HEADERS);
const [token, username, password, additionalHeaders] = await Promise.all([
resolve(options, config.TOKEN),
resolve(options, config.USERNAME),
resolve(options, config.PASSWORD),
resolve(options, config.HEADERS),
]);

const headers = Object.entries({
Accept: 'application/json',
Expand All @@ -162,7 +164,7 @@ export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptio
headers['Authorization'] = `Basic ${credentials}`;
}

if (options.body) {
if (options.body !== undefined) {
if (options.mediaType) {
headers['Content-Type'] = options.mediaType;
} else if (isBlob(options.body)) {
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/models/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
Expand All @@ -25,6 +25,7 @@ export { Role } from './models/Role';
export type { SnapshotOptions } from './models/SnapshotOptions';
export type { SourceProject } from './models/SourceProject';
export type { TargetOptions } from './models/TargetOptions';
export { TrashcanCleanupStatus } from './models/TrashcanCleanupStatus';
export type { UpdateRequest } from './models/UpdateRequest';
export type { UserResponse } from './models/UserResponse';

Expand Down
Loading
Loading