mirror of https://github.com/wg-easy/wg-easy
25 changed files with 386 additions and 455 deletions
@ -1,23 +0,0 @@ |
|||
<template> |
|||
<div> |
|||
<p class="text-lg p-8 text-center"> |
|||
{{ $t('setup.messageSetupValidation') }} |
|||
</p> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
const emit = defineEmits(['validated']); |
|||
|
|||
const props = defineProps<{ |
|||
next: boolean; |
|||
}>(); |
|||
|
|||
const next = toRef(props, 'next'); |
|||
|
|||
watch(next, (newVal) => { |
|||
if (newVal) { |
|||
emit('validated', null); |
|||
} |
|||
}); |
|||
</script> |
@ -1,29 +0,0 @@ |
|||
<template> |
|||
<div> |
|||
<p class="text-2xl pt-8 px-8 text-center"> |
|||
{{ $t('setup.messageWelcome.whatIs') }} |
|||
</p> |
|||
<p class="text-2xl pt-2 text-center text-red-600"> |
|||
{{ $t('setup.messageWelcome.warning') }} |
|||
</p> |
|||
<p class="text-2xl pt-3 text-center"> |
|||
{{ $t('setup.messageWelcome.next') }} |
|||
</p> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
const emit = defineEmits(['validated']); |
|||
|
|||
const props = defineProps<{ |
|||
next: boolean; |
|||
}>(); |
|||
|
|||
const next = toRef(props, 'next'); |
|||
|
|||
watch(next, (newVal) => { |
|||
if (newVal) { |
|||
emit('validated', null); |
|||
} |
|||
}); |
|||
</script> |
@ -1,37 +0,0 @@ |
|||
<template> |
|||
<footer> |
|||
<p 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 |
|||
> |
|||
({{ globalStore.currentRelease }}) © 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"> |
|||
const globalStore = useGlobalStore(); |
|||
</script> |
@ -1,111 +0,0 @@ |
|||
<template> |
|||
<header class="container mx-auto max-w-3xl px-3 md:px-0 mt-4 xs:mt-6"> |
|||
<div |
|||
:class=" |
|||
hasOwnLogo |
|||
? 'flex justify-end' |
|||
: 'flex flex-col-reverse xxs:flex-row flex-auto items-center gap-3' |
|||
" |
|||
> |
|||
<NuxtLink to="/" class="flex-grow self-start mb-4"> |
|||
<h1 |
|||
v-if="!hasOwnLogo" |
|||
class="text-4xl dark:text-neutral-200 font-medium" |
|||
> |
|||
<img |
|||
src="/logo.png" |
|||
width="32" |
|||
class="inline align-middle dark:bg mr-2" |
|||
/><span class="align-middle">WireGuard</span> |
|||
</h1> |
|||
</NuxtLink> |
|||
<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" |
|||
> |
|||
<IconsSun v-if="theme.preference === 'light'" class="w-5 h-5" /> |
|||
<IconsMoon |
|||
v-else-if="theme.preference === 'dark'" |
|||
class="w-5 h-5 text-neutral-400" |
|||
/> |
|||
<IconsHalfMoon |
|||
v-else |
|||
class="w-5 h-5 fill-gray-600 dark:fill-neutral-400" |
|||
/> |
|||
</button> |
|||
<!-- Show / hide charts --> |
|||
<label |
|||
v-if="globalStore.statistics.chartType > 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" |
|||
/> |
|||
<IconsChart |
|||
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" |
|||
/> |
|||
</label> |
|||
<UiUserMenu v-if="loggedIn" /> |
|||
</div> |
|||
</div> |
|||
<div class="text-sm text-gray-400 dark:text-neutral-400 mb-5" /> |
|||
<div |
|||
v-if="globalStore.updateAvailable && globalStore.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${globalStore.currentRelease} → v${globalStore.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>{{ globalStore.latestRelease.changelog }}</p> |
|||
</div> |
|||
|
|||
<a |
|||
:href="`https://github.com/wg-easy/wg-easy/releases/tag/${globalStore.latestRelease.version}`" |
|||
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"> |
|||
const globalStore = useGlobalStore(); |
|||
const route = useRoute(); |
|||
|
|||
const hasOwnLogo = computed( |
|||
() => route.path === '/login' || route.path === '/setup' |
|||
); |
|||
|
|||
const loggedIn = computed( |
|||
() => route.path !== '/login' && route.path !== '/setup' |
|||
); |
|||
|
|||
const theme = useTheme(); |
|||
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'); |
|||
} |
|||
</script> |
@ -0,0 +1,146 @@ |
|||
<template> |
|||
<div> |
|||
<header class="container mx-auto max-w-3xl px-3 md:px-0 mt-4 xs:mt-6"> |
|||
<div |
|||
:class=" |
|||
hasOwnLogo |
|||
? 'flex justify-end' |
|||
: 'flex flex-col-reverse xxs:flex-row flex-auto items-center gap-3' |
|||
" |
|||
> |
|||
<NuxtLink to="/" class="flex-grow self-start mb-4"> |
|||
<h1 |
|||
v-if="!hasOwnLogo" |
|||
class="text-4xl dark:text-neutral-200 font-medium" |
|||
> |
|||
<img |
|||
src="/logo.png" |
|||
width="32" |
|||
class="inline align-middle dark:bg mr-2" |
|||
/><span class="align-middle">WireGuard</span> |
|||
</h1> |
|||
</NuxtLink> |
|||
<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" |
|||
> |
|||
<IconsSun v-if="theme.preference === 'light'" class="w-5 h-5" /> |
|||
<IconsMoon |
|||
v-else-if="theme.preference === 'dark'" |
|||
class="w-5 h-5 text-neutral-400" |
|||
/> |
|||
<IconsHalfMoon |
|||
v-else |
|||
class="w-5 h-5 fill-gray-600 dark:fill-neutral-400" |
|||
/> |
|||
</button> |
|||
<!-- Show / hide charts --> |
|||
<label |
|||
v-if="globalStore.statistics.chartType > 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" |
|||
/> |
|||
<IconsChart |
|||
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" |
|||
/> |
|||
</label> |
|||
<UiUserMenu v-if="loggedIn" /> |
|||
</div> |
|||
</div> |
|||
<div class="text-sm text-gray-400 dark:text-neutral-400 mb-5" /> |
|||
<div |
|||
v-if="globalStore.updateAvailable && globalStore.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${globalStore.currentRelease} → v${globalStore.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>{{ globalStore.latestRelease.changelog }}</p> |
|||
</div> |
|||
|
|||
<a |
|||
:href="`https://github.com/wg-easy/wg-easy/releases/tag/${globalStore.latestRelease.version}`" |
|||
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> |
|||
<slot /> |
|||
<footer> |
|||
<p 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 |
|||
> |
|||
({{ globalStore.currentRelease }}) © 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> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
const globalStore = useGlobalStore(); |
|||
|
|||
const route = useRoute(); |
|||
|
|||
const hasOwnLogo = computed( |
|||
() => route.path === '/login' || route.path === '/setup' |
|||
); |
|||
|
|||
const loggedIn = computed( |
|||
() => route.path !== '/login' && route.path !== '/setup' |
|||
); |
|||
|
|||
const theme = useTheme(); |
|||
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'); |
|||
} |
|||
</script> |
@ -0,0 +1,31 @@ |
|||
<template> |
|||
<main class="container mx-auto px-4"> |
|||
<UiBanner /> |
|||
<Panel> |
|||
<PanelBody class="md:w-[70%] lg:w-[60%] mx-auto mt-10 p-4"> |
|||
<h2 class="mt-8 mb-16 text-3xl font-medium"> |
|||
{{ $t('setup.welcome') }} |
|||
</h2> |
|||
|
|||
<slot /> |
|||
|
|||
<div class="mt-12 flex"> |
|||
<UiStepProgress |
|||
:step="setupStore.step" |
|||
:total-steps="setupStore.totalSteps" |
|||
/> |
|||
</div> |
|||
</PanelBody> |
|||
</Panel> |
|||
|
|||
<ErrorToast |
|||
v-if="setupStore.error" |
|||
:title="setupStore.error.title" |
|||
:message="setupStore.error.message" |
|||
/> |
|||
</main> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
const setupStore = useSetupStore(); |
|||
</script> |
@ -1,142 +0,0 @@ |
|||
<template> |
|||
<main class="container mx-auto px-4"> |
|||
<UiBanner /> |
|||
<Panel> |
|||
<PanelBody class="md:w-[70%] lg:w-[60%] mx-auto mt-10 p-4"> |
|||
<h2 class="mt-8 mb-16 text-3xl font-medium"> |
|||
{{ $t('setup.welcome') }} |
|||
</h2> |
|||
|
|||
<SetupLang |
|||
v-if="step === 1" |
|||
:next="nextStep" |
|||
@validated="handleValidatedStep" |
|||
/> |
|||
|
|||
<SetupWelcome |
|||
v-if="step === 2" |
|||
:next="nextStep" |
|||
@validated="handleValidatedStep" |
|||
/> |
|||
|
|||
<SetupAdminUser |
|||
v-if="step === 3" |
|||
:next="nextStep" |
|||
@validated="handleValidatedStep" |
|||
/> |
|||
|
|||
<SetupHostPort |
|||
v-if="step === 4" |
|||
:next="nextStep" |
|||
@validated="handleValidatedStep" |
|||
/> |
|||
|
|||
<SetupMigration |
|||
v-if="step === 5" |
|||
:next="nextStep" |
|||
@validated="handleValidatedStep" |
|||
/> |
|||
|
|||
<SetupValidation |
|||
v-if="step === 6" |
|||
:next="nextStep" |
|||
@validated="handleValidatedStep" |
|||
/> |
|||
|
|||
<div class="flex justify-between items-center mt-12"> |
|||
<IconsArrowLeftCircle |
|||
:class="[ |
|||
'size-12', |
|||
step === 1 |
|||
? 'text-gray-500' |
|||
: 'text-red-800 hover:text-red-600 dark:text-white dark:hover:text-red-800', |
|||
]" |
|||
@click="decreaseStep" |
|||
/> |
|||
<UiStepProgress :step="step" :total-steps="totalSteps" /> |
|||
<IconsArrowRightCircle |
|||
v-if="step < totalSteps" |
|||
class="size-12 text-red-800 hover:text-red-600 dark:text-white dark:hover:text-red-800" |
|||
@click="increaseStep" |
|||
/> |
|||
<IconsCheckCircle |
|||
v-if="step == totalSteps" |
|||
class="size-12 text-red-800 hover:text-red-600 dark:text-white dark:hover:text-red-800" |
|||
@click="increaseStep" |
|||
/> |
|||
</div> |
|||
</PanelBody> |
|||
</Panel> |
|||
|
|||
<ErrorToast |
|||
v-if="setupError" |
|||
:title="setupError.title" |
|||
:message="setupError.message" |
|||
/> |
|||
</main> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
type SetupError = { |
|||
title: string; |
|||
message: string; |
|||
}; |
|||
|
|||
/* STEP MANAGEMENT */ |
|||
const step = ref(1); |
|||
const totalSteps = ref(6); |
|||
const stepValide = ref<number[]>([]); |
|||
const setupError = ref<null | SetupError>(null); |
|||
const nextStep = ref(false); |
|||
|
|||
watch(setupError, (newVal) => { |
|||
if (newVal) { |
|||
const id = setTimeout(() => { |
|||
setupError.value = null; |
|||
clearTimeout(id); |
|||
}, 8000); |
|||
} |
|||
}); |
|||
|
|||
async function increaseStep() { |
|||
if (step.value === totalSteps.value) { |
|||
navigateTo('/login'); |
|||
} |
|||
|
|||
if (stepValide.value.includes(step.value)) { |
|||
nextStep.value = false; |
|||
step.value += 1; |
|||
return; |
|||
} |
|||
|
|||
// handleValidatedStep() |
|||
nextStep.value = true; |
|||
} |
|||
|
|||
function decreaseStep() { |
|||
if (step.value > 1) { |
|||
nextStep.value = false; |
|||
step.value -= 1; |
|||
} |
|||
} |
|||
|
|||
function handleValidatedStep(error: null | SetupError) { |
|||
nextStep.value = false; |
|||
|
|||
if (error) { |
|||
setupError.value = error; |
|||
return; |
|||
} |
|||
|
|||
if (!error) { |
|||
if (step.value === 3 || step.value === 4) { |
|||
// if new admin user has been created, allow to skip this step if user returns to the previous steps |
|||
stepValide.value.push(step.value); |
|||
} |
|||
|
|||
if (step.value < totalSteps.value) { |
|||
step.value += 1; |
|||
} |
|||
} |
|||
} |
|||
</script> |
@ -0,0 +1,19 @@ |
|||
<template> |
|||
<div> |
|||
<p class="text-lg p-8 text-center"> |
|||
{{ 'Do you have a existing Setup?' }} |
|||
</p> |
|||
<div class="flex justify-center mb-8"> |
|||
<NuxtLink to="/setup/3"><BaseButton>No</BaseButton></NuxtLink> |
|||
<NuxtLink to="/setup/migrate"><BaseButton>Yes</BaseButton></NuxtLink> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
definePageMeta({ |
|||
layout: 'setup', |
|||
}); |
|||
const setupStore = useSetupStore(); |
|||
setupStore.setStep(2); |
|||
</script> |
@ -0,0 +1,16 @@ |
|||
<template> |
|||
<div> |
|||
<p class="text-2xl pt-8 px-8 text-center"> |
|||
{{ $t('setup.messageWelcome.whatIs') }} |
|||
</p> |
|||
<NuxtLink to="/setup/4"><BaseButton>Continue</BaseButton></NuxtLink> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
definePageMeta({ |
|||
layout: 'setup', |
|||
}); |
|||
const setupStore = useSetupStore(); |
|||
setupStore.setStep(3); |
|||
</script> |
@ -0,0 +1,14 @@ |
|||
<template> |
|||
<div> |
|||
<p>Setup successfully</p> |
|||
<NuxtLink to="/login"><BaseButton>Login</BaseButton></NuxtLink> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
definePageMeta({ |
|||
layout: 'setup', |
|||
}); |
|||
const setupStore = useSetupStore(); |
|||
setupStore.setStep(6); |
|||
</script> |
@ -0,0 +1,16 @@ |
|||
export default defineEventHandler(async (event) => { |
|||
const { host, port } = await readValidatedBody( |
|||
event, |
|||
validateZod(hostPortType, event) |
|||
); |
|||
const setupDone = await Database.setup.done(); |
|||
if (setupDone) { |
|||
throw createError({ |
|||
statusCode: 400, |
|||
statusMessage: 'Invalid state', |
|||
}); |
|||
} |
|||
await Database.system.updateClientsHostPort(host, port); |
|||
await Database.setup.set('success'); |
|||
return { success: true }; |
|||
}); |
@ -0,0 +1,11 @@ |
|||
export type Steps = 1 | 2 | 3 | 4 | 5 | 'success'; |
|||
|
|||
/** |
|||
* Interface for setup-related database operations. |
|||
* This interface provides methods for managing setup data. |
|||
*/ |
|||
export abstract class SetupRepository { |
|||
abstract done(): Promise<boolean>; |
|||
abstract get(): Promise<Steps>; |
|||
abstract set(step: Steps): Promise<void>; |
|||
} |
Loading…
Reference in new issue