Dashboard page restructured. Moved some elements to the other views.

This commit is contained in:
Jesse Malotaux 2025-04-30 18:06:53 +02:00
parent 1cf9029a63
commit 315d169cf9
10 changed files with 363 additions and 121 deletions

View file

@ -35,18 +35,18 @@
}
&.block__primary {
@apply bg-sky-300/40;
@apply bg-sky-300/20;
&::before {
@apply from-sky-100/40;
@apply from-sky-100/20;
}
}
&.block__secondary {
@apply bg-amber-300/40;
@apply bg-amber-300/20;
&::before {
@apply from-amber-100/40;
@apply from-amber-100/20;
}
}

View file

@ -16,7 +16,7 @@
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { onMounted, onUpdated, ref } from 'vue'
import ButtonComp from './ButtonComp.vue'
import { IconChevronDown, IconChevronUp } from '@tabler/icons-vue'
@ -35,6 +35,10 @@ onMounted(() => {
if (props.open) toggleAccordion(props.open)
})
onUpdated(() => {
if (props.open) toggleAccordion(props.open)
})
function toggleAccordion(open = false) {
if (open) {
accordionOpen.value = true

View file

@ -45,7 +45,8 @@ button,
tracking-wide
font-normal
transition-all
cursor-pointer;
cursor-pointer
no-underline;
transition:
border-color 0.1s ease-in-out,

View file

@ -0,0 +1,86 @@
<template>
<div id="remote-dashboard">
<div id="panels" class="dashboard-block mcrm-block block__light" v-if="server.handshake">
<div class="icon__container">
<IconLayoutGrid />
</div>
<h4>{{ server.panelCount }} {{ server.panelCount != 1 ? 'Panels' : 'Panel' }}</h4>
<template v-if="server.panelCount == 0">
<p><em>No panels found. </em></p>
<p>Learn how to create a panel <a href="#" target="_blank">here</a>.</p>
</template>
<template v-else>
<p>Start using a panel!</p>
<ButtonComp variant="danger" href="/panels"> <IconLayoutGrid /> View panels </ButtonComp>
</template>
</div>
<div id="server" class="dashboard-block mcrm-block block__light">
<div class="icon__container">
<IconServer />
</div>
<h4>Server</h4>
<template v-if="server.handshake">
<p>
Linked with: <strong class="text-center">{{ server.ip }}</strong>
</p>
<ButtonComp variant="primary" href="/devices"> <IconServer /> View server</ButtonComp>
</template>
<template v-else>
<p>
<em>Not linked</em>
</p>
<ButtonComp variant="primary" href="/devices"> <IconLink /> Link with server</ButtonComp>
</template>
</div>
</div>
</template>
<script setup>
import { IconLayoutGrid, IconLink, IconServer } from '@tabler/icons-vue'
import { onMounted, reactive } from 'vue'
import ButtonComp from '../base/ButtonComp.vue'
import { useDeviceStore } from '@/stores/device'
import { usePanelStore } from '@/stores/panel'
const device = useDeviceStore()
const panel = usePanelStore()
const server = reactive({
ip: '',
handshake: '',
panelCount: 0,
})
onMounted(async () => {
const serverIp = await device.serverGetIP()
server.ip = serverIp
if (device.key()) server.handshake = true
device.$subscribe(() => {
if (device.key()) server.handshake = true
})
const panelCount = await panel.getList(true)
server.panelCount = panelCount
})
</script>
<style scoped>
@reference "@/assets/main.css";
#remote-dashboard {
@apply grid
pt-8
gap-4
md:w-fit
h-fit
content-start;
&.not__linked #server {
@apply row-start-1 md:col-start-1;
}
}
</style>

View file

