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