mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2025-10-04 16:26:18 +00:00
Peer groups are done... ish?
This commit is contained in:
@@ -15,7 +15,7 @@ export default {
|
||||
PeerTagBadge, LocaleText, PeerSettingsDropdown
|
||||
},
|
||||
props: {
|
||||
Peer: Object
|
||||
Peer: Object, ConfigurationInfo: Object
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
@@ -106,7 +106,9 @@ export default {
|
||||
<div class="d-flex align-items-center gap-1"
|
||||
:class="{'ms-auto': dashboardStore.Configuration.Server.dashboard_peer_list_display === 'list'}"
|
||||
>
|
||||
<PeerTagBadge BackgroundColor="#ff3838" GroupName="IDK" Icon="bi-pencil"></PeerTagBadge>
|
||||
<PeerTagBadge :BackgroundColor="group.BackgroundColor" :GroupName="group.GroupName" :Icon="'bi-' + group.Icon"
|
||||
v-for="group in Object.values(ConfigurationInfo.Info.PeerGroups).filter(x => x.Peers.includes(Peer.id))"
|
||||
></PeerTagBadge>
|
||||
<div class="ms-auto px-2 rounded-3 subMenuBtn"
|
||||
:class="{active: this.subMenuOpened}"
|
||||
>
|
||||
@@ -124,6 +126,7 @@ export default {
|
||||
@share="this.$emit('share')"
|
||||
@assign="this.$emit('assign')"
|
||||
:Peer="Peer"
|
||||
:ConfigurationInfo="ConfigurationInfo"
|
||||
v-if="this.subMenuOpened"
|
||||
ref="target"
|
||||
></PeerSettingsDropdown>
|
||||
@@ -138,16 +141,7 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
|
||||
.slide-fade-leave-active, .slide-fade-enter-active{
|
||||
transition: all 0.2s cubic-bezier(0.82, 0.58, 0.17, 1.3);
|
||||
}
|
||||
|
||||
.slide-fade-enter-from,
|
||||
.slide-fade-leave-to {
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
filter: blur(3px);
|
||||
}
|
||||
|
||||
.subMenuBtn.active{
|
||||
background-color: #ffffff20;
|
||||
|
@@ -13,13 +13,6 @@ import PeerListModals from "@/components/configurationComponents/peerListCompone
|
||||
import PeerIntersectionObserver from "@/components/configurationComponents/peerIntersectionObserver.vue";
|
||||
import ConfigurationDescription from "@/components/configurationComponents/configurationDescription.vue";
|
||||
|
||||
// import PeerSearchBar from "@/components/configurationComponents/peerSearchBar.vue"
|
||||
// import PeerJobsAllModal from "@/components/configurationComponents/peerJobsAllModal.vue"
|
||||
// import PeerJobsLogsModal from "@/components/configurationComponents/peerJobsLogsModal.vue"
|
||||
// import EditConfigurationModal from "@/components/configurationComponents/editConfiguration.vue"
|
||||
// import SelectPeersModal from "@/components/configurationComponents/selectPeers.vue"
|
||||
// import PeerAddModal from "@/components/configurationComponents/peerAddModal.vue"
|
||||
|
||||
// Async Components
|
||||
const PeerSearchBar = defineAsyncComponent(() => import("@/components/configurationComponents/peerSearchBar.vue"))
|
||||
const PeerJobsAllModal = defineAsyncComponent(() => import("@/components/configurationComponents/peerJobsAllModal.vue"))
|
||||
@@ -377,6 +370,7 @@ watch(() => route.query.id, (newValue) => {
|
||||
:key="peer.id"
|
||||
v-for="peer in searchPeers">
|
||||
<Peer :Peer="peer"
|
||||
:ConfigurationInfo="configurationInfo"
|
||||
@share="configurationModals.peerShare.modalOpen = true; configurationModalSelectedPeer = peer"
|
||||
@refresh="fetchPeerList()"
|
||||
@jobs="configurationModals.peerScheduleJobs.modalOpen = true; configurationModalSelectedPeer = peer"
|
||||
|
@@ -39,7 +39,8 @@ export default {
|
||||
searchString: "",
|
||||
searchStringTimeout: undefined,
|
||||
showDisplaySettings: false,
|
||||
showMoreSettings: false
|
||||
showMoreSettings: false,
|
||||
tagManager: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -159,11 +160,19 @@ export default {
|
||||
</div>
|
||||
<div class="position-relative">
|
||||
<button
|
||||
@click="tagManager = !tagManager"
|
||||
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>
|
||||
<LocaleText t="Tags"></LocaleText>
|
||||
|
||||
</button>
|
||||
<PeerTag :configuration="configuration"></PeerTag>
|
||||
<Transition name="slide-fade">
|
||||
<PeerTag
|
||||
@update="args => configuration.Info.PeerGroups = args"
|
||||
@close="this.tagManager = false"
|
||||
:configuration="configuration" v-if="this.tagManager"></PeerTag>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-sm text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle ms-lg-auto"
|
||||
|
@@ -4,16 +4,18 @@ import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
import PeerSettingsDropdownTool
|
||||
from "@/components/configurationComponents/peerSettingsDropdownComponents/peerSettingsDropdownTool.vue";
|
||||
import PeerTagSelectDropdown
|
||||
from "@/components/configurationComponents/peerSettingsDropdownComponents/peerTagSelectDropdown.vue";
|
||||
|
||||
export default {
|
||||
name: "peerSettingsDropdown",
|
||||
components: {PeerSettingsDropdownTool, LocaleText},
|
||||
components: {PeerTagSelectDropdown, PeerSettingsDropdownTool, LocaleText},
|
||||
setup(){
|
||||
const dashboardStore = DashboardConfigurationStore()
|
||||
return {dashboardStore}
|
||||
},
|
||||
props: {
|
||||
Peer: Object
|
||||
Peer: Object, ConfigurationInfo: Object
|
||||
},
|
||||
data(){
|
||||
return{
|
||||
@@ -154,6 +156,18 @@ export default {
|
||||
<i class="me-auto bi bi-diagram-2"></i> <LocaleText t="Assign Peer"></LocaleText>
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown dropstart">
|
||||
<a class="dropdown-item d-flex " role="button"
|
||||
data-bs-auto-close="outside"
|
||||
data-bs-toggle="dropdown"
|
||||
|
||||
>
|
||||
<i class="me-auto bi bi-diagram-2"></i> <LocaleText t="Tag Peer"></LocaleText>
|
||||
</a>
|
||||
<PeerTagSelectDropdown
|
||||
@update="this.$emit('refresh')"
|
||||
:Peer="Peer" :ConfigurationInfo="ConfigurationInfo"></PeerTagSelectDropdown>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<a class="dropdown-item d-flex text-warning"
|
||||
|
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import PeerTagBadge from "@/components/configurationComponents/peerTagBadge.vue";
|
||||
import {reactive, watch} from "vue";
|
||||
|
||||
const props = defineProps(['Peer', 'ConfigurationInfo'])
|
||||
const groups = reactive({...props.ConfigurationInfo.Info.PeerGroups})
|
||||
import { fetchPost } from "@/utilities/fetch.js"
|
||||
const emits = defineEmits(['update'])
|
||||
watch(() => groups, (newVal) => {
|
||||
fetchPost("/api/updateWireguardConfigurationInfo", {
|
||||
Name: props.ConfigurationInfo.Name,
|
||||
Key: "PeerGroups",
|
||||
Value: newVal
|
||||
}, (res) => {
|
||||
if (res.status){
|
||||
emits('update', groups)
|
||||
}
|
||||
})
|
||||
}, {
|
||||
deep: true
|
||||
})
|
||||
|
||||
const togglePeer = (groupId, peerId) => {
|
||||
if (groups[groupId].Peers.includes(peerId)){
|
||||
groups[groupId].Peers = groups[groupId].Peers.filter(x => x !== peerId)
|
||||
}else{
|
||||
groups[groupId].Peers.push(peerId)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul class="dropdown-menu">
|
||||
<li v-for="(group, groupId) in groups" >
|
||||
<a role="button"
|
||||
@click="togglePeer(groupId, Peer.id)"
|
||||
class="dropdown-item d-flex align-items-center">
|
||||
<i class="bi bi-check-circle-fill" v-if="group.Peers.includes(Peer.id)"></i>
|
||||
<i class="bi bi-circle" v-else></i>
|
||||
<PeerTagBadge
|
||||
class="ms-auto"
|
||||
:BackgroundColor="group.BackgroundColor" :GroupName="group.GroupName" :Icon="'bi-' + group.Icon"></PeerTagBadge>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import {reactive, ref} from "vue";
|
||||
import {reactive, ref, watch} from "vue";
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
import bootstrapIcons from "bootstrap-icons/font/bootstrap-icons.json"
|
||||
|
||||
const predefinedColors = {
|
||||
"blue-100": "#cfe2ff",
|
||||
@@ -105,17 +106,21 @@ const predefinedColors = {
|
||||
"white": "#fff",
|
||||
"black": "#000",
|
||||
}
|
||||
|
||||
const props = defineProps(['configuration'])
|
||||
const groups = reactive({...props.configuration.Info.PeerGroups})
|
||||
import { v4 } from "uuid"
|
||||
import PeerTagSetting from "@/components/configurationComponents/peerTagComponents/peerTagSetting.vue";
|
||||
import PeerTagIconPicker from "@/components/configurationComponents/peerTagComponents/peerTagIconPicker.vue";
|
||||
import PeerTagColorPicker from "@/components/configurationComponents/peerTagComponents/peerTagColorPicker.vue";
|
||||
import { fetchPost } from "@/utilities/fetch.js"
|
||||
|
||||
const addGroup = () => {
|
||||
groups[v4().toString()] = {
|
||||
GroupName: "",
|
||||
Description: "",
|
||||
BackgroundColor: randomColor(),
|
||||
Icon: "",
|
||||
Icon: randomIcon(),
|
||||
Peers: []
|
||||
}
|
||||
}
|
||||
@@ -125,22 +130,70 @@ const randomColor = () => {
|
||||
const n = Math.floor(Math.random() * keys.length) + 1
|
||||
return predefinedColors[keys[n]]
|
||||
}
|
||||
|
||||
const randomIcon = () => {
|
||||
const keys = Object.keys(bootstrapIcons)
|
||||
const n = Math.floor(Math.random() * keys.length) + 1
|
||||
return keys[n]
|
||||
}
|
||||
|
||||
const iconPickerOpen = ref(false)
|
||||
const colorPickerOpen = ref(false)
|
||||
const selectedKey = ref("")
|
||||
const emits = defineEmits(['close', 'update'])
|
||||
watch(() => groups, (newVal) => {
|
||||
fetchPost("/api/updateWireguardConfigurationInfo", {
|
||||
Name: props.configuration.Name,
|
||||
Key: "PeerGroups",
|
||||
Value: newVal
|
||||
}, (res) => {
|
||||
if (res.status){
|
||||
emits('update', groups)
|
||||
}
|
||||
})
|
||||
}, {
|
||||
deep: true
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card shadow" id="peerTag">
|
||||
<div class="card-body" >
|
||||
<small v-if="Object.keys(groups).length === 0">
|
||||
<LocaleText t="No tag"></LocaleText>
|
||||
</small>
|
||||
<div class="d-flex flex-column gap-2" v-else>
|
||||
<PeerTagSetting v-for="group in groups" :group="group">
|
||||
|
||||
</PeerTagSetting>
|
||||
</div>
|
||||
<div class="card-body p-2" >
|
||||
<Transition name="zoom" mode="out-in">
|
||||
<div v-if="!iconPickerOpen && !colorPickerOpen">
|
||||
<div v-if="Object.keys(groups).length === 0" class="text-center text-muted">
|
||||
<small><LocaleText t="No tag"></LocaleText></small>
|
||||
</div>
|
||||
<div class="d-flex flex-column gap-2" v-else>
|
||||
<PeerTagSetting v-for="(group, key) in groups"
|
||||
@delete="delete groups[key]"
|
||||
@colorPickerOpen="colorPickerOpen = true; selectedKey = key"
|
||||
@iconPickerOpen="iconPickerOpen = true; selectedKey = key"
|
||||
:key="key"
|
||||
:group="group"></PeerTagSetting>
|
||||
</div>
|
||||
</div>
|
||||
<PeerTagIconPicker
|
||||
v-else-if="iconPickerOpen"
|
||||
@close="iconPickerOpen = false"
|
||||
:group="groups[selectedKey]"></PeerTagIconPicker>
|
||||
<PeerTagColorPicker :colors="predefinedColors"
|
||||
@close="colorPickerOpen = false"
|
||||
:group="groups[selectedKey]"
|
||||
v-else-if="colorPickerOpen"></PeerTagColorPicker>
|
||||
</Transition>
|
||||
</div>
|
||||
<div class="card-footer btn" @click="addGroup()">
|
||||
<small><i class="bi bi-plus-lg me-2"></i><LocaleText t="Tag"></LocaleText></small>
|
||||
<div class="card-footer p-2 d-flex gap-2" >
|
||||
<button
|
||||
@click="emits('close')"
|
||||
class="btn btn-sm bg-secondary-subtle text-secondary-emphasis border-secondary-subtle rounded-3">
|
||||
<small><LocaleText t="Close"></LocaleText></small>
|
||||
</button>
|
||||
<button
|
||||
@click="addGroup"
|
||||
class="btn btn-sm bg-primary-subtle text-primary-emphasis border-primary-subtle rounded-3 ms-auto">
|
||||
<small><i class="bi bi-plus-lg me-2"></i><LocaleText t="Tag"></LocaleText></small>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -1,28 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { fromString } from 'css-color-converter';
|
||||
import {computed} from "vue";
|
||||
|
||||
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"
|
||||
const props = defineProps(["BackgroundColor", "GroupName", "Icon"])
|
||||
|
||||
const color = computed(() => {
|
||||
if (props.BackgroundColor){
|
||||
const cssColor = fromString(props.BackgroundColor)
|
||||
if (cssColor) {
|
||||
const rgb = cssColor.toRgbaArray()
|
||||
return +((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 255000).toFixed(2)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
})
|
||||
const store = WireguardConfigurationsStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="badge rounded-3 shadow"
|
||||
:style="{'background-color': BackgroundColor, 'color': color > 0.69 ? '#000':'#fff' }"
|
||||
>
|
||||
<i class="bi me-1" :class="Icon" v-if="Icon"></i>#{{ GroupName }}
|
||||
<h6 class="mb-0">
|
||||
<span
|
||||
class="badge rounded-3 shadow"
|
||||
:style="{'background-color': BackgroundColor, 'color': store.colorText(BackgroundColor) }"
|
||||
>
|
||||
<i class="bi" :class="[Icon, GroupName ? 'me-2': '']" v-if="Icon"></i>{{ GroupName }}
|
||||
</span>
|
||||
</h6>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
@@ -0,0 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import {onMounted, ref} from "vue";
|
||||
const props = defineProps(['colors', 'group'])
|
||||
const emits = defineEmits(['close', 'select', ''])
|
||||
const searchString = ref("")
|
||||
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"
|
||||
const store = WireguardConfigurationsStore();
|
||||
onMounted(() => {
|
||||
let ele = document.querySelector(".icon-grid div.active")
|
||||
if (ele){
|
||||
ele.parentElement.scrollTop =
|
||||
document.querySelector(".icon-grid div.active").offsetTop - 60
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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"
|
||||
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"
|
||||
:class="{active: group.BackgroundColor === color}"
|
||||
style="cursor: pointer"
|
||||
:aria-label="name"
|
||||
:style="{'background-color': color}"
|
||||
:key="color"
|
||||
@click="group.BackgroundColor = color"
|
||||
v-for="(color, name) in colors">
|
||||
<i
|
||||
:style="{color: store.colorText(color)}"
|
||||
class="bi bi-check-circle m-auto" v-if="group.BackgroundColor === color"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2 border-top d-flex gap-2">
|
||||
<button class="btn btn-sm btn-success rounded-2 ms-auto" @click="emits('close')">
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.icon{
|
||||
flex: 1;
|
||||
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
const props = defineProps(['group'])
|
||||
import bootstrapIcons from "bootstrap-icons/font/bootstrap-icons.json"
|
||||
const emits = defineEmits(['close', 'select'])
|
||||
onMounted(() => {
|
||||
let ele = document.querySelector(".icon-grid div.active")
|
||||
if (ele){
|
||||
ele.parentElement.scrollTop =
|
||||
document.querySelector(".icon-grid div.active").offsetTop - 60
|
||||
}
|
||||
})
|
||||
const searchString = ref("")
|
||||
const searchIcon = computed(() => {
|
||||
if (searchString.value){
|
||||
|
||||
return [...Object.keys(bootstrapIcons).filter(
|
||||
x => x.includes(searchString.value.toLowerCase())
|
||||
)]
|
||||
}
|
||||
return Object.keys(bootstrapIcons)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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"
|
||||
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"
|
||||
:class="{'text-bg-success active' : group.Icon === iconName}"
|
||||
style="cursor: pointer"
|
||||
:key="iconName"
|
||||
@click="group.Icon = iconName"
|
||||
v-for="iconName in searchIcon">
|
||||
<i class="bi m-auto" :class="'bi-' + iconName"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2 border-top d-flex gap-2">
|
||||
<button
|
||||
@click="group.Icon = ''"
|
||||
class="btn btn-sm btn-secondary rounded-2 ms-auto">
|
||||
Remove Icon
|
||||
</button>
|
||||
<button class="btn btn-sm btn-success rounded-2" @click="emits('close')">
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.icon{
|
||||
flex: 1;
|
||||
min-width: 30px;
|
||||
max-width: 30px;
|
||||
width: 30px;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
</style>
|
@@ -1,45 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import {computed} from "vue";
|
||||
import { fromString } from 'css-color-converter';
|
||||
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"
|
||||
import {ref} from "vue";
|
||||
const store = WireguardConfigurationsStore();
|
||||
const props = defineProps(['group'])
|
||||
const emits = defineEmits(['delete', 'iconPickerOpen', 'colorPickerOpen'])
|
||||
|
||||
const color = computed(() => {
|
||||
if (props.group.BackgroundColor){
|
||||
const cssColor = fromString(props.group.BackgroundColor)
|
||||
if (cssColor) {
|
||||
const rgb = cssColor.toRgbaArray()
|
||||
return +((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 255000).toFixed(2) > 0.69 ? "#000":"#fff"
|
||||
}
|
||||
}
|
||||
return "#ffffff"
|
||||
})
|
||||
console.log(color)
|
||||
const groupName = ref(props.group.GroupName)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :style="{'background-color': group.BackgroundColor }"
|
||||
class="badge rounded-3 d-flex align-items-center overflow-scroll">
|
||||
<div class="border rounded-3 p-2">
|
||||
<div
|
||||
aria-label="Pick icon button"
|
||||
style="height: 30px;"
|
||||
class="d-flex align-items-center border rounded-2 p-2 btn btn-sm">
|
||||
<i class="bi bi-pencil-fill" :style="{color: color}"></i>
|
||||
</div>
|
||||
<div contenteditable="true" class="flex-grow-1 text-start d-flex align-items-center rounded-2"
|
||||
:style="{color: color}"
|
||||
style="height: 30px">
|
||||
Tag Name
|
||||
</div>
|
||||
<div style="height: 30px;"
|
||||
aria-label="Pick color button"
|
||||
class="d-flex align-items-center border-0 rounded-2 p-2 btn btn-sm">
|
||||
<i class="bi bi-palette-fill" :style="{color: color}"></i>
|
||||
</div>
|
||||
<div style="height: 30px;"
|
||||
aria-label="Pick color button"
|
||||
class="d-flex align-items-center border-0 rounded-2 p-2 btn btn-sm">
|
||||
<i class="bi bi-trash-fill" :style="{color: color}"></i>
|
||||
class="rounded-3 align-items-center overflow-scroll d-flex gap-2 position-relative">
|
||||
<div
|
||||
@click="emits('iconPickerOpen')"
|
||||
aria-label="Pick icon button"
|
||||
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>
|
||||
<span style="white-space: nowrap" v-else>
|
||||
<LocaleText t="No Icon"></LocaleText>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Pick color button"
|
||||
@click="emits('colorPickerOpen')"
|
||||
:style="{'background-color': group.BackgroundColor, 'color': store.colorText(group.BackgroundColor)}"
|
||||
class="d-flex align-items-center p-2 btn btn-sm border rounded-2">
|
||||
<i class="bi bi-eyedropper" ></i>
|
||||
</div>
|
||||
<input
|
||||
v-model="groupName"
|
||||
@change="group.GroupName = groupName"
|
||||
class="form-control form-control-sm p-2 rounded-2 w-100">
|
||||
<div
|
||||
aria-label="Pick color button" @click="emits('delete')"
|
||||
class="rounded-2 border p-2 btn btn-sm btn-outline-danger">
|
||||
<i class="bi bi-trash-fill" ></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -1289,4 +1289,15 @@ samp{
|
||||
}
|
||||
.agent-message-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.slide-fade-leave-active, .slide-fade-enter-active{
|
||||
transition: all 0.2s cubic-bezier(0.82, 0.58, 0.17, 1.3);
|
||||
}
|
||||
|
||||
.slide-fade-enter-from,
|
||||
.slide-fade-leave-to {
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
filter: blur(3px);
|
||||
}
|
@@ -2,6 +2,7 @@ import {defineStore} from "pinia";
|
||||
import {fetchGet} from "@/utilities/fetch.js";
|
||||
import isCidr from "is-cidr";
|
||||
import {GetLocale} from "@/utilities/locale.js";
|
||||
import {fromString} from "css-color-converter";
|
||||
|
||||
export const WireguardConfigurationsStore = defineStore('WireguardConfigurationsStore', {
|
||||
state: () => ({
|
||||
@@ -99,7 +100,16 @@ export const WireguardConfigurationsStore = defineStore('WireguardConfigurations
|
||||
this.ConfigurationLoaded = true
|
||||
});
|
||||
},
|
||||
|
||||
colorText(color){
|
||||
if (color){
|
||||
const cssColor = fromString(color)
|
||||
if (cssColor) {
|
||||
const rgb = cssColor.toRgbaArray()
|
||||
return +((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 255000).toFixed(2) > 0.5 ? "#000":"#fff"
|
||||
}
|
||||
}
|
||||
return "#ffffff"
|
||||
},
|
||||
dotNotation(object, dotNotation){
|
||||
let result = dotNotation.split('.').reduce((o, key) => o && o[key], object)
|
||||
if (typeof result === "string"){
|
||||
|
Reference in New Issue
Block a user