Skip to content

Commit

Permalink
GBICSGO-2277: adds trashcan button to backup dialog (#416)
Browse files Browse the repository at this point in the history
* GBICSGO-2277: adds trashcan button to backup dialog

* GBICSGO-2277: fixes missing method for stub

* GBICSGO-2277: fmts pkg/http/rest/api_test.go

* GBICSGO-2277: fixes description for /trashcans/{backupId}/clean_up API

* GBICSGO-2277: adds confirm dialog

* GBICSGO-2277: makes trashcan deletion async

* GBICSGO-2277: implements missing method for repository mock

* GBICSGO-2277: fixes test TestCleanupTrashcansService_Noop

* GBICSGO-2277: fixes MarkTrashcanCleanupStatusWithError and updates err msg

* GBICSGO-2277: adds timestamp for schedule trashcan cleanup

* GBICSGO-2277: fixes column name for trashcan_cleanup_last_scheduled_timestamp

* GBICSGO-2277: fixes test for TrashcanCleanup

* GBICSGO-2277: fix timestamp type for trashcan_cleanup_last_scheduled_timestamp

* GBICSGO-2277: adds status for cleanup trashcan is in progress

* GBICSGO-2277: adds guard for empty prefix for trashcan cleanup

* GBICSGO-2277: fixes sql migration

* GBICSGO-2277: adds link to cloud storage bucket

* GBICSGO-2277: fmts func declaration

* GBICSGO-2277: fixes inner text for sink bucket a-tag

* GBICSGO-2277: opens bucket sink in new tab
  • Loading branch information
KaanTolunayKilicOG authored Sep 5, 2024
1 parent d3159f7 commit 1b559f7
Show file tree
Hide file tree
Showing 56 changed files with 913 additions and 117 deletions.
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)
.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

0 comments on commit 1b559f7

Please sign in to comment.