<template> <div class="content"> <h1>{{ $t('migrate.titleService', {name: migrator.name}) }}</h1> <p>{{ $t('migrate.descriptionDo') }}</p> <template v-if="message === '' && lastMigrationDate === null"> <template v-if="isMigrating === false"> <template v-if="migrator.isFileMigrator"> <p>{{ $t('migrate.importUpload', {name: migrator.name}) }}</p> <input @change="migrate" class="is-hidden" ref="uploadInput" type="file" /> <x-button :loading="migrationService.loading" :disabled="migrationService.loading || undefined" @click="$refs.uploadInput.click()" > {{ $t('migrate.upload') }} </x-button> </template> <template v-else> <p>{{ $t('migrate.authorize', {name: migrator.name}) }}</p> <x-button :loading="migrationService.loading" :disabled="migrationService.loading || undefined" :href="authUrl" > {{ $t('migrate.getStarted') }} </x-button> </template> </template> <div v-else class="migration-in-progress-container" > <div class="migration-in-progress"> <img :alt="migrator.name" :src="migrator.icon" class="logo"/> <div class="progress-dots"> <span v-for="i in progressDotsCount" :key="i"/> </div> <Logo class="logo"/> </div> <p>{{ $t('migrate.inProgress') }}</p> </div> </template> <div v-else-if="lastMigrationDate"> <p> {{ $t('migrate.alreadyMigrated1', {name: migrator.name, date: formatDateLong(lastMigrationDate)}) }}<br/> {{ $t('migrate.alreadyMigrated2') }} </p> <div class="buttons"> <x-button @click="migrate">{{ $t('migrate.confirm') }}</x-button> <x-button :to="{name: 'home'}" variant="tertiary" class="has-text-danger">{{ $t('misc.cancel') }}</x-button> </div> </div> <div v-else> <message class="mb-4"> {{ message }} </message> <x-button :to="{name: 'home'}">{{ $t('misc.refresh') }}</x-button> </div> </div> </template> <script lang="ts"> import {defineComponent} from 'vue' import AbstractMigrationService from '@/services/migrator/abstractMigration' import AbstractMigrationFileService from '@/services/migrator/abstractMigrationFile' import Logo from '@/assets/logo.svg?component' import Message from '@/components/misc/message.vue' import { setTitle } from '@/helpers/setTitle' import {formatDateLong} from '@/helpers/time/formatDate' import {MIGRATORS} from './migrators' import { useNamespaceStore } from '@/stores/namespaces' const PROGRESS_DOTS_COUNT = 8 export default defineComponent({ name: 'MigrateService', components: { Logo, Message, }, data() { return { progressDotsCount: PROGRESS_DOTS_COUNT, authUrl: '', isMigrating: false, lastMigrationDate: null, message: '', migratorAuthCode: '', migrationService: null, } }, computed: { migrator() { return MIGRATORS[this.$route.params.service] }, }, beforeRouteEnter(to) { if (MIGRATORS[to.params.service] === undefined) { return {name: 'not-found'} } }, created() { this.initMigration() }, mounted() { setTitle(this.$t('migrate.titleService', {name: this.migrator.name})) }, methods: { formatDateLong, async initMigration() { this.migrationService = this.migrator.isFileMigrator ? new AbstractMigrationFileService(this.migrator.id) : new AbstractMigrationService(this.migrator.id) if (this.migrator.isFileMigrator) { return } this.authUrl = await this.migrationService.getAuthUrl().then(({url}) => url) this.migratorAuthCode = location.hash.startsWith('#token=') ? location.hash.substring(7) : this.$route.query.code if (!this.migratorAuthCode) { return } const {time} = await this.migrationService.getStatus() if (time) { this.lastMigrationDate = typeof time === 'string' && time?.startsWith('0001-') ? null : new Date(time) if (this.lastMigrationDate) { return } } await this.migrate() }, async migrate() { this.isMigrating = true this.lastMigrationDate = null this.message = '' let migrationConfig = {code: this.migratorAuthCode} if (this.migrator.isFileMigrator) { if (this.$refs.uploadInput.files.length === 0) { return } migrationConfig = this.$refs.uploadInput.files[0] } try { const {message} = await this.migrationService.migrate(migrationConfig) this.message = message const namespaceStore = useNamespaceStore() return namespaceStore.loadNamespaces() } finally { this.isMigrating = false } }, }, }) </script> <style lang="scss" scoped> .migration-in-progress-container { max-width: 400px; margin: 4rem auto 0; text-align: center; } .migration-in-progress { text-align: center; display: flex; max-width: 400px; justify-content: space-between; align-items: center; margin-bottom: 2rem; } .logo { display: block; max-height: 100px; max-width: 100px; } .progress-dots { height: 40px; width: 140px; overflow: visible; span { transition: all 500ms ease; background: var(--grey-500); height: 10px; width: 10px; display: inline-block; border-radius: 10px; animation: wave 2s ease infinite; margin-right: 5px; &:nth-child(1) { animation-delay: 0; } &:nth-child(2) { animation-delay: 100ms; } &:nth-child(3) { animation-delay: 200ms; } &:nth-child(4) { animation-delay: 300ms; } &:nth-child(5) { animation-delay: 400ms; } &:nth-child(6) { animation-delay: 500ms; } &:nth-child(7) { animation-delay: 600ms; } &:nth-child(8) { animation-delay: 700ms; } } } @keyframes wave { 0%, 40%, 100% { transform: translate(0, 0); background-color: var(--primary); } 10% { transform: translate(0, -15px); background-color: var(--primary-dark); } } @media (prefers-reduced-motion: reduce) { @keyframes wave { 10% { transform: translate(0, 0); background-color: var(--primary); } } } </style>