mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-09 23:48:36 +01:00
shields update
This commit is contained in:
9
dashboard/app.config.ts
Normal file
9
dashboard/app.config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
notifications: {
|
||||
position: 'top-0 bottom-[unset]'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -69,6 +69,7 @@ const { drawerVisible, hideDrawer, drawerClasses } = useDrawer();
|
||||
|
||||
|
||||
<UModals />
|
||||
<UNotifications />
|
||||
|
||||
<LazyOnboarding> </LazyOnboarding>
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ onMounted(() => {
|
||||
<div class="flex overflow-x-auto hide-scrollbars">
|
||||
<div class="flex">
|
||||
<div v-for="(tab, index) of items" @click="onChangeTab(index)"
|
||||
class="px-6 pb-3 poppins font-medium text-lyx-lightmode-text dark:text-lyx-text-darker border-b-[1px] border-lyx-text-darker"
|
||||
class="px-6 whitespace-nowrap pb-3 poppins font-medium text-lyx-lightmode-text dark:text-lyx-text-darker border-b-[1px] border-lyx-text-darker"
|
||||
:class="{
|
||||
'dark:!border-[#FFFFFF] dark:!text-[#FFFFFF] !border-lyx-primary !text-lyx-primary': activeTabIndex === index,
|
||||
'hover:border-lyx-lightmode-text-dark hover:text-lyx-lightmode-text-dark/60 dark:hover:border-lyx-text-dark dark:hover:text-lyx-text-dark cursor-pointer': activeTabIndex !== index
|
||||
|
||||
@@ -11,5 +11,5 @@ const widgetStyle = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :style="widgetStyle" class="bg-lyx-widget-light"></div>
|
||||
<div :style="widgetStyle" class="dark:bg-lyx-widget-light bg-lyx-lightmode-widget"></div>
|
||||
</template>
|
||||
@@ -9,7 +9,23 @@ const avgDuration = computed(() => {
|
||||
return (backendData.value.durations.durations.reduce((a: any, e: any) => a + parseInt(e[1]), 0) / backendData.value.durations.durations.length);
|
||||
})
|
||||
|
||||
const labels = new Array(650).fill('-');
|
||||
const labels = computed(() => {
|
||||
if (!backendData?.value?.durations) return [];
|
||||
|
||||
const sizes = new Map<string, number>();
|
||||
|
||||
for (const e of backendData.value.durations.durations) {
|
||||
if (!sizes.has(e[0])) {
|
||||
sizes.set(e[0], 0);
|
||||
} else {
|
||||
const data = sizes.get(e[0]) ?? 0;
|
||||
sizes.set(e[0], data + 1);
|
||||
}
|
||||
}
|
||||
|
||||
const max = Array.from(sizes.values()).reduce((a, e) => a > e ? a : e, 0);
|
||||
return new Array(max).fill('-');
|
||||
});
|
||||
|
||||
const durationsDatasets = computed(() => {
|
||||
if (!backendData?.value?.durations) return [];
|
||||
@@ -26,7 +42,7 @@ const durationsDatasets = computed(() => {
|
||||
|
||||
datasets.push({
|
||||
points: consumerDurations.map((e: any) => {
|
||||
return 1000 / parseInt(e[1])
|
||||
return 1000 / parseInt(e[1])
|
||||
}),
|
||||
color: colors[i],
|
||||
chartType: 'line',
|
||||
@@ -45,7 +61,7 @@ const durationsDatasets = computed(() => {
|
||||
|
||||
<div class="cursor-default flex justify-center w-full">
|
||||
|
||||
<div v-if="backendData" class="flex flex-col mt-8 gap-6 px-20 items-center w-full">
|
||||
<div v-if="backendData && !backendPending" class="flex flex-col mt-8 gap-6 px-20 items-center w-full">
|
||||
|
||||
<div class="flex gap-8">
|
||||
<div> Queue size: {{ backendData.queue?.size || 'ERROR' }} </div>
|
||||
|
||||
@@ -373,8 +373,9 @@ const legendClasses = ref<string[]>([
|
||||
</div>
|
||||
<div class="mt-3 font-normal flex flex-col text-[.9rem] dark:text-lyx-text-dark text-lyx-lightmode-text-dark"
|
||||
v-if="(currentTooltipData as any).sessions > (currentTooltipData as any).visits">
|
||||
<div> Unique visitors is greater than visits. </div>
|
||||
<div> This can indicate bot traffic. </div>
|
||||
<div> Unique visitors are higher than total visits </div>
|
||||
<div> which often means bots (automated scripts or crawlers)</div>
|
||||
<div> are inflating the numbers.</div>
|
||||
</div>
|
||||
<!-- <div class="bg-lyx-background-lighter h-[2px] w-full my-2"> </div> -->
|
||||
</LyxUiCard>
|
||||
|
||||
67
dashboard/components/dialog/shields/AddDomain.vue
Normal file
67
dashboard/components/dialog/shields/AddDomain.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
const emit = defineEmits(['success', 'cancel']);
|
||||
|
||||
const domain = ref<string>('');
|
||||
|
||||
const canAddDomain = computed(() => {
|
||||
return domain.value.trim().length > 0;
|
||||
})
|
||||
|
||||
async function addDomain() {
|
||||
if (!canAddDomain.value) return;
|
||||
try {
|
||||
const res = await $fetch('/api/shields/domains/add', {
|
||||
method: 'POST',
|
||||
headers: useComputedHeaders({}).value,
|
||||
body: JSON.stringify({ domain: domain.value })
|
||||
});
|
||||
domain.value = '';
|
||||
emit('success');
|
||||
} catch (ex: any) {
|
||||
alert(ex.message);
|
||||
emit('cancel');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UModal :ui="{
|
||||
strategy: 'override',
|
||||
overlay: {
|
||||
background: 'bg-lyx-background/85'
|
||||
},
|
||||
background: 'dark:bg-lyx-widget bg-lyx-lightmode-widget-light',
|
||||
ring: 'border-solid border-[1px] border-[#262626]'
|
||||
}">
|
||||
<div class="h-full flex flex-col gap-2 p-4">
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
|
||||
<div class="font-semibold text-[1.1rem]"> Add Domain to Allow List </div>
|
||||
|
||||
<LyxUiInput class="px-2 py-1" v-model="domain"></LyxUiInput>
|
||||
|
||||
<div class="flex flex-col gap-2 dark:text-lyx-text-dark text-lyx-lightmode-text-dark">
|
||||
<div>
|
||||
<div> You can use a wildcard (*) to match multiple hostnames. </div>
|
||||
<div> For example, *.domain.com will only record traffic on the main domain and all the
|
||||
subdomains.
|
||||
</div>
|
||||
</div>
|
||||
<div> NB: Once added, we will start rejecting traffic from non-matching hostnames within a few
|
||||
minutes.</div>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<LyxUiButton class="w-full text-center" :disabled="!canAddDomain" @click="addDomain()" type="primary">
|
||||
Add domain
|
||||
</LyxUiButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</UModal>
|
||||
|
||||
</template>
|
||||
56
dashboard/components/dialog/shields/DeleteDomain.vue
Normal file
56
dashboard/components/dialog/shields/DeleteDomain.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
const emit = defineEmits(['success', 'cancel']);
|
||||
|
||||
const props = defineProps<{ domain: string }>();
|
||||
|
||||
async function deleteDomain() {
|
||||
if (!props.domain) return;
|
||||
try {
|
||||
const res = await $fetch('/api/shields/domains/delete', {
|
||||
method: 'DELETE',
|
||||
headers: useComputedHeaders({}).value,
|
||||
body: JSON.stringify({ domain: props.domain })
|
||||
});
|
||||
emit('success');
|
||||
} catch (ex: any) {
|
||||
alert(ex.message);
|
||||
emit('cancel');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UModal :ui="{
|
||||
strategy: 'override',
|
||||
overlay: {
|
||||
background: 'bg-lyx-background/85'
|
||||
},
|
||||
background: 'dark:bg-lyx-widget bg-lyx-lightmode-widget-light',
|
||||
ring: 'border-solid border-[1px] border-[#262626]'
|
||||
}">
|
||||
<div class="h-full flex flex-col gap-2 p-4">
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
|
||||
<div class="font-semibold text-[1.1rem]"> Domain delete </div>
|
||||
|
||||
<div> Are you sure to delete the whitelisted domain
|
||||
<span class="font-semibold">{{ props.domain }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<LyxUiButton type="secondary" @click="emit('cancel')">
|
||||
Cancel
|
||||
</LyxUiButton>
|
||||
<LyxUiButton @click="deleteDomain()" type="danger">
|
||||
Delete
|
||||
</LyxUiButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</UModal>
|
||||
|
||||
</template>
|
||||
101
dashboard/components/shields/Addresses.vue
Normal file
101
dashboard/components/shields/Addresses.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<script lang="ts" setup>
|
||||
import { DialogShieldsAddDomain, DialogShieldsDeleteDomain } from '#components';
|
||||
|
||||
definePageMeta({ layout: 'dashboard' });
|
||||
|
||||
const { project } = useProject();
|
||||
|
||||
const { data: blackAddresses, refresh: refreshAddresses, pending: pendingAddresses } = useFetch('/api/shields/ip/list', {
|
||||
headers: useComputedHeaders({})
|
||||
});
|
||||
|
||||
const toast = useToast()
|
||||
const modal = useModal();
|
||||
|
||||
function showAddDomainModal() {
|
||||
modal.open(DialogShieldsAddDomain, {
|
||||
onSuccess: () => {
|
||||
refreshAddresses();
|
||||
modal.close();
|
||||
|
||||
toast.add({
|
||||
id: 'shield_domain_add_success',
|
||||
title: 'Success',
|
||||
description: 'Whitelist updated with the new domain',
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
},
|
||||
onCancel: () => {
|
||||
modal.close();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function showDeleteDomainModal(domain: string) {
|
||||
modal.open(DialogShieldsDeleteDomain, {
|
||||
domain,
|
||||
onSuccess: () => {
|
||||
refreshAddresses();
|
||||
modal.close();
|
||||
toast.add({
|
||||
id: 'shield_domain_remove_success',
|
||||
title: 'Deleted',
|
||||
description: 'Whitelist domain deleted successfully',
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
},
|
||||
onCancel: () => {
|
||||
modal.close();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="py-4 flex">
|
||||
<LyxUiCard class="w-full mx-2">
|
||||
<div>
|
||||
<div class="text-[1.2rem] font-semibold"> IP Block Llist </div>
|
||||
<div class="dark:text-lyx-text-dark text-lyx-lightmode-text-dark">
|
||||
Reject incoming traffic from specific IP addresses
|
||||
</div>
|
||||
</div>
|
||||
<LyxUiSeparator class="my-3"></LyxUiSeparator>
|
||||
|
||||
<div class="flex justify-end pb-3">
|
||||
<LyxUiButton type="primary" @click="showAddDomainModal()"> Add Domain </LyxUiButton>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center pb-8 text-[1.2rem]" v-if="pendingAddresses">
|
||||
<i class="fas fa-loader animate-spin"></i>
|
||||
</div>
|
||||
|
||||
<div v-if="!pendingAddresses && blackAddresses && blackAddresses.length == 0"
|
||||
class="flex flex-col items-center pb-8">
|
||||
<div>
|
||||
No domain rules configured for this project.
|
||||
</div>
|
||||
<div class="font-semibold">
|
||||
Traffic from all domains is currently accepted.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!pendingAddresses && blackAddresses && blackAddresses.length > 0"
|
||||
class="grid grid-cols-[auto_auto_auto_auto] px-10">
|
||||
<div class="col-span-3">Domain</div>
|
||||
<div>Actions</div>
|
||||
<LyxUiSeparator class="col-span-4 my-3"></LyxUiSeparator>
|
||||
<template v-for="domain of blackAddresses">
|
||||
<div class="col-span-3 mb-3">{{ domain }}</div>
|
||||
<div> <i @click="showDeleteDomainModal(domain)"
|
||||
class="far fa-trash cursor-pointer hover:text-lyx-text-dark"></i> </div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</LyxUiCard>
|
||||
</div>
|
||||
</template>
|
||||
101
dashboard/components/shields/Domains.vue
Normal file
101
dashboard/components/shields/Domains.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<script lang="ts" setup>
|
||||
import { DialogShieldsAddDomain, DialogShieldsDeleteDomain } from '#components';
|
||||
|
||||
definePageMeta({ layout: 'dashboard' });
|
||||
|
||||
const { project } = useProject();
|
||||
|
||||
const { data: allowedDomains, refresh: refreshDomains, pending: pendingDomains } = useFetch('/api/shields/domains/list', {
|
||||
headers: useComputedHeaders({})
|
||||
});
|
||||
|
||||
const toast = useToast()
|
||||
const modal = useModal();
|
||||
|
||||
function showAddDomainModal() {
|
||||
modal.open(DialogShieldsAddDomain, {
|
||||
onSuccess: () => {
|
||||
refreshDomains();
|
||||
modal.close();
|
||||
|
||||
toast.add({
|
||||
id: 'shield_domain_add_success',
|
||||
title: 'Success',
|
||||
description: 'Whitelist updated with the new domain',
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
},
|
||||
onCancel: () => {
|
||||
modal.close();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function showDeleteDomainModal(domain: string) {
|
||||
modal.open(DialogShieldsDeleteDomain, {
|
||||
domain,
|
||||
onSuccess: () => {
|
||||
refreshDomains();
|
||||
modal.close();
|
||||
toast.add({
|
||||
id: 'shield_domain_remove_success',
|
||||
title: 'Deleted',
|
||||
description: 'Whitelist domain deleted successfully',
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
},
|
||||
onCancel: () => {
|
||||
modal.close();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div class="py-4 flex">
|
||||
<LyxUiCard class="w-full mx-2">
|
||||
<div>
|
||||
<div class="text-[1.2rem] font-semibold"> Domains allow list </div>
|
||||
<div class="dark:text-lyx-text-dark text-lyx-lightmode-text-dark">
|
||||
Accept incoming traffic only from familiar domains
|
||||
</div>
|
||||
</div>
|
||||
<LyxUiSeparator class="my-3"></LyxUiSeparator>
|
||||
|
||||
<div class="flex justify-end pb-3">
|
||||
<LyxUiButton type="primary" @click="showAddDomainModal()"> Add Domain </LyxUiButton>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center pb-8 text-[1.2rem]" v-if="pendingDomains">
|
||||
<i class="fas fa-loader animate-spin"></i>
|
||||
</div>
|
||||
|
||||
<div v-if="!pendingDomains && allowedDomains && allowedDomains.length == 0"
|
||||
class="flex flex-col items-center pb-8">
|
||||
<div>
|
||||
No domain rules configured for this project.
|
||||
</div>
|
||||
<div class="font-semibold">
|
||||
Traffic from all domains is currently accepted.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!pendingDomains && allowedDomains && allowedDomains.length > 0"
|
||||
class="grid grid-cols-[auto_auto_auto_auto] px-10">
|
||||
<div class="col-span-3">Domain</div>
|
||||
<div>Actions</div>
|
||||
<LyxUiSeparator class="col-span-4 my-3"></LyxUiSeparator>
|
||||
<template v-for="domain of allowedDomains">
|
||||
<div class="col-span-3 mb-3">{{ domain }}</div>
|
||||
<div> <i @click="showDeleteDomainModal(domain)"
|
||||
class="far fa-trash cursor-pointer hover:text-lyx-text-dark"></i> </div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</LyxUiCard>
|
||||
</div>
|
||||
</template>
|
||||
@@ -19,6 +19,7 @@ const sections: Section[] = [
|
||||
{ label: 'Web Analytics', to: '/', icon: 'fal fa-table-layout' },
|
||||
{ label: 'Custom Events', to: '/events', icon: 'fal fa-square-bolt' },
|
||||
{ label: 'Members', to: '/members', icon: 'fal fa-users' },
|
||||
{ label: 'Shields', to: '/shields', icon: 'fal fa-shield' },
|
||||
{ label: 'Ask AI', to: '/analyst', icon: 'fal fa-sparkles' },
|
||||
|
||||
// { label: 'Security', to: '/security', icon: 'fal fa-shield', disabled: selfhosted },
|
||||
|
||||
38
dashboard/pages/shields.vue
Normal file
38
dashboard/pages/shields.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
definePageMeta({ layout: 'dashboard' });
|
||||
|
||||
const selfhosted = useSelfhosted();
|
||||
|
||||
const items = [
|
||||
{ label: 'Domains', slot: 'domains', tab: 'domains' },
|
||||
{ label: 'IP Addresses', slot: 'ipaddresses', tab: 'ipaddresses' },
|
||||
{ label: 'Countries', slot: 'countries', tab: 'countries' },
|
||||
{ label: 'Pages', slot: 'pages', tab: 'pages' },
|
||||
]
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="lg:px-10 h-full lg:py-8 overflow-hidden hide-scrollbars">
|
||||
|
||||
<div class="poppins font-semibold text-[1.3rem] lg:px-0 px-4 lg:py-0 py-4"> Shields </div>
|
||||
|
||||
<CustomTab :items="items" :route="true" class="mt-8">
|
||||
<template #domains>
|
||||
<ShieldsDomains></ShieldsDomains>
|
||||
</template>
|
||||
<template #ipaddresses>
|
||||
<div class="flex items-center justify-center py-20 text-[1.2rem]"> Coming soon </div>
|
||||
<!-- <ShieldsAddresses></ShieldsAddresses> -->
|
||||
</template>
|
||||
<template #countries>
|
||||
<div class="flex items-center justify-center py-20 text-[1.2rem]"> Coming soon </div>
|
||||
</template>
|
||||
<template #pages>
|
||||
<div class="flex items-center justify-center py-20 text-[1.2rem]"> Coming soon </div>
|
||||
</template>
|
||||
</CustomTab>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@@ -59,6 +59,13 @@ export default defineEventHandler(async event => {
|
||||
|
||||
const savedUser = await newUser.save();
|
||||
|
||||
|
||||
setImmediate(() => {
|
||||
const emailData = EmailService.getEmailServerInfo('brevolist_add', { email: payload.email as string });
|
||||
EmailServiceHelper.sendEmail(emailData);
|
||||
});
|
||||
|
||||
|
||||
setImmediate(() => {
|
||||
console.log('SENDING WELCOME EMAIL TO', payload.email);
|
||||
if (!payload.email) return;
|
||||
|
||||
@@ -34,6 +34,11 @@ export default defineEventHandler(async event => {
|
||||
|
||||
await RegisterModel.create({ email, password: hashedPassword });
|
||||
|
||||
setImmediate(() => {
|
||||
const emailData = EmailService.getEmailServerInfo('brevolist_add', { email });
|
||||
EmailServiceHelper.sendEmail(emailData);
|
||||
});
|
||||
|
||||
setImmediate(() => {
|
||||
const emailData = EmailService.getEmailServerInfo('confirm', { target: email, link: `https://dashboard.litlyx.com/api/auth/confirm_email?register_code=${jwt}` });
|
||||
EmailServiceHelper.sendEmail(emailData);
|
||||
|
||||
@@ -12,7 +12,12 @@ export default defineEventHandler(async event => {
|
||||
|
||||
console.log({ project_id, user_id: data.user.id });
|
||||
|
||||
const member = await TeamMemberModel.findOne({ project_id, user_id: data.user.id });
|
||||
const member = await TeamMemberModel.findOne({
|
||||
project_id, $or: [
|
||||
{ user_id: data.user.id },
|
||||
{ email: data.user.user.email }
|
||||
]
|
||||
});
|
||||
if (!member) return setResponseStatus(event, 400, 'member not found');
|
||||
|
||||
member.pending = false;
|
||||
|
||||
21
dashboard/server/api/shields/domains/add.post.ts
Normal file
21
dashboard/server/api/shields/domains/add.post.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { DomainWhitelistModel } from "~/shared/schema/shields/DomainWhitelistSchema";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const data = await getRequestData(event, [], ['OWNER']);
|
||||
if (!data) return;
|
||||
|
||||
const body = await readBody(event);
|
||||
const { domain } = body;
|
||||
|
||||
if (domain.trim().length == 0) return setResponseStatus(event, 400, 'Domain is required');
|
||||
|
||||
const whitelist = await DomainWhitelistModel.updateOne({
|
||||
project_id: data.project_id
|
||||
},
|
||||
{ $push: { domains: domain } },
|
||||
{ upsert: true }
|
||||
);
|
||||
|
||||
return { ok: true };
|
||||
});
|
||||
18
dashboard/server/api/shields/domains/delete.delete.ts
Normal file
18
dashboard/server/api/shields/domains/delete.delete.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { DomainWhitelistModel } from "~/shared/schema/shields/DomainWhitelistSchema";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const data = await getRequestData(event, [], ['OWNER']);
|
||||
if (!data) return;
|
||||
|
||||
const body = await readBody(event);
|
||||
const { domain } = body;
|
||||
|
||||
const removal = await DomainWhitelistModel.updateOne({
|
||||
project_id: data.project_id
|
||||
},
|
||||
{ $pull: { domains: domain } },
|
||||
);
|
||||
|
||||
return { ok: removal.modifiedCount == 1 };
|
||||
});
|
||||
10
dashboard/server/api/shields/domains/list.ts
Normal file
10
dashboard/server/api/shields/domains/list.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { DomainWhitelistModel } from "~/shared/schema/shields/DomainWhitelistSchema";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
const data = await getRequestData(event, [], ['OWNER']);
|
||||
if (!data) return;
|
||||
const whitelist = await DomainWhitelistModel.findOne({ project_id: data.project_id });
|
||||
if (!whitelist) return [];
|
||||
const domains = whitelist.domains;
|
||||
return domains;
|
||||
});
|
||||
11
dashboard/server/api/shields/ip/add.post.ts
Normal file
11
dashboard/server/api/shields/ip/add.post.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { AddressBlacklistModel } from "~/shared/schema/shields/AddressBlacklistSchema";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
const data = await getRequestData(event, [], ['OWNER']);
|
||||
if (!data) return;
|
||||
const body = await readBody(event);
|
||||
const { address, description } = body;
|
||||
if (address.trim().length == 0) return setResponseStatus(event, 400, 'Address is required');
|
||||
const result = await AddressBlacklistModel.updateOne({ project_id: data.project_id, address }, { description }, { upsert: true });
|
||||
return { ok: result.acknowledged };
|
||||
});
|
||||
14
dashboard/server/api/shields/ip/delete.delete.ts
Normal file
14
dashboard/server/api/shields/ip/delete.delete.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { AddressBlacklistModel } from "~/shared/schema/shields/AddressBlacklistSchema";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const data = await getRequestData(event, [], ['OWNER']);
|
||||
if (!data) return;
|
||||
|
||||
const body = await readBody(event);
|
||||
const { address } = body;
|
||||
|
||||
const removal = await AddressBlacklistModel.deleteOne({ project_id: data.project_id, address });
|
||||
|
||||
return { ok: removal.deletedCount == 1 };
|
||||
});
|
||||
8
dashboard/server/api/shields/ip/list.ts
Normal file
8
dashboard/server/api/shields/ip/list.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { AddressBlacklistModel } from "~/shared/schema/shields/AddressBlacklistSchema";
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
const data = await getRequestData(event, [], ['OWNER']);
|
||||
if (!data) return;
|
||||
const blacklist = await AddressBlacklistModel.find({ project_id: data.project_id });
|
||||
return blacklist.map(e => e.toJSON());
|
||||
});
|
||||
Reference in New Issue
Block a user