mirror of
https://github.com/Macrame-App/Macrame
synced 2025-12-29 07:19:26 +00:00
WIP: Update to device components
This commit is contained in:
parent
3b38372b4b
commit
6872269266
8 changed files with 203 additions and 56 deletions
37
fe/package-lock.json
generated
37
fe/package-lock.json
generated
|
|
@ -14,6 +14,7 @@
|
||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@basitcodeenv/vue3-device-detect": "^1.0.3",
|
||||||
"@eslint/js": "^9.20.0",
|
"@eslint/js": "^9.20.0",
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
|
|
@ -504,6 +505,16 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@basitcodeenv/vue3-device-detect": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@basitcodeenv/vue3-device-detect/-/vue3-device-detect-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-BDHoM2KYJx/PrEyEzgYOLEVSTxQmvd1PR7UqRQSMClgnM7O1cGoagG/5K0CZy6LUHGifyU5qnrQdC3Sy9nt3zQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ua-parser-js": "^1.0.37",
|
||||||
|
"vue": "^3.0.0-0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@bufbuild/protobuf": {
|
"node_modules/@bufbuild/protobuf": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.3.tgz",
|
||||||
|
|
@ -4603,6 +4614,32 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ua-parser-js": {
|
||||||
|
"version": "1.0.40",
|
||||||
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz",
|
||||||
|
"integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/ua-parser-js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paypal",
|
||||||
|
"url": "https://paypal.me/faisalman"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/faisalman"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"ua-parser-js": "script/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/unicorn-magic": {
|
"node_modules/unicorn-magic": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@basitcodeenv/vue3-device-detect": "^1.0.3",
|
||||||
"@eslint/js": "^9.20.0",
|
"@eslint/js": "^9.20.0",
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,9 @@
|
||||||
|
|
||||||
.panel__content {
|
.panel__content {
|
||||||
@apply grid
|
@apply grid
|
||||||
|
h-full
|
||||||
pt-4 sm:pt-0
|
pt-4 sm:pt-0
|
||||||
pl-0 sm:pl-4;
|
pl-0 sm:pl-4
|
||||||
|
overflow-auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,9 @@ defineProps({
|
||||||
@reference "@/assets/main.css";
|
@reference "@/assets/main.css";
|
||||||
|
|
||||||
.alert {
|
.alert {
|
||||||
@apply flex
|
@apply grid
|
||||||
items-center
|
grid-cols-[1rem_1fr]
|
||||||
|
items-start
|
||||||
gap-4
|
gap-4
|
||||||
p-4
|
p-4
|
||||||
border
|
border
|
||||||
|
|
@ -36,7 +37,7 @@ defineProps({
|
||||||
backdrop-blur-md;
|
backdrop-blur-md;
|
||||||
|
|
||||||
&.alert__info {
|
&.alert__info {
|
||||||
@apply text-sky-400 bg-sky-400/10;
|
@apply text-sky-100 bg-sky-400/40;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.alert__success {
|
&.alert__success {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,17 @@
|
||||||
Navigate to <em class="font-semibold">http://localhost:6970/devices</em> on your pc to
|
Navigate to <em class="font-semibold">http://localhost:6970/devices</em> on your pc to
|
||||||
authorize.
|
authorize.
|
||||||
</p>
|
</p>
|
||||||
|
<template v-if="server.link == 'checking'">
|
||||||
|
<div class="grid grid-cols-[2rem_1fr] gap-2">
|
||||||
|
<IconReload class="animate-spin" />
|
||||||
|
Checking server for link...
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="server.link === false">
|
||||||
|
<ButtonComp variant="subtle" @click="pingLink()" class="w-fit">
|
||||||
|
<IconReload />Check for server link
|
||||||
|
</ButtonComp>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</AlertComp>
|
</AlertComp>
|
||||||
|
|
||||||
|
|
@ -37,22 +48,37 @@
|
||||||
Disconnect
|
Disconnect
|
||||||
</ButtonComp>
|
</ButtonComp>
|
||||||
</div>
|
</div>
|
||||||
|
<DialogComp ref="linkPinDialog">
|
||||||
|
<template #content>
|
||||||
|
<div class="grid gap-4">
|
||||||
|
<h3>Enter server link pin:</h3>
|
||||||
|
<input class="input" type="number" v-model="server.linkPin" />
|
||||||
|
<ButtonComp variant="primary" @click="getDeviceKey()">Enter</ButtonComp>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</DialogComp>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { IconKey, IconPlugConnectedX, IconServer } from '@tabler/icons-vue'
|
import { IconKey, IconPlugConnectedX, IconReload, IconServer } from '@tabler/icons-vue'
|
||||||
import AlertComp from '../base/AlertComp.vue'
|
import AlertComp from '../base/AlertComp.vue'
|
||||||
import ButtonComp from '../base/ButtonComp.vue'
|
import ButtonComp from '../base/ButtonComp.vue'
|
||||||
import { onMounted, reactive } from 'vue'
|
import { onMounted, reactive, ref } from 'vue'
|
||||||
import { useDeviceStore } from '@/stores/device'
|
import { useDeviceStore } from '@/stores/device'
|
||||||
|
import { deviceType, deviceModel, deviceVendor } from '@basitcodeenv/vue3-device-detect'
|
||||||
|
import DialogComp from '../base/DialogComp.vue'
|
||||||
|
|
||||||
const device = useDeviceStore()
|
const device = useDeviceStore()
|
||||||
|
|
||||||
|
const linkPinDialog = ref()
|
||||||
|
|
||||||
const server = reactive({
|
const server = reactive({
|
||||||
host: '',
|
host: '',
|
||||||
status: false,
|
status: false,
|
||||||
access: false,
|
access: false,
|
||||||
|
link: false,
|
||||||
|
linkPin: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|
@ -62,21 +88,30 @@ onMounted(async () => {
|
||||||
if (Object.keys(state.server).length) server.status = state.server.status
|
if (Object.keys(state.server).length) server.status = state.server.status
|
||||||
})
|
})
|
||||||
|
|
||||||
const status = await device.checkServerAccess()
|
const status = await device.remoteCheckServerAccess()
|
||||||
server.status = status
|
server.status = status
|
||||||
})
|
})
|
||||||
|
|
||||||
function requestAccess() {
|
function requestAccess() {
|
||||||
const request = device.requestServerAccess()
|
let deviceName = `${deviceVendor() ? deviceVendor() : 'Unknown'} ${deviceVendor() ? deviceModel() : deviceType()}`
|
||||||
|
|
||||||
request
|
device.remoteRequestServerAccess(deviceName, deviceType()).then((data) => {
|
||||||
.then((data) => {
|
if (data.data) pingLink()
|
||||||
console.log('1', data)
|
|
||||||
})
|
})
|
||||||
.then((data) => {
|
}
|
||||||
console.log('2', data)
|
|
||||||
|
function pingLink() {
|
||||||
|
server.link = 'checking'
|
||||||
|
device.remotePingLink((link) => {
|
||||||
|
server.link = true
|
||||||
|
linkPinDialog.value.toggleDialog(true)
|
||||||
|
console.log(link, 'opendialog')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDeviceKey() {
|
||||||
|
device.remoteHandshake(server.linkPin)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
|
|
@ -8,60 +8,97 @@
|
||||||
</AlertComp>
|
</AlertComp>
|
||||||
|
|
||||||
<div class="mcrm-block block__light flex flex-wrap items-start">
|
<div class="mcrm-block block__light flex flex-wrap items-start">
|
||||||
<!-- {{ remote.devices }} -->
|
<!-- {{ Object.keys(remote.devices).length }} -->
|
||||||
|
<template v-if="Object.keys(remote.devices).length > 0">
|
||||||
<div
|
<div
|
||||||
class="mcrm-block block__dark block-size__sm w-64 grid !gap-4 content-start"
|
class="mcrm-block block__dark block-size__sm w-64 grid !gap-4 content-start"
|
||||||
v-for="(device, id) in remote.devices"
|
v-for="(remoteDevice, id) in remote.devices"
|
||||||
:key="id"
|
:key="id"
|
||||||
>
|
>
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
<h5 class="grid grid-cols-[auto_1fr] gap-2">
|
<h5 class="grid grid-cols-[auto_1fr] gap-2">
|
||||||
<IconDeviceUnknown v-if="device.settings.type == 'unknown'" />
|
<IconDeviceUnknown v-if="remoteDevice.settings.type == 'unknown'" />
|
||||||
<IconDeviceMobile v-if="device.settings.type == 'phone'" />
|
<IconDeviceMobile v-if="remoteDevice.settings.type == 'mobile'" />
|
||||||
<IconDeviceTablet v-if="device.settings.type == 'tablet'" />
|
<IconDeviceTablet v-if="remoteDevice.settings.type == 'tablet'" />
|
||||||
|
<IconDeviceDesktop v-if="remoteDevice.settings.type == 'desktop'" />
|
||||||
<span class="w-full truncate">
|
<span class="w-full truncate">
|
||||||
{{ device.settings.name }}
|
{{ remoteDevice.settings.name }}
|
||||||
</span>
|
</span>
|
||||||
</h5>
|
</h5>
|
||||||
<em>{{ id }}</em>
|
<em>{{ id }}</em>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="device.key">
|
<template v-if="remoteDevice.key">
|
||||||
<AlertComp type="success">Registered</AlertComp>
|
<AlertComp type="success">Linked</AlertComp>
|
||||||
<ButtonComp variant="danger"> <IconDevicesMinus />Unregister device </ButtonComp>
|
<ButtonComp variant="danger"> <IconLinkOff />Unlink device </ButtonComp>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<AlertComp type="warning">Not registered</AlertComp>
|
<AlertComp type="warning">Not linked</AlertComp>
|
||||||
<ButtonComp variant="primary"> <IconDevicesPlus />Register device </ButtonComp>
|
<ButtonComp variant="primary" @click="startLink(id)">
|
||||||
|
<IconLink />Link device
|
||||||
|
</ButtonComp>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<div class="grid w-full gap-4">
|
||||||
|
<em class="text-slate-300">No remote devices</em>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<ButtonComp variant="primary" @click="device.serverGetRemotes()">
|
||||||
|
<IconReload />Check for access requests
|
||||||
|
</ButtonComp>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<DialogComp ref="pinDialog">
|
||||||
|
<template #content>
|
||||||
|
<div class="grid gap-4">
|
||||||
|
<h3>Pin code</h3>
|
||||||
|
<span class="text-4xl font-mono tracking-wide">{{ remote.pinlink }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</DialogComp>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, reactive } from 'vue'
|
import { onMounted, reactive, ref } from 'vue'
|
||||||
import AlertComp from '../base/AlertComp.vue'
|
import AlertComp from '../base/AlertComp.vue'
|
||||||
import { useDeviceStore } from '@/stores/device'
|
import { useDeviceStore } from '@/stores/device'
|
||||||
import {
|
import {
|
||||||
|
IconDeviceDesktop,
|
||||||
IconDeviceMobile,
|
IconDeviceMobile,
|
||||||
IconDevicesMinus,
|
IconDevicesMinus,
|
||||||
IconDevicesPlus,
|
IconDevicesPlus,
|
||||||
IconDeviceTablet,
|
IconDeviceTablet,
|
||||||
IconDeviceUnknown,
|
IconDeviceUnknown,
|
||||||
|
IconLink,
|
||||||
|
IconLinkOff,
|
||||||
|
IconReload,
|
||||||
} from '@tabler/icons-vue'
|
} from '@tabler/icons-vue'
|
||||||
import ButtonComp from '../base/ButtonComp.vue'
|
import ButtonComp from '../base/ButtonComp.vue'
|
||||||
|
import DialogComp from '../base/DialogComp.vue'
|
||||||
|
|
||||||
const device = useDeviceStore()
|
const device = useDeviceStore()
|
||||||
|
|
||||||
const remote = reactive({ devices: [] })
|
const pinDialog = ref()
|
||||||
|
|
||||||
|
const remote = reactive({ devices: [], pinlink: '' })
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
device.getRemoteDevices()
|
device.serverGetRemotes()
|
||||||
|
|
||||||
device.$subscribe((mutation, state) => {
|
device.$subscribe((mutation, state) => {
|
||||||
if (Object.keys(state.remote).length) remote.devices = device.remote
|
if (Object.keys(state.remote).length) remote.devices = device.remote
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async function startLink(deviceUuid) {
|
||||||
|
const pin = await device.serverStartLink(deviceUuid)
|
||||||
|
|
||||||
|
remote.pinlink = pin
|
||||||
|
pinDialog.value.toggleDialog(true)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { appUrl } from '@/services/ApiService'
|
import { appUrl, encrypt } from '@/services/ApiService'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
export const useDeviceStore = defineStore('device', () => {
|
export const useDeviceStore = defineStore('device', () => {
|
||||||
|
|
@ -15,6 +15,7 @@ export const useDeviceStore = defineStore('device', () => {
|
||||||
status: false,
|
status: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Current device
|
||||||
const uuid = () => {
|
const uuid = () => {
|
||||||
if (!current.value.uuid && localStorage.getItem('deviceId')) {
|
if (!current.value.uuid && localStorage.getItem('deviceId')) {
|
||||||
current.value.uuid = localStorage.getItem('deviceId')
|
current.value.uuid = localStorage.getItem('deviceId')
|
||||||
|
|
@ -30,32 +31,65 @@ export const useDeviceStore = defineStore('device', () => {
|
||||||
return uuid
|
return uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRemoteDevices = async () => {
|
// Server application
|
||||||
// remote.value = { penis: 'penis' }
|
const serverGetRemotes = async (remoteUuid) => {
|
||||||
// return
|
axios.post(appUrl() + '/device/list', { uuid: remoteUuid }).then((data) => {
|
||||||
axios.post(appUrl() + '/device/list').then((data) => {
|
|
||||||
if (data.data.devices) remote.value = data.data.devices
|
if (data.data.devices) remote.value = data.data.devices
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkServerAccess = async () => {
|
const serverStartLink = async (deviceUuid) => {
|
||||||
|
const request = await axios.post(appUrl() + '/device/link/start', { uuid: deviceUuid })
|
||||||
|
return request.data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote application
|
||||||
|
const remoteCheckServerAccess = async () => {
|
||||||
const check = await axios.post(appUrl() + '/device/access/check', { uuid: uuid() })
|
const check = await axios.post(appUrl() + '/device/access/check', { uuid: uuid() })
|
||||||
server.value.access = check.data
|
server.value.access = check.data
|
||||||
return check.data
|
return check.data
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestServerAccess = async () => {
|
const remoteRequestServerAccess = async (deviceName, deviceType) => {
|
||||||
const request = await axios.post(appUrl() + '/device/access/request', { uuid: uuid() })
|
const request = await axios.post(appUrl() + '/device/access/request', {
|
||||||
|
uuid: uuid(),
|
||||||
|
name: deviceName,
|
||||||
|
type: deviceType,
|
||||||
|
})
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
const remotePingLink = async (cb) => {
|
||||||
|
// const linkRequest = await axios.post(appUrl() + '/device/link/ping', { uuid: deviceUuid })
|
||||||
|
// if (linkRequest.data)
|
||||||
|
const pingInterval = setInterval(() => {
|
||||||
|
axios.post(appUrl() + '/device/link/ping', { uuid: uuid() }).then((data) => {
|
||||||
|
if (data.data) {
|
||||||
|
clearInterval(pingInterval)
|
||||||
|
cb(data.data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteHandshake = async (pin) => {
|
||||||
|
// send encrypt(uuid + pin)
|
||||||
|
// then decrypt data with pin = key
|
||||||
|
|
||||||
|
axios.post(appUrl() + '/device/handshake', { shake: pin }).then((data) => {
|
||||||
|
console.log(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
remote,
|
remote,
|
||||||
server,
|
server,
|
||||||
uuid,
|
uuid,
|
||||||
setDeviceId,
|
setDeviceId,
|
||||||
getRemoteDevices,
|
serverGetRemotes,
|
||||||
checkServerAccess,
|
serverStartLink,
|
||||||
requestServerAccess,
|
remoteCheckServerAccess,
|
||||||
|
remoteRequestServerAccess,
|
||||||
|
remotePingLink,
|
||||||
|
remoteHandshake,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
<h1 class="panel__title">
|
<h1 class="panel__title">
|
||||||
Devices <span class="text-sm">{{ isLocal() ? 'remote' : 'servers' }}</span>
|
Devices <span class="text-sm">{{ isLocal() ? 'remote' : 'servers' }}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="panel__content">
|
<div class="panel__content grid sm:grid-cols-2 gap-8">
|
||||||
<ServerView v-if="!isLocal()" />
|
<ServerView />
|
||||||
<RemoteView v-else />
|
<RemoteView />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue