Browse Source

add client page, move api routes

pull/1397/head
Bernd Storath 6 months ago
parent
commit
e9368c05f8
  1. 60
      src/app/components/Client/Address4.vue
  2. 17
      src/app/components/Client/Delete.vue
  3. 91
      src/app/components/Client/ExpireDate.vue
  4. 65
      src/app/components/Client/Name.vue
  5. 11
      src/app/components/ClientCard/Address4.vue
  6. 0
      src/app/components/ClientCard/Avatar.vue
  7. 0
      src/app/components/ClientCard/Charts.vue
  8. 28
      src/app/components/ClientCard/ClientCard.vue
  9. 2
      src/app/components/ClientCard/Config.vue
  10. 14
      src/app/components/ClientCard/Edit.vue
  11. 25
      src/app/components/ClientCard/ExpireDate.vue
  12. 0
      src/app/components/ClientCard/InlineTransfer.vue
  13. 0
      src/app/components/ClientCard/LastSeen.vue
  14. 16
      src/app/components/ClientCard/Name.vue
  15. 0
      src/app/components/ClientCard/OneTimeLink.vue
  16. 0
      src/app/components/ClientCard/OneTimeLinkBtn.vue
  17. 4
      src/app/components/ClientCard/QRCode.vue
  18. 0
      src/app/components/ClientCard/Switch.vue
  19. 0
      src/app/components/ClientCard/Transfer.vue
  20. 2
      src/app/components/Clients/Clients.vue
  21. 112
      src/app/pages/clients/[id].vue
  22. 20
      src/app/utils/api.ts
  23. 50
      src/server/api/admin/migration.post.ts
  24. 0
      src/server/api/client/[clientId]/address4.put.ts
  25. 0
      src/server/api/client/[clientId]/configuration.get.ts
  26. 0
      src/server/api/client/[clientId]/disable.post.ts
  27. 0
      src/server/api/client/[clientId]/enable.post.ts
  28. 0
      src/server/api/client/[clientId]/expireDate.put.ts
  29. 0
      src/server/api/client/[clientId]/generateOneTimeLink.post.ts
  30. 0
      src/server/api/client/[clientId]/index.delete.ts
  31. 7
      src/server/api/client/[clientId]/index.get.ts
  32. 0
      src/server/api/client/[clientId]/name.put.ts
  33. 0
      src/server/api/client/[clientId]/qrcode.svg.get.ts
  34. 0
      src/server/api/client/index.get.ts
  35. 0
      src/server/api/client/index.post.ts
  36. 83
      src/server/api/setup/migration.post.ts
  37. 71
      src/server/utils/config.ts

60
src/app/components/Client/Address4.vue

@ -1,60 +0,0 @@
<template>
<span class="group">
<!-- Show -->
<input
v-show="clientEditAddress4Id === client.id"
ref="clientAddress4Input"
v-model="clientEditAddress4"
class="rounded border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 outline-none w-20 text-black dark:text-neutral-300 dark:placeholder:text-neutral-500"
@keyup.enter="
updateClientAddress4(client, clientEditAddress4);
clientEditAddress4 = null;
clientEditAddress4Id = null;
"
@keyup.escape="
clientEditAddress4 = null;
clientEditAddress4Id = null;
"
/>
<span v-show="clientEditAddress4Id !== client.id" class="inline-block">{{
client.address4
}}</span>
<!-- Edit -->
<span
v-show="clientEditAddress4Id !== client.id"
class="cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity"
@click="
clientEditAddress4 = client.address4;
clientEditAddress4Id = client.id;
nextTick(() => clientAddress4Input?.select());
"
>
<IconsEdit
class="h-4 w-4 inline align-middle opacity-25 hover:opacity-100"
/>
</span>
</span>
</template>
<script setup lang="ts">
defineProps<{
client: LocalClient;
}>();
const clientsStore = useClientsStore();
const clientAddress4Input = ref<HTMLInputElement | null>(null);
const clientEditAddress4 = ref<null | string>(null);
const clientEditAddress4Id = ref<null | string>(null);
function updateClientAddress4(client: WGClient, address4: string | null) {
if (address4 === null) {
return;
}
api
.updateClientAddress4({ clientId: client.id, address4 })
.catch((err) => alert(err.message || err.toString()))
.finally(() => clientsStore.refresh().catch(console.error));
}
</script>

