This commit is contained in:
Donald Zou
2025-08-19 17:56:46 +08:00
parent 9c6d0b56c3
commit 574aff605f
8 changed files with 148 additions and 89 deletions

View File

@@ -76,7 +76,6 @@ const configurationModals = ref({
} }
}) })
const peerSearchBar = ref(false) const peerSearchBar = ref(false)
// Fetch Peer ===================================== // Fetch Peer =====================================
const fetchPeerList = async () => { const fetchPeerList = async () => {
await fetchGet("/api/getWireguardConfigurationInfo", { await fetchGet("/api/getWireguardConfigurationInfo", {
@@ -110,6 +109,7 @@ setFetchPeerListInterval()
onBeforeUnmount(() => { onBeforeUnmount(() => {
clearInterval(fetchPeerListInterval.value); clearInterval(fetchPeerListInterval.value);
fetchPeerListInterval.value = undefined; fetchPeerListInterval.value = undefined;
wireguardConfigurationStore.Filter.HiddenTags = []
}) })
watch(() => { watch(() => {
@@ -155,13 +155,28 @@ const configurationSummary = computed(() => {
const showPeersCount = ref(10) const showPeersCount = ref(10)
const showPeersThreshold = 20; const showPeersThreshold = 20;
const hiddenPeers = computed(() => {
return wireguardConfigurationStore.Filter.HiddenTags.map(tag => {
return configurationInfo.value.Info.PeerGroups[tag].Peers
}).flat()
})
const taggedPeers = computed(() => {
return Object.values(configurationInfo.value.Info.PeerGroups).map(x => x.Peers).flat()
})
const searchPeers = computed(() => { const searchPeers = computed(() => {
const result = wireguardConfigurationStore.searchString ? const result = wireguardConfigurationStore.searchString ?
configurationPeers.value.filter(x => { configurationPeers.value.filter(x => {
return x.name.includes(wireguardConfigurationStore.searchString) || return (x.name.includes(wireguardConfigurationStore.searchString) ||
x.id.includes(wireguardConfigurationStore.searchString) || x.id.includes(wireguardConfigurationStore.searchString) ||
x.allowed_ip.includes(wireguardConfigurationStore.searchString) x.allowed_ip.includes(wireguardConfigurationStore.searchString))
}) : configurationPeers.value; && !hiddenPeers.value.includes(x.id)
&& (
wireguardConfigurationStore.Filter.ShowAllPeersWhenHiddenTags || (!wireguardConfigurationStore.Filter.ShowAllPeersWhenHiddenTags && taggedPeers.value.includes(x.id))
)
}) : configurationPeers.value.filter(x => !hiddenPeers.value.includes(x.id) && (
wireguardConfigurationStore.Filter.ShowAllPeersWhenHiddenTags || (!wireguardConfigurationStore.Filter.ShowAllPeersWhenHiddenTags && taggedPeers.value.includes(x.id))
));
if (dashboardStore.Configuration.Server.dashboard_sort === "restricted"){ if (dashboardStore.Configuration.Server.dashboard_sort === "restricted"){
return result.sort((a, b) => { return result.sort((a, b) => {
@@ -201,6 +216,7 @@ watch(() => route.query.id, (newValue) => {
}) })
</script> </script>
<template> <template>
@@ -352,10 +368,10 @@ watch(() => route.query.id, (newValue) => {
:configurationInfo="configurationInfo" :configurationInfo="configurationInfo"
></PeerDataUsageCharts> ></PeerDataUsageCharts>
<hr> <hr>
<div style="margin-bottom: 80px"> <div style="margin-bottom: 10rem">
<PeerSearch <PeerSearch
v-if="configurationPeers.length > 0" v-if="configurationPeers.length > 0"
@search="peerSearchBar = true" @search="peerSearchBar = !peerSearchBar"
@jobsAll="configurationModals.peerScheduleJobsAll.modalOpen = true" @jobsAll="configurationModals.peerScheduleJobsAll.modalOpen = true"
@jobLogs="configurationModals.peerScheduleJobsLogs.modalOpen = true" @jobLogs="configurationModals.peerScheduleJobsLogs.modalOpen = true"
@editConfiguration="configurationModals.editConfiguration.modalOpen = true" @editConfiguration="configurationModals.editConfiguration.modalOpen = true"
@@ -383,8 +399,11 @@ watch(() => route.query.id, (newValue) => {
</TransitionGroup> </TransitionGroup>
</div> </div>
<Transition name="slideUp"> <Transition name="slide-fade">
<PeerSearchBar @close="peerSearchBar = false" v-if="peerSearchBar"></PeerSearchBar> <PeerSearchBar
v-if="peerSearchBar"
:ConfigurationInfo="configurationInfo"
@close="peerSearchBar = false"></PeerSearchBar>
</Transition> </Transition>
<PeerListModals <PeerListModals
:configurationModals="configurationModals" :configurationModals="configurationModals"

View File

@@ -16,7 +16,7 @@ export default {
return {store, wireguardConfigurationStore} return {store, wireguardConfigurationStore}
}, },
props: { props: {
configuration: Object configuration: Object, displayTags: Array
}, },
data(){ data(){
return { return {
@@ -163,7 +163,7 @@ export default {
@click="tagManager = !tagManager" @click="tagManager = !tagManager"
class="btn btn-sm w-100 text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle position-relative"> class="btn btn-sm w-100 text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle position-relative">
<i class="bi me-2 bi-hash"></i> <i class="bi me-2 bi-tag"></i>
<LocaleText t="Tags"></LocaleText> <LocaleText t="Tags"></LocaleText>
</button> </button>

View File

@@ -2,8 +2,7 @@
import {GetLocale} from "@/utilities/locale.js"; import {GetLocale} from "@/utilities/locale.js";
import {computed, onMounted, ref, useTemplateRef} from "vue"; import {computed, onMounted, ref, useTemplateRef} from "vue";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"; import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import LocaleText from "@/components/text/localeText.vue"; import {onBeforeRouteUpdate, useRoute, useRouter} from "vue-router";
import {useRoute, useRouter} from "vue-router";
const searchBarPlaceholder = computed(() => { const searchBarPlaceholder = computed(() => {
return GetLocale("Search Peers...") return GetLocale("Search Peers...")
@@ -27,7 +26,7 @@ const debounce = () => {
const emits = defineEmits(['close']) const emits = defineEmits(['close'])
const input = useTemplateRef('searchBar') const input = useTemplateRef('searchBar')
const props = defineProps(["ConfigurationInfo"])
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
if (route.query.peer){ if (route.query.peer){
@@ -35,53 +34,42 @@ if (route.query.peer){
router.replace({ query: null }) router.replace({ query: null })
} }
const show = ref(true)
onMounted(() => { onMounted(() => {
input.value.focus(); document.querySelector("#searchPeers").focus()
}) })
onBeforeRouteUpdate(() => {
show.value = false
})
</script> </script>
<template> <template>
<Transition name="slideUp" appear type="animation" style="animation-delay: 1s"> <div class="fixed-bottom w-100 bottom-0 z-2 p-3" style="z-index: 1;" v-if="show">
<div class="fixed-bottom w-100 bottom-0 z-2" style="z-index: 1;"> <div class="d-flex flex-column searchPeersContainer ms-auto p-2 rounded-5"
<div class="container-fluid"> style="width: 300px;">
<div class="row g-0"> <div class="rounded-5 border border-white p-2 d-flex align-items-center gap-1 w-100">
<div class="col-md-3 col-lg-2"></div>
<div class="col-md-9 col-lg-10 d-flex justify-content-center py-2">
<div class="rounded-3 p-2 border shadow searchPeersContainer bg-body-tertiary">
<div class="d-flex gap-1 align-items-center px-2">
<h6 class="mb-0 me-2">
<label for="searchPeers">
<i class="bi bi-search"></i>
</label>
</h6>
<input <input
ref="searchBar" ref="searchBar"
class="flex-grow-1 form-control rounded-3 bg-secondary-subtle border-1 border-secondary-subtle "
class="flex-grow-1 form-control form-control-sm rounded-5 bg-transparent border-0 border-secondary-subtle "
:placeholder="searchBarPlaceholder" :placeholder="searchBarPlaceholder"
id="searchPeers" id="searchPeers"
@keyup="debounce()" @keyup="debounce()"
v-model="searchString"> v-model="searchString">
<button
@click="emits('close')"
style="white-space: nowrap"
class="btn bg-secondary-subtle text-secondary-emphasis border-secondary-subtle rounded-3 d-flex align-items-center">
<span>
<i class="bi bi-x-circle-fill me-2"></i><LocaleText t="Done"></LocaleText>
</span>
</button>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</div>
</Transition>
</template> </template>
<style scoped> <style scoped>
.searchPeersContainer{ .searchPeersContainer{
backdrop-filter: blur(8px);
width: 100%; width: 100%;
background: linear-gradient(var(--degree), rgba(45, 173, 255, 0.4), rgba(255, 108, 109, 0.4), var(--brandColor2) 100%);
}
#searchPeers::placeholder{
color: white;
} }
</style> </style>

View File

@@ -162,7 +162,7 @@ export default {
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
> >
<i class="me-auto bi bi-diagram-2"></i> <LocaleText t="Tag Peer"></LocaleText> <i class="me-auto bi bi-tag"></i> <LocaleText t="Tag Peer"></LocaleText>
</a> </a>
<PeerTagSelectDropdown <PeerTagSelectDropdown
@update="this.$emit('refresh')" @update="this.$emit('refresh')"

View File

@@ -106,7 +106,8 @@ const predefinedColors = {
"white": "#fff", "white": "#fff",
"black": "#000", "black": "#000",
} }
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"
const store = WireguardConfigurationsStore()
const props = defineProps(['configuration']) const props = defineProps(['configuration'])
const groups = reactive({...props.configuration.Info.PeerGroups}) const groups = reactive({...props.configuration.Info.PeerGroups})
import { v4 } from "uuid" import { v4 } from "uuid"
@@ -154,10 +155,22 @@ watch(() => groups, (newVal) => {
}, { }, {
deep: true deep: true
}) })
const edit = ref(false)
</script> </script>
<template> <template>
<div class="card shadow" id="peerTag"> <div class="card shadow rounded-3" id="peerTag">
<div class="card-header">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="showAllPeers" v-model="store.Filter.ShowAllPeersWhenHiddenTags">
<label class="form-check-label" for="showAllPeers">
<small>
<LocaleText t="Show All Peers"></LocaleText>
</small>
</label>
</div>
</div>
<div class="card-body p-2" > <div class="card-body p-2" >
<Transition name="zoom" mode="out-in"> <Transition name="zoom" mode="out-in">
<div v-if="!iconPickerOpen && !colorPickerOpen"> <div v-if="!iconPickerOpen && !colorPickerOpen">
@@ -165,12 +178,16 @@ watch(() => groups, (newVal) => {
<small><LocaleText t="No tag"></LocaleText></small> <small><LocaleText t="No tag"></LocaleText></small>
</div> </div>
<div class="d-flex flex-column gap-2" v-else> <div class="d-flex flex-column gap-2" v-else>
<TransitionGroup name="slide-fade">
<PeerTagSetting v-for="(group, key) in groups" <PeerTagSetting v-for="(group, key) in groups"
@delete="delete groups[key]" :groupId="key"
@delete="delete groups[key]; store.Filter.HiddenTags = store.Filter.HiddenTags.filter(x => x !== key)"
@colorPickerOpen="colorPickerOpen = true; selectedKey = key" @colorPickerOpen="colorPickerOpen = true; selectedKey = key"
@iconPickerOpen="iconPickerOpen = true; selectedKey = key" @iconPickerOpen="iconPickerOpen = true; selectedKey = key"
:key="key" :key="key"
:edit="edit"
:group="group"></PeerTagSetting> :group="group"></PeerTagSetting>
</TransitionGroup>
</div> </div>
</div> </div>
<PeerTagIconPicker <PeerTagIconPicker
@@ -184,16 +201,31 @@ watch(() => groups, (newVal) => {
</Transition> </Transition>
</div> </div>
<div class="card-footer p-2 d-flex gap-2" > <div class="card-footer p-2 d-flex gap-2" >
<template v-if="!edit">
<button <button
@click="emits('close')" @click="emits('close')"
class="btn btn-sm bg-secondary-subtle text-secondary-emphasis border-secondary-subtle rounded-3"> class="btn btn-sm bg-secondary-subtle text-secondary-emphasis border-secondary-subtle rounded-3">
<small><LocaleText t="Close"></LocaleText></small> <small><LocaleText t="Close"></LocaleText></small>
</button> </button>
<button <button
@click="addGroup" @click="edit = true"
class="btn btn-sm bg-primary-subtle text-primary-emphasis border-primary-subtle rounded-3 ms-auto"> class="btn btn-sm bg-primary-subtle text-primary-emphasis border-primary-subtle rounded-3 ms-auto">
<small><i class="bi bi-pen me-2"></i><LocaleText t="Edit"></LocaleText></small>
</button>
</template>
<template v-else>
<button
@click="addGroup"
class="btn btn-sm bg-primary-subtle text-primary-emphasis border-primary-subtle rounded-3 ">
<small><i class="bi bi-plus-lg me-2"></i><LocaleText t="Tag"></LocaleText></small> <small><i class="bi bi-plus-lg me-2"></i><LocaleText t="Tag"></LocaleText></small>
</button> </button>
<button
@click="edit = false"
class="btn btn-sm bg-secondary-subtle text-secondary-emphasis border-secondary-subtle rounded-3 ms-auto">
<small><LocaleText t="Done"></LocaleText></small>
</button>
</template>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -16,14 +16,6 @@ onMounted(() => {
<template> <template>
<div class="w-100 bg-body top-0 border rounded-2"> <div class="w-100 bg-body top-0 border rounded-2">
<div class="p-2 d-flex align-items-center gap-2 border-bottom">
<label>
<i class="bi bi-search"></i>
</label>
<input v-model="searchString"
placeholder="Search Icon"
class="form-control form-control-sm rounded-2">
</div>
<div class="p-2 d-grid icon-grid" <div class="p-2 d-grid icon-grid"
style="grid-template-columns: repeat(auto-fit, minmax(30px, 30px)); gap: 3px; max-height: 300px; overflow-y: scroll"> style="grid-template-columns: repeat(auto-fit, minmax(30px, 30px)); gap: 3px; max-height: 300px; overflow-y: scroll">
<div class="rounded-1 border icon d-flex" <div class="rounded-1 border icon d-flex"

View File

@@ -3,41 +3,65 @@ import LocaleText from "@/components/text/localeText.vue";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js" import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"
import {ref} from "vue"; import {ref} from "vue";
const store = WireguardConfigurationsStore(); const store = WireguardConfigurationsStore();
const props = defineProps(['group']) const props = defineProps(['group', 'edit', 'groupId'])
const emits = defineEmits(['delete', 'iconPickerOpen', 'colorPickerOpen']) const emits = defineEmits(['delete', 'iconPickerOpen', 'colorPickerOpen', 'toggle'])
const groupName = ref(props.group.GroupName) const groupName = ref(props.group.GroupName)
const toggleTag = () => {
if (store.Filter.HiddenTags.includes(props.groupId)){
store.Filter.HiddenTags = store.Filter.HiddenTags.filter(x => x !== props.groupId)
}else{
store.Filter.HiddenTags.push(props.groupId)
}
}
</script> </script>
<template> <template>
<div class="border rounded-3 p-2"> <div class="border rounded-3 p-2">
<div <div
class="rounded-3 align-items-center overflow-scroll d-flex gap-2 position-relative"> class=" align-items-center overflow-scroll d-flex gap-2 position-relative">
<div <button
@click="emits('iconPickerOpen')" @click="emits('iconPickerOpen')"
aria-label="Pick icon button" aria-label="Pick icon button"
:class="{disabled: !edit}"
class="d-flex align-items-center p-2 btn btn-sm border rounded-2"> class="d-flex align-items-center p-2 btn btn-sm border rounded-2">
<i class="bi" :class="'bi-' + group.Icon" :aria-label="group.Icon" v-if="group.Icon"></i> <i class="bi" :class="'bi-' + group.Icon" :aria-label="group.Icon" v-if="group.Icon"></i>
<span style="white-space: nowrap" v-else> <span style="white-space: nowrap" v-else>
<LocaleText t="No Icon"></LocaleText> <LocaleText t="No Icon"></LocaleText>
</span> </span>
</div> </button>
<div <button
:class="{disabled: !edit}"
aria-label="Pick color button" aria-label="Pick color button"
@click="emits('colorPickerOpen')" @click="emits('colorPickerOpen')"
:style="{'background-color': group.BackgroundColor, 'color': store.colorText(group.BackgroundColor)}" :style="{'background-color': group.BackgroundColor, 'color': store.colorText(group.BackgroundColor)}"
class="d-flex align-items-center p-2 btn btn-sm border rounded-2"> class="d-flex align-items-center p-2 btn btn-sm border rounded-2">
<i class="bi bi-eyedropper" ></i> <i class="bi bi-eyedropper" ></i>
</div> </button>
<input <input
:disabled="!edit"
v-model="groupName" v-model="groupName"
@change="group.GroupName = groupName" @change="group.GroupName = groupName"
placeholder="Tag Name"
class="form-control form-control-sm p-2 rounded-2 w-100"> class="form-control form-control-sm p-2 rounded-2 w-100">
<div <button
aria-label="Pick color button" @click="emits('delete')" v-if="edit"
aria-label="Delete Tag Button" @click="emits('delete')"
class="rounded-2 border p-2 btn btn-sm btn-outline-danger"> class="rounded-2 border p-2 btn btn-sm btn-outline-danger">
<i class="bi bi-trash-fill"></i> <i class="bi bi-trash-fill"></i>
</div> </button>
<button
v-else
aria-label="Show / Hide Button"
style="white-space: nowrap"
:class="{active: !store.Filter.HiddenTags.includes(groupId)}"
@click="toggleTag()"
class="rounded-2 p-2 btn btn-sm btn-outline-primary">
<i class="bi"
:class="[!store.Filter.HiddenTags.includes(groupId) ? 'bi-eye-fill':'bi-eye-slash-fill']"
></i>
</button>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -10,6 +10,10 @@ export const WireguardConfigurationsStore = defineStore('WireguardConfigurations
ConfigurationLoaded: false, ConfigurationLoaded: false,
searchString: "", searchString: "",
ConfigurationListInterval: undefined, ConfigurationListInterval: undefined,
Filter: {
HiddenTags: [],
ShowAllPeersWhenHiddenTags: true
},
SortOptions: { SortOptions: {
Name: GetLocale("Name"), Name: GetLocale("Name"),
Status: GetLocale("Status"), Status: GetLocale("Status"),
@@ -131,7 +135,7 @@ export const WireguardConfigurationsStore = defineStore('WireguardConfigurations
}, },
persist: { persist: {
pick: [ pick: [
"CurrentSort", "CurrentDisplay" "CurrentSort", "CurrentDisplay", "Filter.ShowAllPeersWhenHiddenTags"
] ]
} }
}); });