mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2025-10-03 07:46:18 +00:00
Finished client assigning
This commit is contained in:
2915
src/static/app/package-lock.json
generated
2915
src/static/app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,8 @@
|
|||||||
<script setup async>
|
<script setup async>
|
||||||
import LocaleText from "@/components/text/localeText.vue";
|
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 SearchClients from "@/components/configurationComponents/peerAssignModalComponents/searchClients.vue";
|
||||||
import AssignedClients from "@/components/configurationComponents/peerAssignModalComponents/assignedClients.vue";
|
import AssignedClients from "@/components/configurationComponents/peerAssignModalComponents/assignedClients.vue";
|
||||||
|
import {DashboardClientAssignmentStore} from "@/stores/DashboardClientAssignmentStore.js";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
selectedPeer: Object
|
selectedPeer: Object
|
||||||
@@ -12,44 +10,17 @@ const props = defineProps({
|
|||||||
const emits = defineEmits([
|
const emits = defineEmits([
|
||||||
'close'
|
'close'
|
||||||
])
|
])
|
||||||
const assignments = ref([])
|
const assignmentStore = DashboardClientAssignmentStore()
|
||||||
const clients = ref([])
|
|
||||||
await fetchGet('/api/clients/allClients', {},(res) => {
|
|
||||||
clients.value = res.data;
|
|
||||||
console.log(clients.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const getAssignedClients = async () => {
|
if (assignmentStore.clients.length > 0){
|
||||||
await fetchGet('/api/clients/assignedClients', {
|
assignmentStore.getClients()
|
||||||
ConfigurationName: props.selectedPeer.configuration.Name,
|
}else{
|
||||||
Peer: props.selectedPeer.id
|
await assignmentStore.getClients()
|
||||||
}, (res) => {
|
|
||||||
assignments.value = res.data
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await getAssignedClients()
|
await assignmentStore.getAssignedClients(props.selectedPeer.configuration.Name, props.selectedPeer.id)
|
||||||
|
|
||||||
const assignClient = async (clientID) => {
|
const assignClient = async (clientID) => {
|
||||||
await fetchPost('/api/clients/assignClient', {
|
await assignmentStore.assignClient(props.selectedPeer.configuration.Name, props.selectedPeer.id, clientID)
|
||||||
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -66,12 +37,11 @@ const unassignClient = async (assignmentID) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body px-4 pb-4 d-flex gap-2 flex-column">
|
<div class="card-body px-4 pb-4 d-flex gap-2 flex-column">
|
||||||
<AssignedClients
|
<AssignedClients
|
||||||
@unassign="args => unassignClient(args)"
|
:configuration-name="props.selectedPeer.configuration.Name"
|
||||||
:assignments="assignments"></AssignedClients>
|
:peer="props.selectedPeer.id"
|
||||||
|
></AssignedClients>
|
||||||
<SearchClients
|
<SearchClients
|
||||||
:assignments="assignments"
|
@assign="args => assignClient(args)"></SearchClients>
|
||||||
@assign="args => assignClient(args)"
|
|
||||||
:clients="clients"></SearchClients>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,7 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import LocaleText from "@/components/text/localeText.vue";
|
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 emits = defineEmits(['unassign'])
|
||||||
|
const props = defineProps(['configurationName', 'peer'])
|
||||||
|
const assignmentStore = DashboardClientAssignmentStore()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -10,26 +14,11 @@ const emits = defineEmits(['unassign'])
|
|||||||
<LocaleText t="Assigned Clients"></LocaleText>
|
<LocaleText t="Assigned Clients"></LocaleText>
|
||||||
</h6>
|
</h6>
|
||||||
<TransitionGroup name="list" tag="div" class="position-relative">
|
<TransitionGroup name="list" tag="div" class="position-relative">
|
||||||
<div class="bg-body-secondary rounded-3 text-start p-2 d-flex mb-2 assignment"
|
<Assignment :assignment="a" :key="a.AssignmentID"
|
||||||
:key="a.AssignmentID"
|
@unassign="assignmentStore.unassignClient(configurationName, peer, a.AssignmentID)"
|
||||||
v-for="a in assignments">
|
v-for="a in assignmentStore.assignments"></Assignment>
|
||||||
<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>
|
|
||||||
</TransitionGroup>
|
</TransitionGroup>
|
||||||
<div class="text-center" v-if="assignments.length === 0">
|
<div class="text-center" v-if="assignmentStore.assignments.length === 0">
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
<LocaleText t="No client assigned to this peer yet"></LocaleText>
|
<LocaleText t="No client assigned to this peer yet"></LocaleText>
|
||||||
</small>
|
</small>
|
||||||
|
@@ -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>
|
@@ -4,22 +4,25 @@ import LocaleText from "@/components/text/localeText.vue";
|
|||||||
import {computed, reactive, ref} from "vue";
|
import {computed, reactive, ref} from "vue";
|
||||||
import SearchClientsGroup from "@/components/configurationComponents/peerAssignModalComponents/searchClientsGroup.vue";
|
import SearchClientsGroup from "@/components/configurationComponents/peerAssignModalComponents/searchClientsGroup.vue";
|
||||||
import {fetchPost} from "@/utilities/fetch.js";
|
import {fetchPost} from "@/utilities/fetch.js";
|
||||||
|
import {DashboardClientAssignmentStore} from "@/stores/DashboardClientAssignmentStore.js";
|
||||||
|
|
||||||
const props = defineProps(['clients', 'newAssignClients', 'assignments'])
|
const props = defineProps(['clients', 'newAssignClients', 'assignments'])
|
||||||
|
|
||||||
|
const assignmentStore = DashboardClientAssignmentStore()
|
||||||
|
|
||||||
const selectedGroup = ref("")
|
const selectedGroup = ref("")
|
||||||
const searchString = ref("")
|
const searchString = ref("")
|
||||||
const getSelectedGroup = computed(() => {
|
const getSelectedGroup = computed(() => {
|
||||||
if (selectedGroup.value){
|
if (selectedGroup.value){
|
||||||
return {
|
return {
|
||||||
[selectedGroup.value] : props.clients[selectedGroup.value]
|
[selectedGroup.value] : assignmentStore.clients[selectedGroup.value]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return props.clients
|
return assignmentStore.clients
|
||||||
})
|
})
|
||||||
const groupCount = reactive({})
|
const groupCount = reactive({})
|
||||||
Object.keys(props.clients).forEach(
|
Object.keys(assignmentStore.clients).forEach(
|
||||||
x => groupCount[x] = props.clients[x].length
|
x => groupCount[x] = assignmentStore.clients[x].length
|
||||||
)
|
)
|
||||||
|
|
||||||
const emits = defineEmits(['assign'])
|
const emits = defineEmits(['assign'])
|
||||||
@@ -52,7 +55,7 @@ const emits = defineEmits(['assign'])
|
|||||||
@click="selectedGroup = groupName"
|
@click="selectedGroup = groupName"
|
||||||
:class="{'active': selectedGroup === groupName}"
|
:class="{'active': selectedGroup === groupName}"
|
||||||
class="btn bg-primary-subtle text-primary-emphasis btn-sm me-2 rounded-3"
|
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 }}
|
{{ groupName }}
|
||||||
<span class="ms-1 badge" :class="[ groupCount[groupName] > 0 ? 'bg-primary' : 'bg-secondary' ]">
|
<span class="ms-1 badge" :class="[ groupCount[groupName] > 0 ? 'bg-primary' : 'bg-secondary' ]">
|
||||||
{{ groupCount[groupName] }}
|
{{ groupCount[groupName] }}
|
||||||
@@ -62,11 +65,11 @@ 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">
|
<div class="p-3 border rounded-3 d-flex flex-column gap-2 overflow-y-scroll" style="height: 400px">
|
||||||
<SearchClientsGroup
|
<SearchClientsGroup
|
||||||
:assignments="assignments"
|
|
||||||
@assign="(args) => emits('assign', args)"
|
@assign="(args) => emits('assign', args)"
|
||||||
@count="(args) => groupCount[groupName] = args"
|
@count="(args) => groupCount[groupName] = args"
|
||||||
:searchString="searchString"
|
: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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,14 +1,15 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {computed} from "vue";
|
import {computed} from "vue";
|
||||||
import LocaleText from "@/components/text/localeText.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 emits = defineEmits(['count', 'assign'])
|
||||||
|
const assignmentStore = DashboardClientAssignmentStore()
|
||||||
|
|
||||||
const filterGroup = computed(() => {
|
const filterGroup = computed(() => {
|
||||||
let g = props.group.filter(x =>
|
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){
|
if (props.searchString){
|
||||||
let v = g.filter(
|
let v = g.filter(
|
||||||
x => (x.Name && x.Name.includes(props.searchString)) || (x.Email && x.Email.includes(props.searchString))
|
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>
|
<small>{{groupName}}</small>
|
||||||
</h6>
|
</h6>
|
||||||
<div v-if="filterGroup.length > 0" class="d-flex flex-column gap-2">
|
<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"
|
<div class="bg-body-secondary rounded-3 text-start p-2 d-flex"
|
||||||
@click="emits('assign', client.ClientID)"
|
|
||||||
v-for="client in filterGroup">
|
v-for="client in filterGroup">
|
||||||
<small class="mb-0">
|
<div class="d-flex flex-column">
|
||||||
{{ client.Email }}
|
<small class="mb-0">
|
||||||
</small>
|
{{ client.Email }}
|
||||||
<small class="text-muted ms-auto">{{ client.Name }}</small>
|
</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>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
70
src/static/app/src/stores/DashboardClientAssignmentStore.js
Normal file
70
src/static/app/src/stores/DashboardClientAssignmentStore.js
Normal 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
|
||||||
|
}
|
||||||
|
})
|
@@ -48,12 +48,12 @@ window.dayjs = dayjs
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="card rounded-3 border shadow">
|
<div class="card rounded-3 border-0 shadow">
|
||||||
<div class="card-header border-0 align-items-center d-flex p-3 flex-column flex-sm-row gap-2">
|
<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">
|
<small class="fw-bold">
|
||||||
{{ props.config.name }}
|
{{ props.config.name }}
|
||||||
</small>
|
</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' ]"
|
:class="[props.config.protocol === 'wg' ? 'wireguardBg' : 'amneziawgBg' ]"
|
||||||
v-if="props.config.protocol === 'wg'">
|
v-if="props.config.protocol === 'wg'">
|
||||||
{{ props.config.protocol === 'wg' ? 'WireGuard': 'AmneziaWG' }}
|
{{ props.config.protocol === 'wg' ? 'WireGuard': 'AmneziaWG' }}
|
||||||
|
@@ -72,8 +72,10 @@ const signOut = async () => {
|
|||||||
<small>No configuration available</small>
|
<small>No configuration available</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="d-flex py-4">
|
<div v-else class="d-flex p-3">
|
||||||
<div class="spinner-border m-auto"></div>
|
<div class="bg-body rounded-3 d-flex" style="width: 100%; height: 211px;">
|
||||||
|
<div class="spinner-border m-auto"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user