Skip to content

Commit

Permalink
feat(#117): refactor and polish task viewer and backup viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
lukashornych committed Oct 15, 2024
1 parent b0e5684 commit cc8573c
Show file tree
Hide file tree
Showing 33 changed files with 804 additions and 278 deletions.
41 changes: 41 additions & 0 deletions src/modules/backup-viewer/components/BackupCatalogButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<script setup lang="ts">
import BackupCatalogDialog from '@/modules/backup-viewer/components/BackupCatalogDialog.vue'
import VTabMainActionButton from '@/modules/base/component/VTabMainActionButton.vue'
import { Connection } from '@/modules/connection/model/Connection'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = defineProps<{
connection: Connection
}>()
const emit = defineEmits<{
(e: 'backup'): void
}>()
const showBackupCatalogDialog = ref<boolean>(false)
</script>

<template>
<BackupCatalogDialog
v-model="showBackupCatalogDialog"
:connection="connection"
@backup="emit('backup')"
>
<template #activator="{ props }">
<VTabMainActionButton
prepend-icon="mdi-cloud-download-outline"
@click="showBackupCatalogDialog = true"
v-bind="props"
>
{{ t('backupViewer.button.backupCatalog') }}
</VTabMainActionButton>
</template>
</BackupCatalogDialog>
</template>

<style lang="scss" scoped>
</style>
104 changes: 82 additions & 22 deletions src/modules/backup-viewer/components/BackupCatalogDialog.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { OffsetDateTime } from '@/modules/connection/model/data-type/OffsetDateTime'
import { ref } from 'vue'
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import VFormDialog from '@/modules/base/component/VFormDialog.vue'
import { DateTime } from 'luxon'
Expand All @@ -9,35 +9,89 @@ import { BackupViewerService, useBackupViewerService } from '@/modules/backup-vi
import { Connection } from '@/modules/connection/model/Connection'
import { Toaster, useToaster } from '@/modules/notification/service/Toaster'
import { CatalogVersionAtResponse } from '@/modules/connection/model/CatalogVersionAtResponse'
import Immutable from 'immutable'
import { Catalog } from '@/modules/connection/model/Catalog'
const backupViewerService: BackupViewerService = useBackupViewerService()
const toaster: Toaster = useToaster()
const { t } = useI18n()
const props = defineProps<{
modelValue: boolean,
connection: Connection,
catalogName: string
connection: Connection
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void,
(e: 'backup', date: string): void
(e: 'backup'): void
}>()
const availableCatalogs = ref<string[]>([])
const availableCatalogsLoaded = ref<boolean>(false)
const minimalDate = ref<string | undefined>()
const minimalDateLoaded = ref<boolean>(false)
const catalogName = ref<string | undefined>(undefined)
watch(catalogName, async () => {
minimalDateLoaded.value = false
pastMoment.value = undefined
if (catalogName.value != undefined && catalogName.value.trim().length > 0) {
await loadMinimalDate()
} else {
minimalDate.value = undefined
}
})
const pastMoment = ref<string>()
const includeWal = ref<boolean>(false)
async function loadMinimalDate(): Promise<CatalogVersionAtResponse> {
return await backupViewerService.getMinimalBackupDate(
props.connection,
props.catalogName
)
const changed = computed<boolean>(() =>
catalogName.value != undefined && catalogName.value.trim().length > 0)
const catalogNameRules = [
(value: string): any => {
if (value != undefined && value.trim().length > 0) return true
return t('backupViewer.backup.form.catalogName.validations.required')
},
async (value: string): Promise<any> => {
const available: boolean = await backupViewerService.isCatalogExists(props.connection, value)
if (available) return true
return t('backupViewer.backup.form.catalogName.validations.notExists')
}
]
async function loadAvailableCatalogs(): Promise<void> {
try {
const fetchedAvailableCatalogs: Immutable.List<Catalog> = await backupViewerService.getAvailableCatalogs(props.connection)
availableCatalogs.value = fetchedAvailableCatalogs
.filter(it => !it.corrupted)
.map(it => it.name)
.toArray()
availableCatalogsLoaded.value = true
} catch (e: any) {
toaster.error(t(
'backupViewer.backup.notification.couldNotLoadAvailableCatalogs',
{ reason: e.message }
))
}
}
async function loadMinimalDate(): Promise<void> {
try {
const minimalBackupDate: CatalogVersionAtResponse = await backupViewerService.getMinimalBackupDate(
props.connection,
catalogName.value!
)
minimalDate.value = minimalBackupDate.introducedAt.toString()
minimalDateLoaded.value = true
} catch (e: any) {
toaster.error(t(
'backupViewer.backup.notification.couldNotLoadMinimalDate',
{ reason: e.message }
))
}
}
function reset(): void {
catalogName.value = ''
pastMoment.value = ''
includeWal.value = false
}
Expand All @@ -58,55 +112,61 @@ async function backup(): Promise<boolean> {
try {
await backupViewerService.backupCatalog(
props.connection,
props.catalogName,
catalogName.value!,
includeWal.value,
convertPastMoment()
)
toaster.success(t(
'backupViewer.backup.notification.backupRequested',
{ catalogName: props.catalogName }
{ catalogName: catalogName.value }
))
emit('backup')
return true
} catch (e: any) {
toaster.error(t(
'backupViewer.backup.notification.couldNotRequestBackup',
{
catalogName: props.catalogName,
catalogName: catalogName.value,
reason: e.message
}
))
return false
}
}
loadMinimalDate().then((catalogVersionAt) => {
minimalDate.value = catalogVersionAt.introducedAt.toString()
minimalDateLoaded.value = true
})
loadAvailableCatalogs().then()
</script>

<template>
<VFormDialog
:model-value="modelValue"
changed
:changed="changed"
confirm-button-icon="mdi-cloud-download-outline"
:confirm="backup"
:reset="reset"
@update:model-value="emit('update:modelValue', $event)"
>
<template #activator="{ props }">
<slot name="activator" v-bind="{ props }"/>
</template>

<template #title>
<I18nT keypath="backupViewer.backup.title">
<template #catalogName>
<strong>{{ catalogName }}</strong>
</template>
</I18nT>
{{ t('backupViewer.backup.title') }}
</template>

<template #prepend-form>
{{ t('backupViewer.backup.description') }}
</template>

<template #default>
<VAutocomplete
v-model="catalogName"
:label="t('backupViewer.backup.form.catalogName.label')"
:items="availableCatalogs"
:rules="catalogNameRules"
:disabled="!availableCatalogsLoaded"
required
/>
<VDateInput
v-model="pastMoment"
:label="t('backupViewer.backup.form.pastMoment.label')"
Expand Down
140 changes: 140 additions & 0 deletions src/modules/backup-viewer/components/BackupList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<script setup lang="ts">
import BackupListItem from '@/modules/backup-viewer/components/BackupListItem.vue'
import VListItemDivider from '@/modules/base/component/VListItemDivider.vue'
import VMissingDataIndicator from '@/modules/base/component/VMissingDataIndicator.vue'
import { computed, onUnmounted, ref, watch } from 'vue'
import { PaginatedList } from '@/modules/connection/model/PaginatedList'
import { ServerFile } from '@/modules/connection/model/server-file/ServerFile'
import { BackupViewerService, useBackupViewerService } from '@/modules/backup-viewer/service/BackupViewerService'
import { Toaster, useToaster } from '@/modules/notification/service/Toaster'
import { useI18n } from 'vue-i18n'
import { Connection } from '@/modules/connection/model/Connection'
const backupViewerService: BackupViewerService = useBackupViewerService()
const toaster: Toaster = useToaster()
const { t } = useI18n()
const props = defineProps<{
connection: Connection
backupsInPreparationPresent: boolean
}>()
const emit = defineEmits<{
(e: 'tasksUpdate'): void
}>()
const backupFilesLoaded = ref<boolean>(false)
const backupFiles = ref<PaginatedList<ServerFile>>()
const backupFileItems = computed<ServerFile[]>(() => {
if (backupFiles.value == undefined) {
return []
}
return backupFiles.value.data.toArray()
})
const pageNumber = ref<number>(1)
watch(pageNumber, async () => {
await loadBackupFiles()
})
const pageCount = computed<number>(() => {
if (backupFiles.value == undefined) {
return 1
}
return Math.ceil(backupFiles.value.totalNumberOfRecords / pageSize.value)
})
const pageSize = ref<number>(20)
async function loadBackupFiles(): Promise<boolean> {
try {
backupFiles.value = await backupViewerService.getBackupFiles(
props.connection,
pageNumber.value,
pageSize.value
)
backupFilesLoaded.value = true
return true
} catch (e: any) {
toaster.error(t(
'backupViewer.notification.couldNotLoadBackupFiles',
{ reason: e.message }
))
return false
}
}
loadBackupFiles().then()
let canReload: boolean = true
let reloadBackupFilesTimeoutId: ReturnType<typeof setTimeout> | undefined = undefined
async function reload(manual: boolean = false): Promise<void> {
if (!canReload && !manual) {
return
}
const loaded: boolean = await loadBackupFiles()
if (loaded) {
if (manual && canReload) {
// do nothing if the reloading process is working and user
// requests additional reload in between
} else {
// set new timeout only for automatic reload or reload recovery
reloadBackupFilesTimeoutId = setTimeout(reload, 5000)
}
canReload = true
} else {
// we don't want to spam user server is down, user needs to refresh manually
canReload = false
}
}
reloadBackupFilesTimeoutId = setTimeout(reload, 5000)
onUnmounted(() => clearInterval(reloadBackupFilesTimeoutId))
defineExpose<{
reload(manual: boolean): Promise<void>
}>({
reload
})
</script>

<template>
<VList v-if="backupFilesLoaded && backupFileItems.length > 0">
<VListSubheader v-if="backupsInPreparationPresent">
{{ t('backupViewer.list.title') }}
</VListSubheader>

<VDataIterator
:items="backupFileItems"
:page="pageNumber"
:items-per-page="pageSize"
>
<template #default="{ items }">
<template v-for="(item, index) in items" :key="item.raw.fileId.code">
<BackupListItem
:connection="connection"
:backup-file="item.raw"
@files-update="reload(true)"
@tasks-update="emit('tasksUpdate')"
/>

<VListItemDivider
v-if="index < backupFileItems.length - 1"
inset
/>
</template>
</template>

<template #footer>
<VPagination v-model="pageNumber" :length="pageCount" />
</template>
</VDataIterator>
</VList>

<VMissingDataIndicator
v-else
icon="mdi-cloud-download-outline"
:title="t('backupViewer.list.noFiles')"
/>
</template>

<style lang="scss" scoped>
</style>
Loading

0 comments on commit cc8573c

Please sign in to comment.