17
src/app/components/Client/Delete.vue

@ -1,17 +0,0 @@
<template>
<button
class="align-middle bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white p-2 rounded transition"
:title="$t('deleteClient')"
@click="modalStore.clientDelete = client"
>
<IconsDelete class="w-5" />
</button>
</template>
<script setup lang="ts">
defineProps<{
client: LocalClient;
}>();
const modalStore = useModalStore();
</script>

91
src/app/components/Client/ExpireDate.vue

@ -1,91 +0,0 @@
<template>
<div
v-show="globalStore.features.clientExpiration.enabled"
class="block md:inline-block pb-1 md:pb-0 text-gray-500 dark:text-neutral-400 text-xs"
>
<span class="group">
<!-- Show -->
<input
v-show="clientEditExpireDateId === client.id"
ref="clientExpireDateInput"
v-model="clientEditExpireDate"
type="text"
class="rounded border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 outline-none w-70 text-black dark:text-neutral-300 dark:placeholder:text-neutral-500 text-xs p-0"
@keyup.enter="
updateClientExpireDate(client, clientEditExpireDate);
clientEditExpireDate = null;
clientEditExpireDateId = null;
"
@keyup.escape="
clientEditExpireDate = null;
clientEditExpireDateId = null;
"
/>
<span
v-show="clientEditExpireDateId !== client.id"
class="inline-block"
>{{ expiredDateFormat(client.expiresAt) }}</span
>
<!-- Edit -->
<span
v-show="clientEditExpireDateId !== client.id"
class="cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity"
@click="
clientEditExpireDate = client.expiresAt
? client.expiresAt.slice(0, 10)
: 'yyyy-mm-dd';
clientEditExpireDateId = client.id;
nextTick(() => clientExpireDateInput?.select());
"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4 inline align-middle opacity-25 hover:opacity-100"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
</span>
</span>
</div>
</template>
<script setup lang="ts">
defineProps<{ client: LocalClient }>();
const globalStore = useGlobalStore();
const clientsStore = useClientsStore();
const clientEditExpireDate = ref<string | null>(null);
const clientEditExpireDateId = ref<string | null>(null);
const { t, locale } = useI18n();
const clientExpireDateInput = ref<HTMLInputElement | null>(null);
function updateClientExpireDate(
client: LocalClient,
expireDate: string | null
) {
api
.updateClientExpireDate({ clientId: client.id, expireDate })
.catch((err) => alert(err.message || err.toString()))
.finally(() => clientsStore.refresh().catch(console.error));
}
function expiredDateFormat(value: string | null) {
if (value === null) return t('Permanent');
const dateTime = new Date(value);
return dateTime.toLocaleDateString(locale.value, {
year: 'numeric',
month: 'long',
day: 'numeric',
});
}
</script>

65
src/app/components/Client/Name.vue

