Compare commits

..

No commits in common. "a199f2465f6223ed1dccf069f44e161e51f09063" and "2ec8bb430380c06d7dfa13627f9b14a702ca67f0" have entirely different histories.

16 changed files with 170 additions and 194 deletions

View file

@ -12,12 +12,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Changed
- The style has been entirely reworked using Bootstrap instead of Bulma
## [0.3.0] - 2023-08-25 ## [0.3.0] - 2023-08-25
### Added ### Added
@ -29,7 +23,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The local part cannot contain the separator - The local part cannot contain the separator
- The HTML lang attribute is now set to the appropriate language - The HTML lang attribute is now set to the appropriate language
## [0.2.0] - 2023-08-11 ## [0.2.0] - 2023-08-11
### Added ### Added

View file

@ -1,10 +1,10 @@
# Sub-Address KEy (SAKE) app # Sub-Address KEy (SAKE) app
Web application that can be used to generate new sub-addresses as defined in the [Sub-Address KEy (SAKE) filter](https://git.what.tf/rodolphe/opensmtpd-filter-sake). Web application that can be used to generate new sub-addresses as defined in the [Sub-Address KEy (SAKE) filter](https://github.com/breard-r/opensmtpd-filter-sake).
## Install ## Install
Download the build from [the latest released version](https://git.what.tf/rodolphe/sake-app/releases). Extract the archive and configure your web server to serve those files. Download the build from [the latest released version](https://github.com/breard-r/sake-app/releases). Extract the archive and configure your web server to serve those files.
That's it. The final build is plain HTML/CSS/JS with a few assets, therefore there is no back-end to configure. That's it. The final build is plain HTML/CSS/JS with a few assets, therefore there is no back-end to configure.

View file

@ -1,7 +1,7 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="UTF-8">
<link rel="icon" href="favicon.ico"> <link rel="icon" href="favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sub-Address KEy</title> <title>Sub-Address KEy</title>

47
package-lock.json generated
View file

@ -10,10 +10,9 @@
"license": "(MIT OR Apache-2.0)", "license": "(MIT OR Apache-2.0)",
"dependencies": { "dependencies": {
"@noble/hashes": "^1.3.1", "@noble/hashes": "^1.3.1",
"@popperjs/core": "^2.11.8",
"@vueuse/core": "^10.2.1", "@vueuse/core": "^10.2.1",
"base32-encode": "^2.0.0", "base32-encode": "^2.0.0",
"bootstrap": "^5.3.2", "bulma": "^0.9.4",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.2.2",
"vue-qrcode-reader": "^5.1.0", "vue-qrcode-reader": "^5.1.0",
@ -584,15 +583,6 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rollup/pluginutils": { "node_modules/@rollup/pluginutils": {
"version": "5.0.4", "version": "5.0.4",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz",
@ -917,24 +907,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/bootstrap": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz",
"integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
@ -947,6 +919,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/bulma": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.9.4.tgz",
"integrity": "sha512-86FlT5+1GrsgKbPLRRY7cGDg8fsJiP/jzTqXXVqiUZZ2aZT8uemEOHlU1CDU+TxklPEZ11HZNNWclRBBecP4CQ=="
},
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "3.5.3", "version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@ -1546,9 +1523,9 @@
} }
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.68.0", "version": "1.67.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.68.0.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.67.0.tgz",
"integrity": "sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==", "integrity": "sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"chokidar": ">=3.0.0 <4.0.0", "chokidar": ">=3.0.0 <4.0.0",
@ -1756,9 +1733,9 @@
} }
}, },
"node_modules/vue-router": { "node_modules/vue-router": {
"version": "4.2.5", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.4.tgz",
"integrity": "sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==", "integrity": "sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==",
"dependencies": { "dependencies": {
"@vue/devtools-api": "^6.5.0" "@vue/devtools-api": "^6.5.0"
}, },

View file

@ -15,10 +15,9 @@
}, },
"dependencies": { "dependencies": {
"@noble/hashes": "^1.3.1", "@noble/hashes": "^1.3.1",
"@popperjs/core": "^2.11.8",
"@vueuse/core": "^10.2.1", "@vueuse/core": "^10.2.1",
"base32-encode": "^2.0.0", "base32-encode": "^2.0.0",
"bootstrap": "^5.3.2", "bulma": "^0.9.4",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.2.2",
"vue-qrcode-reader": "^5.1.0", "vue-qrcode-reader": "^5.1.0",

View file

@ -1,4 +1,9 @@
@charset "utf-8" @charset "utf-8"
@import "variables.scss" @import "node_modules/bulma/bulma"
@import "bootstrap/scss/bootstrap"
a[href]
text-decoration: underline
.navbar-menu a[class~="navbar-item"]
text-decoration: none

View file

@ -1,7 +0,0 @@
$container-max-widths: (
sm: 600px,
md: 700px,
lg: 800px,
xl: 820px,
xxl: 840px
);

View file

@ -1,5 +0,0 @@
<template>
<div class="d-grid gap-2 col-6 mx-auto">
<slot></slot>
</div>
</template>

View file

@ -1,5 +1,11 @@
<template> <template>
<section class="section">
<div class="container"> <div class="container">
<div class="columns is-centered">
<div class="column is-three-fifths">
<slot></slot> <slot></slot>
</div> </div>
</div>
</div>
</section>
</template> </template>

View file

@ -1,34 +1,35 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { RouterLink } from 'vue-router'; import { RouterLink } from 'vue-router';
import LayoutComponent from '../components/LayoutComponent.vue';
import { Popover } from 'bootstrap'; const menuActive = ref(false);
const toggleBurger = () => {
menuActive.value = !menuActive.value;
};
</script> </script>
<template> <template>
<nav class="navbar navbar-expand-lg bg-body-tertiary"> <div class="container">
<div class="container-fluid"> <div class="columns is-centered">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNavBar" aria-controls="mainNavBar" aria-expanded="false" aria-label="Toggle navigation"> <div class="column is-three-fifths">
<span class="navbar-toggler-icon"></span> <nav class="navbar" role="navigation" aria-label="main navigation">
</button> <div class="navbar-brand">
<div class="collapse navbar-collapse" id="mainNavBar"> <a role="button" class="navbar-burger" :class="{ 'is-active': menuActive }" aria-label="menu" aria-expanded="false" @click="toggleBurger">
<LayoutComponent> <span aria-hidden="true"></span>
<ul class="navbar-nav justify-content-end flex-grow-1 pe-3"> <span aria-hidden="true"></span>
<li class="nav-item"> <span aria-hidden="true"></span>
<RouterLink to="/add-account" class="nav-link">{{ $t("navbar.addAccount") }}</RouterLink> </a>
</li> </div>
<li class="nav-item"> <div class="navbar-menu" :class="{ 'is-active': menuActive }">
<RouterLink to="/manage-accounts" class="nav-link">{{ $t("navbar.manageAccounts") }}</RouterLink> <div class="navbar-end">
</li> <RouterLink to="/add-account" class="navbar-item">{{ $t("navbar.addAccount") }}</RouterLink>
<li class="nav-item"> <RouterLink to="/manage-accounts" class="navbar-item">{{ $t("navbar.manageAccounts") }}</RouterLink>
<RouterLink to="/config" class="nav-link">{{ $t("navbar.config") }}</RouterLink> <RouterLink to="/config" class="navbar-item">{{ $t("navbar.config") }}</RouterLink>
</li> <RouterLink to="/about" class="navbar-item">{{ $t("navbar.about") }}</RouterLink>
<li class="nav-item">
<RouterLink to="/about" class="nav-link">{{ $t("navbar.about") }}</RouterLink>
</li>
</ul>
</LayoutComponent>
</div> </div>
</div> </div>
</nav> </nav>
</div>
</div>
</div>
</template> </template>

View file

@ -1,7 +1,6 @@
<script setup> <script setup>
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { version } from '../../package.json'; import { version } from '../../package.json';
import ButtonGroupComponent from '../components/ButtonGroupComponent.vue';
import LayoutComponent from '../components/LayoutComponent.vue'; import LayoutComponent from '../components/LayoutComponent.vue';
import ExternalLinkComponent from '../components/ExternalLinkComponent.vue'; import ExternalLinkComponent from '../components/ExternalLinkComponent.vue';
@ -15,9 +14,9 @@ const toMainView = () => {
<template> <template>
<LayoutComponent> <LayoutComponent>
<h1>{{ $t("about.title") }}</h1> <h1 class="title is-1">{{ $t("about.title") }}</h1>
<h4>{{ $t("about.name") }}</h4> <h4 class="subtitle is-4">{{ $t("about.name") }}</h4>
<div class="block">
<i18n-t scope="global" keypath="about.version" tag="p"> <i18n-t scope="global" keypath="about.version" tag="p">
<template v-slot:version>{{ version }}</template> <template v-slot:version>{{ version }}</template>
</i18n-t> </i18n-t>
@ -34,9 +33,11 @@ const toMainView = () => {
<ExternalLinkComponent :url="repoUrl" :name="repoUrl" /> <ExternalLinkComponent :url="repoUrl" :name="repoUrl" />
</template> </template>
</i18n-t> </i18n-t>
</div>
<ButtonGroupComponent> <div class="block">
<button type="button" class="btn btn-secondary" @click="toMainView">{{ $t("about.close") }}</button> <div class="buttons is-centered">
</ButtonGroupComponent> <button class="button is-light" @click="toMainView">{{ $t("about.close") }}</button>
</div>
</div>
</LayoutComponent> </LayoutComponent>
</template> </template>

View file

@ -5,7 +5,6 @@ import { useStorage } from '@vueuse/core';
import { QrcodeStream, setZXingModuleOverrides } from 'vue-qrcode-reader'; import { QrcodeStream, setZXingModuleOverrides } from 'vue-qrcode-reader';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import base32Encode from 'base32-encode'; import base32Encode from 'base32-encode';
import ButtonGroupComponent from '../components/ButtonGroupComponent.vue';
import LayoutComponent from '../components/LayoutComponent.vue'; import LayoutComponent from '../components/LayoutComponent.vue';
import wasmFile from "../../node_modules/@sec-ant/zxing-wasm/dist/reader/zxing_reader.wasm?url"; import wasmFile from "../../node_modules/@sec-ant/zxing-wasm/dist/reader/zxing_reader.wasm?url";
@ -132,38 +131,43 @@ const resetErrorMessage = () => {
<template> <template>
<LayoutComponent> <LayoutComponent>
<h1>{{ $t("addAccount.title") }}</h1> <h1 class="title is-1">{{ $t("addAccount.title") }}</h1>
<div class="notification is-danger is-light" v-if="errorMessageId">
<div class="alert alert-danger alert-dismissible fade show" role="alert" v-if="errorMessageId"> <button class="delete" @click="resetErrorMessage"></button>
{{ $t(errorMessageId) }} {{ $t(errorMessageId) }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close" @click="resetErrorMessage"></button>
</div> </div>
<div class="container" id="new-account-error-msg-container"></div>
<div class="mb-3"> <div class="field">
<label class="form-label" for="new-addr-local-part">{{ $t("addAccount.localPart") }}</label> <label class="label" for="new-addr-local-part">{{ $t("addAccount.localPart") }}</label>
<input class="form-control" type="text" id="new-addr-local-part" v-model="localPart"> <div class="control">
</div> <input class="input" type="text" id="new-addr-local-part" v-model="localPart">
<div class="mb-3">
<label class="form-label" for="new-addr-separator">{{ $t("addAccount.separator") }}</label>
<input class="form-control" type="text" id="new-addr-separator" v-model="separator">
</div>
<div class="mb-3">
<label class="form-label" for="new-addr-domain">{{ $t("addAccount.domainName") }}</label>
<input class="form-control" type="text" id="new-addr-domain" placeholder="example.org" v-model="domainName">
</div>
<div class="mb-3">
<label class="form-label" for="new-addr-key">{{ $t("addAccount.privateKey") }}</label>
<div class="input-group">
<input class="form-control" type="text" id="new-addr-key" v-model="privateKey">
<button class="btn btn-primary" type="button" @click="showQrCodeScanner">{{ $t("addAccount.scan") }}</button>
</div> </div>
</div> </div>
<div class="field">
<label class="label" for="new-addr-separator">{{ $t("addAccount.separator") }}</label>
<div class="control">
<input class="input" type="text" id="new-addr-separator" v-model="separator">
</div>
</div>
<div class="field">
<label class="label" for="new-addr-domain">{{ $t("addAccount.domainName") }}</label>
<div class="control">
<input class="input" type="text" id="new-addr-domain" placeholder="example.org" v-model="domainName">
</div>
</div>
<label class="label" for="new-addr-key">{{ $t("addAccount.privateKey") }}</label>
<div class="field has-addons">
<div class="control is-expanded">
<input class="input" type="text" id="new-addr-key" v-model="privateKey">
</div>
<p class="control">
<a class="button is-primary" @click="showQrCodeScanner">{{ $t("addAccount.scan") }}</a>
</p>
</div>
<qrcode-stream v-if="scanQrCode" @detect="onQrCodeDetected" @error="onQrCodeError"></qrcode-stream> <qrcode-stream v-if="scanQrCode" @detect="onQrCodeDetected" @error="onQrCodeError"></qrcode-stream>
<div class="buttons is-centered">
<ButtonGroupComponent> <button class="button is-primary" :disabled="addDisabled" @click="addAccount">{{ $t("addAccount.addAccount") }}</button>
<button type="button" class="btn btn-primary" :disabled="addDisabled" @click="addAccount">{{ $t("addAccount.addAccount") }}</button> <button class="button is-light" v-if="!cancellDisabled" @click="toMainView">{{ $t("addAccount.cancel") }}</button>
<button type="button" class="btn btn-secondary" v-if="!cancellDisabled" @click="toMainView">{{ $t("addAccount.cancel") }}</button> </div>
</ButtonGroupComponent>
</LayoutComponent> </LayoutComponent>
</template> </template>

View file

@ -3,7 +3,6 @@ import { watch } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useStorage } from '@vueuse/core'; import { useStorage } from '@vueuse/core';
import ButtonGroupComponent from '../components/ButtonGroupComponent.vue';
import LayoutComponent from '../components/LayoutComponent.vue'; import LayoutComponent from '../components/LayoutComponent.vue';
const router = useRouter(); const router = useRouter();
@ -22,17 +21,19 @@ watch(locale, async (newLocale) => {
<template> <template>
<LayoutComponent> <LayoutComponent>
<h1>{{ $t("config.title") }}</h1> <h1 class="title is-1">{{ $t("config.title") }}</h1>
<div class="field">
<div class="mb-3"> <label class="label" for="app-language">{{ $t("config.language") }}</label>
<label class="form-label" for="app-language">{{ $t("config.language") }}</label> <div class="control">
<select class="form-select" id="app-language" v-model="$i18n.locale"> <div class="select is-fullwidth">
<select id="app-language" v-model="$i18n.locale">
<option v-for="locale_id in $i18n.availableLocales" :key="`locale-${locale_id}`" :value="locale_id">{{ $t("locale_name", 1, { locale: locale_id}) }}</option> <option v-for="locale_id in $i18n.availableLocales" :key="`locale-${locale_id}`" :value="locale_id">{{ $t("locale_name", 1, { locale: locale_id}) }}</option>
</select> </select>
</div> </div>
</div>
<ButtonGroupComponent> </div>
<button type="button" class="btn btn-secondary" @click="toMainView">{{ $t("about.close") }}</button> <div class="buttons is-centered">
</ButtonGroupComponent> <button class="button is-light" @click="toMainView">{{ $t("config.close") }}</button>
</div>
</LayoutComponent> </LayoutComponent>
</template> </template>

View file

@ -1,7 +1,6 @@
<script setup> <script setup>
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useStorage } from '@vueuse/core'; import { useStorage } from '@vueuse/core';
import ButtonGroupComponent from '../components/ButtonGroupComponent.vue';
import LayoutComponent from '../components/LayoutComponent.vue'; import LayoutComponent from '../components/LayoutComponent.vue';
const accounts = useStorage('sake-accounts', []); const accounts = useStorage('sake-accounts', []);
@ -20,15 +19,13 @@ const toMainView = () => {
<template> <template>
<LayoutComponent> <LayoutComponent>
<h1>{{ $t("deleteAccount.title") }}</h1> <h1 class="title is-1">{{ $t("deleteAccount.title") }}</h1>
<p>{{ $t("deleteAccount.account") }}</p> <p>{{ $t("deleteAccount.account") }}</p>
<p class="has-text-weight-semibold is-size-5">{{ account.localPart }}@{{ account.domain }}</p> <p class="has-text-weight-semibold is-size-5">{{ account.localPart }}@{{ account.domain }}</p>
<p>{{ $t("deleteAccount.confirm") }}</p> <p>{{ $t("deleteAccount.confirm") }}</p>
<div class="buttons is-centered">
<ButtonGroupComponent> <button class="button is-danger" @click="deleteAccount">{{ $t("deleteAccount.delete") }}</button>
<button type="button" class="btn btn-danger" @click="deleteAccount">{{ $t("deleteAccount.delete") }}</button> <button class="button is-light" @click="toMainView">{{ $t("deleteAccount.cancel") }}</button>
<button type="button" class="btn btn-secondary" @click="toMainView">{{ $t("deleteAccount.cancel") }}</button> </div>
</ButtonGroupComponent>
</LayoutComponent> </LayoutComponent>
</template> </template>

View file

@ -6,7 +6,6 @@ import { useStorage } from '@vueuse/core';
import { hmac } from '@noble/hashes/hmac'; import { hmac } from '@noble/hashes/hmac';
import { sha256 } from '@noble/hashes/sha256'; import { sha256 } from '@noble/hashes/sha256';
import base32Encode from 'base32-encode'; import base32Encode from 'base32-encode';
import ButtonGroupComponent from '../components/ButtonGroupComponent.vue';
import LayoutComponent from '../components/LayoutComponent.vue'; import LayoutComponent from '../components/LayoutComponent.vue';
import NavBarComponent from '../components/NavBarComponent.vue'; import NavBarComponent from '../components/NavBarComponent.vue';
@ -53,28 +52,32 @@ const resetForm = () => {
<template> <template>
<NavBarComponent /> <NavBarComponent />
<LayoutComponent> <LayoutComponent>
<h1>{{ $t("main.title") }}</h1> <h1 class="title is-1">{{ $t("main.title") }}</h1>
<div class="field">
<div class="mb-3"> <label class="label" for="account-name">{{ $t("main.account") }}</label>
<label class="form-label" for="account-name">{{ $t("main.account") }}</label> <div class="control">
<select class="form-select" id="account-name" v-model="selectedAccountId"> <div class="select is-fullwidth">
<select id="account-name" v-model="selectedAccountId">
<option v-for="account in accounts" :key="account.id" :value="account.id">{{ account.localPart }}@{{ account.domain }}</option> <option v-for="account in accounts" :key="account.id" :value="account.id">{{ account.localPart }}@{{ account.domain }}</option>
</select> </select>
</div> </div>
<div class="mb-3">
<label class="form-label" for="sub-addr-name">{{ $t("main.name") }}</label>
<input class="form-control" type="text" id="sub-addr-name" :placeholder="$t('main.input')" v-model="subAddrName">
</div> </div>
<div class="mb-3">
<label class="form-label" for="generated-addr">{{ $t("main.address") }}</label>
<input class="form-control" type="text" id="generated-addr" v-model="generatedAddr" disabled>
</div> </div>
<div class="field">
<ButtonGroupComponent> <label class="label" for="sub-addr-name">{{ $t("main.name") }}</label>
<button type="button" class="btn btn-primary" @click="copyAddr">{{ $t("main.copy") }}</button> <div class="control">
<button type="button" class="btn btn-secondary" @click="resetForm">{{ $t("main.reset") }}</button> <input class="input" type="text" id="sub-addr-name" :placeholder="$t('main.input')" v-model="subAddrName">
</ButtonGroupComponent> </div>
</div>
<div class="field">
<label class="label" for="generated-addr">{{ $t("main.address") }}</label>
<div class="control">
<input class="input" type="text" id="generated-addr" v-model="generatedAddr" disabled>
</div>
</div>
<div class="buttons is-centered">
<button class="button is-primary" @click="copyAddr">{{ $t("main.copy") }}</button>
<button class="button is-light" @click="resetForm">{{ $t("main.reset") }}</button>
</div>
</LayoutComponent> </LayoutComponent>
</template> </template>

View file

@ -2,7 +2,6 @@
import { sortAccounts } from '../accounts'; import { sortAccounts } from '../accounts';
import { RouterLink, useRouter } from 'vue-router'; import { RouterLink, useRouter } from 'vue-router';
import { useStorage } from '@vueuse/core'; import { useStorage } from '@vueuse/core';
import ButtonGroupComponent from '../components/ButtonGroupComponent.vue';
import LayoutComponent from '../components/LayoutComponent.vue'; import LayoutComponent from '../components/LayoutComponent.vue';
const router = useRouter(); const router = useRouter();
@ -18,23 +17,25 @@ const toMainView = () => {
<template> <template>
<LayoutComponent> <LayoutComponent>
<h1>{{ $t("manageAccounts.title") }}</h1> <h1 class="title is-1">{{ $t("manageAccounts.title") }}</h1>
<div class="block">
<table class="table"> <table class="table is-fullwidth">
<tbody> <tbody>
<tr v-for="account in accounts"> <tr v-for="account in accounts">
<th class="text-end align-middle"> <th class="has-text-right is-vcentered">
{{ account.localPart }}@{{ account.domain }} {{ account.localPart }}@{{ account.domain }}
</th> </th>
<th> <th>
<button type="button" class="btn btn-danger" @click="deleteAccount(account.id)">{{ $t("manageAccounts.delete") }}</button> <button class="button is-danger" @click="deleteAccount(account.id)">{{ $t("manageAccounts.delete") }}</button>
</th> </th>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
<ButtonGroupComponent> <div class="block">
<button type="button" class="btn btn-secondary" @click="toMainView">{{ $t("manageAccounts.close") }}</button> <div class="buttons is-centered">
</ButtonGroupComponent> <button class="button is-light" @click="toMainView">{{ $t("manageAccounts.close") }}</button>
</div>
</div>
</LayoutComponent> </LayoutComponent>
</template> </template>