@ -15,58 +15,64 @@
[v-cloak] {
display: none;
}
.line-chart .apexcharts-svg{
.line-chart .apexcharts-svg {
transform: translateY(3px);
}
< / style >
< body class = "bg-gray-50 dark:bg-neutral-800" >
< div id = "app" >
< div v-cloak class = "container mx-auto max-w-3 xl px-3 md:px-0 mt-4 xs:mt-6" >
< div v-cloak class = "container mx-auto max-w-5 xl px-3 md:px-0 mt-4 xs:mt-6" >
< div v-if = "authenticated === true" >
< div class = "flex flex-col-reverse xxs:flex-row flex-auto items-center items- end gap-3" >
< div class = "flex flex-col-reverse xxs:flex-row flex-auto items-end gap-3" >
< h1 class = "text-4xl dark:text-neutral-200 font-medium flex-grow self-start mb-4" >
< img src = "./img/logo.png" width = "32" class = "inline align-middle dark:bg mr-2" / > < span class = "align-middle" > WireGuard< / span >
< img src = "./img/logo.png" width = "32" class = "inline align-middle dark:bg mr-2" / > < span
class="align-middle">WireGuard< / span >
< / h1 >
< div class = "flex items-center grow-0 gap-3 items-end self-end xxs:self-center" >
< div class = "flex grow-0 gap-3 items-end self-end xxs:self-center" >
<!-- Dark / light theme -->
< button @ click = "toggleTheme"
class="flex items-center justify-center w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 transition" :title="$t(`theme.${uiTheme}`)">
< svg v-if = "uiTheme === 'light'" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24" stroke-width = "1.5" stroke = "currentColor"
class="w-5 h-5">
class="flex items-center justify-center w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 transition"
:title="$t(`theme.${uiTheme}`)">
< svg v-if = "uiTheme === 'light'" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
< path stroke-linecap = "round" stroke-linejoin = "round"
d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"/>
< / svg >
< svg v-else-if = "uiTheme === 'dark'" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24" stroke-width = "1.5" stroke = "currentColor"
class="w-5 h-5 text-neutral-400">
< svg v-else-if = "uiTheme === 'dark'" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-5 h-5 text-neutral-400">
< path stroke-linecap = "round" stroke-linejoin = "round"
d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z"/>
< / svg >
< svg v-else xmlns = "http://www.w3.org/2000/svg" fill = "currentColor" viewBox = "0 0 24 24"
< svg v-else xmlns = "http://www.w3.org/2000/svg" fill = "currentColor" viewBox = "0 0 24 24"
class="w-5 h-5 fill-gray-600 dark:fill-neutral-400">
< path
d="M12,2.2c-5.4,0-9.8,4.4-9.8,9.8s4.4,9.8,9.8,9.8s9.8-4.4,9.8-9.8S17.4,2.2,12,2.2z M3.8,12c0-4.5,3.7-8.2,8.2-8.2v16.5C7.5,20.2,3.8,16.5,3.8,12z" />
d="M12,2.2c-5.4,0-9.8,4.4-9.8,9.8s4.4,9.8,9.8,9.8s9.8-4.4,9.8-9.8S17.4,2.2,12,2.2z M3.8,12c0-4.5,3.7-8.2,8.2-8.2v16.5C7.5,20.2,3.8,16.5,3.8,12z"/>
< / svg >
< path stroke-linecap = "round" stroke-linejoin = "round"
d="M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0V12a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 12V5.25" />
< path stroke-linecap = "round" stroke-linejoin = "round"
d="M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0V12a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 12V5.25"/>
< / svg >
< / button >
<!-- Show / hide charts -->
< label v-if = "uiChartType > 0" class = "inline-flex items-center justify-center cursor-pointer w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 whitespace-nowrap transition group" :title = "$t('toggleCharts')" >
< label v-if = "uiChartType > 0"
class="inline-flex items-center justify-center cursor-pointer w-8 h-8 rounded-full bg-gray-200 hover:bg-gray-300 dark:bg-neutral-700 dark:hover:bg-neutral-600 whitespace-nowrap transition group"
:title="$t('toggleCharts')">
< input type = "checkbox" value = "" class = "sr-only peer" v-model = "uiShowCharts" @ change = "toggleCharts" >
< svg xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24" stroke-width = "1.5" fill = "currentColor"
< svg xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24" stroke-width = "1.5"
fill="currentColor"
class="w-5 h-5 peer fill-gray-400 peer-checked:fill-gray-600 dark:fill-neutral-600 peer-checked:dark:fill-neutral-400 group-hover:dark:fill-neutral-500 transition">
< path
d="M18.375 2.25c-1.035 0-1.875.84-1.875 1.875v15.75c0 1.035.84 1.875 1.875 1.875h.75c1.035 0 1.875-.84 1.875-1.875V4.125c0-1.036-.84-1.875-1.875-1.875h-.75ZM9.75 8.625c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v11.25c0 1.035-.84 1.875-1.875 1.875h-.75a1.875 1.875 0 0 1-1.875-1.875V8.625ZM3 13.125c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v6.75c0 1.035-.84 1.875-1.875 1.875h-.75A1.875 1.875 0 0 1 3 19.875v-6.75Z" />
< path
d="M18.375 2.25c-1.035 0-1.875.84-1.875 1.875v15.75c0 1.035.84 1.875 1.875 1.875h.75c1.035 0 1.875-.84 1.875-1.875V4.125c0-1.036-.84-1.875-1.875-1.875h-.75ZM9.75 8.625c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v11.25c0 1.035-.84 1.875-1.875 1.875h-.75a1.875 1.875 0 0 1-1.875-1.875V8.625ZM3 13.125c0-1.036.84-1.875 1.875-1.875h.75c1.036 0 1.875.84 1.875 1.875v6.75c0 1.035-.84 1.875-1.875 1.875h-.75A1.875 1.875 0 0 1 3 19.875v-6.75Z"/>
< / svg >
< / label >
< span v-if = "requiresPassword"
class="text-sm text-gray-400 dark:text-neutral-400 cursor-pointer hover:underline"
@click="logout">
class="text-sm text-gray-400 dark:text-neutral-400 cursor-pointer hover:underline" @click="logout">
{{$t("logout")}}
< svg class = "h-3 inline" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" >
< svg class = "h-3 inline" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
< / svg >
< / span >
< / div >
@ -88,299 +94,337 @@
< / div >
< / div >
< div class = "shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden" >
< div class = "flex flex-row flex-auto items-center p-3 px-5 border-b-2 border-gray-100 dark:border-neutral-600" >
< div class = "flex-grow" >
< p class = "text-2xl font-medium dark:text-neutral-200" > {{$t("clients")}}< / p >
< / div >
< div class = "flex md:block md:flex-shrink-0" >
<!-- Restore configuration -->
< label for = "inputRC" :title = "$t('titleRestoreConfig')"
class="hover:cursor-pointer hover:bg-red-800 hover:border-red-800 hover:text-white text-gray-700 dark:text-neutral-200 max-md:border-r-0 border-2 border-gray-100 dark:border-neutral-600 py-2 px-4 rounded-l-full md:rounded inline-flex items-center transition">
< svg inline class = "w-4 md:mr-2" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24" stroke-width = "1.5" stroke = "currentColor" >
< path stroke-linecap = "round" stroke-linejoin = "round" d = "M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" > < / path >
< / svg >
< span class = "max-md:hidden text-sm" > {{$t("restore")}}< / span >
< input id = "inputRC" type = "file" name = "configurationfile" accept = "text/*,.json" @ change = "restoreConfig" class = "hidden" / >
< / label >
<!-- Backup configuration -->
< a href = "./api/wireguard/backup" :title = "$t('titleBackupConfig')"
class="hover:bg-red-800 hover:border-red-800 hover:text-white text-gray-700 dark:text-neutral-200 max-md:border-x-0 border-2 border-gray-100 dark:border-neutral-600 py-2 px-4 md:rounded inline-flex items-center transition">
< svg inline class = "w-4 md:mr-2" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24" stroke-width = "1.5" stroke = "currentColor" class = "size-6" >
< path stroke-linecap = "round" stroke-linejoin = "round" d = "M5.25 14.25h13.5m-13.5 0a3 3 0 0 1-3-3m3 3a3 3 0 1 0 0 6h13.5a3 3 0 1 0 0-6m-16.5-3a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3m-19.5 0a4.5 4.5 0 0 1 .9-2.7L5.737 5.1a3.375 3.375 0 0 1 2.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 0 1 .9 2.7m0 0a3 3 0 0 1-3 3m0 3h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Zm-3 6h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Z" > < / path >
< / svg >
< span class = "max-md:hidden text-sm" > {{$t("backup")}}< / span >
< / a >
<!-- New client -->
< button @ click = "clientCreate = true; clientCreateName = '';"
class="hover:bg-red-800 hover:border-red-800 hover:text-white text-gray-700 dark:text-neutral-200 max-md:border-l-0 border-2 border-gray-100 dark:border-neutral-600 py-2 px-4 rounded-r-full md:rounded inline-flex items-center transition">
< svg class = "w-4 md:mr-2" inline xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
< / svg >
< span class = "max-md:hidden text-sm" > {{$t("new")}}< / span >
< / button >
< div class = "flex max-md:flex-col max-md:gap-y-8 md:gap-x-8" >
<!-- Navigation -->
< nav class = "shadow-md rounded-lg bg-white dark:bg-neutral-700 text-white p-4" >
< div class = "container mx-auto flex justify-between items-center" >
< div class = "flex flex-col" >
< button @ click = "showPage = 'home'"
class="flex items-center rounded hover:bg-red-800 transition-colors px-3 py-2 text-lg">
{{$t('navHome')}}
< / button >
< button @ click = "showPage = 'dashboard'"
class="flex items-center rounded hover:bg-red-800 transition-colors px-3 py-2 text-lg">
{{$t('navDashboard')}}
< / button >
< / div >
< / div >
< / div >
< div >
<!-- Client -->
< div v-if = "clients && clients.length > 0" v-for = "client in clients" :key = "client.id"
class="relative overflow-hidden border-b last:border-b-0 border-gray-100 dark:border-neutral-600 border-solid">
< / nav >
<!-- Chart -->
< div v-if = "uiChartType" :class = "`absolute z-0 bottom-0 left-0 right-0 h-6 ${uiChartType === 1 && 'line-chart'}`" >
< apexchart width = "100%" height = "100%" :options = "chartOptionsTX" :series = "client.transferTxSeries" >
< / apexchart >
<!-- Clients (main) -->
< div v-if = "showPage === 'home'"
class="w-full shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden">
< div
class="flex flex-row flex-auto items-center p-3 px-5 border-b-2 border-gray-100 dark:border-neutral-600">
< div class = "flex-grow" >
< p class = "text-2xl font-medium dark:text-neutral-200" > {{$t("clients")}}< / p >
< / div >
< div v-if = "uiChartType" :class = "`absolute z-0 top-0 left-0 right-0 h-6 ${uiChartType === 1 && 'line-chart'}`" >
< apexchart width = "100%" height = "100%" :options = "chartOptionsRX" :series = "client.transferRxSeries"
style="transform: scaleY(-1);">
< / apexchart >
< div class = "flex md:block md:flex-shrink-0" >
<!-- Restore configuration -->
< label for = "inputRC" :title = "$t('titleRestoreConfig')"
class="hover:cursor-pointer hover:bg-red-800 hover:border-red-800 hover:text-white text-gray-700 dark:text-neutral-200 max-md:border-r-0 border-2 border-gray-100 dark:border-neutral-600 py-2 px-4 rounded-l-full md:rounded inline-flex items-center transition">
< svg inline class = "w-4 md:mr-2" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke-width="1.5" stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99">
< / path >
< / svg >
< span class = "max-md:hidden text-sm" > {{$t("restore")}}< / span >
< input id = "inputRC" type = "file" name = "configurationfile" accept = "text/*,.json" @ change = "restoreConfig"
class="hidden"/>
< / label >
<!-- Backup configuration -->
< a href = "./api/wireguard/backup" :title = "$t('titleBackupConfig')"
class="hover:bg-red-800 hover:border-red-800 hover:text-white text-gray-700 dark:text-neutral-200 max-md:border-x-0 border-2 border-gray-100 dark:border-neutral-600 py-2 px-4 md:rounded inline-flex items-center transition">
< svg inline class = "w-4 md:mr-2" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="size-6">
< path stroke-linecap = "round" stroke-linejoin = "round"
d="M5.25 14.25h13.5m-13.5 0a3 3 0 0 1-3-3m3 3a3 3 0 1 0 0 6h13.5a3 3 0 1 0 0-6m-16.5-3a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3m-19.5 0a4.5 4.5 0 0 1 .9-2.7L5.737 5.1a3.375 3.375 0 0 1 2.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 0 1 .9 2.7m0 0a3 3 0 0 1-3 3m0 3h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Zm-3 6h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Z">
< / path >
< / svg >
< span class = "max-md:hidden text-sm" > {{$t("backup")}}< / span >
< / a >
<!-- New client -->
< button @ click = "clientCreate = true; clientCreateName = '';"
class="hover:bg-red-800 hover:border-red-800 hover:text-white text-gray-700 dark:text-neutral-200 max-md:border-l-0 border-2 border-gray-100 dark:border-neutral-600 py-2 px-4 rounded-r-full md:rounded inline-flex items-center transition">
< svg class = "w-4 md:mr-2" inline xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
< / svg >
< span class = "max-md:hidden text-sm" > {{$t("new")}}< / span >
< / button >
< / div >
< / div >
< div class = "relative py-3 md:py-5 px-3 z-10 flex flex-col sm:flex-row justify-between gap-3" >
< div class = "flex gap-3 md:gap-4 w-full items-center " >
< div >
<!-- Client -->
< div v-if = "clients && clients.length > 0" v-for = "client in clients" :key = "client.id"
class="relative overflow-hidden border-b last:border-b-0 border-gray-100 dark:border-neutral-600 border-solid">
<!-- Avatar -->
< div class = "h-10 w-10 mt-2 self-start rounded-full bg-gray-50 relative" >
< svg class = "w-6 m-2 text-gray-300" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 20 20"
fill="currentColor">
< path fill-rule = "evenodd" d = "M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd" />
< / svg >
< img v-if = "client.avatar" :src = "client.avatar" class = "w-10 rounded-full absolute top-0 left-0" / >
<!-- Chart -->
< div v-if = "uiChartType"
:class="`absolute z-0 bottom-0 left-0 right-0 h-6 ${uiChartType === 1 & & 'line-chart'}`">
< apexchart width = "100%" height = "100%" :options = "chartOptionsTX" :series = "client.transferTxSeries" >
< / apexchart >
< / div >
< div v-if = "uiChartType"
:class="`absolute z-0 top-0 left-0 right-0 h-6 ${uiChartType === 1 & & 'line-chart'}`">
< apexchart width = "100%" height = "100%" :options = "chartOptionsRX" :series = "client.transferRxSeries"
style="transform: scaleY(-1);">
< / apexchart >
< / div >
< div class = "relative py-3 md:py-5 px-3 z-10 flex flex-col sm:flex-row justify-between gap-3" >
< div class = "flex gap-3 md:gap-4 w-full items-center " >
<!-- Avatar -->
< div class = "h-10 w-10 mt-2 self-start rounded-full bg-gray-50 relative" >
< svg class = "w-6 m-2 text-gray-300" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 20 20"
fill="currentColor">
< path fill-rule = "evenodd" d = "M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"/>
< / svg >
< img v-if = "client.avatar" :src = "client.avatar" class = "w-10 rounded-full absolute top-0 left-0" / >
< div
v-if="client.latestHandshakeAt & & ((new Date() - new Date(client.latestHandshakeAt) < 1000 * 60 * 10 ) ) " >
< div
class="animate-ping w-4 h-4 p-1 bg-red-100 dark:bg-red-100 rounded-full absolute -bottom-1 -right-1">
v-if="client.latestHandshakeAt & & ((new Date() - new Date(client.latestHandshakeAt) < 1000 * 60 * 10 ) ) " >
< div
class="animate-ping w-4 h-4 p-1 bg-red-100 dark:bg-red-100 rounded-full absolute -bottom-1 -right-1">
< / div >
< div class = "w-2 h-2 bg-red-800 dark:bg-red-600 rounded-full absolute bottom-0 right-0" > < / div >
< / div >
< div class = "w-2 h-2 bg-red-800 dark:bg-red-600 rounded-full absolute bottom-0 right-0" > < / div >
< / div >
< / div >
<!-- Name & Info -->
< div class = "flex flex-col xxs:flex-row w-full gap-2" >
<!-- Name -->
< div class = "flex flex-col flex-grow gap-1" >
< div class = "text-gray-700 dark:text-neutral-200 group text-sm md:text-base"
:title="$t('createdOn') + dateTime(new Date(client.createdAt))">
<!-- Show -->
< input v-show = "clientEditNameId === client.id" v-model = "clientEditName"
v-on:keyup.enter="updateClientName(client, clientEditName); clientEditName = null; clientEditNameId = null;"
v-on:keyup.escape="clientEditName = null; clientEditNameId = null;"
:ref="'client-' + client.id + '-name'"
class="rounded px-1 border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 dark:placeholder:text-neutral-500 outline-none w-30" />
< span v-show = "clientEditNameId !== client.id"
class="border-t-2 border-b-2 border-transparent">{{client.name}}< / span >
<!-- Edit -->
< span v-show = "clientEditNameId !== client.id"
@click="clientEditName = client.name; clientEditNameId = client.id; setTimeout(() => $refs['client-' + client.id + '-name'][0].select(), 1);"
class="cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity">
< svg xmlns = "http://www.w3.org/2000/svg"
class="h-4 w-4 inline align-middle opacity-25 hover:opacity-100" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
< / svg >
< / span >
< / div >
<!-- Address -->
< div class = " block md:inline-block pb-1 md:pb-0 text-gray-500 dark:text-neutral-400 text-xs" >
< span class = "group" >
<!-- Name & Info -->
< div class = "flex flex-col xxs:flex-row w-full gap-2" >
<!-- Name -->
< div class = "flex flex-col flex-grow gap-1" >
< div class = "text-gray-700 dark:text-neutral-200 group text-sm md:text-base"
:title="$t('createdOn') + dateTime(new Date(client.createdAt))">
<!-- Show -->
< input v-show = "clientEditAddress Id === client.id" v-model = "clientEditAddress "
v-on:keyup.enter="updateClientAddress(client, clientEditAddress); clientEditAddress = null; clientEditAddress Id = null;"
v-on:keyup.escape="clientEditAddress = null; clientEditAddress Id = null;"
:ref="'client-' + client.id + '-address '"
class="rounded border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 outline-none w-20 text-black dark:text-neutral-300 dark:placeholder:text-neutral-500" />
< span v-show = "clientEditAddress Id !== client.id"
class="inline-block ">{{client.address }}< / span >
< input v-show = "clientEditNameId === client.id" v-model = "clientEditName"
v-on:keyup.enter="updateClientName(client, clientEditName); clientEditName = null; clientEditNameId = null;"
v-on:keyup.escape="clientEditName = null; clientEditNameId = null;"
:ref="'client-' + client.id + '-name'"
class="rounded px-1 border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 dark:placeholder:text-neutral-500 outline-none w-30"/>
< span v-show = "clientEditNameId !== client.id"
class="border-t-2 border-b-2 border-transparent">{{client.name}}< / span >
<!-- Edit -->
< span v-show = "clientEditAddress Id !== client.id"
@click="clientEditAddress = client.address; clientEditAddressId = client.id; setTimeout(() => $refs['client-' + client.id + '-address '][0].select(), 1);"
< span v-show = "clientEditNameId !== client.id"
@click="clientEditName = client.name; clientEditNameId = client.id; setTimeout(() => $refs['client-' + client.id + '-name'][0].select(), 1);"
class="cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity">
< svg xmlns = "http://www.w3.org/2000/svg"
class="h-4 w-4 inline align-middle opacity-25 hover:opacity-100" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
< / svg >
< / span >
< / div >
<!-- Address -->
< div class = " block md:inline-block pb-1 md:pb-0 text-gray-500 dark:text-neutral-400 text-xs" >
< span class = "group" >
<!-- Show -->
< input v-show = "clientEditAddressId === client.id" v-model = "clientEditAddress"
v-on:keyup.enter="updateClientAddress(client, clientEditAddress); clientEditAddress = null; clientEditAddressId = null;"
v-on:keyup.escape="clientEditAddress = null; clientEditAddressId = null;"
:ref="'client-' + client.id + '-address'"
class="rounded border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 outline-none w-20 text-black dark:text-neutral-300 dark:placeholder:text-neutral-500"/>
< span v-show = "clientEditAddressId !== client.id"
class="inline-block ">{{client.address}}< / span >
<!-- Edit -->
< span v-show = "clientEditAddressId !== client.id"
@click="clientEditAddress = client.address; clientEditAddressId = client.id; setTimeout(() => $refs['client-' + client.id + '-address'][0].select(), 1);"
class="cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity">
< svg xmlns = "http://www.w3.org/2000/svg"
class="h-4 w-4 inline align-middle opacity-25 hover:opacity-100" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
< / svg >
< / span >
< / span >
<!-- Inline Transfer TX -->
< span v-if = "!uiTrafficStats && client.transferTx" class = "whitespace-nowrap"
:title="$t('totalDownload') + bytes(client.transferTx)">
·
< svg class = "align-middle h-3 inline" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 20 20"
fill="currentColor">
< path fill-rule = "evenodd"
d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l4.293-4.293a1 1 0 011.414 0z"
clip-rule="evenodd"/>
< / svg >
{{client.transferTxCurrent | bytes}}/s
< / span >
<!-- Inline Transfer RX -->
< span v-if = "!uiTrafficStats && client.transferRx" class = "whitespace-nowrap"
:title="$t('totalUpload') + bytes(client.transferRx)">
·
< svg class = "align-middle h-3 inline" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 20 20"
fill="currentColor">
< path fill-rule = "evenodd"
d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z"
clip-rule="evenodd"/>
< / svg >
{{client.transferRxCurrent | bytes}}/s
< / span >
<!-- Last seen -->
< span class = "text-gray-400 dark:text-neutral-500 whitespace-nowrap"
v-if="client.latestHandshakeAt"
:title="$t('lastSeen') + dateTime(new Date(client.latestHandshakeAt))">
{{!uiTrafficStats ? " · " : ""}}{{new Date(client.latestHandshakeAt) | timeago}}
< / span >
< / span >
<!-- Inline Transfer TX -->
< span v-if = "!uiTrafficStats && client.transferTx" class = "whitespace-nowrap" :title = "$t('totalDownload') + bytes(client.transferTx)" >
·
< svg class = "align-middle h-3 inline" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 20 20" fill = "currentColor" >
< path fill-rule = "evenodd"
d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l4.293-4.293a1 1 0 011.414 0z"
clip-rule="evenodd" />
< / svg >
{{client.transferTxCurrent | bytes}}/s
< / span >
<!-- Inline Transfer RX -->
< span v-if = "!uiTrafficStats && client.transferRx" class = "whitespace-nowrap" :title = "$t('totalUpload') + bytes(client.transferRx)" >
·
< svg class = "align-middle h-3 inline" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 20 20" fill = "currentColor" >
< path fill-rule = "evenodd"
d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z"
clip-rule="evenodd" />
< / svg >
{{client.transferRxCurrent | bytes}}/s
< / span >
<!-- Last seen -->
< span class = "text-gray-400 dark:text-neutral-500 whitespace-nowrap" v-if = "client.latestHandshakeAt"
:title="$t('lastSeen') + dateTime(new Date(client.latestHandshakeAt))">
{{!uiTrafficStats ? " · " : ""}}{{new Date(client.latestHandshakeAt) | timeago}}
< / span >
< / div >
< / div >
< / div >
<!-- Info -->
< div v-if = "uiTrafficStats"
class="flex gap-2 items-center shrink-0 text-gray-400 dark:text-neutral-400 text-xs mt-px justify-end">
<!-- Transfer TX -->
< div class = "min-w-20 md:min-w-24" v-if = "client.transferTx" >
< span class = "flex gap-1" :title = "$t('totalDownload') + bytes(client.transferTx)" >
< svg class = "align-middle h-3 inline mt-0.5" xmlns = "http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
< path fill-rule = "evenodd"
d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l4.293-4.293a1 1 0 011.414 0z"
clip-rule="evenodd" />
< / svg >
< div >
< span class = "text-gray-700 dark:text-neutral-200" > {{client.transferTxCurrent |
bytes}}/s< / span >
<!-- Total TX -->
< br > < span class = "font-regular" style = "font-size:0.85em" > {{bytes(client.transferTx)}}< / span >
< / div >
< / span >
<!-- Info -->
< div v-if = "uiTrafficStats"
class="flex gap-2 items-center shrink-0 text-gray-400 dark:text-neutral-400 text-xs mt-px justify-end">
<!-- Transfer TX -->
< div class = "min-w-20 md:min-w-24" v-if = "client.transferTx" >
< span class = "flex gap-1" :title = "$t('totalDownload') + bytes(client.transferTx)" >
< svg class = "align-middle h-3 inline mt-0.5" xmlns = "http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
< path fill-rule = "evenodd"
d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l4.293-4.293a1 1 0 011.414 0z"
clip-rule="evenodd"/>
< / svg >
< div >
< span class = "text-gray-700 dark:text-neutral-200" > {{client.transferTxCurrent |
bytes}}/s< / span >
<!-- Total TX -->
< br > < span class = "font-regular"
style="font-size:0.85em">{{bytes(client.transferTx)}}< / span >
< / div >
< / span >
< / div >
< / div >
<!-- Transfer RX -->
< div class = "min-w-20 md:min-w-24" v-if = "client.transferRx" >
< span class = "flex gap-1" :title = "$t('totalUpload') + bytes(client.transferRx)" >
<!-- Transfer RX -->
< div class = "min-w-20 md:min-w-24" v-if = "client.transferRx" >
< span class = "flex gap-1" :title = "$t('totalUpload') + bytes(client.transferRx)" >
< svg class = "align-middle h-3 inline mt-0.5" xmlns = "http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
< path fill-rule = "evenodd"
d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z"
clip-rule="evenodd" />
< / svg >
< div >
< span class = "text-gray-700 dark:text-neutral-200" > {{client.transferRxCurrent |
bytes}}/s< / span >
<!-- Total RX -->
< br > < span class = "font-regular" style = "font-size:0.85em" > {{bytes(client.transferRx)}}< / span >
< / div >
< / span >
< / div >
< svg class = "align-middle h-3 inline mt-0.5" xmlns = "http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
< path fill-rule = "evenodd"
d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z"
clip-rule="evenodd"/>
< / svg >
< div >
< span class = "text-gray-700 dark:text-neutral-200" > {{client.transferRxCurrent |
bytes}}/s< / span >
<!-- Total RX -->
< br > < span class = "font-regular"
style="font-size:0.85em">{{bytes(client.transferRx)}}< / span >
< / div >
< / span >
< / div >
< / div >
< / div >
<!-- </div> --> <!-- <div class="flex flex - grow items - center"> -->
< / div >
<!-- </div> --> <!-- <div class="flex flex - grow items - center"> -->
< / div >
< div class = "flex items-center justify-end" >
< div class = "text-gray-400 dark:text-neutral-400 flex gap-1 items-center justify-between" >
< div class = "flex items-center justify-end" >
< div class = "text-gray-400 dark:text-neutral-400 flex gap-1 items-center justify-between" >
<!-- Enable/Disable -->
< div @ click = "disableClient(client)" v-if = "client.enabled === true" :title = "$t('disableClient')"
class="inline-block align-middle rounded-full w-10 h-6 mr-1 bg-red-800 cursor-pointer hover:bg-red-700 transition-all">
< div class = "rounded-full w-4 h-4 m-1 ml-5 bg-white" > < / div >
< / div >
<!-- Enable/Disable -->
< div @ click = "disableClient(client)" v-if = "client.enabled === true" :title = "$t('disableClient')"
class="inline-block align-middle rounded-full w-10 h-6 mr-1 bg-red-800 cursor-pointer hover:bg-red-700 transition-all">
< div class = "rounded-full w-4 h-4 m-1 ml-5 bg-white" > < / div >
< / div >
< div @ click = "enableClient(client)" v-if = "client.enabled === false" :title = "$t('enableClient')"
class="inline-block align-middle rounded-full w-10 h-6 mr-1 bg-gray-200 dark:bg-neutral-400 cursor-pointer hover:bg-gray-300 dark:hover:bg-neutral-500 transition-all">
< div @ click = "enableClient(client)" v-if = "client.enabled === false" :title = "$t('enableClient')"
class="inline-block align-middle rounded-full w-10 h-6 mr-1 bg-gray-200 dark:bg-neutral-400 cursor-pointer hover:bg-gray-300 dark:hover:bg-neutral-500 transition-all">
< div class = "rounded-full w-4 h-4 m-1 bg-white" > < / div >
< / div >
< div class = "rounded-full w-4 h-4 m-1 bg-white" > < / div >
< / div >
<!-- Show QR -->
<!-- Show QR -->
< button :disabled = "!client.downloadableConfig"
class="align-middle bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 p-2 rounded transition"
:class="{
< button :disabled = "!client.downloadableConfig"
class="align-middle bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 p-2 rounded transition"
:class="{
'hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white': client.downloadableConfig,
'is-disabled': !client.downloadableConfig
}"
:title="!client.downloadableConfig ? $t('noPrivKey') : $t('showQR')"
@click="qrcode = `./api/wireguard/client/${client.id}/qrcode.svg`">
< svg class = "w-5" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z" />
< / svg >
< / button >
<!-- Download Config -->
< a :disabled = "!client.downloadableConfig"
:href="'./api/wireguard/client/' + client.id + '/configuration'"
:download="client.downloadableConfig ? 'configuration' : null"
class="align-middle inline-block bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 p-2 rounded transition"
:class="{
}" :title="!client.downloadableConfig ? $t('noPrivKey') : $t('showQR')"
@click="qrcode = `./api/wireguard/client/${client.id}/qrcode.svg`">
< svg class = "w-5" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z"/>
< / svg >
< / button >
<!-- Download Config -->
< a :disabled = "!client.downloadableConfig"
:href="'./api/wireguard/client/' + client.id + '/configuration'"
:download="client.downloadableConfig ? 'configuration' : null"
class="align-middle inline-block bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 p-2 rounded transition"
:class="{
'hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white': client.downloadableConfig,
'is-disabled': !client.downloadableConfig
}"
:title="!client.downloadableConfig ? $t('noPrivKey') : $t('downloadConfig')"
@click="if(!client.downloadableConfig) { $event.preventDefault(); }">
< svg class = "w-5" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
< / svg >
< / a >
<!-- Delete -->
< button
class="align-middle bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white p-2 rounded transition"
:title="$t('deleteClient')" @click="clientDelete = client">
< svg class = "w-5" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 20 20" fill = "currentColor" >
< path fill-rule = "evenodd"
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
clip-rule="evenodd" />
< / svg >
< / button >
}" :title="!client.downloadableConfig ? $t('noPrivKey') : $t('downloadConfig')"
@click="if(!client.downloadableConfig) { $event.preventDefault(); }">
< svg class = "w-5" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
< / svg >
< / a >
<!-- Delete -->
< button
class="align-middle bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white p-2 rounded transition"
:title="$t('deleteClient')" @click="clientDelete = client">
< svg class = "w-5" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 20 20" fill = "currentColor" >
< path fill-rule = "evenodd"
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
clip-rule="evenodd"/>
< / svg >
< / button >
< / div >
< / div >
< / div >
< / div >
< / div >
< div v-if = "clients && clients.length === 0" >
< p class = "text-center m-10 text-gray-400 dark:text-neutral-400 text-sm" >
{{$t("noClients")}}< br / > < br / >
< button @ click = "clientCreate = true; clientCreateName = '';"
class="bg-red-800 hover:bg-red-700 text-white border-2 border-none py-2 px-4 rounded inline-flex items-center transition">
< svg class = "w-4 mr-2" inline xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
< / svg >
< span class = "text-sm" > {{$t("newClient")}}< / span >
< / button >
< / p >
< / div >
< div v-if = "clients === null" class = "text-gray-200 dark:text-red-300 p-5" >
< svg class = "w-5 animate-spin mx-auto" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24"
fill="currentColor">
< circle class = "opacity-25" cx = "12" cy = "12" r = "10" stroke = "currentColor" stroke-width = "4" > < / circle >
< path class = "opacity-75" fill = "currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
< / path >
< / svg >
< div v-if = "clients && clients.length === 0" >
< p class = "text-center m-10 text-gray-400 dark:text-neutral-400 text-sm" >
{{$t("noClients")}}< br / > < br / >
< button @ click = "clientCreate = true; clientCreateName = '';"
class="bg-red-800 hover:bg-red-700 text-white border-2 border-none py-2 px-4 rounded inline-flex items-center transition">
< svg class = "w-4 mr-2" inline xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
< / svg >
< span class = "text-sm" > {{$t("newClient")}}< / span >
< / button >
< / p >
< / div >
< div v-if = "clients === null" class = "text-gray-200 dark:text-red-300 p-5" >
< svg class = "w-5 animate-spin mx-auto" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 24 24"
fill="currentColor">
< circle class = "opacity-25" cx = "12" cy = "12" r = "10" stroke = "currentColor" stroke-width = "4" > < / circle >
< path class = "opacity-75" fill = "currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
< / path >
< / svg >
< / div >
< / div >
< / div >
<!-- Dashboard -->
< dashboard v-if = "showPage === 'dashboard'" > < / dashboard >
< / div >
<!-- QR Code -->
@ -391,10 +435,10 @@
class="absolute right-4 top-4 text-gray-600 dark:text-neutral-500 hover:text-gray-800 dark:hover:text-neutral-700">
< svg class = "w-8" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M6 18L18 6M6 6l12 12" / >
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2" d = "M6 18L18 6M6 6l12 12" / >
< / svg >
< / button >
< img :src = "qrcode" / >
< img :src = "qrcode" / >
< / div >
< / div >
< / div >
@ -435,10 +479,10 @@
< div class = "sm:flex sm:items-start" >
< div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-800 sm:mx-0 sm:h-10 sm:w-10">
< svg class = "h-6 w-6 text-white" inline xmlns = "http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
< svg class = "h-6 w-6 text-white" inline xmlns = "http://www.w3.org/2000/svg" fill = "none"
viewBox="0 0 24 24" stroke="currentColor">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
d="M12 6v6m0 0v6m0-6h6m-6 0H6"/>
< / svg >
< / div >
< div class = "flex-grow mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left" >
@ -449,7 +493,7 @@
< p class = "text-sm text-gray-500" >
< input
class="rounded p-2 border-2 dark:bg-neutral-700 dark:text-neutral-200 border-gray-100 dark:border-neutral-600 focus:border-gray-200 focus:dark:border-neutral-500 dark:placeholder:text-neutral-400 outline-none w-full"
type="text" v-model.trim="clientCreateName" :placeholder="$t('name')" />
type="text" v-model.trim="clientCreateName" :placeholder="$t('name')"/>
< / p >
< / div >
< / div >
@ -513,7 +557,7 @@
< svg class = "h-6 w-6 text-red-600" xmlns = "http://www.w3.org/2000/svg" fill = "none" viewBox = "0 0 24 24"
stroke="currentColor" aria-hidden="true">
< path stroke-linecap = "round" stroke-linejoin = "round" stroke-width = "2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
< / svg >
< / div >
< div class = "mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left" >
@ -545,7 +589,7 @@
< div v-if = "authenticated === false" >
< h1 class = "text-4xl font-medium my-16 text-gray-700 dark:text-neutral-200 text-center" >
< img src = "./img/logo.png" width = "32" class = "inline align-middle dark:bg" / >
< img src = "./img/logo.png" width = "32" class = "inline align-middle dark:bg" / >
< span class = "align-middle" > WireGuard< / span >
< / h1 >
@ -553,14 +597,14 @@
class="shadow rounded-md bg-white dark:bg-neutral-700 mx-auto w-64 p-5 overflow-hidden mt-10">
<!-- Avatar -->
< div class = "h-20 w-20 mb-10 mt-5 mx-auto rounded-full bg-red-800 dark:bg-red-800 relative overflow-hidden" >
< svg class = "w-10 h-10 m-5 text-white dark:text-white" xmlns = "http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor">
< path fill-rule = "evenodd" d = "M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule = "evenodd" / >
< svg class = "w-10 h-10 m-5 text-white dark:text-white" xmlns = "http://www.w3.org/2000/svg" viewBox = "0 0 20 20"
fill="currentColor">
< path fill-rule = "evenodd" d = "M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule = "evenodd" / >
< / svg >
< / div >
< input type = "password" name = "password" :placeholder = "$t('password')" v-model = "password"
class="px-3 py-2 text-sm dark:bg-neutral-700 text-gray-500 dark:text-gray-500 mb-5 border-2 border-gray-100 dark:border-neutral-800 rounded-lg w-full focus:border-red-800 dark:focus:border-red-800 dark:placeholder:text-neutral-400 outline-none" />
class="px-3 py-2 text-sm dark:bg-neutral-700 text-gray-500 dark:text-gray-500 mb-5 border-2 border-gray-100 dark:border-neutral-800 rounded-lg w-full focus:border-red-800 dark:focus:border-red-800 dark:placeholder:text-neutral-400 outline-none"/>
< button v-if = "authenticating"
class="bg-red-800 dark:bg-red-800 w-full rounded shadow py-2 text-sm text-white dark:text-white cursor-not-allowed">
@ -595,9 +639,10 @@
< / div >
< p v-cloak class = "text-center m-10 text-gray-300 dark:text-neutral-600 text-xs" > < a class = "hover:underline" target = "_blank"
href="https://github.com/wg-easy/wg-easy">WireGuard Easy< / a > © 2021-2024 by < a class = "hover:underline" target = "_blank"
href="https://emilenijssen.nl/?ref=wg-easy">Emile Nijssen< / a > is licensed under < a class = "hover:underline" target = "_blank"
< p v-cloak class = "text-center m-10 text-gray-300 dark:text-neutral-600 text-xs" > < a class = "hover:underline"
target="_blank" href="https://github.com/wg-easy/wg-easy">WireGuard Easy< / a > © 2021-2024 by < a
class="hover:underline" target="_blank" href="https://emilenijssen.nl/?ref=wg-easy">Emile Nijssen< / a > is
licensed under < a class = "hover:underline" target = "_blank"
href="http://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0< / a > · < a class = "hover:underline"
href="https://github.com/sponsors/WeeJeWel" target="_blank">{{$t("donate")}}< / a > < / p >
@ -609,6 +654,7 @@
< script src = "./js/vendor/vue-apexcharts.min.js" > < / script >
< script src = "./js/vendor/sha256.min.js" > < / script >
< script src = "./js/vendor/timeago.full.min.js" > < / script >
< script src = "./js/vendor/vue-dashboard.umd.min.js" > < / script >
< script src = "./js/api.js" > < / script >
< script src = "./js/i18n.js" > < / script >
< script src = "./js/app.js" > < / script >