update shields

This commit is contained in:
Emily
2025-03-24 18:54:15 +01:00
parent 87c9aca5c4
commit 94a28b31d3
19 changed files with 967 additions and 53 deletions

View File

@@ -0,0 +1,80 @@
<script lang="ts" setup>
const emit = defineEmits(['success', 'cancel']);
const address = ref<string>('');
const description = ref<string>('');
const { data: currentIP } = useFetch<any>('https://api.ipify.org/?format=json');
const canAddAddress = computed(() => {
return address.value.trim().length > 0;
})
async function addAddress() {
if (!canAddAddress.value) return;
try {
const res = await $fetch('/api/shields/ip/add', {
method: 'POST',
headers: useComputedHeaders({}).value,
body: JSON.stringify({ address: address.value, description: description.value })
});
address.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 IP to Block List </div>
<div class="flex flex-col gap-2 dark:text-lyx-text-dark text-lyx-lightmode-text-dark">
<div> Your current IP address is: {{ currentIP?.ip || '...' }} </div>
<div> Copy and Paste your IP address in the box below or enter a custom address </div>
</div>
<div class="flex flex-col gap-2">
<div class="font-medium"> IP Address </div>
<LyxUiInput class="px-2 py-1" v-model="address" placeholder="127.0.0.1"></LyxUiInput>
</div>
<div class="flex flex-col gap-2">
<div class="font-medium"> Description (optional) </div>
<LyxUiInput class="px-2 py-1" v-model="description" placeholder="e.g. localhost or office">
</LyxUiInput>
</div>
<div class="flex flex-col gap-2 dark:text-lyx-text-dark text-lyx-lightmode-text-dark">
<div> Once added, we will start rejecting traffic from this IP within a few minutes.</div>
</div>
<div class="flex">
<LyxUiButton class="w-full text-center" :disabled="!canAddAddress" @click="addAddress()"
type="primary">
Add IP Address
</LyxUiButton>
</div>
</div>
</div>
</UModal>
</template>

View File

@@ -50,7 +50,7 @@ async function addDomain() {
subdomains.
</div>
</div>
<div> NB: Once added, we will start rejecting traffic from non-matching hostnames within a few
<div> NB: Once added, we will start allowing traffic only from matching hostnames within a few
minutes.</div>
</div>

View File

@@ -0,0 +1,56 @@
<script lang="ts" setup>
const emit = defineEmits(['success', 'cancel']);
const props = defineProps<{ address: string }>();
async function deleteAddress() {
if (!props.address) return;
try {
const res = await $fetch('/api/shields/ip/delete', {
method: 'DELETE',
headers: useComputedHeaders({}).value,
body: JSON.stringify({ address: props.address })
});
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]"> IP Address delete </div>
<div> Are you sure to delete the blacklisted IP Address
<span class="font-semibold">{{ props.address }}</span>
</div>
<div class="flex justify-end gap-2">
<LyxUiButton type="secondary" @click="emit('cancel')">
Cancel
</LyxUiButton>
<LyxUiButton @click="deleteAddress()" type="danger">
Delete
</LyxUiButton>
</div>
</div>
</div>
</UModal>
</template>

View File

@@ -1,10 +1,8 @@
<script lang="ts" setup>
import { DialogShieldsAddDomain, DialogShieldsDeleteDomain } from '#components';
import { DialogShieldsDeleteAddress, DialogShieldsAddAddress } from '#components';
definePageMeta({ layout: 'dashboard' });
const { project } = useProject();
const { data: blackAddresses, refresh: refreshAddresses, pending: pendingAddresses } = useFetch('/api/shields/ip/list', {
headers: useComputedHeaders({})
});
@@ -12,16 +10,16 @@ const { data: blackAddresses, refresh: refreshAddresses, pending: pendingAddress
const toast = useToast()
const modal = useModal();
function showAddDomainModal() {
modal.open(DialogShieldsAddDomain, {
function showAddAddressModal() {
modal.open(DialogShieldsAddAddress, {
onSuccess: () => {
refreshAddresses();
modal.close();
toast.add({
id: 'shield_domain_add_success',
id: 'shield_address_add_success',
title: 'Success',
description: 'Whitelist updated with the new domain',
description: 'Blacklist updated with the new address',
timeout: 5000
});
@@ -32,16 +30,16 @@ function showAddDomainModal() {
})
}
function showDeleteDomainModal(domain: string) {
modal.open(DialogShieldsDeleteDomain, {
domain,
function showDeleteAddressModal(address: string) {
modal.open(DialogShieldsDeleteAddress, {
address,
onSuccess: () => {
refreshAddresses();
modal.close();
toast.add({
id: 'shield_domain_remove_success',
id: 'shield_address_remove_success',
title: 'Deleted',
description: 'Whitelist domain deleted successfully',
description: 'Blacklist address deleted successfully',
timeout: 5000
});
@@ -59,15 +57,15 @@ function showDeleteDomainModal(domain: string) {
<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="text-[1.2rem] font-semibold"> IP Block List </div>
<div class="dark:text-lyx-text-dark text-lyx-lightmode-text-dark">
Reject incoming traffic from specific IP addresses
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>
<LyxUiButton type="primary" @click="showAddAddressModal()"> Add IP Address </LyxUiButton>
</div>
<div class="flex justify-center pb-8 text-[1.2rem]" v-if="pendingAddresses">
@@ -86,12 +84,14 @@ function showDeleteDomainModal(domain: string) {
<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>
<div> Domain </div>
<div class="col-span-2"> Description </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)"
<template v-for="entry of blackAddresses">
<div class="mb-2"> {{ entry.address }} </div>
<div class="col-span-2">{{ entry.description || 'No description' }}</div>
<div> <i @click="showDeleteAddressModal(entry.address)"
class="far fa-trash cursor-pointer hover:text-lyx-text-dark"></i> </div>
</template>
</div>

View File

@@ -0,0 +1,47 @@
<script lang="ts" setup>
definePageMeta({ layout: 'dashboard' });
const { data: botOptions, refresh: refreshBotOptions, pending: pendingBotOptions } = useFetch('/api/shields/bots/options', {
headers: useComputedHeaders({})
});
async function onChange(newValue: boolean) {
await $fetch('/api/shields/bots/update_options', {
method: 'POST',
headers: useComputedHeaders({ custom: { 'Content-Type': 'application/json' } }).value,
body: JSON.stringify({ block: newValue })
})
await refreshBotOptions();
}
</script>
<template>
<div class="py-4 flex">
<LyxUiCard class="w-full mx-2">
<div>
<div class="text-[1.2rem] font-semibold"> Block bot traffic </div>
<div class="dark:text-lyx-text-dark text-lyx-lightmode-text-dark">
Automatically block unwanted bot and crawler traffic to protect your site from spam, scrapers, and
unnecessary server load.
</div>
</div>
<LyxUiSeparator class="my-3"></LyxUiSeparator>
<div class="flex justify-center pb-8 text-[1.2rem]" v-if="pendingBotOptions">
<i class="fas fa-loader animate-spin"></i>
</div>
<div v-if="!pendingBotOptions && botOptions">
<div class="flex gap-2">
<UToggle :modelValue="botOptions.block" @change="onChange"></UToggle>
<div> Enable bot protection </div>
</div>
</div>
</LyxUiCard>
</div>
</template>

View File

@@ -3,8 +3,6 @@ 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({})
});
@@ -61,7 +59,7 @@ function showDeleteDomainModal(domain: string) {
<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
Accept incoming traffic only from familiar domains.
</div>
</div>
<LyxUiSeparator class="my-3"></LyxUiSeparator>

View File

@@ -7,8 +7,9 @@ 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' },
{ label: 'Bot traffic', slot: 'bots', tab: 'bots' },
// { label: 'Countries', slot: 'countries', tab: 'countries' },
// { label: 'Pages', slot: 'pages', tab: 'pages' },
]
</script>
@@ -23,14 +24,10 @@ const items = [
<ShieldsDomains></ShieldsDomains>
</template>
<template #ipaddresses>
<div class="flex items-center justify-center py-20 text-[1.2rem]"> Coming soon </div>
<!-- <ShieldsAddresses></ShieldsAddresses> -->
<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 #bots>
<ShieldsBots></ShieldsBots>
</template>
</CustomTab>

View File

@@ -0,0 +1,9 @@
import { BotTrafficOptionModel } from "~/shared/schema/shields/BotTrafficOptionSchema";
export default defineEventHandler(async event => {
const data = await getRequestData(event, [], ['OWNER']);
if (!data) return;
const result = await BotTrafficOptionModel.findOne({ project_id: data.project_id });
if (!result) return { block: false };
return { block: result.block }
});

View File

@@ -0,0 +1,14 @@
import { BotTrafficOptionModel } from "~/shared/schema/shields/BotTrafficOptionSchema";
export default defineEventHandler(async event => {
const data = await getRequestData(event, [], ['OWNER']);
if (!data) return;
const body = await readBody(event);
const { block } = body;
if (block != true && block != false)
return setResponseStatus(event, 400, 'block is required and must be true or false');
const result = await BotTrafficOptionModel.updateOne({ project_id: data.project_id }, { block }, { upsert: true });
return { ok: result.acknowledged };
});

View File

@@ -0,0 +1,11 @@
import { CountryBlacklistModel } from "~/shared/schema/shields/CountryBlacklistSchema";
export default defineEventHandler(async event => {
const data = await getRequestData(event, [], ['OWNER']);
if (!data) return;
const body = await readBody(event);
const { country, description } = body;
if (country.trim().length == 0) return setResponseStatus(event, 400, 'Country is required');
const result = await CountryBlacklistModel.updateOne({ project_id: data.project_id, country }, { description }, { upsert: true });
return { ok: result.acknowledged };
});

View File

@@ -0,0 +1,14 @@
import { CountryBlacklistModel } from "~/shared/schema/shields/CountryBlacklistSchema";
export default defineEventHandler(async event => {
const data = await getRequestData(event, [], ['OWNER']);
if (!data) return;
const body = await readBody(event);
const { country } = body;
const removal = await CountryBlacklistModel.deleteOne({ project_id: data.project_id, country });
return { ok: removal.deletedCount == 1 };
});

View File

@@ -0,0 +1,8 @@
import { CountryBlacklistModel } from "~/shared/schema/shields/CountryBlacklistSchema";
export default defineEventHandler(async event => {
const data = await getRequestData(event, [], ['OWNER']);
if (!data) return;
const blacklist = await CountryBlacklistModel.find({ project_id: data.project_id });
return blacklist.map(e => e.toJSON());
});