@ -1,65 +0,0 @@
<template>
<div
class="text-gray-700 dark:text-neutral-200 group text-sm md:text-base"
:title="$t('createdOn') + dateTime(new Date(client.createdAt))"
>
<!-- Show -->
<input
v-show="clientEditNameId === client.id"
ref="clientNameInput"
v-model="clientEditName"
class="rounded px-1 border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 dark:placeholder:text-neutral-500 outline-none w-30"
@keyup.enter="
updateClientName(client, clientEditName);
clientEditName = null;
clientEditNameId = null;
"
@keyup.escape="
clientEditName = null;
clientEditNameId = null;
"
/>
<span
v-show="clientEditNameId !== client.id"
class="border-t-2 border-b-2 border-transparent"
>{{ client.name }}</span
>
<!-- Edit -->
<span
v-show="clientEditNameId !== client.id"
class="cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity"
@click="
clientEditName = client.name;
clientEditNameId = client.id;
nextTick(() => clientNameInput?.select());
"
>
<IconsEdit
class="h-4 w-4 inline align-middle opacity-25 hover:opacity-100"
/>
</span>
</div>
</template>
<script setup lang="ts">
defineProps<{
client: LocalClient;
}>();
const clientsStore = useClientsStore();
const clientNameInput = ref<HTMLInputElement | null>(null);
const clientEditName = ref<null | string>(null);
const clientEditNameId = ref<null | string>(null);
function updateClientName(client: LocalClient, name: string | null) {
if (name === null) {
return;
}
api
.updateClientName({ clientId: client.id, name })
.catch((err) => alert(err.message || err.toString()))
.finally(() => clientsStore.refresh().catch(console.error));
}
</script>

11
src/app/components/ClientCard/Address4.vue

@ -0,0 +1,11 @@
<template>
<span class="inline-block">
{{ client.address4 }}
</span>
</template>
<script setup lang="ts">
defineProps<{
client: LocalClient;
}>();
</script>

0
src/app/components/Client/Avatar.vue → src/app/components/ClientCard/Avatar.vue

0
src/app/components/Client/Charts.vue → src/app/components/ClientCard/Charts.vue

28
src/app/components/Client/Client.vue → src/app/components/ClientCard/ClientCard.vue

@ -1,26 +1,26 @@
<template> <template>
<ClientCharts :client="client" /> <ClientCardCharts :client="client" />
<div <div
class="relative py-3 md:py-5 px-3 z-10 flex flex-col sm:flex-row justify-between gap-3" class="relative py-3 md:py-5 px-3 z-10 flex flex-col sm:flex-row justify-between gap-3"
> >
<div class="flex gap-3 md:gap-4 w-full items-center"> <div class="flex gap-3 md:gap-4 w-full items-center">
<ClientAvatar :client="client" /> <ClientCardAvatar :client="client" />
<!-- Name & Info --> <!-- Name & Info -->
<div class="flex flex-col xxs:flex-row w-full gap-2"> <div class="flex flex-col xxs:flex-row w-full gap-2">
<div class="flex flex-col flex-grow gap-1"> <div class="flex flex-col flex-grow gap-1">
<ClientName :client="client" /> <ClientCardName :client="client" />
<div <div
class="block md:inline-block pb-1 md:pb-0 text-gray-500 dark:text-neutral-400 text-xs" class="block md:inline-block pb-1 md:pb-0 text-gray-500 dark:text-neutral-400 text-xs"
> >
<ClientAddress4 :client="client" /> <ClientCardAddress4 :client="client" />
<ClientInlineTransfer <ClientCardInlineTransfer
v-if="!globalStore.statistics.enabled" v-if="!globalStore.statistics.enabled"
:client="client" :client="client"
/> />
<ClientLastSeen :client="client" /> <ClientCardLastSeen :client="client" />
</div> </div>
<ClientOneTimeLink :client="client" /> <ClientCardOneTimeLink :client="client" />
<ClientExpireDate :client="client" /> <ClientCardExpireDate :client="client" />
</div> </div>
<!-- Info --> <!-- Info -->
@ -28,7 +28,7 @@
v-if="globalStore.statistics.enabled" v-if="globalStore.statistics.enabled"
class="flex gap-2 items-center shrink-0 text-gray-400 dark:text-neutral-400 text-xs mt-px justify-end" class="flex gap-2 items-center shrink-0 text-gray-400 dark:text-neutral-400 text-xs mt-px justify-end"
> >
<ClientTransfer :client="client" /> <ClientCardTransfer :client="client" />
</div> </div>
</div> </div>
<!-- </div> --> <!-- </div> -->
@ -39,11 +39,11 @@
<div <div
class="text-gray-400 dark:text-neutral-400 flex gap-1 items-center justify-between" class="text-gray-400 dark:text-neutral-400 flex gap-1 items-center justify-between"
> >
<ClientSwitch :client="client" /> <ClientCardSwitch :client="client" />
<ClientQRCode :client="client" /> <ClientCardEdit :client="client" />
<ClientConfig :client="client" /> <ClientCardQRCode :client="client" />
<ClientOneTimeLinkBtn :client="client" /> <ClientCardConfig :client="client" />
<ClientDelete :client="client" /> <ClientCardOneTimeLinkBtn :client="client" />
</div> </div>
</div> </div>
</div> </div>

