Browse Source

Merge 5654b2456a into bc95a2851f

pull/2551/merge
jonaskn 1 day ago
committed by GitHub
parent
commit
a5ded4061b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      src/app/components/ClientCard/ClientCard.vue
  2. 8
      src/app/components/ClientCard/DuplicateBtn.vue
  3. 51
      src/app/components/ClientCard/DuplicateDialog.vue
  4. 8
      src/app/components/Icons/Duplicate.vue
  5. 1
      src/i18n/locales/de.json
  6. 1
      src/i18n/locales/en.json
  7. 26
      src/server/api/client/[clientId]/duplicate.post.ts
  8. 72
      src/server/database/repositories/client/service.ts
  9. 37
      src/server/database/repositories/client/types.ts

1
src/app/components/ClientCard/ClientCard.vue

@ -39,6 +39,7 @@
<ClientCardQRCode :client="client" />
<ClientCardConfig :client="client" />
<ClientCardOneTimeLinkBtn :client="client" />
<ClientCardDuplicateBtn :client="client" />
</div>
</div>
</div>

8
src/app/components/ClientCard/DuplicateBtn.vue

@ -0,0 +1,8 @@
<template>
<ClientCardDuplicateDialog>
<BaseSecondaryButton class="inline-block rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white"
:title="$t('client.duplicate')">
<IconsDuplicate class="w-5" />
</BaseSecondaryButton>
</ClientCardDuplicateDialog>
</template>

51
src/app/components/ClientCard/DuplicateDialog.vue

@ -0,0 +1,51 @@
<template>
<BaseDialog :trigger-class="triggerClass">
<template #trigger>
<slot />
</template>
<template #title>
{{ $t('client.duplicate') }}
</template>
<template #description>
<div class="flex flex-col">
<FormTextField id="name" v-model="name" :label="$t('client.name')" />
</div>
</template>
<template #actions>
<DialogClose as-child>
<BaseSecondaryButton>{{ $t('dialog.cancel') }}</BaseSecondaryButton>
</DialogClose>
<DialogClose as-child>
<BasePrimaryButton @click="duplicateClient">
{{ $t('client.create') }}
</BasePrimaryButton>
</DialogClose>
</template>
</BaseDialog>
</template>
<script lang="ts" setup>
//const name = ref<string>('');
//const name = computed(() => `${props.client.name}`);
const clientsStore = useClientsStore();
const { t } = useI18n();
const props = defineProps<{triggerClass?: string ; name?: string; client: LocalClient}>();
const name = ref<string>(`${props.client.name}`);
function duplicateClient() {
return _duplicateClient({ name: name.value});
}
const _duplicateClient = useSubmit(
`/api/client/${props.client.id}/duplicate`,
{
method: 'post',
},
{
revert: () => clientsStore.refresh(),
successMsg: t('client.created'),
}
);
</script>

8
src/app/components/Icons/Duplicate.vue

@ -0,0 +1,8 @@
<template>
<DuplicateIcon />
</template>
<script lang="ts" setup>
import DuplicateIcon from '@heroicons/vue/24/outline/esm/DocumentDuplicateIcon';
</script>

1
src/i18n/locales/de.json

@ -84,6 +84,7 @@
"sort": "Sortieren",
"create": "Client erstellen",
"created": "Client wurde erstellt",
"duplicate": "Client duplizieren",
"new": "Neuer Client",
"name": "Name",
"expireDate": "Ablaufdatum",

1
src/i18n/locales/en.json

@ -84,6 +84,7 @@
"sort": "Sort",
"create": "Create Client",
"created": "Client created",
"duplicate": "Duplicate Client",
"new": "New Client",
"name": "Name",
"expireDate": "Expire Date",

26
src/server/api/client/[clientId]/duplicate.post.ts