@ -0,0 +1,133 @@
<template>
<div
id="server-dashboard"
:class="`${server.remoteCount == 0 ? 'no__devices' : 'devices__found'} ${server.macroCount == 0 ? 'no__macros' : 'macros__found'}`"
>
<div id="devices" class="dashboard-block mcrm-block block__light">
<div class="icon__container">
<IconDevices />
</div>
<h4>{{ server.remoteCount }} {{ server.remoteCount != 1 ? 'Devices' : 'Device' }}</h4>
<template v-if="server.remoteCount == 0">
<p><em>No devices found.</em></p>
<ButtonComp variant="primary" href="/devices"> <IconLink /> Link a device</ButtonComp>
</template>
<template v-else>
<p>Unlink a device or add new devices.</p>
<ButtonComp variant="primary" href="/devices"><IconDevices /> View devices</ButtonComp>
</template>
</div>
<div id="macros" class="dashboard-block mcrm-block block__light">
<div class="icon__container">
<IconKeyboard />
</div>
<h4>{{ server.macroCount }} {{ server.macroCount != 1 ? 'Macros' : 'Macro' }}</h4>
<template v-if="server.macroCount == 0">
<p><em>No macros found.</em></p>
<ButtonComp variant="secondary" href="/macros"> <IconLayoutGrid /> Create macro</ButtonComp>
</template>
<template v-else>
<p>Edit and view your macros.</p>
<ButtonComp variant="secondary" href="/macros"><IconKeyboard /> View macros</ButtonComp>
</template>
</div>
<div id="panels" class="dashboard-block mcrm-block block__light">
<div class="icon__container">
<IconLayoutGrid />
</div>
<h4>{{ server.panelCount }} {{ server.panelCount != 1 ? 'Panels' : 'Panel' }}</h4>
<template v-if="server.panelCount == 0">
<p><em>No panels found. </em></p>
<p>Learn how to create a panel <a href="#" target="_blank">here</a>.</p>
</template>
<template v-else>
<p>Link macros to panels or view a panel.</p>
<ButtonComp variant="danger" href="/panels"> <IconLayoutGrid /> View panels </ButtonComp>
</template>
</div>
</div>
</template>
<script setup>
import { useDeviceStore } from '@/stores/device'
import { usePanelStore } from '@/stores/panel'
import { IconDevices, IconKeyboard, IconLayoutGrid, IconLink } from '@tabler/icons-vue'
import { onMounted, reactive } from 'vue'
import ButtonComp from '../base/ButtonComp.vue'
import { GetMacroList } from '@/services/MacroService'
const device = useDeviceStore()
const panel = usePanelStore()
const server = reactive({
ip: '',
port: '',
fullPath: '',
remoteCount: 0,
macroCount: 0,
panelCount: 0,
})
onMounted(async () => {
const serverIP = await device.serverGetIP()
server.ip = serverIP
// server.port = window.__CONFIG__.MCRM__PORT
// server.fullPath = `http://${server.ip}:${server.port}`
const remoteCount = await device.serverGetRemotes(true)
server.remoteCount = remoteCount
const macroCount = await GetMacroList(true)
server.macroCount = macroCount
const panelCount = await panel.getList(true)
server.panelCount = panelCount
console.log(server)
})
</script>
<style scoped>
@reference "@/assets/main.css";
#server-dashboard {
@apply grid
grid-cols-1
grid-rows-3
md:grid-cols-3
md:grid-rows-1
gap-4
w-fit
h-fit
pt-8;
&.no__devices #devices {
@apply row-start-1 md:col-start-1;
}
&.no__macros.devices__found #devices {
@apply row-start-3 md:col-start-3;
}
&.devices__found #devices {
@apply row-start-3 md:col-start-3;
}
&.no__devices.no__macros #macros {
@apply row-start-2 md:col-start-2;
}
&.no__macros #macros {
@apply row-start-1 md:col-start-1;
}
&.macros__found #macros {
@apply row-start-2 md:col-start-2;
}
&.no__devices.macros__found #macros {
@apply row-start-3 md:col-start-3;
}
}
</style>

View file