2
src/app/components/Client/Config.vue → src/app/components/ClientCard/Config.vue

@ -1,7 +1,7 @@
<template> <template>
<a <a
:disabled="!client.downloadableConfig" :disabled="!client.downloadableConfig"
:href="'./api/wireguard/client/' + client.id + '/configuration'" :href="'./api/client/' + client.id + '/configuration'"
:download="client.downloadableConfig ? 'configuration' : null" :download="client.downloadableConfig ? 'configuration' : null"
class="align-middle inline-block bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 p-2 rounded transition" class="align-middle inline-block bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 p-2 rounded transition"
:class="{ :class="{

14
src/app/components/ClientCard/Edit.vue

@ -0,0 +1,14 @@
<template>
<NuxtLink
class="align-middle bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 p-2 rounded transition hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white"
:to="`/clients/${client.id}`"
>
<IconsEdit class="w-5" />
</NuxtLink>
</template>
<script setup lang="ts">
defineProps<{
client: LocalClient;
}>();
</script>

25
src/app/components/ClientCard/ExpireDate.vue

@ -0,0 +1,25 @@
<template>
<div
v-show="globalStore.features.clientExpiration.enabled"
class="block md:inline-block pb-1 md:pb-0 text-gray-500 dark:text-neutral-400 text-xs"
>
<span class="inline-block">{{ expiredDateFormat(client.expiresAt) }}</span>
</div>
</template>
<script setup lang="ts">
defineProps<{ client: LocalClient }>();
const globalStore = useGlobalStore();
const { t, locale } = useI18n();
function expiredDateFormat(value: string | null) {
if (value === null) return t('Permanent');
const dateTime = new Date(value);
return dateTime.toLocaleDateString(locale.value, {
year: 'numeric',
month: 'long',
day: 'numeric',
});
}
</script>

0
src/app/components/Client/InlineTransfer.vue → src/app/components/ClientCard/InlineTransfer.vue

0
src/app/components/Client/LastSeen.vue → src/app/components/ClientCard/LastSeen.vue

16
src/app/components/ClientCard/Name.vue

@ -0,0 +1,16 @@
<template>
<div
class="text-gray-700 dark:text-neutral-200 group text-sm md:text-base"
:title="$t('createdOn') + dateTime(new Date(client.createdAt))"
>
<span class="border-t-2 border-b-2 border-transparent">
{{ client.name }}
</span>
</div>
</template>
<script setup lang="ts">
defineProps<{
client: LocalClient;
}>();
</script>

0
src/app/components/Client/OneTimeLink.vue → src/app/components/ClientCard/OneTimeLink.vue

0
src/app/components/Client/OneTimeLinkBtn.vue → src/app/components/ClientCard/OneTimeLinkBtn.vue

4
src/app/components/Client/QRCode.vue → src/app/components/ClientCard/QRCode.vue

@ -8,9 +8,7 @@
'is-disabled': !client.downloadableConfig, 'is-disabled': !client.downloadableConfig,
}" }"
:title="!client.downloadableConfig ? $t('noPrivKey') : $t('showQR')" :title="!client.downloadableConfig ? $t('noPrivKey') : $t('showQR')"
@click=" @click="modalStore.qrcode = `./api/client/${client.id}/qrcode.svg`"
modalStore.qrcode = `./api/wireguard/client/${client.id}/qrcode.svg`
"
> >
<IconsQRCode class="w-5" /> <IconsQRCode class="w-5" />
</button> </button>

