Feat: Initial Setup through env vars (#1736)

* initial support for initial setup

* improve setup

* improve mobile view

* move base admin route

* admin panel mobile view

* set initial host and port

* add docs

* properly setup everything, use for dev env

* change userconfig and interface port on setup, note users afterwards
This commit is contained in:
Bernd Storath
2025-03-13 11:28:05 +01:00
committed by GitHub
parent 4890bb28e5
commit 86bdbe4c3d
26 changed files with 277 additions and 129 deletions
+9 -4
View File
@@ -1,14 +1,17 @@
<template>
<TooltipProvider>
<TooltipRoot>
<TooltipRoot :open="open" @update:open="open = $event">
<TooltipTrigger
class="inline-flex h-8 w-8 items-center justify-center rounded-full text-gray-400 outline-none focus:shadow-sm focus:shadow-black"
class="mx-2 inline-flex h-4 w-4 items-center justify-center rounded-full text-gray-400 outline-none focus:shadow-sm focus:shadow-black"
as-child
>
<slot />
<button @click="open = !open">
<slot />
</button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent
class="select-none rounded bg-gray-600 px-3 py-2 text-sm leading-none text-white shadow-lg will-change-[transform,opacity]"
class="select-none whitespace-pre-line rounded bg-gray-600 px-3 py-2 text-sm leading-none text-white shadow-lg will-change-[transform,opacity]"
:side-offset="5"
>
{{ text }}
@@ -21,4 +24,6 @@
<script lang="ts" setup>
defineProps<{ text: string }>();
const open = ref(false);
</script>
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<section class="grid grid-cols-1 gap-4 md:grid-cols-2">
<section class="grid grid-cols-2 gap-4">
<slot />
<Separator
decorative
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<NuxtLink to="/" class="mb-4 flex-grow self-start">
<NuxtLink to="/" class="mb-4">
<h1 class="text-4xl font-medium dark:text-neutral-200">
<img
src="/logo.png"
+6 -3
View File
@@ -1,6 +1,10 @@
<template>
<div
v-if="globalStore.release?.updateAvailable"
v-if="
globalStore.release?.updateAvailable &&
authStore.userData &&
hasPermissions(authStore.userData, 'admin', 'any')
"
class="font-small mb-10 rounded-md bg-red-800 p-4 text-sm text-white shadow-lg dark:bg-red-100 dark:text-red-600"
:title="`v${globalStore.release.currentRelease} → v${globalStore.release.latestRelease.version}`"
>
@@ -23,6 +27,5 @@
<script lang="ts" setup>
const globalStore = useGlobalStore();
// TODO: only show this to admins
const authStore = useAuthStore();
</script>
+5 -5
View File
@@ -1,23 +1,23 @@
<template>
<div>
<header class="container mx-auto mt-4 max-w-3xl px-3 xs:mt-6 md:px-0">
<header class="mx-auto mt-4 flex max-w-3xl flex-col justify-center">
<div
class="mb-5"
class="mb-5 w-full"
:class="
loggedIn
? 'flex flex-auto flex-col-reverse items-center gap-3 xxs:flex-row'
? 'flex flex-col items-center justify-between sm:flex-row'
: 'flex justify-end'
"
>
<HeaderLogo v-if="loggedIn" />
<div class="flex grow-0 items-center gap-3 self-end xxs:self-center">
<div class="flex flex-row gap-3">
<HeaderLangSelector />
<HeaderThemeSwitch />
<HeaderChartToggle v-if="loggedIn" />
<UiUserMenu v-if="loggedIn" />
</div>
</div>
<HeaderUpdate class="mt-5" />
<HeaderUpdate class="mt-4" />
</header>
<slot />
<UiFooter />
+2 -2
View File
@@ -11,8 +11,8 @@
</header>
<main>
<Panel>
<PanelBody class="mx-auto mt-10 p-4 md:w-[70%] lg:w-[60%]">
<h2 class="mb-16 mt-8 text-3xl font-medium">
<PanelBody class="m-4 mx-auto mt-10 md:w-[70%] lg:w-[60%]">
<h2 class="mb-16 mt-8 text-center text-3xl font-medium">
{{ $t('setup.welcome') }}
</h2>
+10 -5
View File
@@ -1,8 +1,8 @@
<template>
<div>
<div class="container mx-auto p-4">
<div class="flex">
<div class="mr-4 w-64 rounded-lg bg-white p-4 dark:bg-neutral-700">
<div class="flex flex-col gap-4 lg:flex-row">
<div class="rounded-lg bg-white p-4 lg:w-64 dark:bg-neutral-700">
<NuxtLink to="/admin">
<h2 class="mb-4 text-xl font-bold dark:text-neutral-200">
{{ t('pages.admin.panel') }}
@@ -13,6 +13,7 @@
v-for="(item, index) in menuItems"
:key="index"
:to="`/admin/${item.id}`"
active-class="bg-red-800 rounded"
>
<BaseButton
as="span"
@@ -27,7 +28,7 @@
<div
class="flex-1 rounded-lg bg-white p-6 dark:bg-neutral-700 dark:text-neutral-200"
>
<h1 class="mb-6 text-3xl font-bold">{{ activeMenuItem?.name }}</h1>
<h1 class="mb-6 text-3xl font-bold">{{ activeMenuItem.name }}</h1>
<NuxtPage />
</div>
</div>
@@ -44,13 +45,17 @@ const { t } = useI18n();
const route = useRoute();
const menuItems = [
{ id: '', name: t('pages.admin.general') },
{ id: 'general', name: t('pages.admin.general') },
{ id: 'config', name: t('pages.admin.config') },
{ id: 'interface', name: t('pages.admin.interface') },
{ id: 'hooks', name: t('pages.admin.hooks') },
];
const defaultItem = { id: '', name: t('pages.admin.panel') };
const activeMenuItem = computed(() => {
return menuItems.find((item) => route.path === `/admin/${item.id}`);
return (
menuItems.find((item) => route.path === `/admin/${item.id}`) ?? defaultItem
);
});
</script>
+64
View File
@@ -0,0 +1,64 @@
<template>
<main v-if="data">
<FormElement @submit.prevent="submit">
<FormGroup>
<FormNumberField
id="session"
v-model="data.sessionTimeout"
:label="$t('admin.general.sessionTimeout')"
:description="$t('admin.general.sessionTimeoutDesc')"
/>
</FormGroup>
<FormGroup>
<FormHeading>{{ $t('admin.general.metrics') }}</FormHeading>
<FormNullTextField
id="password"
v-model="data.metricsPassword"
:label="$t('admin.general.metricsPassword')"
:description="$t('admin.general.metricsPasswordDesc')"
/>
<FormSwitchField
id="prometheus"
v-model="data.metricsPrometheus"
:label="$t('admin.general.prometheus')"
:description="$t('admin.general.prometheusDesc')"
/>
<FormSwitchField
id="json"
v-model="data.metricsJson"
:label="$t('admin.general.json')"
:description="$t('admin.general.jsonDesc')"
/>
</FormGroup>
<FormGroup>
<FormHeading>{{ $t('form.actions') }}</FormHeading>
<FormActionField type="submit" :label="$t('form.save')" />
<FormActionField :label="$t('form.revert')" @click="revert" />
</FormGroup>
</FormElement>
</main>
</template>
<script setup lang="ts">
const { data: _data, refresh } = await useFetch(`/api/admin/general`, {
method: 'get',
});
const data = toRef(_data.value);
const _submit = useSubmit(
`/api/admin/general`,
{
method: 'post',
},
{ revert }
);
function submit() {
return _submit(data.value);
}
async function revert() {
await refresh();
data.value = toRef(_data.value).value;
}
</script>
+2 -61
View File
@@ -1,64 +1,5 @@
<template>
<main v-if="data">
<FormElement @submit.prevent="submit">
<FormGroup>
<FormNumberField
id="session"
v-model="data.sessionTimeout"
:label="$t('admin.general.sessionTimeout')"
:description="$t('admin.general.sessionTimeoutDesc')"
/>
</FormGroup>
<FormGroup>
<FormHeading>{{ $t('admin.general.metrics') }}</FormHeading>
<FormNullTextField
id="password"
v-model="data.metricsPassword"
:label="$t('admin.general.metricsPassword')"
:description="$t('admin.general.metricsPasswordDesc')"
/>
<FormSwitchField
id="prometheus"
v-model="data.metricsPrometheus"
:label="$t('admin.general.prometheus')"
:description="$t('admin.general.prometheusDesc')"
/>
<FormSwitchField
id="json"
v-model="data.metricsJson"
:label="$t('admin.general.json')"
:description="$t('admin.general.jsonDesc')"
/>
</FormGroup>
<FormGroup>
<FormHeading>{{ $t('form.actions') }}</FormHeading>
<FormActionField type="submit" :label="$t('form.save')" />
<FormActionField :label="$t('form.revert')" @click="revert" />
</FormGroup>
</FormElement>
<main class="flex flex-col gap-3">
<p class="whitespace-pre-line">{{ $t('admin.introText') }}</p>
</main>
</template>
<script setup lang="ts">
const { data: _data, refresh } = await useFetch(`/api/admin/general`, {
method: 'get',
});
const data = toRef(_data.value);
const _submit = useSubmit(
`/api/admin/general`,
{
method: 'post',
},
{ revert }
);
function submit() {
return _submit(data.value);
}
async function revert() {
await refresh();
data.value = toRef(_data.value).value;
}
</script>
+3
View File
@@ -54,6 +54,9 @@
</template>
<script setup lang="ts">
const authStore = useAuthStore();
authStore.update();
const authenticating = ref(false);
const remember = ref(false);
const username = ref<null | string>(null);
+3 -3
View File
@@ -1,9 +1,9 @@
<template>
<div>
<p class="px-8 pt-8 text-center text-2xl">
<div class="flex flex-col items-center">
<p class="px-8 text-center text-2xl">
{{ $t('setup.welcomeDesc') }}
</p>
<NuxtLink to="/setup/2">
<NuxtLink to="/setup/2" class="mt-8">
<BaseButton as="span">{{ $t('general.continue') }}</BaseButton>
</NuxtLink>
</div>
+3 -3
View File
@@ -1,9 +1,9 @@
<template>
<div>
<p class="p-8 text-center text-lg">
<p class="text-center text-lg">
{{ $t('setup.createAdminDesc') }}
</p>
<div class="flex flex-col gap-3">
<div class="mt-8 flex flex-col gap-3">
<div class="flex flex-col">
<FormNullTextField
id="username"
@@ -28,7 +28,7 @@
:label="$t('general.confirmPassword')"
/>
</div>
<div>
<div class="mt-4 flex justify-center">
<BaseButton @click="submit">{{ $t('setup.createAccount') }}</BaseButton>
</div>
</div>
+10 -6
View File
@@ -1,14 +1,18 @@
<template>
<div>
<p class="p-8 text-center text-lg">
<p class="text-center text-lg">
{{ $t('setup.existingSetup') }}
</p>
<div class="mb-8 flex justify-center">
<NuxtLink to="/setup/4">
<BaseButton as="span">{{ $t('general.no') }}</BaseButton>
<div class="mt-4 flex justify-center gap-3">
<NuxtLink to="/setup/4" class="w-20">
<BaseButton as="span" class="w-full justify-center">
{{ $t('general.no') }}
</BaseButton>
</NuxtLink>
<NuxtLink to="/setup/migrate">
<BaseButton as="span">{{ $t('general.yes') }}</BaseButton>
<NuxtLink to="/setup/migrate" class="w-20">
<BaseButton as="span" class="w-full justify-center">
{{ $t('general.yes') }}
</BaseButton>
</NuxtLink>
</div>
</div>
+10 -4
View File
@@ -1,21 +1,27 @@
<template>
<div>
<p class="p-8 text-center text-lg">
<p class="text-center text-lg">
{{ $t('setup.setupConfigDesc') }}
</p>
<div class="flex flex-col gap-3">
<div class="mt-8 flex flex-col gap-3">
<div class="flex flex-col">
<FormNullTextField
id="host"
v-model="host"
:label="$t('general.host')"
placeholder="vpn.example.com"
:description="$t('setup.hostDesc')"
/>
</div>
<div class="flex flex-col">
<FormNumberField id="port" v-model="port" :label="$t('general.port')" />
<FormNumberField
id="port"
v-model="port"
:label="$t('general.port')"
:description="$t('setup.portDesc')"
/>
</div>
<div>
<div class="mt-4 flex justify-center">
<BaseButton @click="submit">{{ $t('general.continue') }}</BaseButton>
</div>
</div>
+6 -4
View File
@@ -1,13 +1,15 @@
<template>
<div>
<p class="p-8 text-center text-lg">
<div class="flex flex-col items-center">
<p class="text-center text-lg">
{{ $t('setup.setupMigrationDesc') }}
</p>
<div>
<div class="mt-8 flex gap-3">
<Label for="migration">{{ $t('setup.migration') }}</Label>
<input id="migration" type="file" @change="onChangeFile" />
</div>
<BaseButton @click="submit">{{ $t('setup.upload') }}</BaseButton>
<div class="mt-4">
<BaseButton @click="submit">{{ $t('setup.upload') }}</BaseButton>
</div>
</div>
</template>
+2 -2
View File
@@ -1,7 +1,7 @@
<template>
<div>
<div class="flex flex-col items-center">
<p>{{ $t('setup.successful') }}</p>
<NuxtLink to="/login">
<NuxtLink to="/login" class="mt-4">
<BaseButton as="span">{{ $t('login.signIn') }}</BaseButton>
</NuxtLink>
</div>