@ -7,12 +7,14 @@
<div class="flex flex-wrap items-start gap-4 mcrm-block block__light">
<h4 class="flex items-center justify-between w-full gap-4 mb-4">
<span class="flex gap-4" v-if="Object.keys(remote.devices).length > 0">
<span class="flex gap-4">
<IconDevices />{{ Object.keys(remote.devices).length }}
{{ Object.keys(remote.devices).length > 1 ? 'Devices' : 'Device' }}
{{ Object.keys(remote.devices).length == 1 ? 'Device' : 'Devices' }}
</span>
<ButtonComp variant="primary" @click="device.serverGetRemotes()"><IconReload /></ButtonComp>
<ButtonComp v-if="!remote.poll" variant="primary" @click="device.serverGetRemotes()"
><IconReload
/></ButtonComp>
</h4>
<template v-if="Object.keys(remote.devices).length > 0">
<template v-for="(remoteDevice, id) in remote.devices" :key="id">
@ -51,28 +53,34 @@
</template>
</template>
<template v-else>
<!-- <template v-else>
<div class="grid w-full gap-4">
<em class="text-slate-300">No remote devices</em>
</div>
</template>
</template> -->
<AccordionComp
class="w-full mt-8 border-t border-t-white/50"
title="How to connect a device?"
:open="Object.keys(remote.devices).length == 0"
>
<div class="grid py-4">
<ul class="space-y-1">
<ul class="space-y-2">
<li>
To connect a device, open <strong>http://{{ server.ip }}:{{ server.port }}</strong> in
a browser on the device.
Scan the QR code with the remote device.
<div class="grid gap-4 py-4 pl-6">
<canvas ref="serverQr"></canvas>
<p>
Or manually type the IP address: <br />
<strong>{{ server.ip }}/devices</strong>
</p>
</div>
</li>
<li>Open the menu, and click on <strong>Server.</strong></li>
<li>
The device will automatically request access, if you see "Access requested" on the
device.
</li>
<li>
<li v-if="!remote.poll">
<div class="inline-flex items-center gap-2">
Click the
<span class="p-1 border rounded-sm"><IconReload class="size-4" /></span> to reload
@ -85,14 +93,14 @@
<span class="flex items-center gap-1 p-1 text-sm border rounded-sm">
<IconLink class="size-4" /> Link device
</span>
to generate a one-time-pin to link the device.
A one-time-pin will be shown in a dialog.
</div>
</li>
<li>Enter the pin on the remote device.</li>
<li>
Enter the pin that is shown on this server in the dialog that will appear on the
Congratulations! You have linked a device! You can now start using panels on that
device.
</li>
<li>Congratulations! You have linked a device! (Hopefully)</li>
</ul>
</div>
</AccordionComp>
@ -110,7 +118,7 @@
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { onMounted, onUpdated, reactive, ref } from 'vue'
import AlertComp from '../base/AlertComp.vue'
import { useDeviceStore } from '@/stores/device'
import {
@ -128,16 +136,18 @@ import DialogComp from '../base/DialogComp.vue'
import axios from 'axios'
import { appUrl } from '@/services/ApiService'
import AccordionComp from '../base/AccordionComp.vue'
import QRCode from 'qrcode'
const device = useDeviceStore()
const pinDialog = ref()
const serverQr = ref()
const server = reactive({
ip: '',
port: '',
})
const remote = reactive({ devices: [], pinlink: false })
const remote = reactive({ devices: [], pinlink: false, poll: false })
onMounted(async () => {
device.serverGetRemotes()
@ -146,11 +156,32 @@ onMounted(async () => {
if (state.remote !== remote.devices) remote.devices = device.remote
})
getIp()
})
onUpdated(() => {
getIp()
if (Object.keys(remote.devices).length == 0 && !remote.poll) {
remote.poll = setInterval(() => {
device.serverGetRemotes()
}, 1000)
}
if (Object.keys(remote.devices).length > 0 && remote.poll) {
clearInterval(remote.poll)
remote.poll = false
}
})
async function getIp() {
const serverIP = await device.serverGetIP()
server.ip = serverIP
server.port = window.__CONFIG__.MCRM__PORT
QRCode.toCanvas(serverQr.value, `${server.ip}/devices`, (error) => {
if (error) console.log('QRCode error: ', error)
})
}
async function startLink(deviceUuid) {
const pin = await device.serverStartLink(deviceUuid)

View file

@ -2,9 +2,13 @@ import axios from 'axios'
import { appUrl, isLocal } from './ApiService'
import { AuthCall } from './EncryptService'
export const GetMacroList = async () => {
export const GetMacroList = async (count = false) => {
const request = await axios.post(appUrl() + '/macro/list')
return sortMacroList(request.data)
if (!request.data) return 0
if (!count) return sortMacroList(request.data)
else return request.data.length
}
const sortMacroList = (list) => {

View file

@ -52,16 +52,19 @@ export const useDeviceStore = defineStore('device', () => {
const serverGetIP = async () => {
const request = await axios.post(appUrl() + '/device/server/ip')
return request.data
return `http://${request.data}:${window.__CONFIG__.MCRM__PORT}`
}
// Server application
const serverGetRemotes = async (remoteUuid) => {
axios.post(appUrl() + '/device/list', { uuid: remoteUuid }).then((data) => {
if (data.data.devices) {
remote.value = data.data.devices
}
})
const serverGetRemotes = async (count = false) => {
const request = await axios.post(appUrl() + '/device/list')
if (!request.data.devices) return false
remote.value = request.data.devices
if (!count) return remote.value
else return Object.keys(remote.value).length
}
const serverStartLink = async (deviceUuid) => {

View file

@ -36,15 +36,20 @@ export const usePanelStore = defineStore('panel', () => {
return current.value
}
const getList = async () => {
if (list.value.length > 0) return list.value
const getList = async (count = false) => {
if (list.value.length > 0 && !count) return list.value
else if (list.value.length > 0 && count) return list.value.length
const data = AuthCall()
const resp = await axios.post(appUrl() + '/panel/list', data)
list.value = resp.data
return list.value
if (!resp.data && !count) return false
else if (!resp.data && count) return 0
if (!count) return list.value
else return list.value.length
}
return {

View file

@ -1,97 +1,72 @@
<template>
<div id="dashboard" class="panel">
<h1 class="panel__title">Dashboard</h1>
<div class="panel__content">
<div class="grid gap-1 opacity-50 h-fit">
<div class="panel__title">
<h1>Dashboard</h1>
<div>
<em v-if="isLocal()">This is the server dashboard.</em>
<em v-else>This is the remote dashboard.</em>
</div>
<div v-if="isLocal()">
<h4>Start: Authenticate a device</h4>
<ul>
<li>
Open <strong>{{ server.ip }}:{{ server.port }}</strong> in a browser on your phone.
<div class="p-4 qr-container">
<canvas ref="serverQr"></canvas>
</div>
</li>
<li>Navigate to the Server page. An authentication request will start automatically.</li>
<li>
Open the
<RouterLink to="/devices"> Devices</RouterLink>
page in this window.
</li>
<li>
<div class="inline-flex items-center gap-2">
The device will appear, if not click the
<span class="p-1 border rounded-sm"><IconReload class="size-4" /></span> button.
</div>
</li>
</ul>
<h4>Using a panel</h4>
<ul>
<li>Once authenticated you can access the Test Panel on your device.</li>
<li>Open the Panels page on your device and click the Test Panel.</li>
</ul>
</div>
<div v-else>
<h4>Start: Authenticate this device</h4>
<ul>
<li>
Open
<RouterLink to="/devices"> Server</RouterLink>
to start.
</li>
<li>An authentication request will start automatically.</li>
</ul>
<h4>Using a panel</h4>
<ul>
<li>
Once authenticated you can access the
<RouterLink to="/panel/view/test_panel"> Test Panel</RouterLink>
on your device.
</li>
<li>
Open the
<RouterLink to="/panels">Panels page</RouterLink>
you can edit the panel.
</li>
</ul>
</div>
<div class="panel__content">
<ServerView v-if="isLocal()" />
<RemoteView v-else />
</div>
</div>
</template>
<script setup>
import { RouterLink } from 'vue-router'
import { isLocal } from '@/services/ApiService'
import { useDeviceStore } from '@/stores/device'
import { onMounted, reactive, ref } from 'vue'
import { IconReload } from '@tabler/icons-vue'
import QRCode from 'qrcode'
const device = useDeviceStore()
const server = reactive({
ip: '',
port: '',
fullPath: '',
})
const serverQr = ref()
onMounted(async () => {
const serverIP = await device.serverGetIP()
server.ip = serverIP
server.port = window.__CONFIG__.MCRM__PORT
server.fullPath = `http://${server.ip}:${server.port}`
QRCode.toCanvas(serverQr.value, server.fullPath, (error) => {
console.log(error)
})
})
import ServerView from '@/components/dashboard/ServerView.vue'
import RemoteView from '@/components/dashboard/RemoteView.vue'
</script>
<style lang="scss" scoped></style>
<style>
@reference "@/assets/main.css";
.dashboard-block {
@apply md:!row-start-1
grid
justify-items-center
gap-4;
&#devices .icon__container,
&#server .icon__container {
@apply bg-sky-300/30
text-sky-400
border-sky-300/60;
}
&#macros .icon__container {
@apply bg-amber-300/30
text-amber-400
border-amber-300/60;
}
&#panels .icon__container {
@apply bg-rose-300/30
text-rose-400
border-rose-300/60;
}
.icon__container {
@apply flex
justify-center
items-center
size-16
aspect-square
rounded-full
border;
svg {
@apply size-8;
}
}
p {
@apply opacity-50
w-42
text-center;
}
}
</style>