Browse Source

Split components (#2)

* update: starting split components

* upd: rebase & continue splitting components

- layouts: header & footer
- components: basic buttton
- pages: login page

* update: login page

* package.json: remove dev:pass script
pull/1244/head
tetuaoro 10 months ago
committed by Bernd Storath
parent
commit
c0ff57b433
  1. 45
      src/app.vue
  2. 9
      src/components/Clients/RestoreConfig.vue
  3. 34
      src/components/base/Button.vue
  4. 0
      src/components/base/Container.vue
  5. 0
      src/components/ui/Icon.vue
  6. 0
      src/components/ui/Modal.vue
  7. 0
      src/components/ui/NavBar.vue
  8. 42
      src/layouts/Footer.vue
  9. 218
      src/layouts/Header.vue
  10. 342
      src/pages/index.vue
  11. 122
      src/pages/login.vue
  12. 7
      src/tailwind.config.ts

45
src/app.vue

@ -0,0 +1,45 @@
<template>
<NuxtLayout>
<NuxtLayout name="header" />
<main>
<NuxtPage />
</main>
<NuxtLayout name="footer" />
</NuxtLayout>
</template>
<script setup lang="ts">
import '~/assets/css/app.css';
useHead({
bodyAttrs: {
class: 'bg-gray-50 dark:bg-neutral-800',
},
link: [
{
rel: 'manifest',
href: '/manifest.json',
},
{
rel: 'icon',
type: 'image/png',
href: '/favicon.png',
},
{
rel: 'apple-touch-icon',
href: '/apple-touch-icon.png',
},
],
meta: [
{
name: 'apple-mobile-web-app-capable',
content: 'yes',
},
{
name: 'apple-mobile-web-app-status-bar-style',
content: 'black-translucent',
},
],
title: 'WireGuard',
});
</script>

9
src/components/Clients/RestoreConfig.vue

@ -34,15 +34,14 @@
<script setup lang="ts">
function restoreConfig(e: Event) {
e.preventDefault();
const isFile = e.currentTarget;
if (isFile) {
const file = (isFile as HTMLInputElement).files?.item(0);
const file = (e.currentTarget as HTMLInputElement).files?.item(0);
if (file) {
file
?.text()
.text()
.then((content) => {
api
.restoreConfiguration(content)
.then((_result) => alert('The configuration was updated.'))
.then(() => alert('The configuration was updated.'))
.catch((err) => alert(err.message || err.toString()));
})
.catch((err) => alert(err.message || err.toString()));

34
src/components/base/Button.vue

@ -0,0 +1,34 @@
<template>
<component :is="elementType" role="button" :class="btnClasses" v-bind="attrs">
<slot />
</component>
</template>
<script setup lang="ts">
const props = defineProps({
as: {
type: String,
default: 'button',
},
class: {
type: String,
default: '',
},
});
const elementType = computed(() => props.as);
const attrs = computed(() => {
const { as, class: _, ...attrs } = props;
return attrs;
});
const btnClasses =
props.class ||
`
py-2 px-4 transition-colors
hover:bg-primary hover:border-primary
hover:text-white text-gray-700 dark:text-neutral-200
max-md:border-l-0 border-2 border-gray-100 dark:border-neutral-600
`.replaceAll('\n', ' ');
</script>

0
src/components/base/Container.vue

0
src/components/ui/Icon.vue

0
src/components/ui/Modal.vue

0
src/components/ui/NavBar.vue

42
src/layouts/Footer.vue

@ -0,0 +1,42 @@
<template>
<footer>
<p
v-cloak
class="text-center m-10 text-gray-300 dark:text-neutral-600 text-xs"
>
<a
class="hover:underline"
target="_blank"
href="https://github.com/wg-easy/wg-easy"
>WireGuard Easy</a
>
© 2021-2024 by
<a
class="hover:underline"
target="_blank"
href="https://emilenijssen.nl/?ref=wg-easy"
>Emile Nijssen</a
>
is licensed under
<a
class="hover:underline"
target="_blank"
href="http://creativecommons.org/licenses/by-nc-sa/4.0/"
>CC BY-NC-SA 4.0</a
>
·
<a
class="hover:underline"
href="https://github.com/sponsors/WeeJeWel"
target="_blank"
>{{ $t('donate') }}</a
>
</p>
</footer>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'footer',
});
</script>

218
src/layouts/Header.vue

@ -0,0 +1,218 @@
<template>
<header class="container mx-auto max-w-3xl">
<div
class="flex flex-col-reverse xxs:flex-row flex-auto items-center gap-3"
>
<h1
class="text-4xl dark:text-neutral-200 font-medium flex-grow self-start mb-4"
>
<img
src="/logo.png"
width="32"
class="inline align-middle dark:bg mr-2"
/><span class="align-middle">WireGuard</span>
</h1>
<div class="flex items-center grow-0 gap-3 self-end xxs:self-center">
<!-- Dark / light theme -->
<button
class="flex items-center justify-center w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 transition"
:title="$t(`theme.${theme.preference}`)"
@click="toggleTheme"
>
<svg
v-if="theme.preference === 'light'"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"
/>
</svg>
<svg
v-else-if="theme.preference === 'dark'"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5 text-neutral-400"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z"
/>
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
class="w-5 h-5 fill-gray-600 dark:fill-neutral-400"
>
<path
d="M12,2.2c-5.4,0-9.8,4.4-9.8,9.8s4.4,9.8,9.8,9.8s9.8-4.4,9.8-9.8S17.4,2.2,12,2.2z M3.8,12c0-4.5,3.7-8.2,8.2-8.2v16.5C7.5,20.2,3.8,16.5,3.8,12z"
/>
</svg>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0V12a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 12V5.25"
/>
</button>
<!-- Show / hide charts -->
<label
v-if="uiChartType > 0"
class="inline-flex items-center justify-center cursor-pointer w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 whitespace-nowrap transition group"
:title="$t('toggleCharts')"
>
<input
v-model="uiShowCharts"
type="checkbox"
value=""
class="sr-only peer"
@change="toggleCharts"
/>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
stroke-width="1.5"
fill="currentColor"
class="w-5 h-5 peer fill-gray-400 peer-checked:fill-gray-600 dark:fill-neutral-600 peer-checked:dark:fill-neutral-400 group-hover:dark:fill-neutral-500 transition"
>
<path
d="M18.375 2.25c-1.035 0-1.875.84-1.875 1.875v15.75c0 1.035.84 1.875 1.875 1.875h.75c1.035 0 1.875-.84 1.875-1.875V4.125c0-1.036-.84-1.875-1.875-1.875h-.75ZM9.75 8.625c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v11.25c0 1.035-.84 1.875-1.875 1.875h-.75a1.875 1.875 0 0 1-1.875-1.875V8.625ZM3 13.125c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v6.75c0 1.035-.84 1.875-1.875 1.875h-.75A1.875 1.875 0 0 1 3 19.875v-6.75Z"
/>
</svg>
</label>
<span
v-if="requiresPassword && authenticated"
class="text-sm text-gray-400 dark:text-neutral-400 cursor-pointer hover:underline"
@click="logout"
>
{{ $t('logout') }}
<svg
class="h-3 inline"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
</span>
</div>
</div>
<div class="text-sm text-gray-400 dark:text-neutral-400 mb-5" />
<div
v-if="latestRelease"
class="bg-red-800 dark:bg-red-100 p-4 text-white dark:text-red-600 text-sm font-small mb-10 rounded-md shadow-lg"
:title="`v${currentRelease} → v${latestRelease.version}`"
>
<div class="container mx-auto flex flex-row flex-auto items-center">
<div class="flex-grow">
<p class="font-bold">{{ $t('updateAvailable') }}</p>
<p>{{ latestRelease.changelog }}</p>
</div>
<a
href="https://github.com/wg-easy/wg-easy#updating"
target="_blank"
class="p-3 rounded-md bg-white dark:bg-red-100 float-right font-sm font-semibold text-red-800 dark:text-red-600 flex-shrink-0 border-2 border-red-800 dark:border-red-600 hover:border-white dark:hover:border-red-600 hover:text-white dark:hover:text-red-100 hover:bg-red-800 dark:hover:bg-red-600 transition-all"
>
{{ $t('update') }}
</a>
</div>
</div>
</header>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'header',
});
type ClientPersist = {
transferRxHistory: number[];
transferRxPrevious: number;
transferRxCurrent: number;
transferRxSeries: { name: string; data: number[] }[];
hoverRx?: unknown;
transferTxHistory: number[];
transferTxPrevious: number;
transferTxCurrent: number;
transferTxSeries: { name: string; data: number[] }[];
hoverTx?: unknown;
};
type LocalClient = WGClient & {
avatar?: string;
transferMax?: number;
} & Omit<ClientPersist, 'transferRxPrevious' | 'transferTxPrevious'>;
const authenticated = ref<null | boolean>(null);
const requiresPassword = ref<null | boolean>(null);
const clients = ref<null | LocalClient[]>(null);
const currentRelease = ref<null | number>(null);
const latestRelease = ref<null | { version: number; changelog: string }>(null);
const theme = useTheme();
const uiChartType = ref(0);
const uiShowCharts = ref(getItem('uiShowCharts') === '1');
function toggleTheme() {
const themeCycle = {
system: 'light',
light: 'dark',
dark: 'system',
} as const;
theme.preference = themeCycle[theme.preference];
}
function toggleCharts() {
setItem('uiShowCharts', uiShowCharts.value ? '1' : '0');
}
function logout(e: Event) {
e.preventDefault();
api
.deleteSession()
.then(() => {
authenticated.value = false;
clients.value = null;
window.location.replace('/login');
})
.catch((err) => {
alert(err.message || err.toString());
});
}
onMounted(() => {
api
.getSession()
.then((session) => {
authenticated.value = session.authenticated;
requiresPassword.value = session.requiresPassword;
})
.catch((err) => {
alert(err.message || err.toString());
});
});
</script>