0
src/app/components/Client/Switch.vue → src/app/components/ClientCard/Switch.vue

0
src/app/components/Client/Transfer.vue → src/app/components/ClientCard/Transfer.vue

2
src/app/components/Clients/Clients.vue

@ -4,7 +4,7 @@
:key="client.id" :key="client.id"
class="relative overflow-hidden border-b last:border-b-0 border-gray-100 dark:border-neutral-600 border-solid" class="relative overflow-hidden border-b last:border-b-0 border-gray-100 dark:border-neutral-600 border-solid"
> >
<Client :client="client" /> <ClientCard :client="client" />
</div> </div>
</template> </template>

112
src/app/pages/clients/[id].vue

@ -0,0 +1,112 @@
<template>
<main v-if="data">
<Panel>
<PanelHead>
<PanelHeadTitle :text="data.name" />
</PanelHead>
<PanelBody>
<section class="grid grid-cols-1 gap-4 md:grid-cols-2">
<h4 class="text-2xl col-span-full py-6">
{{ $t('me.sectionGeneral') }}
</h4>
<Label for="name" class="font-semibold md:align-middle md:leading-10">
{{ 'Name' }}
</Label>
<input
id="name"
v-model.trim="data.name"
type="text"
class="dark:bg-neutral-700 text-gray-500 dark:text-neutral-200 border-2 border-gray-100 dark:border-neutral-800 rounded-lg focus:border-red-800 dark:placeholder:text-neutral-400 focus:outline-0 focus:ring-0"
/>
<Label
for="enabled"
class="font-semibold md:align-middle md:leading-10"
>
{{ 'Enabled' }}
</Label>
<input
id="enabled"
v-model.trim="data.enabled"
type="checkbox"
class="dark:bg-neutral-700 text-gray-500 dark:text-neutral-200 border-2 border-gray-100 dark:border-neutral-800 rounded-lg focus:border-red-800 dark:placeholder:text-neutral-400 focus:outline-0 focus:ring-0"
/>
</section>
<section class="grid grid-cols-1 gap-4 md:grid-cols-2">
<h4 class="text-2xl col-span-full py-6">
{{ 'Address' }}
</h4>
<Label for="ipv4" class="font-semibold md:align-middle md:leading-10">
{{ 'IPv4' }}
</Label>
<input
id="ipv4"
v-model.trim="data.address4"
type="text"
class="dark:bg-neutral-700 text-gray-500 dark:text-neutral-200 border-2 border-gray-100 dark:border-neutral-800 rounded-lg focus:border-red-800 dark:placeholder:text-neutral-400 focus:outline-0 focus:ring-0"
/>
<Label for="ipv6" class="font-semibold md:align-middle md:leading-10">
{{ 'IPv6' }}
</Label>
<input
id="ipv6"
v-model.trim="data.address6"
type="text"
class="dark:bg-neutral-700 text-gray-500 dark:text-neutral-200 border-2 border-gray-100 dark:border-neutral-800 rounded-lg focus:border-red-800 dark:placeholder:text-neutral-400 focus:outline-0 focus:ring-0"
/>
</section>
<section class="grid grid-cols-1 gap-4 md:grid-cols-2">
<h4 class="text-2xl col-span-full py-6">
{{ 'Advanced' }}
</h4>
<Label
for="keepalive"
class="font-semibold md:align-middle md:leading-10"
>
{{ 'Persistent Keepalive' }}
</Label>
<input
id="keepalive"
v-model.number="data.persistentKeepalive"
type="number"
class="dark:bg-neutral-700 text-gray-500 dark:text-neutral-200 border-2 border-gray-100 dark:border-neutral-800 rounded-lg focus:border-red-800 dark:placeholder:text-neutral-400 focus:outline-0 focus:ring-0"
/>
</section>
<section class="grid grid-cols-1 gap-4 md:grid-cols-2">
<h4 class="text-2xl col-span-full py-6">
{{ 'Action' }}
</h4>
<Label
for="rotateprivkey"
class="font-semibold md:align-middle md:leading-10"
>
{{ 'Rotate Private Key' }}
</Label>
<input
id="rotateprivkey"
value="Do !"
type="button"
class="dark:bg-neutral-700 text-gray-500 dark:text-neutral-200 border-2 border-gray-100 dark:border-neutral-800 rounded-lg focus:border-red-800 dark:placeholder:text-neutral-400 focus:outline-0 focus:ring-0"
/>
<Label
for="delete"
class="font-semibold md:align-middle md:leading-10"
>
{{ 'Delete' }}
</Label>
<input
id="delete"
value="Do !"
type="button"
class="dark:bg-neutral-700 text-gray-500 dark:text-neutral-200 border-2 border-gray-100 dark:border-neutral-800 rounded-lg focus:border-red-800 dark:placeholder:text-neutral-400 focus:outline-0 focus:ring-0"
/>
</section>
</PanelBody>
</Panel>
</main>
</template>
<script lang="ts" setup>
const route = useRoute();
const id = route.params.id as string;
const { data } = await useFetch(`/api/client/${id}`, { method: 'get' });
</script>

