mirror of
https://github.com/Macrame-App/Macrame
synced 2025-12-29 07:19:26 +00:00
Major update, devices view and components
The view for devices is functional now. It is possible to view, link and unlink devices and possible to gain access to authenticated endpoints with a key encrypted by a one time pin.
This commit is contained in:
parent
b598a090bc
commit
a01e026aa1
12 changed files with 1282 additions and 673 deletions
1583
fe/package-lock.json
generated
1583
fe/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -4,7 +4,7 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --profile",
|
||||||
"build": "vite build --emptyOutDir",
|
"build": "vite build --emptyOutDir",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "eslint . --fix",
|
"lint": "eslint . --fix",
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
@import './style/_macro';
|
@import "./style/_macro.css";
|
||||||
@import './style/_mcrm-block';
|
@import "./style/_mcrm-block.css";
|
||||||
@import './style/_panel';
|
@import "./style/_panel.css";
|
||||||
|
|
||||||
@import 'tailwindcss';
|
@import "tailwindcss";
|
||||||
|
|
||||||
@variant dark (&:where(.dark, .dark *));
|
@variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--font-sans: 'Roboto', sans-serif;
|
--font-sans: "Roboto", sans-serif;
|
||||||
--font-mono: 'Fira Code', monospace;
|
--font-mono: "Fira Code", monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,50 @@
|
||||||
<template>
|
<template>
|
||||||
<nav id="main-menu">
|
<nav id="main-menu">
|
||||||
<button id="menu-toggle" :class="menuOpen ? 'open' : ''" @click="menuOpen = !menuOpen">
|
<button
|
||||||
<img class="logo" src="@/assets/img/Macrame-Logo-gradient.svg" aria-hidden="true" />
|
id="menu-toggle"
|
||||||
|
:class="menuOpen ? 'open' : ''"
|
||||||
|
@click="menuOpen = !menuOpen"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="logo p-1"
|
||||||
|
:class="{ 'opacity-0': menuOpen }"
|
||||||
|
src="@/assets/img/Macrame-Logo-gradient.svg"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
<IconX :class="{ 'opacity-0': !menuOpen }" />
|
<IconX :class="{ 'opacity-0': !menuOpen }" />
|
||||||
</button>
|
</button>
|
||||||
<ul :class="menuOpen ? 'open' : ''">
|
<ul :class="menuOpen ? 'open' : ''">
|
||||||
<li>
|
<li>
|
||||||
<RouterLink @click="menuOpen = false" to="/"> <IconHome />Dashboard </RouterLink>
|
<RouterLink @click="menuOpen = false" to="/">
|
||||||
|
<IconHome />Dashboard
|
||||||
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<RouterLink @click="menuOpen = false" to="/panels"> <IconLayoutGrid />Panels </RouterLink>
|
<RouterLink @click="menuOpen = false" to="/panels">
|
||||||
|
<IconLayoutGrid />Panels
|
||||||
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<RouterLink @click="menuOpen = false" to="/macros"> <IconKeyboard />Macros </RouterLink>
|
<RouterLink @click="menuOpen = false" to="/macros">
|
||||||
|
<IconKeyboard />Macros
|
||||||
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<RouterLink @click="menuOpen = false" to="/devices"> <IconDevices />Devices </RouterLink>
|
<RouterLink @click="menuOpen = false" to="/devices">
|
||||||
|
<IconDevices />Device
|
||||||
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<RouterLink @click="menuOpen = false" to="/settings"> <IconSettings />Settings </RouterLink>
|
<RouterLink @click="menuOpen = false" to="/settings">
|
||||||
|
<IconSettings />Settings
|
||||||
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { RouterLink } from 'vue-router'
|
import { RouterLink } from "vue-router";
|
||||||
import {
|
import {
|
||||||
IconDevices,
|
IconDevices,
|
||||||
IconHome,
|
IconHome,
|
||||||
|
|
@ -33,10 +52,10 @@ import {
|
||||||
IconLayoutGrid,
|
IconLayoutGrid,
|
||||||
IconSettings,
|
IconSettings,
|
||||||
IconX,
|
IconX,
|
||||||
} from '@tabler/icons-vue'
|
} from "@tabler/icons-vue";
|
||||||
import { ref } from 'vue'
|
import { ref } from "vue";
|
||||||
|
|
||||||
const menuOpen = ref(false)
|
const menuOpen = ref(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,19 @@
|
||||||
</AlertComp>
|
</AlertComp>
|
||||||
|
|
||||||
<div class="mcrm-block block__light grid gap-4">
|
<div class="mcrm-block block__light grid gap-4">
|
||||||
<h4 class="text-lg flex gap-4 items-center"><IconServer />Server</h4>
|
<h4 class="text-lg flex gap-4 items-center justify-between">
|
||||||
|
<span class="flex gap-4"><IconServer />Server</span>
|
||||||
|
<ButtonComp variant="primary" @click="checkServerStatus()"><IconReload /></ButtonComp>
|
||||||
|
</h4>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Connected to: <strong>{{ server.host }}</strong>
|
Connected to: <strong>{{ server.host }}</strong>
|
||||||
</p>
|
</p>
|
||||||
<AlertComp v-if="server.status === 'unauthorized'" type="warning">Not authorized</AlertComp>
|
|
||||||
|
<!-- Alerts -->
|
||||||
<AlertComp v-if="server.status === 'authorized'" type="success">Authorized</AlertComp>
|
<AlertComp v-if="server.status === 'authorized'" type="success">Authorized</AlertComp>
|
||||||
<AlertComp v-if="server.status === 'requested'" type="info">
|
<AlertComp v-if="server.status === 'unlinked'" type="warning">Not linked</AlertComp>
|
||||||
|
<AlertComp v-if="server.status === 'unauthorized'" type="info">
|
||||||
<div class="grid gap-2">
|
<div class="grid gap-2">
|
||||||
<strong>Access requested</strong>
|
<strong>Access requested</strong>
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -34,7 +40,6 @@
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</AlertComp>
|
</AlertComp>
|
||||||
|
|
||||||
<ButtonComp
|
<ButtonComp
|
||||||
v-if="server.status === 'unauthorized'"
|
v-if="server.status === 'unauthorized'"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
|
|
@ -43,17 +48,29 @@
|
||||||
<IconKey />
|
<IconKey />
|
||||||
Request access
|
Request access
|
||||||
</ButtonComp>
|
</ButtonComp>
|
||||||
<ButtonComp variant="danger" v-if="server.status === 'authorized'">
|
<ButtonComp
|
||||||
|
variant="danger"
|
||||||
|
v-if="server.status === 'authorized'"
|
||||||
|
@click="disonnectFromServer()"
|
||||||
|
>
|
||||||
<IconPlugConnectedX />
|
<IconPlugConnectedX />
|
||||||
Disconnect
|
Disconnect
|
||||||
</ButtonComp>
|
</ButtonComp>
|
||||||
</div>
|
</div>
|
||||||
<DialogComp ref="linkPinDialog">
|
<DialogComp ref="linkPinDialog">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="grid gap-4">
|
<div class="grid gap-4 w-64">
|
||||||
<h3>Enter server link pin:</h3>
|
<h3>Server link pin:</h3>
|
||||||
<input class="input" type="number" v-model="server.linkPin" />
|
<form class="grid gap-4" @submit.prevent="decryptKey()">
|
||||||
<ButtonComp variant="primary" @click="getDeviceKey()">Enter</ButtonComp>
|
<input
|
||||||
|
class="input"
|
||||||
|
id="input-pin"
|
||||||
|
type="text"
|
||||||
|
pattern="[0-9]{4}"
|
||||||
|
v-model="server.inputPin"
|
||||||
|
/>
|
||||||
|
<ButtonComp variant="primary">Enter</ButtonComp>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</DialogComp>
|
</DialogComp>
|
||||||
|
|
@ -61,13 +78,23 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
// TODO
|
||||||
|
// - [Delete local key button]
|
||||||
|
// - if not local key
|
||||||
|
// - - if !checkAccess -> requestAccess -> put settings.json (go)
|
||||||
|
// - - if checkAccess -> pingLink -> check for device.tmp (go)
|
||||||
|
// - - if [devicePin] -> handshake -> save key local, close dialog, update server status
|
||||||
|
|
||||||
import { IconKey, IconPlugConnectedX, IconReload, 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, ref } from 'vue'
|
import { onMounted, onUpdated, reactive, ref } from 'vue'
|
||||||
import { useDeviceStore } from '@/stores/device'
|
import { useDeviceStore } from '@/stores/device'
|
||||||
import { deviceType, deviceModel, deviceVendor } from '@basitcodeenv/vue3-device-detect'
|
import { deviceType, deviceModel, deviceVendor } from '@basitcodeenv/vue3-device-detect'
|
||||||
import DialogComp from '../base/DialogComp.vue'
|
import DialogComp from '../base/DialogComp.vue'
|
||||||
|
import { AuthCall, decryptAES } from '@/services/EncryptService'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { appUrl } from '@/services/ApiService'
|
||||||
|
|
||||||
const device = useDeviceStore()
|
const device = useDeviceStore()
|
||||||
|
|
||||||
|
|
@ -76,41 +103,82 @@ const linkPinDialog = ref()
|
||||||
const server = reactive({
|
const server = reactive({
|
||||||
host: '',
|
host: '',
|
||||||
status: false,
|
status: false,
|
||||||
access: false,
|
|
||||||
link: false,
|
link: false,
|
||||||
linkPin: '',
|
inputPin: '',
|
||||||
|
encryptedKey: '',
|
||||||
|
key: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
server.host = window.location.host
|
server.host = window.location.host
|
||||||
|
|
||||||
device.$subscribe((mutation, state) => {
|
|
||||||
if (Object.keys(state.server).length) server.status = state.server.status
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onUpdated(() => {
|
||||||
|
if (!server.status) checkServerStatus()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function checkServerStatus(request = true) {
|
||||||
const status = await device.remoteCheckServerAccess()
|
const status = await device.remoteCheckServerAccess()
|
||||||
|
|
||||||
server.status = status
|
server.status = status
|
||||||
})
|
|
||||||
|
if (status === 'unlinked' || status === 'unauthorized') {
|
||||||
|
if (request) requestAccess()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device.key()) {
|
||||||
|
server.status = 'unauthorized'
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handshake = await device.remoteHandshake(device.key())
|
||||||
|
|
||||||
|
if (handshake) server.key = device.key()
|
||||||
|
else {
|
||||||
|
device.removeDeviceKey()
|
||||||
|
server.status = 'unlinked'
|
||||||
|
if (request) requestAccess()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
function requestAccess() {
|
function requestAccess() {
|
||||||
let deviceName = `${deviceVendor() ? deviceVendor() : 'Unknown'} ${deviceVendor() ? deviceModel() : deviceType()}`
|
let deviceName = `${deviceVendor() ? deviceVendor() : 'Unknown'} ${deviceVendor() ? deviceModel() : deviceType()}`
|
||||||
|
|
||||||
device.remoteRequestServerAccess(deviceName, deviceType()).then((data) => {
|
device.remoteRequestServerAccess(deviceName, deviceType()).then((data) => {
|
||||||
if (data.data) pingLink()
|
if (data.data) (server.status = data.data), pingLink()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function pingLink() {
|
function pingLink() {
|
||||||
server.link = 'checking'
|
server.link = 'checking'
|
||||||
device.remotePingLink((link) => {
|
|
||||||
|
device.remotePingLink((encryptedKey) => {
|
||||||
server.link = true
|
server.link = true
|
||||||
|
server.encryptedKey = encryptedKey
|
||||||
|
|
||||||
linkPinDialog.value.toggleDialog(true)
|
linkPinDialog.value.toggleDialog(true)
|
||||||
console.log(link, 'opendialog')
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDeviceKey() {
|
async function decryptKey() {
|
||||||
device.remoteHandshake(server.linkPin)
|
const decryptedKey = decryptAES(server.inputPin, server.encryptedKey)
|
||||||
|
const handshake = await device.remoteHandshake(decryptedKey)
|
||||||
|
|
||||||
|
if (handshake) {
|
||||||
|
device.setDeviceKey(decryptedKey)
|
||||||
|
server.key = decryptedKey
|
||||||
|
linkPinDialog.value.toggleDialog(false)
|
||||||
|
server.status = 'authorized'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function disonnectFromServer() {
|
||||||
|
axios.post(appUrl() + '/device/link/remove', AuthCall({ uuid: device.uuid() })).then((data) => {
|
||||||
|
if (data.data) checkServerStatus(false)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -122,4 +190,7 @@ function getDeviceKey() {
|
||||||
gap-4
|
gap-4
|
||||||
content-start;
|
content-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#input-pin {
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,11 @@
|
||||||
</div>
|
</div>
|
||||||
</AlertComp>
|
</AlertComp>
|
||||||
|
|
||||||
<div class="mcrm-block block__light flex flex-wrap items-start">
|
<div class="mcrm-block block__light flex flex-wrap items-start gap-4">
|
||||||
|
<h4 class="w-full flex gap-4 items-center justify-between mb-4">
|
||||||
|
<span class="flex gap-4"> <IconDevices />Remote devices </span>
|
||||||
|
<ButtonComp variant="primary" @click="device.serverGetRemotes()"><IconReload /></ButtonComp>
|
||||||
|
</h4>
|
||||||
<!-- {{ Object.keys(remote.devices).length }} -->
|
<!-- {{ Object.keys(remote.devices).length }} -->
|
||||||
<template v-if="Object.keys(remote.devices).length > 0">
|
<template v-if="Object.keys(remote.devices).length > 0">
|
||||||
<div
|
<div
|
||||||
|
|
@ -28,32 +32,32 @@
|
||||||
<em>{{ id }}</em>
|
<em>{{ id }}</em>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="remoteDevice.key">
|
<template v-if="remoteDevice.key">
|
||||||
<AlertComp type="success">Linked</AlertComp>
|
<AlertComp type="success">Authorized</AlertComp>
|
||||||
<ButtonComp variant="danger"> <IconLinkOff />Unlink device </ButtonComp>
|
<ButtonComp variant="danger" @click="unlinkDevice(id)">
|
||||||
|
<IconLinkOff />Unlink device
|
||||||
|
</ButtonComp>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<AlertComp type="warning">Not linked</AlertComp>
|
<AlertComp type="warning">Unauthorized</AlertComp>
|
||||||
<ButtonComp variant="primary" @click="startLink(id)">
|
<ButtonComp variant="primary" @click="startLink(id)">
|
||||||
<IconLink />Link device
|
<IconLink />Link device
|
||||||
</ButtonComp>
|
</ButtonComp>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="remote.pinlink.uuid == id">
|
||||||
|
<AlertComp type="info">One time pin: {{ remote.pinlink.pin }}</AlertComp>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="grid w-full gap-4">
|
<div class="grid w-full gap-4">
|
||||||
<em class="text-slate-300">No remote devices</em>
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<DialogComp ref="pinDialog">
|
<DialogComp ref="pinDialog">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="grid gap-4">
|
<div class="grid gap-4">
|
||||||
<h3>Pin code</h3>
|
<h3>Pin code</h3>
|
||||||
<span class="text-4xl font-mono tracking-wide">{{ remote.pinlink }}</span>
|
<span class="text-4xl font-mono tracking-wide">{{ remote.pinlink.pin }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</DialogComp>
|
</DialogComp>
|
||||||
|
|
@ -62,14 +66,18 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
// TODO
|
||||||
|
// - startLink -> responsePin also in device block
|
||||||
|
// - startLink -> poll removal of pin file, if removed close dialog, update device list
|
||||||
|
// - Make unlink work
|
||||||
|
|
||||||
import { onMounted, reactive, ref } 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 {
|
||||||
|
IconDevices,
|
||||||
IconDeviceDesktop,
|
IconDeviceDesktop,
|
||||||
IconDeviceMobile,
|
IconDeviceMobile,
|
||||||
IconDevicesMinus,
|
|
||||||
IconDevicesPlus,
|
|
||||||
IconDeviceTablet,
|
IconDeviceTablet,
|
||||||
IconDeviceUnknown,
|
IconDeviceUnknown,
|
||||||
IconLink,
|
IconLink,
|
||||||
|
|
@ -78,12 +86,14 @@ import {
|
||||||
} 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'
|
import DialogComp from '../base/DialogComp.vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { appUrl } from '@/services/ApiService'
|
||||||
|
|
||||||
const device = useDeviceStore()
|
const device = useDeviceStore()
|
||||||
|
|
||||||
const pinDialog = ref()
|
const pinDialog = ref()
|
||||||
|
|
||||||
const remote = reactive({ devices: [], pinlink: '' })
|
const remote = reactive({ devices: [], pinlink: false })
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
device.serverGetRemotes()
|
device.serverGetRemotes()
|
||||||
|
|
@ -96,8 +106,37 @@ onMounted(() => {
|
||||||
async function startLink(deviceUuid) {
|
async function startLink(deviceUuid) {
|
||||||
const pin = await device.serverStartLink(deviceUuid)
|
const pin = await device.serverStartLink(deviceUuid)
|
||||||
|
|
||||||
remote.pinlink = pin
|
remote.pinlink = { uuid: deviceUuid, pin: pin }
|
||||||
pinDialog.value.toggleDialog(true)
|
pinDialog.value.toggleDialog(true)
|
||||||
|
|
||||||
|
pollLink()
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
resetPinLink()
|
||||||
|
}, 60000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function pollLink() {
|
||||||
|
const pollInterval = setInterval(() => {
|
||||||
|
axios.post(appUrl() + '/device/link/poll', { uuid: remote.pinlink.uuid }).then((data) => {
|
||||||
|
if (!data.data) {
|
||||||
|
clearInterval(pollInterval)
|
||||||
|
resetPinLink()
|
||||||
|
device.serverGetRemotes()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetPinLink() {
|
||||||
|
remote.pinlink = false
|
||||||
|
if (pinDialog.value) pinDialog.value.toggleDialog(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function unlinkDevice(id) {
|
||||||
|
axios.post(appUrl() + '/device/link/remove', { uuid: id }).then((data) => {
|
||||||
|
if (data.data) device.serverGetRemotes()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ import { IconKeyboard } from '@tabler/icons-vue'
|
||||||
import ButtonComp from '../base/ButtonComp.vue'
|
import ButtonComp from '../base/ButtonComp.vue'
|
||||||
import { onMounted, reactive } from 'vue'
|
import { onMounted, reactive } from 'vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { appUrl } from '@/services/ApiService'
|
import { appUrl, isLocal } from '@/services/ApiService'
|
||||||
|
import { AuthCall } from '@/services/EncryptService'
|
||||||
|
|
||||||
const macros = reactive({
|
const macros = reactive({
|
||||||
list: [],
|
list: [],
|
||||||
|
|
@ -29,8 +30,9 @@ onMounted(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
function runMacro(macro) {
|
function runMacro(macro) {
|
||||||
console.log(macro)
|
const data = isLocal() ? { macro: macro } : AuthCall({ macro: macro })
|
||||||
axios.post(appUrl() + '/macro/play', { macro: macro }).then((data) => {
|
|
||||||
|
axios.post(appUrl() + '/macro/play', data).then((data) => {
|
||||||
console.log(data)
|
console.log(data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
// import './assets/jemx.scss'
|
// import './assets/jemx.scss'
|
||||||
import './assets/main.css'
|
import "@/assets/main.css";
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from "vue";
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from "pinia";
|
||||||
|
|
||||||
import App from './App.vue'
|
import App from "@/App.vue";
|
||||||
import router from './router'
|
import router from "@/router";
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(createPinia())
|
app.use(createPinia());
|
||||||
app.use(router)
|
app.use(router);
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount("#app");
|
||||||
|
|
|
||||||
51
fe/src/services/EncryptService.js
Normal file
51
fe/src/services/EncryptService.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { useDeviceStore } from '@/stores/device'
|
||||||
|
import { AES, enc, pad } from 'crypto-js'
|
||||||
|
|
||||||
|
export const encryptAES = (key, str) => {
|
||||||
|
key = keyPad(key)
|
||||||
|
|
||||||
|
let iv = enc.Utf8.parse(import.meta.env.VITE_MCRM__IV)
|
||||||
|
let encrypted = AES.encrypt(str, key, {
|
||||||
|
iv: iv,
|
||||||
|
padding: pad.Pkcs7,
|
||||||
|
})
|
||||||
|
return encrypted.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const decryptAES = (key, str) => {
|
||||||
|
key = keyPad(key)
|
||||||
|
|
||||||
|
let iv = enc.Utf8.parse(import.meta.env.VITE_MCRM__IV)
|
||||||
|
let encrypted = AES.decrypt(str.toString(), key, {
|
||||||
|
iv: iv,
|
||||||
|
padding: pad.Pkcs7,
|
||||||
|
})
|
||||||
|
return encrypted.toString(enc.Utf8)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AuthCall = (data) => {
|
||||||
|
const device = useDeviceStore()
|
||||||
|
|
||||||
|
return {
|
||||||
|
uuid: device.uuid(),
|
||||||
|
d: encryptAES(device.key(), JSON.stringify(data)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyPad(key) {
|
||||||
|
let returnKey = key
|
||||||
|
|
||||||
|
if (key.length == 4) {
|
||||||
|
returnKey = key + import.meta.env.VITE_MCRM__SALT
|
||||||
|
}
|
||||||
|
|
||||||
|
return enc.Utf8.parse(returnKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getDateStr = () => {
|
||||||
|
const date = new Date()
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
return `${year}${month}${day}`
|
||||||
|
}
|
||||||
|
|
@ -3,11 +3,13 @@ import { defineStore } from 'pinia'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { appUrl, encrypt } from '@/services/ApiService'
|
import { appUrl, encrypt } from '@/services/ApiService'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { encryptAES, getDateStr } from '@/services/EncryptService'
|
||||||
|
|
||||||
export const useDeviceStore = defineStore('device', () => {
|
export const useDeviceStore = defineStore('device', () => {
|
||||||
// Properties - State values
|
// Properties - State values
|
||||||
const current = ref({
|
const current = ref({
|
||||||
uuid: false,
|
uuid: false,
|
||||||
|
key: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const remote = ref([])
|
const remote = ref([])
|
||||||
|
|
@ -31,6 +33,23 @@ export const useDeviceStore = defineStore('device', () => {
|
||||||
return uuid
|
return uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const key = () => {
|
||||||
|
if (!current.value.key && localStorage.getItem('deviceKey')) {
|
||||||
|
current.value.key = localStorage.getItem('deviceKey')
|
||||||
|
}
|
||||||
|
return current.value.key
|
||||||
|
}
|
||||||
|
|
||||||
|
const setDeviceKey = (key) => {
|
||||||
|
current.value.key = key
|
||||||
|
localStorage.setItem('deviceKey', key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeDeviceKey = () => {
|
||||||
|
current.value.key = false
|
||||||
|
localStorage.removeItem('deviceKey')
|
||||||
|
}
|
||||||
|
|
||||||
// Server application
|
// Server application
|
||||||
const serverGetRemotes = async (remoteUuid) => {
|
const serverGetRemotes = async (remoteUuid) => {
|
||||||
axios.post(appUrl() + '/device/list', { uuid: remoteUuid }).then((data) => {
|
axios.post(appUrl() + '/device/list', { uuid: remoteUuid }).then((data) => {
|
||||||
|
|
@ -71,13 +90,14 @@ export const useDeviceStore = defineStore('device', () => {
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
const remoteHandshake = async (pin) => {
|
const remoteHandshake = async (key) => {
|
||||||
// send encrypt(uuid + pin)
|
const handshake = await axios.post(appUrl() + '/device/handshake', {
|
||||||
// then decrypt data with pin = key
|
uuid: uuid(),
|
||||||
|
shake: encryptAES(key, getDateStr()),
|
||||||
axios.post(appUrl() + '/device/handshake', { shake: pin }).then((data) => {
|
|
||||||
console.log(data)
|
|
||||||
})
|
})
|
||||||
|
console.log(handshake)
|
||||||
|
|
||||||
|
return handshake.data
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -85,6 +105,9 @@ export const useDeviceStore = defineStore('device', () => {
|
||||||
server,
|
server,
|
||||||
uuid,
|
uuid,
|
||||||
setDeviceId,
|
setDeviceId,
|
||||||
|
key,
|
||||||
|
setDeviceKey,
|
||||||
|
removeDeviceKey,
|
||||||
serverGetRemotes,
|
serverGetRemotes,
|
||||||
serverStartLink,
|
serverStartLink,
|
||||||
remoteCheckServerAccess,
|
remoteCheckServerAccess,
|
||||||
|
|
|
||||||
|
|
@ -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 grid sm:grid-cols-2 gap-8">
|
<div class="panel__content grid gap-8">
|
||||||
<ServerView />
|
<ServerView v-if="isLocal()" />
|
||||||
<RemoteView />
|
<RemoteView v-else />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,25 @@ import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
host: 'localhost',
|
||||||
|
port: 5173,
|
||||||
|
watch: {
|
||||||
|
usePolling: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
plugins: [vue(), vueDevTools(), tailwindcss()],
|
plugins: [vue(), vueDevTools(), tailwindcss()],
|
||||||
|
envDir: '../',
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
base: '/',
|
base: '/',
|
||||||
publicDir: '../public',
|
// publicDir: "../public",
|
||||||
build: {
|
build: {
|
||||||
outDir: '../public',
|
outDir: '../public',
|
||||||
sourcemap: true,
|
sourcemap: false,
|
||||||
|
minify: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue