mirror of https://github.com/wg-easy/wg-easy
22 changed files with 4805 additions and 870 deletions
@ -1 +0,0 @@ |
|||
** |
|||
@ -1,6 +0,0 @@ |
|||
{ |
|||
"trailingComma": "es5", |
|||
"tabWidth": 2, |
|||
"semi": true, |
|||
"singleQuote": true |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,2 @@ |
|||
[*] |
|||
end_of_line= lf |
|||
@ -0,0 +1,21 @@ |
|||
module.exports = { |
|||
env: { |
|||
browser: true, |
|||
node: true, |
|||
es2021: true, |
|||
}, |
|||
extends: ['eslint:recommended', 'plugin:vue/vue3-recommended', 'prettier'], |
|||
parser: 'vue-eslint-parser', |
|||
// parserOptions: { |
|||
// parser: '@typescript-eslint/parser', |
|||
// // sourceType: 'module', |
|||
// }, |
|||
plugins: ['vue', 'prettier'], |
|||
rules: { |
|||
'vue/no-v-html': 'off', |
|||
'vue/multi-word-component-names': 'off', |
|||
'prettier/prettier': ['error'], |
|||
'vue/no-undef-components': ['error'], |
|||
'vue/no-undef-properties': ['error'], |
|||
}, |
|||
}; |
|||
@ -0,0 +1,7 @@ |
|||
module.exports = { |
|||
trailingComma: 'es5', |
|||
tabWidth: 2, |
|||
semi: true, |
|||
singleQuote: true, |
|||
printWidth: 120, |
|||
}; |
|||
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,46 @@ |
|||
<template> |
|||
<div> |
|||
<h1 class="text-4xl font-medium my-16 text-gray-700 dark:text-neutral-200 text-center">WireGuard</h1> |
|||
|
|||
<form class="shadow rounded-md bg-white dark:bg-neutral-700 mx-auto w-64 p-5 overflow-hidden mt-10" @submit="login"> |
|||
<!-- 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"> |
|||
<IconAvatarDefault class="w-10 h-10 m-5 text-white dark:text-white" /> |
|||
</div> |
|||
|
|||
<input |
|||
v-model="password" |
|||
type="password" |
|||
name="password" |
|||
placeholder="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" |
|||
/> |
|||
|
|||
<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" |
|||
> |
|||
<LoadingSpinner /> |
|||
</button> |
|||
<input |
|||
v-if="!authenticating && password" |
|||
type="submit" |
|||
class="bg-red-800 dark:bg-red-800 w-full rounded shadow py-2 text-sm text-white dark:text-white hover:bg-red-700 dark:hover:bg-red-700 transition cursor-pointer" |
|||
value="Sign In" |
|||
/> |
|||
<input |
|||
v-if="!authenticating && !password" |
|||
type="submit" |
|||
class="bg-gray-200 dark:bg-neutral-800 w-full rounded shadow py-2 text-sm text-white dark:text-white cursor-not-allowed" |
|||
value="Sign In" |
|||
/> |
|||
</form> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import IconAvatarDefault from '@/components/icons/IconAvatarDefault.vue'; |
|||
import LoadingSpinner from './LoadingSpinner.vue'; |
|||
|
|||
|
|||
</script> |
|||
@ -0,0 +1,34 @@ |
|||
<template> |
|||
<div |
|||
v-if="client" |
|||
class="relative overflow-hidden border-b last:border-b-0 border-gray-100 dark:border-neutral-600 border-solid" |
|||
> |
|||
<!-- Chart --> |
|||
<ClientCharts :client="client" /> |
|||
|
|||
<div class="relative p-5 z-10 flex flex-col md:flex-row justify-between"> |
|||
<div class="flex items-center pb-2 md:pb-0"> |
|||
<ClientAvatar :client="client" /> |
|||
<ClientInfo :client="client" /> |
|||
</div> |
|||
|
|||
<div class="flex items-center justify-end"> |
|||
<ClientControls :client="client" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref } from 'vue'; |
|||
import ClientCharts from '@/components/ClientCharts.vue'; |
|||
import ClientInfo from '@/components/ClientInfo.vue'; |
|||
import ClientAvatar from '@/components/ClientAvatar.vue'; |
|||
import ClientControls from '@/components/ClientControls.vue'; |
|||
|
|||
|
|||
defineProps({ |
|||
client: {}, |
|||
}); |
|||
|
|||
</script> |
|||
@ -0,0 +1,34 @@ |
|||
<template> |
|||
<div v-if="client"> |
|||
<!-- TODO: Individual bars are too wide --> |
|||
<div class="absolute z-0 bottom-0 left-0 right-0" style="top: 60%"> |
|||
<VueApexCharts |
|||
width="100%" |
|||
height="100%" |
|||
type="bar" |
|||
:options="chartOptions" |
|||
:series="[{ name: 'Upload (TX)', data: client?.transferTxHistory, },]" |
|||
/> |
|||
</div> |
|||
<div class="absolute z-0 top-0 left-0 right-0" style="bottom: 60%"> |
|||
<VueApexCharts |
|||
width="100%" |
|||
height="100%" |
|||
type="bar" |
|||
:options="chartOptions" |
|||
:series="[{ name: 'Download (RX)', data: client?.transferRxHistory, },]" |
|||
style="transform: scaleY(-1)" |
|||
/> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import VueApexCharts from 'vue3-apexcharts'; |
|||
import chartOptions from '@/utils/chartOptions'; |
|||
|
|||
defineProps({ |
|||
client: {}, |
|||
}); |
|||
|
|||
</script> |
|||
@ -0,0 +1,73 @@ |
|||
<template> |
|||
<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="Disable Client" |
|||
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="Enable Client" |
|||
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> |
|||
|
|||
<!-- Show QR--> |
|||
<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="Show QR Code" |
|||
@click="qrcode = `./api/wireguard/client/${client.id}/qrcode.svg`" |
|||
> |
|||
<IconQRCode /> |
|||
</button> |
|||
|
|||
<!-- Download Config --> |
|||
<a |
|||
:href="'./api/wireguard/client/' + client.id + '/configuration'" |
|||
download |
|||
class="align-middle inline-block 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="Download Configuration" |
|||
> |
|||
<IconDownload /> |
|||
</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="Delete Client" |
|||
@click="clientDelete = client" |
|||
> |
|||
<IconDelete /> |
|||
</button> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import IconQRCode from '@/components/icons/IconQRCode.vue'; |
|||
import IconDownload from '@/components/icons/IconDownload.vue'; |
|||
import IconDelete from '@/components/icons/IconDelete.vue'; |
|||
|
|||
import API from '@/services/api'; |
|||
|
|||
const api = new API(); |
|||
|
|||
defineProps({ |
|||
client: {}, |
|||
}); |
|||
|
|||
function enableClient(client) { |
|||
api.enableClient({ clientId: client.id }).catch((err) => alert(err.message || err.toString())); |
|||
// .finally(() => refresh().catch(console.error)); |
|||
} |
|||
|
|||
function disableClient(client) { |
|||
api.disableClient({ clientId: client.id }).catch((err) => alert(err.message || err.toString())); |
|||
// .finally(() => refresh().catch(console.error)); |
|||
} |
|||
</script> |
|||
@ -0,0 +1,67 @@ |
|||
<template> |
|||
<div class="flex-grow"> |
|||
<!-- Name --> |
|||
<ClientName |
|||
:client="client" |
|||
@update-client-name="updateClientName" |
|||
/> |
|||
|
|||
<!-- Info --> |
|||
<div class="text-gray-400 dark:text-neutral-400 text-xs"> |
|||
<!-- Address --> |
|||
<ClientAddress :client="client" @update-address="updateClientAddress" /> |
|||
<!-- Transfer TX --> |
|||
<ClientTransfer |
|||
:transferData="client.transferTx" |
|||
:transferDataCurrent="client.transferTxCurrent" |
|||
:title="'Total Download: ' + bytes(client.transferTx)" |
|||
><IconDownArrow |
|||
/></ClientTransfer> |
|||
|
|||
<!-- Transfer RX --> |
|||
<ClientTransfer |
|||
:transferData="client.transferRx" |
|||
:transferDataCurrent="client.transferRxCurrent" |
|||
:title="'Total Upload: ' + bytes(client.transferRx)" |
|||
> |
|||
<IconUpArrow /> |
|||
</ClientTransfer> |
|||
|
|||
<!-- Last seen --> |
|||
<span |
|||
v-if="client.latestHandshakeAt" |
|||
:title="'Last seen on ' + dateTime(new Date(client.latestHandshakeAt))" |
|||
> |
|||
<!-- FIXME: add "timeago" --> |
|||
<!-- · {{ new Date(client.latestHandshakeAt) | timeago }} --> |
|||
· {{ timeago(client.latestHandshakeAt) }} |
|||
</span> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref } from 'vue'; |
|||
import ClientName from '@/components/ClientName.vue'; |
|||
import ClientAddress from '@/components/ClientAddress.vue'; |
|||
import IconUpArrow from '@/components/icons/IconUpArrow.vue'; |
|||
import IconDownArrow from '@/components/icons/IconDownArrow.vue'; |
|||
import ClientTransfer from '@/components/ClientTransfer.vue'; |
|||
|
|||
import { useDateTime } from '@/composables/useDateTime'; |
|||
import { useTimeAgo } from '@/composables/useTimeAgo'; |
|||
import { useBytes } from '@/composables/useBytes'; |
|||
import API from '@/services/api'; |
|||
|
|||
defineProps({ |
|||
client: {}, |
|||
}); |
|||
|
|||
const { dateTime } = useDateTime(); |
|||
const { timeago } = useTimeAgo(); |
|||
const { bytes } = useBytes(); |
|||
|
|||
const api = new API(); |
|||
|
|||
|
|||
</script> |
|||
@ -0,0 +1,12 @@ |
|||
<template> |
|||
<p v-cloak class="text-center m-10 text-gray-300 dark:text-neutral-600 text-xs"> |
|||
Made by |
|||
<a target="_blank" class="hover:underline" href="https://emilenijssen.nl/?ref=wg-easy">Emile Nijssen</a> |
|||
· |
|||
<a class="hover:underline" href="https://github.com/sponsors/WeeJeWel" target="_blank">Donate</a> |
|||
· |
|||
<a class="hover:underline" href="https://github.com/wg-easy/wg-easy" target="_blank">GitHub</a> |
|||
</p> |
|||
</template> |
|||
|
|||
<script setup></script> |
|||
@ -0,0 +1,69 @@ |
|||
import { reactive } from 'vue'; |
|||
|
|||
const chartOptions = reactive({ |
|||
chart: { |
|||
background: 'transparent', |
|||
type: 'bar', |
|||
stacked: false, |
|||
toolbar: { |
|||
show: false, |
|||
}, |
|||
animations: { |
|||
enabled: false, |
|||
}, |
|||
}, |
|||
colors: [ |
|||
'#DDDDDD', // rx
|
|||
'#EEEEEE', // tx
|
|||
], |
|||
dataLabels: { |
|||
enabled: false, |
|||
}, |
|||
plotOptions: { |
|||
bar: { |
|||
horizontal: false, |
|||
}, |
|||
}, |
|||
xaxis: { |
|||
labels: { |
|||
show: false, |
|||
}, |
|||
axisTicks: { |
|||
show: true, |
|||
}, |
|||
axisBorder: { |
|||
show: true, |
|||
}, |
|||
}, |
|||
yaxis: { |
|||
labels: { |
|||
show: false, |
|||
}, |
|||
min: 0, |
|||
}, |
|||
tooltip: { |
|||
enabled: false, |
|||
}, |
|||
legend: { |
|||
show: false, |
|||
}, |
|||
grid: { |
|||
show: false, |
|||
padding: { |
|||
left: -10, |
|||
right: 0, |
|||
bottom: -15, |
|||
top: -15, |
|||
}, |
|||
column: { |
|||
opacity: 0, |
|||
}, |
|||
xaxis: { |
|||
lines: { |
|||
show: false, |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
export default chartOptions; |
|||
Loading…
Reference in new issue