Set new UI for the Import view

This commit is contained in:
Bubka 2023-11-21 13:10:50 +01:00
parent da134a4f76
commit 73b2c60311
4 changed files with 141 additions and 57 deletions

View File

@ -1281,3 +1281,30 @@ footer.main .field.is-grouped {
transform: translateY(-2rem); transform: translateY(-2rem);
} }
} }
:root[data-theme="dark"] .table {
background-color: $black-ter;
color: $white-bis;
}
:root[data-theme="dark"] .table th,
:root[data-theme="dark"] .table thead th {
color: $grey;
}
:root[data-theme="dark"] .table td,
:root[data-theme="dark"] .table th {
border: 1px solid $grey-darker;
border-width: 0 0 1px;
}
:root[data-theme="dark"] .card {
background-color: $black-ter;
border: 1px solid $grey-darker;
}
:root[data-theme="dark"] .card-footer {
border-top: 1px solid $grey-darker;
}
:root[data-theme="dark"] .card-footer-item:not(:last-child) {
border-right: 1px solid $grey-darker;
}

View File

@ -1,5 +1,3 @@
// import Vue from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
@ -42,7 +40,10 @@ import {
faSun, faSun,
faMoon, faMoon,
faDesktop, faDesktop,
faCircleNotch faCircleNotch,
faCircleCheck,
faTriangleExclamation,
faFileLines
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { import {
@ -89,9 +90,10 @@ library.add(
faSun, faSun,
faMoon, faMoon,
faDesktop, faDesktop,
faCircleNotch faCircleNotch,
faCircleCheck,
faTriangleExclamation,
faFileLines
); );
export default FontAwesomeIcon export default FontAwesomeIcon
// Vue.component('font-awesome-icon', FontAwesomeIcon)

View File

@ -2,6 +2,7 @@
import Form from '@/components/formElements/Form' import Form from '@/components/formElements/Form'
import twofaccountService from '@/services/twofaccountService' import twofaccountService from '@/services/twofaccountService'
import OtpDisplay from '@/components/OtpDisplay.vue' import OtpDisplay from '@/components/OtpDisplay.vue'
import Spinner from '@/components/Spinner.vue'
import { useNotifyStore } from '@/stores/notify' import { useNotifyStore } from '@/stores/notify'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { useBusStore } from '@/stores/bus' import { useBusStore } from '@/stores/bus'
@ -15,9 +16,7 @@
const twofaccounts = useTwofaccounts() const twofaccounts = useTwofaccounts()
const otpDisplay = ref(null) const otpDisplay = ref(null)
const fileInput = ref(null) const fileInput = ref(null)
const fileInputLabel = ref(null)
const qrcodeInput = ref(null) const qrcodeInput = ref(null)
const qrcodeInputLabel = ref(null)
const form = reactive(new Form({ const form = reactive(new Form({
service: '', service: '',
account: '', account: '',
@ -234,53 +233,111 @@
<h1 class="title has-text-grey-dark"> <h1 class="title has-text-grey-dark">
{{ $t('twofaccounts.import.import') }} {{ $t('twofaccounts.import.import') }}
</h1> </h1>
<div v-if="exportedAccounts.length == 0"> <div v-if="!isFetching && exportedAccounts.length == 0">
<div class="block is-size-7-mobile" v-html="$t('twofaccounts.import.import_legend')"></div> <div class="block is-size-7-mobile" v-html="$t('twofaccounts.import.import_legend')"></div>
<h5 class="title is-5 mb-3 has-text-grey">{{ $t('twofaccounts.import.qr_code') }}</h5> <div class="columns">
<!-- scan button that launch camera stream --> <div class="column">
<div class="block"> <div class="block">
<div class="buttons mb-0"> <div class="card">
<RouterLink id="btnCapture" :to="{ name: 'capture' }" class="button is-link is-rounded mr-0"> <div class="card-content">
<span class="icon"> <div class="media">
<FontAwesomeIcon :icon="['fas', 'camera']" /> <div class="media-left">
</span> <figure class="image is-32x32">
<span>{{ $t('twofaccounts.import.scan') }}</span> <FontAwesomeIcon :icon="['fas', 'qrcode']" size="2x" class="has-text-grey-darker" />
</RouterLink> </figure>
<span class="p-2 mb-2">{{ $t('commons.or') }}</span> </div>
<!-- upload a qr code (with basic file field and backend decoding) --> <div class="media-content">
<label role="button" tabindex="0" class="button is-link is-rounded is-outlined" ref="qrcodeInputLabel" @keyup.enter="qrcodeInputLabel.click()"> <p class="title is-5 has-text-grey" v-html="$t('twofaccounts.import.qr_code')" />
{{ $t('twofaccounts.import.upload') }} <p class="subtitle is-6 is-size-7-mobile">{{ $t('twofaccounts.import.supported_formats_for_qrcode_upload') }}</p>
<input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="submitQrCode" ref="qrcodeInput"> </div>
</label> </div>
</div> <FieldError v-if="qrcodeForm.errors.hasAny('qrcode')" :error="qrcodeForm.errors.get('qrcode')" :field="'qrcode'" />
<FieldError v-if="qrcodeForm.errors.hasAny('qrcode')" :error="qrcodeForm.errors.get('qrcode')" :field="'qrcode'" /> </div>
<p class="help">{{ $t('twofaccounts.import.supported_formats_for_qrcode_upload') }}</p> <footer class="card-footer">
</div> <RouterLink id="btnCapture" :to="{ name: 'capture' }" class="card-footer-item">
<h5 class="title is-5 mb-3 has-text-grey">{{ $t('commons.file') }}</h5> {{ $t('twofaccounts.import.scan') }}
<!-- upload a file --> </RouterLink>
<div class="block mb-6"> <a role="button" tabindex="0" class="card-footer-item is-relative" @keyup.enter="qrcodeInput.click()">
<label role="button" tabindex="0" class="button is-link is-rounded is-outlined" ref="fileInputLabel" @keyup.enter="fileInputLabel.click()"> <input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="submitQrCode" ref="qrcodeInput">
<input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="text/plain,application/json,text/csv,.2fas" v-on:change="submitFile" ref="fileInput"> {{ $t('twofaccounts.import.upload') }}
{{ $t('twofaccounts.import.upload') }} </a>
</label> </footer>
<FieldError v-if="fileForm.errors.hasAny('file')" :error="fileForm.errors.get('file')" :field="'file'" /> </div>
<p class="help">{{ $t('twofaccounts.import.supported_formats_for_file_upload') }}</p> </div>
</div> <div class="block">
<!-- Supported migration resources --> <div class="card">
<h5 class="title is-6 mb-3 has-text-grey-dark">{{ $t('twofaccounts.import.supported_migration_formats') }}</h5> <div class="card-content">
<div class="field is-grouped is-grouped-multiline pt-0"> <div class="media">
<div v-for="(source, index) in supportedSources" :key="index" class="control"> <div class="media-left">
<div class="tags has-addons"> <figure class="image is-32x32">
<UseColorMode v-slot="{ mode }"> <FontAwesomeIcon :icon="['fas', 'file-lines']" size="2x" class="has-text-grey-darker" />
<span class="tag" :class="mode == 'dark' ? 'is-dark' : 'is-white'">{{ source.app }}</span> </figure>
<span class="tag" :class="mode == 'dark' ? 'is-black' : 'has-background-grey-lighter has-text-black'">{{ source.format }}</span> </div>
</UseColorMode> <div class="media-content">
<p class="title is-5 has-text-grey">{{ $t('twofaccounts.import.text_file') }}</p>
<p class="subtitle is-6 is-size-7-mobile">{{ $t('twofaccounts.import.supported_formats_for_file_upload') }}</p>
</div>
</div>
<FieldError v-if="fileForm.errors.hasAny('file')" :error="fileForm.errors.get('file')" :field="'file'" />
</div>
<footer class="card-footer">
<a role="button" tabindex="0" class="card-footer-item is-relative" @keyup.enter="fileInput.click()">
<input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="text/plain,application/json,text/csv,.2fas" v-on:change="submitFile" ref="fileInput">
{{ $t('twofaccounts.import.upload') }}
</a>
</footer>
</div>
</div> </div>
</div> </div>
</div> </div>
<span class="is-size-7" v-html="$t('twofaccounts.import.do_not_set_password_or_encryption')"></span> <!-- Supported migration resources -->
<h2 class="title is-5 has-text-grey-dark">{{ $t('twofaccounts.import.supported_migration_formats') }}</h2>
<div class="block is-size-7-mobile">
<FontAwesomeIcon :icon="['fas', 'fa-triangle-exclamation']" class="has-text-warning-dark" />
{{ $t('twofaccounts.import.do_not_set_password_or_encryption') }}
</div>
<table class="table is-size-7-mobile is-fullwidth">
<thead>
<tr>
<th></th>
<th>Plain text</th>
<th>QR code</th>
<th>JSON</th>
</tr>
</thead>
<tbody>
<tr>
<th>Google Authenticator</th>
<td></td>
<td><FontAwesomeIcon :icon="['fas', 'circle-check']" /></td>
<td></td>
</tr>
<tr>
<th>Aegis Auth</th>
<td><FontAwesomeIcon :icon="['fas', 'circle-check']" /></td>
<td></td>
<td><FontAwesomeIcon :icon="['fas', 'circle-check']" /></td>
</tr>
<tr>
<th>2FAS auth</th>
<td></td>
<td></td>
<td><FontAwesomeIcon :icon="['fas', 'circle-check']" /></td>
</tr>
<tr>
<th>2FAuth</th>
<td></td>
<td></td>
<td><FontAwesomeIcon :icon="['fas', 'circle-check']" /></td>
</tr>
</tbody>
</table>
</div>
<div v-else-if="isFetching && exportedAccounts.length === 0">
<Spinner :type="'fullscreen-overlay'" :isVisible="true" :message="'twofaccounts.import.parsing_data'" />
</div> </div>
<div v-else> <div v-else>
<div class="block is-size-7-mobile" v-html="$t('twofaccounts.import.submitted_data_parsed_now_accounts_are_awaiting_import')"></div>
<div v-for="(account, index) in exportedAccounts" :key="account.name" class="group-item is-size-5 is-size-6-mobile"> <div v-for="(account, index) in exportedAccounts" :key="account.name" class="group-item is-size-5 is-size-6-mobile">
<div class="is-flex is-justify-content-space-between"> <div class="is-flex is-justify-content-space-between">
<!-- Account name --> <!-- Account name -->
@ -338,11 +395,6 @@
<button @click="exportedAccounts = []" class="has-text-grey button is-small is-ghost">{{ $t('commons.clear') }}</button> <button @click="exportedAccounts = []" class="has-text-grey button is-small is-ghost">{{ $t('commons.clear') }}</button>
</div> </div>
</div> </div>
<div v-if="isFetching && exportedAccounts.length === 0" class="has-text-centered">
<span class="is-size-4">
<FontAwesomeIcon :icon="['fas', 'spinner']" spin />
</span>
</div>
<!-- footer --> <!-- footer -->
<VueFooter :showButtons="true"> <VueFooter :showButtons="true">
<!-- Import all button --> <!-- Import all button -->

View File

@ -135,18 +135,21 @@
'import' => [ 'import' => [
'import' => 'Import', 'import' => 'Import',
'to_import' => 'Import', 'to_import' => 'Import',
'import_legend' => '2FAuth can import data from various 2FA apps.<br />Use the Export feature of these apps to get a migration resource (a QR code or a file) and load it using your preferred method below.', 'import_legend' => '2FAuth can import data from various 2FA apps.<br />Use the Export feature of these apps to get a migration resource like a QR code or a JSON file then load it here.',
'upload' => 'Upload', 'upload' => 'Upload',
'scan' => 'Scan', 'scan' => 'Scan',
'supported_formats_for_qrcode_upload' => 'Accepted: jpg, jpeg, png, bmp, gif, svg, or webp', 'supported_formats_for_qrcode_upload' => 'Accepted: jpg, jpeg, png, bmp, gif, svg, or webp',
'supported_formats_for_file_upload' => 'Accepted: Plain text, json, 2fas', 'supported_formats_for_file_upload' => 'Accepted: Plain text, json, 2fas',
'supported_migration_formats' => 'Supported migration formats', 'supported_migration_formats' => 'Supported migration formats',
'qr_code' => 'QR Code', 'qr_code' => 'QR Code',
'text_file' => 'Text file',
'plain_text' => 'Plain text', 'plain_text' => 'Plain text',
'parsing_data' => 'Parsing data...',
'issuer' => 'Issuer', 'issuer' => 'Issuer',
'imported' => 'Imported', 'imported' => 'Imported',
'failure' => 'Failure', 'failure' => 'Failure',
'x_valid_accounts_found' => ':count valid accounts found', 'x_valid_accounts_found' => ':count valid accounts found',
'submitted_data_parsed_now_accounts_are_awaiting_import' => 'The following 2FA accounts were found in the migration resource, so far none of them have been added to 2FAuth. Use the available buttons to permanently save them to your 2FA collection or discard them.',
'import_all' => 'Import all', 'import_all' => 'Import all',
'import_this_account' => 'Import this account', 'import_this_account' => 'Import this account',
'discard_all' => 'Discard all', 'discard_all' => 'Discard all',
@ -156,7 +159,7 @@
'possible_duplicate' => 'An account with the exact same data already exists', 'possible_duplicate' => 'An account with the exact same data already exists',
'invalid_account' => '- invalid account -', 'invalid_account' => '- invalid account -',
'invalid_service' => '- invalid service -', 'invalid_service' => '- invalid service -',
'do_not_set_password_or_encryption' => 'Do NOT enable Password protection or Encryption when you export data (from a 2FA app) you want to import into 2FAuth.', 'do_not_set_password_or_encryption' => 'Do NOT enable Password protection or Encryption when you export data from a 2FA app otherwise 2FAuth will not be able to decipher them.',
], ],
]; ];