@ -0,0 +1,26 @@
import { ClientDuplicateSchema, ClientGetSchema } from '#db/repositories/client/types';
export default definePermissionEventHandler(
'clients',
'create',
async ({ event, checkPermissions }) => {
const { clientId } = await getValidatedRouterParams(
event,
validateZod(ClientGetSchema, event)
);
const { name } = await readValidatedBody(
event,
validateZod(ClientDuplicateSchema, event)
);
const client = await Database.clients.get(clientId);
checkPermissions(client);
const result = await Database.clients.createFromExistingId({ name, clientId });
await WireGuard.saveConfig();
const newClientId = result[0]!.clientId;
return { success: true, newClientId };
}
);

72
src/server/database/repositories/client/service.ts

@ -3,6 +3,7 @@ import { containsCidr, parseCidr } from 'cidr-tools';
import { client } from './schema';
import type {
ClientCreateFromExistingType,
ClientCreateFromExistingIdType,
ClientCreateType,
UpdateClientType,
} from './types';
@ -301,4 +302,75 @@ export class ClientService {
})
.execute();
}
async createFromExistingId({ name, clientId }: ClientCreateFromExistingIdType) {
const privateKey = await wg.generatePrivateKey();
const publicKey = await wg.getPublicKey(privateKey);
const preSharedKey = await wg.generatePreSharedKey();
return this.#db.transaction(async (tx) => {
const clients = await tx.query.client.findMany().execute();
const sourceClient = await tx.query.client.findFirst({
where: eq(client.id, clientId),
})
.execute();
if (!sourceClient) {
throw new Error('No Client with provided Id found');
}
const clientInterface = await tx.query.wgInterface
.findFirst({
where: eq(wgInterface.name, sourceClient.interfaceId),
})
.execute();
if (!clientInterface) {
throw new Error('WireGuard interface not found');
}
const ipv4Cidr = parseCidr(clientInterface.ipv4Cidr);
const ipv4Address = nextIP(4, ipv4Cidr, clients);
const ipv6Cidr = parseCidr(clientInterface.ipv6Cidr);
const ipv6Address = nextIP(6, ipv6Cidr, clients);
return await tx
.insert(client)
.values({
userId: sourceClient.userId,
interfaceId: sourceClient.interfaceId,
name: name,
ipv4Address: ipv4Address,
ipv6Address: ipv6Address,
preUp: sourceClient.preUp,
postUp: sourceClient.postUp,
preDown: sourceClient.preDown,
postDown: sourceClient.postDown,
privateKey: privateKey,
publicKey: publicKey,
preSharedKey: preSharedKey,
expiresAt: sourceClient.expiresAt,
allowedIps: sourceClient.allowedIps,
serverAllowedIps: sourceClient.serverAllowedIps,
firewallIps: sourceClient.firewallIps,
persistentKeepalive: sourceClient.persistentKeepalive,
mtu: sourceClient.mtu,
jC: sourceClient.jC,
jMin: sourceClient.jMin,
jMax: sourceClient.jMax,
i1: sourceClient.i1,
i2: sourceClient.i2,
i3: sourceClient.i3,
i4: sourceClient.i4,
i5: sourceClient.i5,
dns: sourceClient.dns,
serverEndpoint: sourceClient.serverEndpoint,
enabled: sourceClient.enabled,
})
.returning({ clientId: client.id })
.execute();
});
}
}

37
src/server/database/repositories/client/types.ts

@ -94,6 +94,43 @@ export const ClientGetSchema = z.object({
clientId: clientId,
});
export const ClientDuplicateSchema = z.object({
name: name,
});
export type ClientCreateFromExistingIdType = Pick<
ClientType,
| 'userId'
| 'interfaceId'
| 'name'
| 'ipv4Address'
| 'ipv6Address'
| 'preUp'
| 'postUp'
| 'preDown'
| 'postDown'
| 'privateKey'
| 'publicKey'
| 'preSharedKey'
| 'expiresAt'
| 'allowedIps'
| 'serverAllowedIps'
| 'firewallIps'
| 'persistentKeepalive'
| 'mtu'
| 'jC'
| 'jMin'
| 'jMax'
| 'i1'
| 'i2'
| 'i3'
| 'i4'
| 'i5'
| 'dns'
| 'serverEndpoint'
| 'enabled'
>;
export type ClientCreateFromExistingType = Pick<
ClientType,
| 'name'

Loading…
Cancel
Save