Finished client assigning

This commit is contained in:
Donald Zou 2025-07-18 18:49:19 +08:00
parent 68e757aafc
commit a9d74e834d
9 changed files with 1638 additions and 1555 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,8 @@
<script setup async>
import LocaleText from "@/components/text/localeText.vue";
import {onMounted, ref} from "vue";
import {GetLocale} from "@/utilities/locale.js";
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
import SearchClients from "@/components/configurationComponents/peerAssignModalComponents/searchClients.vue";
import AssignedClients from "@/components/configurationComponents/peerAssignModalComponents/assignedClients.vue";
import {DashboardClientAssignmentStore} from "@/stores/DashboardClientAssignmentStore.js";
const props = defineProps({
selectedPeer: Object
@ -12,44 +10,17 @@ const props = defineProps({
const emits = defineEmits([
'close'
])
const assignments = ref([])
const clients = ref([])
await fetchGet('/api/clients/allClients', {},(res) => {
clients.value = res.data;
console.log(clients.value)
})
const assignmentStore = DashboardClientAssignmentStore()
const getAssignedClients = async () => {
await fetchGet('/api/clients/assignedClients', {
ConfigurationName: props.selectedPeer.configuration.Name,
Peer: props.selectedPeer.id
}, (res) => {
assignments.value = res.data
})
if (assignmentStore.clients.length > 0){
assignmentStore.getClients()
}else{
await assignmentStore.getClients()
}
await getAssignedClients()
await assignmentStore.getAssignedClients(props.selectedPeer.configuration.Name, props.selectedPeer.id)
const assignClient = async (clientID) => {
await fetchPost('/api/clients/assignClient', {
ConfigurationName: props.selectedPeer.configuration.Name,
Peer: props.selectedPeer.id,
ClientID: clientID
}, async (res) => {
if (res.status){
await getAssignedClients()
}
})
}
const unassignClient = async (assignmentID) => {
await fetchPost('/api/clients/unassignClient', {
AssignmentID: assignmentID
}, async (res) => {
if (res.status){
await getAssignedClients()
}
})
await assignmentStore.assignClient(props.selectedPeer.configuration.Name, props.selectedPeer.id, clientID)
}
</script>
@ -65,13 +36,12 @@ const unassignClient = async (assignmentID) => {
<button type="button" class="btn-close ms-auto" @click="emits('close')"></button>
</div>
<div class="card-body px-4 pb-4 d-flex gap-2 flex-column">
<AssignedClients
@unassign="args => unassignClient(args)"
:assignments="assignments"></AssignedClients>
<AssignedClients
:configuration-name="props.selectedPeer.configuration.Name"
:peer="props.selectedPeer.id"
></AssignedClients>
<SearchClients
:assignments="assignments"
@assign="args => assignClient(args)"
:clients="clients"></SearchClients>
@assign="args => assignClient(args)"></SearchClients>
</div>
</div>
</div>

View File

@ -1,7 +1,11 @@
<script setup>
import LocaleText from "@/components/text/localeText.vue";
const props = defineProps(['assignments'])
import {ref} from "vue";
import Assignment from "@/components/configurationComponents/peerAssignModalComponents/assignment.vue";
import {DashboardClientAssignmentStore} from "@/stores/DashboardClientAssignmentStore.js";
const emits = defineEmits(['unassign'])
const props = defineProps(['configurationName', 'peer'])
const assignmentStore = DashboardClientAssignmentStore()
</script>
<template>
@ -10,26 +14,11 @@ const emits = defineEmits(['unassign'])
<LocaleText t="Assigned Clients"></LocaleText>
</h6>
<TransitionGroup name="list" tag="div" class="position-relative">
<div class="bg-body-secondary rounded-3 text-start p-2 d-flex mb-2 assignment"
:key="a.AssignmentID"
v-for="a in assignments">
<div class="d-flex flex-column">
<small>
{{ a.Client.Email }}
</small>
<small class="text-muted">
{{ a.Client.Name ? a.Client.Name + ' | ' : '' }}{{ a.Client.ClientGroup ? a.Client.ClientGroup : 'Local' }}
</small>
</div>
<button
@click="emits('unassign', a.AssignmentID)"
aria-label="Delete Assignment"
class="btn bg-danger-subtle text-danger-emphasis ms-auto">
<i class="bi bi-trash-fill"></i>
</button>
</div>
<Assignment :assignment="a" :key="a.AssignmentID"
@unassign="assignmentStore.unassignClient(configurationName, peer, a.AssignmentID)"
v-for="a in assignmentStore.assignments"></Assignment>
</TransitionGroup>
<div class="text-center" v-if="assignments.length === 0">
<div class="text-center" v-if="assignmentStore.assignments.length === 0">
<small class="text-muted">
<LocaleText t="No client assigned to this peer yet"></LocaleText>
</small>

View File

@ -0,0 +1,64 @@
<script setup>
import {ref} from "vue";
import LocaleText from "@/components/text/localeText.vue";
import {DashboardClientAssignmentStore} from "@/stores/DashboardClientAssignmentStore.js";
const props = defineProps(['assignment'])
const emits = defineEmits(['unassign'])
const confirmDelete = ref(false)
const assignmentStore = DashboardClientAssignmentStore()
</script>
<template>
<div class="bg-body-secondary rounded-3 text-start p-2 mb-2 assignment">
<div class="d-flex" v-if="!confirmDelete">
<div class="d-flex flex-column">
<small>
{{ assignment.Client.Email }}
</small>
<small class="text-muted">
{{ assignment.Client.Name ? assignment.Client.Name + ' | ' : '' }}{{ assignment.Client.ClientGroup ? assignment.Client.ClientGroup : 'Local' }}
</small>
</div>
<button
v-if="!confirmDelete"
@click="confirmDelete = !confirmDelete"
:class="{disabled: assignmentStore.unassigning}"
aria-label="Delete Assignment"
class="btn bg-danger-subtle text-danger-emphasis ms-auto">
<i class="bi bi-trash-fill"></i>
</button>
</div>
<div class="d-flex gap-2" v-else>
<div class="d-flex flex-column">
<small>
<LocaleText t="Are you sure to delete assignment for"></LocaleText>
</small>
<small class="text-muted">
<LocaleText :t="assignment.Client.Email + ' in group ' + (assignment.Client.ClientGroup ? assignment.Client.ClientGroup : 'Local') + '?'"></LocaleText>
</small>
</div>
<button
@click="emits('unassign')"
aria-label="Delete Assignment"
:class="{disabled: assignmentStore.unassigning}"
class="btn bg-danger-subtle text-danger-emphasis ms-auto">
<span class="spinner-border spinner-border-sm" v-if="assignmentStore.unassigning"></span>
<i class="bi bi-check-lg" v-else></i>
</button>
<button
:class="{disabled: assignmentStore.unassigning}"
@click="confirmDelete = !confirmDelete"
aria-label="Cancel Delete Assignment"
class="btn bg-secondary-subtle text-secondary-emphasis ">
<i class="bi bi-x-lg"></i>
</button>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -4,22 +4,25 @@ import LocaleText from "@/components/text/localeText.vue";
import {computed, reactive, ref} from "vue";
import SearchClientsGroup from "@/components/configurationComponents/peerAssignModalComponents/searchClientsGroup.vue";
import {fetchPost} from "@/utilities/fetch.js";
import {DashboardClientAssignmentStore} from "@/stores/DashboardClientAssignmentStore.js";
const props = defineProps(['clients', 'newAssignClients', 'assignments'])
const assignmentStore = DashboardClientAssignmentStore()
const selectedGroup = ref("")
const searchString = ref("")
const getSelectedGroup = computed(() => {
if (selectedGroup.value){
return {
[selectedGroup.value] : props.clients[selectedGroup.value]
[selectedGroup.value] : assignmentStore.clients[selectedGroup.value]
}
}
return props.clients
return assignmentStore.clients
})
const groupCount = reactive({})
Object.keys(props.clients).forEach(
x => groupCount[x] = props.clients[x].length
Object.keys(assignmentStore.clients).forEach(
x => groupCount[x] = assignmentStore.clients[x].length
)
const emits = defineEmits(['assign'])
@ -52,7 +55,7 @@ const emits = defineEmits(['assign'])
@click="selectedGroup = groupName"
:class="{'active': selectedGroup === groupName}"
class="btn bg-primary-subtle text-primary-emphasis btn-sm me-2 rounded-3"
v-for="(_, groupName) in clients">
v-for="(_, groupName) in assignmentStore.clients">
{{ groupName }}
<span class="ms-1 badge" :class="[ groupCount[groupName] > 0 ? 'bg-primary' : 'bg-secondary' ]">
{{ groupCount[groupName] }}
@ -61,12 +64,12 @@ const emits = defineEmits(['assign'])
</div>
</div>
<div class="p-3 border rounded-3 d-flex flex-column gap-2 overflow-y-scroll" style="height: 400px">
<SearchClientsGroup
:assignments="assignments"
<SearchClientsGroup
@assign="(args) => emits('assign', args)"
@count="(args) => groupCount[groupName] = args"
:searchString="searchString"
:group="group" :groupName="groupName" v-for="(group, groupName) in getSelectedGroup"></SearchClientsGroup>
:group="group" :groupName="groupName"
v-for="(group, groupName) in getSelectedGroup"></SearchClientsGroup>
</div>
</div>
</div>

View File

@ -1,14 +1,15 @@
<script setup>
import {computed} from "vue";
import LocaleText from "@/components/text/localeText.vue";
import {DashboardClientAssignmentStore} from "@/stores/DashboardClientAssignmentStore.js";
const props = defineProps(['group', 'groupName', 'searchString', 'assignments'])
const props = defineProps(['group', 'groupName', 'searchString'])
const emits = defineEmits(['count', 'assign'])
const assignmentStore = DashboardClientAssignmentStore()
const filterGroup = computed(() => {
let g = props.group.filter(x =>
!props.assignments.map(a => a.Client.ClientID).includes(x.ClientID))
!assignmentStore.assignments.map(a => a.Client.ClientID).includes(x.ClientID))
if (props.searchString){
let v = g.filter(
x => (x.Name && x.Name.includes(props.searchString)) || (x.Email && x.Email.includes(props.searchString))
@ -27,13 +28,22 @@ const filterGroup = computed(() => {
<small>{{groupName}}</small>
</h6>
<div v-if="filterGroup.length > 0" class="d-flex flex-column gap-2">
<div class="bg-body-secondary rounded-3 text-start p-2 d-flex p-1" role="button"
@click="emits('assign', client.ClientID)"
<div class="bg-body-secondary rounded-3 text-start p-2 d-flex"
v-for="client in filterGroup">
<small class="mb-0">
{{ client.Email }}
</small>
<small class="text-muted ms-auto">{{ client.Name }}</small>
<div class="d-flex flex-column">
<small class="mb-0">
{{ client.Email }}
</small>
<small class="text-muted">{{ client.Name ? client.Name : 'No Name' }}</small>
</div>
<button
@click="emits('assign', client.ClientID)"
:class="{disabled: assignmentStore.assigning}"
class="btn bg-success-subtle text-success-emphasis ms-auto">
<span class="spinner-border spinner-border-sm" v-if="assignmentStore.assigning === client.ClientID"></span>
<i class="bi bi-plus-circle-fill" v-else></i>
</button>
</div>
</div>
<div v-else>

View File

@ -0,0 +1,70 @@
import {defineStore} from "pinia";
import {ref} from "vue";
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
export const DashboardClientAssignmentStore =
defineStore('DashboardClientAssignmentStore', () => {
const assignments = ref([])
const clients = ref({})
const unassigning = ref(false)
const assigning = ref("")
const getClients = async () => {
await fetchGet('/api/clients/allClients', {},(res) => {
clients.value = res.data;
})
}
const getClientById = (ClientID) => {
return Object.values(clients.value).flat().find(x => x.ClientID === ClientID)
}
const getAssignedClients = async (ConfigurationName, Peer) => {
await fetchGet('/api/clients/assignedClients', {
ConfigurationName: ConfigurationName,
Peer: Peer
}, (res) => {
assignments.value = res.data
})
}
const assignClient = async (ConfigurationName, Peer, ClientID) => {
assigning.value = ClientID;
await fetchPost('/api/clients/assignClient', {
ConfigurationName: ConfigurationName,
Peer: Peer,
ClientID: ClientID
}, async (res) => {
if (res.status){
await getAssignedClients(ConfigurationName, Peer)
assigning.value = "";
}else{
assigning.value = "";
}
})
}
const unassignClient = async (ConfigurationName, Peer, AssignmentID) => {
unassigning.value = true;
await fetchPost('/api/clients/unassignClient', {
AssignmentID: AssignmentID
}, async (res) => {
if (res.status){
await getAssignedClients(ConfigurationName, Peer)
}
unassigning.value = false;
})
}
return {
assignments,
getAssignedClients,
getClients,
clients,
unassignClient,
assignClient,
getClientById,
unassigning,
assigning
}
})

View File

@ -48,12 +48,12 @@ window.dayjs = dayjs
</script>
<template>
<div class="card rounded-3 border shadow">
<div class="card-header border-0 align-items-center d-flex p-3 flex-column flex-sm-row gap-2">
<div class="card rounded-3 border-0 shadow">
<div class="card-header rounded-top-3 border-0 align-items-center d-flex p-3 flex-column flex-sm-row gap-2">
<small class="fw-bold">
{{ props.config.name }}
</small>
<span class="badge rounded-3 shadow ms-sm-auto"
<span class="badge rounded-3 ms-sm-auto"
:class="[props.config.protocol === 'wg' ? 'wireguardBg' : 'amneziawgBg' ]"
v-if="props.config.protocol === 'wg'">
{{ props.config.protocol === 'wg' ? 'WireGuard': 'AmneziaWG' }}

View File

@ -72,8 +72,10 @@ const signOut = async () => {
<small>No configuration available</small>
</div>
</div>
<div v-else class="d-flex py-4">
<div class="spinner-border m-auto"></div>
<div v-else class="d-flex p-3">
<div class="bg-body rounded-3 d-flex" style="width: 100%; height: 211px;">
<div class="spinner-border m-auto"></div>
</div>
</div>
</Transition>
</div>