20
src/app/utils/api.ts

@ -27,7 +27,7 @@ class API {
} }
async getClients() { async getClients() {
return useFetch('/api/wireguard/client', { return useFetch('/api/client', {
method: 'get', method: 'get',
}); });
} }
@ -39,32 +39,32 @@ class API {
name: string; name: string;
expireDate: string | null; expireDate: string | null;
}) { }) {
return $fetch('/api/wireguard/client', { return $fetch('/api/client', {
method: 'post', method: 'post',
body: { name, expireDate }, body: { name, expireDate },
}); });
} }
async deleteClient({ clientId }: { clientId: string }) { async deleteClient({ clientId }: { clientId: string }) {
return $fetch(`/api/wireguard/client/${clientId}`, { return $fetch(`/api/client/${clientId}`, {
method: 'delete', method: 'delete',
}); });
} }
async showOneTimeLink({ clientId }: { clientId: string }) { async showOneTimeLink({ clientId }: { clientId: string }) {
return $fetch(`/api/wireguard/client/${clientId}/generateOneTimeLink`, { return $fetch(`/api/client/${clientId}/generateOneTimeLink`, {
method: 'post', method: 'post',
}); });
} }
async enableClient({ clientId }: { clientId: string }) { async enableClient({ clientId }: { clientId: string }) {
return $fetch(`/api/wireguard/client/${clientId}/enable`, { return $fetch(`/api/client/${clientId}/enable`, {
method: 'post', method: 'post',
}); });
} }
async disableClient({ clientId }: { clientId: string }) { async disableClient({ clientId }: { clientId: string }) {
return $fetch(`/api/wireguard/client/${clientId}/disable`, { return $fetch(`/api/client/${clientId}/disable`, {
method: 'post', method: 'post',
}); });
} }
@ -76,7 +76,7 @@ class API {
clientId: string; clientId: string;
name: string; name: string;
}) { }) {
return $fetch(`/api/wireguard/client/${clientId}/name`, { return $fetch(`/api/client/${clientId}/name`, {
method: 'put', method: 'put',
body: { name }, body: { name },
}); });
@ -89,7 +89,7 @@ class API {
clientId: string; clientId: string;
address4: string; address4: string;
}) { }) {
return $fetch(`/api/wireguard/client/${clientId}/address4`, { return $fetch(`/api/client/${clientId}/address4`, {
method: 'put', method: 'put',
body: { address4 }, body: { address4 },
}); });
@ -102,7 +102,7 @@ class API {
clientId: string; clientId: string;
expireDate: string | null; expireDate: string | null;
}) { }) {
return $fetch(`/api/wireguard/client/${clientId}/expireDate`, { return $fetch(`/api/client/${clientId}/expireDate`, {
method: 'put', method: 'put',
body: { expireDate }, body: { expireDate },
}); });
@ -145,7 +145,7 @@ class API {
} }
async setupMigration({ file }: { file: string }) { async setupMigration({ file }: { file: string }) {
return $fetch('/api/admin/migration', { return $fetch('/api/setup/migration', {
method: 'post', method: 'post',
body: { file }, body: { file },
}); });

50
src/server/api/admin/migration.post.ts

@ -1,50 +0,0 @@
import { parseCidr } from 'cidr-tools';
import { stringifyIp } from 'ip-bigint';
import { z } from 'zod';
// TODO: check what are missing
const clientSchema = z.object({
id: z.string(),
name: z.string(),
address: z.string(),
privateKey: z.string(),
publicKey: z.string(),
preSharedKey: z.string(),
createdAt: z.string(),
updatedAt: z.string(),
enabled: z.boolean(),
});
const oldConfigSchema = z.object({
server: z.object({
privateKey: z.string(),
publicKey: z.string(),
address: z.string(),
}),
clients: z.record(z.string(), clientSchema),
});
export default defineEventHandler(async (event) => {
const { file } = await readValidatedBody(event, validateZod(fileType, event));
const file_ = await oldConfigSchema.parseAsync(JSON.parse(file));
for (const [_, value] of Object.entries(file_.clients)) {
// remove the unused field
const { address: _, ...filterValue } = value;
const cidr4 = parseCidr(value.address);
const address4 = stringifyIp({ number: cidr4.start + 0n, version: 4 });
await Database.client.create({
...filterValue,
address4,
address6: '',
expiresAt: null,
allowedIPs: ['0.0.0.0/0', '::/0'],
oneTimeLink: null,
serverAllowedIPs: [],
persistentKeepalive: 0,
});
}
return { success: true };
});

0
src/server/api/wireguard/client/[clientId]/address4.put.ts → src/server/api/client/[clientId]/address4.put.ts

0
src/server/api/wireguard/client/[clientId]/configuration.get.ts → src/server/api/client/[clientId]/configuration.get.ts

0
src/server/api/wireguard/client/[clientId]/disable.post.ts → src/server/api/client/[clientId]/disable.post.ts

0
src/server/api/wireguard/client/[clientId]/enable.post.ts → src/server/api/client/[clientId]/enable.post.ts

0
src/server/api/wireguard/client/[clientId]/expireDate.put.ts → src/server/api/client/[clientId]/expireDate.put.ts

0
src/server/api/wireguard/client/[clientId]/generateOneTimeLink.post.ts → src/server/api/client/[clientId]/generateOneTimeLink.post.ts

0
src/server/api/wireguard/client/[clientId]/index.delete.ts → src/server/api/client/[clientId]/index.delete.ts

7
src/server/api/client/[clientId]/index.get.ts

@ -0,0 +1,7 @@
export default defineEventHandler(async (event) => {
const { clientId } = await getValidatedRouterParams(
event,
validateZod(clientIdType)
);
return WireGuard.getClient({ clientId });
});

0
src/server/api/wireguard/client/[clientId]/name.put.ts → src/server/api/client/[clientId]/name.put.ts

0
src/server/api/wireguard/client/[clientId]/qrcode.svg.get.ts → src/server/api/client/[clientId]/qrcode.svg.get.ts

0
src/server/api/wireguard/client/index.get.ts → src/server/api/client/index.get.ts

0
src/server/api/wireguard/client/index.post.ts → src/server/api/client/index.post.ts

83
src/server/api/setup/migration.post.ts

@ -0,0 +1,83 @@
import { parseCidr } from 'cidr-tools';
import { stringifyIp } from 'ip-bigint';
import { z } from 'zod';
import type { Database } from '~~/services/database/repositories/database';
export default defineEventHandler(async (event) => {
const { file } = await readValidatedBody(event, validateZod(fileType, event));
const setupDone = await Database.setup.done();
if (setupDone) {
throw createError({
statusCode: 400,
statusMessage: 'Invalid state',
});
}
const schema = z.object({
server: z.object({
privateKey: z.string(),
publicKey: z.string(),
address: z.string(),
}),
clients: z.record(
z.string(),
z.object({
name: z.string(),
address: z.string(),
privateKey: z.string(),
publicKey: z.string(),
preSharedKey: z.string(),
createdAt: z.string(),
updatedAt: z.string(),
enabled: z.boolean(),
})
),
});
const res = await schema.safeParseAsync(JSON.parse(file));
if (!res.success) {
throw new Error('Invalid Config');
}
const system = await Database.system.get();
const oldConfig = res.data;
const oldCidr = parseCidr(oldConfig.server.address + '/24');
const db = {
system: {
...system,
// TODO: migrate to db calls
interface: {
...system.interface,
address4: oldConfig.server.address,
privateKey: oldConfig.server.privateKey,
publicKey: oldConfig.server.publicKey,
},
userConfig: {
...system.userConfig,
address4Range:
stringifyIp({ number: oldCidr.start, version: 4 }) + '/24',
},
} satisfies Partial<Database['system']>,
clients: {} as Database['clients'],
};
for (const [oldId, oldClient] of Object.entries(oldConfig.clients)) {
const address6 = nextIPv6(db.system, db.clients);
await Database.client.create({
id: oldId,
address4: oldClient.address,
enabled: oldClient.enabled,
name: oldClient.name,
preSharedKey: oldClient.preSharedKey,
privateKey: oldClient.privateKey,
publicKey: oldClient.publicKey,
expiresAt: null,
oneTimeLink: null,
allowedIPs: db.system.userConfig.allowedIps,
serverAllowedIPs: [],
persistentKeepalive: 0,
address6: address6,
});
}
return { success: true };
});

71
src/server/utils/config.ts

@ -1,77 +1,6 @@
import debug from 'debug'; import debug from 'debug';
import packageJson from '@@/package.json'; import packageJson from '@@/package.json';
import { z } from 'zod';
import type { Database } from '~~/services/database/repositories/database';
import { parseCidr } from 'cidr-tools';
import { stringifyIp } from 'ip-bigint';
export const RELEASE = 'v' + packageJson.version; export const RELEASE = 'v' + packageJson.version;
export const SERVER_DEBUG = debug('Server'); export const SERVER_DEBUG = debug('Server');
export async function migrateConfig(input: unknown) {
const schema = z.object({
server: z.object({
privateKey: z.string(),
publicKey: z.string(),
address: z.string(),
}),
clients: z.record(
z.string(),
z.object({
name: z.string(),
address: z.string(),
privateKey: z.string(),
publicKey: z.string(),
preSharedKey: z.string(),
createdAt: z.string(),
updatedAt: z.string(),
enabled: z.boolean(),
})
),
});
const res = await schema.safeParseAsync(input);
if (!res.success) {
throw new Error('Invalid Config');
}
const system = await Database.system.get();
const oldConfig = res.data;
const oldCidr = parseCidr(oldConfig.server.address + '/24');
const db = {
system: {
...system,
interface: {
...system.interface,
address4: oldConfig.server.address,
privateKey: oldConfig.server.privateKey,
publicKey: oldConfig.server.publicKey,
},
userConfig: {
...system.userConfig,
address4Range:
stringifyIp({ number: oldCidr.start, version: 4 }) + '/24',
},
} satisfies Partial<Database['system']>,
clients: {} as Database['clients'],
};
for (const [oldId, oldClient] of Object.entries(oldConfig.clients)) {
const address6 = nextIPv6(db.system, db.clients);
db.clients[oldId] = {
id: oldId,
address4: oldClient.address,
createdAt: oldClient.createdAt,
enabled: oldClient.enabled,
name: oldClient.name,
preSharedKey: oldClient.preSharedKey,
privateKey: oldClient.privateKey,
publicKey: oldClient.publicKey,
updatedAt: oldClient.updatedAt,
expiresAt: null,
oneTimeLink: null,
allowedIPs: db.system.userConfig.allowedIps,
serverAllowedIPs: [],
persistentKeepalive: 0,
address6: address6,
};
}
}

Loading…
Cancel
Save