mirror of https://github.com/wg-easy/wg-easy
7 changed files with 160 additions and 10 deletions
@ -0,0 +1,115 @@ |
|||||
|
<template> |
||||
|
<div class="flex flex-col"> |
||||
|
<div class="flex items-center justify-between"> |
||||
|
<div> |
||||
|
<h3 class="text-lg font-medium text-gray-900 dark:text-neutral-200"> |
||||
|
Traffic Stats |
||||
|
</h3> |
||||
|
<p class="text-sm text-gray-500 dark:text-neutral-300"> |
||||
|
Show more concise Stats about Traffic Usage |
||||
|
</p> |
||||
|
</div> |
||||
|
<SwitchRoot |
||||
|
v-model:checked="enabled" |
||||
|
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-800" |
||||
|
:class="enabled ? 'bg-red-800' : 'bg-gray-200'" |
||||
|
> |
||||
|
<SwitchThumb |
||||
|
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform" |
||||
|
:class="enabled ? 'translate-x-6' : 'translate-x-1'" |
||||
|
/> |
||||
|
</SwitchRoot> |
||||
|
</div> |
||||
|
<div class="flex items-center justify-between"> |
||||
|
<div> |
||||
|
<h3 class="text-lg font-medium text-gray-900 dark:text-neutral-200"> |
||||
|
Chart Type |
||||
|
</h3> |
||||
|
<p class="text-sm text-gray-500 dark:text-neutral-300"> |
||||
|
Select Type of Chart you want to show |
||||
|
</p> |
||||
|
</div> |
||||
|
<SelectRoot v-model="chartType"> |
||||
|
<SelectTrigger |
||||
|
class="inline-flex min-w-[160px] items-center justify-between rounded px-[15px] text-[13px] leading-none h-[35px] gap-[5px] bg-white text-grass11 shadow-[0_2px_10px] shadow-black/10 hover:bg-mauve3 focus:shadow-[0_0_0_2px] focus:shadow-black data-[placeholder]:text-green9 outline-none" |
||||
|
aria-label="Customize options" |
||||
|
> |
||||
|
<SelectValue placeholder="Select a fruit..." /> |
||||
|
<IconsArrowDown class="h-3.5 w-3.5" /> |
||||
|
</SelectTrigger> |
||||
|
|
||||
|
<SelectPortal> |
||||
|
<SelectContent |
||||
|
class="min-w-[160px] bg-white rounded shadow-[0px_10px_38px_-10px_rgba(22,_23,_24,_0.35),_0px_10px_20px_-15px_rgba(22,_23,_24,_0.2)] will-change-[opacity,transform] data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade z-[100]" |
||||
|
:side-offset="5" |
||||
|
> |
||||
|
<SelectScrollUpButton |
||||
|
class="flex items-center justify-center h-[25px] bg-white text-violet11 cursor-default" |
||||
|
> |
||||
|
<IconsArrowUp /> |
||||
|
</SelectScrollUpButton> |
||||
|
<SelectViewport class="p-[5px]"> |
||||
|
<SelectItem |
||||
|
v-for="(option, index) in options" |
||||
|
:key="index" |
||||
|
class="text-[13px] leading-none text-grass11 rounded-[3px] flex items-center h-[25px] pr-[35px] pl-[25px] relative select-none data-[disabled]:text-mauve8 data-[disabled]:pointer-events-none data-[highlighted]:outline-none data-[highlighted]:bg-green9 data-[highlighted]:text-green1" |
||||
|
:value="option" |
||||
|
> |
||||
|
<SelectItemText> |
||||
|
{{ option }} |
||||
|
</SelectItemText> |
||||
|
</SelectItem> |
||||
|
</SelectViewport> |
||||
|
<SelectScrollDownButton |
||||
|
class="flex items-center justify-center h-[25px] bg-white text-violet11 cursor-default" |
||||
|
> |
||||
|
<IconsArrowDown /> |
||||
|
</SelectScrollDownButton> |
||||
|
</SelectContent> |
||||
|
</SelectPortal> |
||||
|
</SelectRoot> |
||||
|
</div> |
||||
|
<BaseButton class="self-end" @click="submit">Save</BaseButton> |
||||
|
<UiToast |
||||
|
v-model:open="open" |
||||
|
title="Saved successfully" |
||||
|
content="Statistics saved successfully" |
||||
|
/> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
const globalStore = useGlobalStore(); |
||||
|
const open = ref(false); |
||||
|
const enabled = ref(globalStore.statistics.enabled); |
||||
|
const options: Record<number, string> = { |
||||
|
0: 'None', |
||||
|
1: 'Line', |
||||
|
2: 'Area', |
||||
|
3: 'Bar', |
||||
|
}; |
||||
|
const stringToIndex = Object.entries(options).reduce( |
||||
|
(obj, [k, v]) => { |
||||
|
obj[v] = Number.parseInt(k); |
||||
|
return obj; |
||||
|
}, |
||||
|
{} as Record<string, number> |
||||
|
); |
||||
|
const chartType = ref(options[globalStore.statistics.chartType]); |
||||
|
|
||||
|
async function submit() { |
||||
|
const response = await $fetch('/api/admin/statistics', { |
||||
|
method: 'post', |
||||
|
body: { |
||||
|
statistics: { |
||||
|
enabled: enabled.value, |
||||
|
chartType: stringToIndex[chartType.value!], |
||||
|
}, |
||||
|
}, |
||||
|
}); |
||||
|
if (response.success) { |
||||
|
open.value = true; |
||||
|
} |
||||
|
globalStore.fetchStatistics(); |
||||
|
} |
||||
|
</script> |
@ -0,0 +1,8 @@ |
|||||
|
export default defineEventHandler(async (event) => { |
||||
|
const { statistics } = await readValidatedBody( |
||||
|
event, |
||||
|
validateZod(statisticsType) |
||||
|
); |
||||
|
await Database.system.updateStatistics(statistics); |
||||
|
return { success: true }; |
||||
|
}); |
Loading…
Reference in new issue