mirror of https://github.com/wg-easy/wg-easy
20 changed files with 333 additions and 208 deletions
@ -1,33 +1,25 @@ |
|||
<script> |
|||
import IconAvatarDefault from './icons/IconAvatarDefault.vue'; |
|||
|
|||
export default { |
|||
props: ['client'], |
|||
components: { IconAvatarDefault }, |
|||
}; |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="h-10 w-10 mr-5 rounded-full bg-gray-50 relative"> |
|||
<IconAvatarDefault class="w-6 m-2 text-gray-300" /> |
|||
<img |
|||
v-if="client.avatar" |
|||
:src="client.avatar" |
|||
class="w-10 rounded-full absolute top-0 left-0" |
|||
/> |
|||
<img v-if="props.client.avatar" :src="props.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 |
|||
" |
|||
v-if="props.client.latestHandshakeAt && new Date() - new Date(props.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 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> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { defineProps } from 'vue'; |
|||
import IconAvatarDefault from './icons/IconAvatarDefault.vue'; |
|||
|
|||
const props = defineProps({ |
|||
client: { |
|||
type: Object, |
|||
required: true, |
|||
}, |
|||
}); |
|||
</script> |
|||
|
|||
@ -1,67 +1,82 @@ |
|||
<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 class="flex flex-col xxs:flex-row w-full gap-2"> |
|||
<div class="flex flex-col flex-grow gap-1"> |
|||
<!-- Name --> |
|||
<ClientName :client="client" /> |
|||
<!-- Info --> |
|||
<div class="text-gray-400 dark:text-neutral-400 text-xs"> |
|||
<!-- Address --> |
|||
<ClientAddress :client="client" /> |
|||
<!-- Transfer TX --> |
|||
<ClientTransfer |
|||
v-if="!uiTrafficStats && client.transferTx" |
|||
:transfer-data="client.transferTx" |
|||
:transfer-data-current="client.transferTxCurrent" |
|||
:title="$t('totalDownload') + bytes(client.transferTx)" |
|||
><IconDownArrow |
|||
/></ClientTransfer> |
|||
<!-- Transfer RX --> |
|||
<ClientTransfer |
|||
v-if="!uiTrafficStats && client.transferTx" |
|||
:transfer-data="client.transferRx" |
|||
:transfer-data-current="client.transferRxCurrent" |
|||
:title="$t('totalUpload') + 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 }} --> |
|||
{{ !uiTrafficStats ? ' · ' : '' }}{{ timeago(client.latestHandshakeAt) }} |
|||
</span> |
|||
</div> |
|||
</div> |
|||
<div |
|||
v-if="uiTrafficStats && client.transferTx && client.transferRx" |
|||
class="flex gap-2 items-center shrink-0 text-gray-400 dark:text-neutral-400 text-xs mt-px justify-end" |
|||
> |
|||
<ClientTrafficStat |
|||
:transfer-data-current="client.transferTxCurrent" |
|||
:transfer-data="client.transferTx" |
|||
type="tx" |
|||
/> |
|||
<ClientTrafficStat |
|||
:transfer-data-current="client.transferRxCurrent" |
|||
:transfer-data="client.transferRx" |
|||
type="rx" |
|||
/> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref } from 'vue'; |
|||
import { storeToRefs } from 'pinia'; |
|||
|
|||
import API from '@/services/api'; |
|||
import { useStore } from '@/store/store'; |
|||
|
|||
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 ClientTrafficStat from '@/components/ClientTrafficStat.vue'; |
|||
|
|||
import { useDateTime } from '@/composables/useDateTime'; |
|||
import { useTimeAgo } from '@/composables/useTimeAgo'; |
|||
import { useBytes } from '@/composables/useBytes'; |
|||
import API from '@/services/api'; |
|||
|
|||
defineProps({ |
|||
client: {}, |
|||
}); |
|||
|
|||
const store = useStore(); |
|||
const { uiTrafficStats } = storeToRefs(store); |
|||
|
|||
const { dateTime } = useDateTime(); |
|||
const { timeago } = useTimeAgo(); |
|||
const { bytes } = useBytes(); |
|||
|
|||
const api = new API(); |
|||
|
|||
|
|||
</script> |
|||
|
|||
@ -0,0 +1,39 @@ |
|||
<template> |
|||
<div class="flex gap-2 items-center shrink-0 text-gray-400 dark:text-neutral-400 text-xs mt-px justify-end"> |
|||
<div class="min-w-20 md:min-w-24"> |
|||
<span class="flex gap-1" :title="$t('totalDownload') + bytes(transferData)"> |
|||
<IconTX v-if="type === 'tx'" /> |
|||
<IconRX v-if="type === 'rx'" /> |
|||
<div> |
|||
<span class="text-gray-700 dark:text-neutral-200">{{ bytes(transferDataCurrent) }}/s</span> |
|||
<!-- Total --> |
|||
<br /><span class="font-regular" style="font-size: 0.85em">{{ bytes(transferData) }}</span> |
|||
</div> |
|||
</span> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { useBytes } from '@/composables/useBytes'; |
|||
|
|||
import IconRX from '@/components/icons/IconRX.vue'; |
|||
import IconTX from '@/components/icons/IconTX.vue'; |
|||
|
|||
const { bytes } = useBytes(); |
|||
|
|||
defineProps({ |
|||
transferData: { |
|||
type: Object, |
|||
default: null, |
|||
}, |
|||
transferDataCurrent: { |
|||
type: Object, |
|||
default: null, |
|||
}, |
|||
type: { |
|||
type: String, |
|||
default: '', |
|||
}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,16 @@ |
|||
<template> |
|||
<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> |
|||
</template> |
|||
|
|||
<script setup></script> |
|||
@ -0,0 +1,16 @@ |
|||
<template> |
|||
<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> |
|||
</template> |
|||
|
|||
<script setup></script> |
|||
Loading…
Reference in new issue