342
src/pages/index.vue

@ -1,142 +1,6 @@
<template>
<div v-cloak class="container mx-auto max-w-3xl px-3 md:px-0 mt-4 xs:mt-6">
<div v-if="authenticated === true">
<div
class="flex flex-col-reverse xxs:flex-row flex-auto items-center gap-3"
>
<h1
class="text-4xl dark:text-neutral-200 font-medium flex-grow self-start mb-4"
>
<img
src="/logo.png"
width="32"
class="inline align-middle dark:bg mr-2"
/><span class="align-middle">WireGuard</span>
</h1>
<div class="flex items-center grow-0 gap-3 self-end xxs:self-center">
<!-- Dark / light theme -->
<button
class="flex items-center justify-center w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 transition"
:title="$t(`theme.${theme.preference}`)"
@click="toggleTheme"
>
<svg
v-if="theme.preference === 'light'"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"
/>
</svg>
<svg
v-else-if="theme.preference === 'dark'"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5 text-neutral-400"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z"
/>
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
class="w-5 h-5 fill-gray-600 dark:fill-neutral-400"
>
<path
d="M12,2.2c-5.4,0-9.8,4.4-9.8,9.8s4.4,9.8,9.8,9.8s9.8-4.4,9.8-9.8S17.4,2.2,12,2.2z M3.8,12c0-4.5,3.7-8.2,8.2-8.2v16.5C7.5,20.2,3.8,16.5,3.8,12z"
/>
</svg>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0V12a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 12V5.25"
/>
</button>
<!-- Show / hide charts -->
<label
v-if="uiChartType > 0"
class="inline-flex items-center justify-center cursor-pointer w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 whitespace-nowrap transition group"
:title="$t('toggleCharts')"
>
<input
v-model="uiShowCharts"
type="checkbox"
value=""
class="sr-only peer"
@change="toggleCharts"
/>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
stroke-width="1.5"
fill="currentColor"
class="w-5 h-5 peer fill-gray-400 peer-checked:fill-gray-600 dark:fill-neutral-600 peer-checked:dark:fill-neutral-400 group-hover:dark:fill-neutral-500 transition"
>
<path
d="M18.375 2.25c-1.035 0-1.875.84-1.875 1.875v15.75c0 1.035.84 1.875 1.875 1.875h.75c1.035 0 1.875-.84 1.875-1.875V4.125c0-1.036-.84-1.875-1.875-1.875h-.75ZM9.75 8.625c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v11.25c0 1.035-.84 1.875-1.875 1.875h-.75a1.875 1.875 0 0 1-1.875-1.875V8.625ZM3 13.125c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v6.75c0 1.035-.84 1.875-1.875 1.875h-.75A1.875 1.875 0 0 1 3 19.875v-6.75Z"
/>
</svg>
</label>
<span
v-if="requiresPassword"
class="text-sm text-gray-400 dark:text-neutral-400 cursor-pointer hover:underline"
@click="logout"
>
{{ $t('logout') }}
<svg
class="h-3 inline"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
/>
</svg>
</span>
</div>
</div>
<div class="text-sm text-gray-400 dark:text-neutral-400 mb-5" />
<div
v-if="latestRelease"
class="bg-red-800 dark:bg-red-100 p-4 text-white dark:text-red-600 text-sm font-small mb-10 rounded-md shadow-lg"
:title="`v${currentRelease} → v${latestRelease.version}`"
>
<div class="container mx-auto flex flex-row flex-auto items-center">
<div class="flex-grow">
<p class="font-bold">{{ $t('updateAvailable') }}</p>
<p>{{ latestRelease.changelog }}</p>
</div>
<a
href="https://github.com/wg-easy/wg-easy#updating"
target="_blank"
class="p-3 rounded-md bg-white dark:bg-red-100 float-right font-sm font-semibold text-red-800 dark:text-red-600 flex-shrink-0 border-2 border-red-800 dark:border-red-600 hover:border-white dark:hover:border-red-600 hover:text-white dark:hover:text-red-100 hover:bg-red-800 dark:hover:bg-red-600 transition-all"
>
{{ $t('update') }}
</a>
</div>
</div>
<div
class="shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden"
>
@ -964,85 +828,6 @@
</div>
</div>
<div v-if="authenticated === false">
<h1
class="text-4xl font-medium my-16 text-gray-700 dark:text-neutral-200 text-center"
>
<img src="/logo.png" width="32" class="inline align-middle dark:bg" />
<span class="align-middle">WireGuard</span>
</h1>
<form
class="shadow rounded-md bg-white dark:bg-neutral-700 mx-auto w-64 p-5 overflow-hidden mt-10"
@submit="login"
>
<!-- Avatar -->
<div
class="h-20 w-20 mb-10 mt-5 mx-auto rounded-full bg-red-800 dark:bg-red-800 relative overflow-hidden"
>
<svg
class="w-10 h-10 m-5 text-white dark:text-white"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"
/>
</svg>
</div>
<input
v-model="password"
type="password"
name="password"
:placeholder="$t('password')"
autocomplete="current-password"
class="px-3 py-2 text-sm dark:bg-neutral-700 text-gray-500 dark:text-gray-500 mb-5 border-2 border-gray-100 dark:border-neutral-800 rounded-lg w-full focus:border-red-800 dark:focus:border-red-800 dark:placeholder:text-neutral-400 outline-none"
/>
<button
v-if="authenticating"
class="bg-red-800 dark:bg-red-800 w-full rounded shadow py-2 text-sm text-white dark:text-white cursor-not-allowed"
>
<svg
class="w-5 animate-spin mx-auto"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</button>
<input
v-if="!authenticating && password"
type="submit"
class="bg-red-800 dark:bg-red-800 w-full rounded shadow py-2 text-sm text-white dark:text-white hover:bg-red-700 dark:hover:bg-red-700 transition cursor-pointer"
:value="$t('signIn')"
/>
<input
v-if="!authenticating && !password"
type="submit"
class="bg-gray-200 dark:bg-neutral-800 w-full rounded shadow py-2 text-sm text-white dark:text-white cursor-not-allowed"
:value="$t('signIn')"
/>
</form>
</div>
<div
v-if="authenticated === null"
class="text-gray-300 dark:text-red-300 pt-24 pb-12"
@ -1068,79 +853,13 @@
/>
</svg>
</div>
<p
v-cloak
class="text-center m-10 text-gray-300 dark:text-neutral-600 text-xs"
>
<a
class="hover:underline"
target="_blank"
href="https://github.com/wg-easy/wg-easy"
>WireGuard Easy</a
>
© 2021-2024 by
<a
class="hover:underline"
target="_blank"
href="https://emilenijssen.nl/?ref=wg-easy"
>Emile Nijssen</a
>
is licensed under
<a
class="hover:underline"
target="_blank"
href="http://creativecommons.org/licenses/by-nc-sa/4.0/"
>CC BY-NC-SA 4.0</a
>
·
<a
class="hover:underline"
href="https://github.com/sponsors/WeeJeWel"
target="_blank"
>{{ $t('donate') }}</a
>
</p>
</div>
</template>
<script setup lang="ts">
import '~/assets/css/app.css';
import { sha256 } from 'js-sha256';
import { format as timeago } from 'timeago.js';
useHead({
bodyAttrs: {
class: 'bg-gray-50 dark:bg-neutral-800',
},
link: [
{
rel: 'manifest',
href: '/manifest.json',
},
{
rel: 'icon',
type: 'image/png',
href: '/favicon.png',
},
{
rel: 'apple-touch-icon',
href: '/apple-touch-icon.png',
},
],
meta: [
{
name: 'apple-mobile-web-app-capable',
content: 'yes',
},
{
name: 'apple-mobile-web-app-status-bar-style',
content: 'black-translucent',
},
],
title: 'WireGuard',
});
const UI_CHART_TYPES = [
{ type: false, strokeWidth: 0 },
{ type: 'line', strokeWidth: 3 },
@ -1158,8 +877,6 @@ const CHART_COLORS = {
};
const authenticated = ref<null | boolean>(null);
const authenticating = ref(false);
const password = ref<null | string>(null);
const requiresPassword = ref<null | boolean>(null);
type LocalClient = WGClient & {
@ -1378,47 +1095,6 @@ async function refresh({ updateCharts = false } = {}) {
};
});
}
function login(e: Event) {
e.preventDefault();
if (!password.value) return;
if (authenticating.value) return;
authenticating.value = true;
api
.createSession({
password: password.value,
})
.then(async () => {
const session = await api.getSession();
authenticated.value = session.authenticated;
requiresPassword.value = session.requiresPassword;
return refresh();
})
.catch((err) => {
// TODO: replace alert with actual ui error message
alert(err.message || err.toString());
})
.finally(() => {
authenticating.value = false;
password.value = null;
});
}
function logout(e: Event) {
e.preventDefault();
api
.deleteSession()
.then(() => {
authenticated.value = false;
clients.value = null;
})
.catch((err) => {
alert(err.message || err.toString());
});
}
function createClient() {
const name = clientCreateName.value;
if (!name) return;
@ -1468,26 +1144,16 @@ function updateClientAddress(client: WGClient, address: string | null) {
.finally(() => refresh().catch(console.error));
}
function toggleTheme() {
const themeCycle = {
system: 'light',
light: 'dark',
dark: 'system',
} as const;
theme.preference = themeCycle[theme.preference];
}
function toggleCharts() {
setItem('uiShowCharts', uiShowCharts.value ? '1' : '0');
}
const { availableLocales, locale } = useI18n();
onMounted(() => {
api
.getSession()
.then((session) => {
if (session.requiresPassword && !session.authenticated) {
window.location.replace('/login');
}
authenticated.value = session.authenticated;
requiresPassword.value = session.requiresPassword;
refresh({

122
src/pages/login.vue

@ -0,0 +1,122 @@
<template>
<section>
<h1
class="text-4xl font-medium my-16 text-gray-700 dark:text-neutral-200 text-center"
>
<img src="/logo.png" width="32" class="inline align-middle dark:bg" />
<span class="align-middle">WireGuard</span>
</h1>
<form
class="shadow rounded-md bg-white dark:bg-neutral-700 mx-auto w-64 p-5 overflow-hidden mt-10"
@submit="login"
>
<!-- Avatar -->
<div
class="h-20 w-20 mb-10 mt-5 mx-auto rounded-full bg-red-800 dark:bg-red-800 relative overflow-hidden"
>
<svg
class="w-10 h-10 m-5 text-white dark:text-white"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"
/>
</svg>
</div>
<input
v-model="password"
type="password"
name="password"
:placeholder="$t('password')"
autocomplete="current-password"
class="px-3 py-2 text-sm dark:bg-neutral-700 text-gray-500 dark:text-gray-500 mb-5 border-2 border-gray-100 dark:border-neutral-800 rounded-lg w-full focus:border-red-800 dark:focus:border-red-800 dark:placeholder:text-neutral-400 outline-none"
/>
<button
v-if="authenticating"
class="bg-red-800 dark:bg-red-800 w-full rounded shadow py-2 text-sm text-white dark:text-white cursor-not-allowed"
>
<svg
class="w-5 animate-spin mx-auto"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
</button>
<input
v-if="!authenticating && password"
type="submit"
class="bg-red-800 dark:bg-red-800 w-full rounded shadow py-2 text-sm text-white dark:text-white hover:bg-red-700 dark:hover:bg-red-700 transition cursor-pointer"
:value="$t('signIn')"
/>
<input
v-if="!authenticating && !password"
type="submit"
class="bg-gray-200 dark:bg-neutral-800 w-full rounded shadow py-2 text-sm text-white dark:text-white cursor-not-allowed"
:value="$t('signIn')"
/>
</form>
</section>
</template>
<script setup lang="ts">
const authenticated = ref<null | boolean>(null);
const authenticating = ref(false);
const password = ref<null | string>(null);
const requiresPassword = ref<null | boolean>(null);
function login(e: Event) {
e.preventDefault();
if (!password.value) return;
if (authenticating.value) return;
authenticating.value = true;
api
.createSession({
password: password.value,
})
.then(async () => {
const session = await api.getSession();
authenticated.value = session.authenticated;
requiresPassword.value = session.requiresPassword;
window.location.replace('/');
})
.catch((err) => {
// TODO: replace alert with actual ui error message
alert(err.message || err.toString());
})
.finally(() => {
authenticating.value = false;
password.value = null;
});
}
onMounted(() => {
api.getSession().then((session) => {
if (session.authenticated || !session.requiresPassword) {
window.location.replace('/');
}
});
});
</script>

7
src/tailwind.config.ts

@ -1,5 +1,6 @@
import type { Config } from 'tailwindcss';
import type { PluginAPI } from 'tailwindcss/types/config';
import * as colors from 'tailwindcss/colors.js';
export default {
darkMode: 'selector',
@ -14,6 +15,12 @@ export default {
xl: '1280px',
'2xl': '1536px',
},
extend: {
colors: {
DEFAULT: colors.red[800],
primary: colors.red[800],
},
},
},
plugins: [
function addDisabledClass({ addUtilities }: PluginAPI) {

Loading…
Cancel
Save