diff --git a/console/src/App.vue b/console/src/App.vue index 8def24e..c2bcd82 100644 --- a/console/src/App.vue +++ b/console/src/App.vue @@ -1,47 +1,59 @@ - - - - - - - - + + + + + + + + + + - - - + - diff --git a/console/src/assets/styles/main.css b/console/src/assets/styles/main.css index dc3fd4a..6753131 100644 --- a/console/src/assets/styles/main.css +++ b/console/src/assets/styles/main.css @@ -97,6 +97,18 @@ body { color: var(--p-primary-200); } +.offline { + color: color-mix(in srgb, red, transparent 40%); + border-color: color-mix(in srgb, red, transparent 70%); + background-color: color-mix(in srgb, red, transparent 80%); +} + +.p-dark .offline { + color: color-mix(in srgb, red, transparent 40%); + border-color: color-mix(in srgb, red, transparent 70%); + background-color: color-mix(in srgb, red, transparent 90%); +} + .stats-header { display: flex; align-items: flex-start; diff --git a/console/src/components/AppConfig.vue b/console/src/components/AppConfig.vue index 41f54a6..ccb8acc 100644 --- a/console/src/components/AppConfig.vue +++ b/console/src/components/AppConfig.vue @@ -4,10 +4,6 @@ const { primaryColors, surfaces, primary, surface, isDarkMode, updateColors, toggleDarkMode } = useLayout() - - onMounted(() => { - toggleDarkMode() - }) diff --git a/console/src/components/SplashPage.vue b/console/src/components/SplashPage.vue new file mode 100644 index 0000000..08be75a --- /dev/null +++ b/console/src/components/SplashPage.vue @@ -0,0 +1,60 @@ + + + + + + MQTT Login + + + Login + + + + + diff --git a/console/src/components/dashboard/CommandsWidget.vue b/console/src/components/dashboard/CommandsWidget.vue index 22fd811..ac0299a 100644 --- a/console/src/components/dashboard/CommandsWidget.vue +++ b/console/src/components/dashboard/CommandsWidget.vue @@ -1,6 +1,8 @@ - {{ device.title }} - + {{ device.meta['type'] }} + - {{ device.value }} - {{ device.subtitle }} + {{ device.meta['name'] }} + {{ device.id }} on {{ device.meta['host'] }} + +.selected-device { + border: 2px solid var(--p-primary-color); + box-shadow: 0 0 8px var(--p-primary-color); +} + \ No newline at end of file diff --git a/console/src/components/dashboard/ProductOverviewWidget.vue b/console/src/components/dashboard/ProductOverviewWidget.vue deleted file mode 100644 index 9a200e1..0000000 --- a/console/src/components/dashboard/ProductOverviewWidget.vue +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - Products Overview - - - - - - - - - - - ${{ data.price }} - - - - - {{ data.status }} - - - - - - - diff --git a/console/src/components/dashboard/PropertiesWidget.vue b/console/src/components/dashboard/PropertiesWidget.vue index 061143f..0e6a666 100644 --- a/console/src/components/dashboard/PropertiesWidget.vue +++ b/console/src/components/dashboard/PropertiesWidget.vue @@ -1,6 +1,9 @@ diff --git a/console/src/components/dashboard/RecentActivityWidget.vue b/console/src/components/dashboard/RecentActivityWidget.vue deleted file mode 100644 index ba64110..0000000 --- a/console/src/components/dashboard/RecentActivityWidget.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - - - Recent Activity - - - - - {{ activity.text }} - {{ activity.time }} - - - - - diff --git a/console/src/components/dashboard/SalesTrendWidget.vue b/console/src/components/dashboard/SalesTrendWidget.vue deleted file mode 100644 index d01d5f2..0000000 --- a/console/src/components/dashboard/SalesTrendWidget.vue +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - Sales Trend - - - - - - diff --git a/console/src/components/dashboard/StatsWidget.vue b/console/src/components/dashboard/StatsWidget.vue deleted file mode 100644 index 2f2fa9a..0000000 --- a/console/src/components/dashboard/StatsWidget.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - {{ stat.title }} - - - - - - {{ stat.value }} - {{ stat.subtitle }} - - - - diff --git a/console/src/services/mqtt.js b/console/src/services/mqtt.js index 0411996..d81376f 100644 --- a/console/src/services/mqtt.js +++ b/console/src/services/mqtt.js @@ -1,27 +1,79 @@ import mqtt from 'mqtt' export default class MQTTService { - constructor(brokerUrl = 'ws://127.0.0.1:8083/mqtt', clientId = null) { + /** + * @param {string} brokerUrl - MQTT broker URL (e.g., ws://127.0.0.1:8083/mqtt) + * @param {string|null} clientId - Optional MQTT client ID + * @param {string|null} username - Optional MQTT username + * @param {string|null} password - Optional MQTT password + */ + constructor( + brokerUrl = 'ws://127.0.0.1:8083/mqtt', + username = null, + password = null, + clientId = null, + ) { this.clientId = clientId || 'vue-client-' + Math.random().toString(16).substr(2, 8) - this.client = mqtt.connect(brokerUrl, { clientId: this.clientId, protocolVersion: 5 }) - this.subscriptions = [] // array of {topic, callback} - this.client.on('connect', () => console.log('Connected to MQTT broker')) + // Connect options + const options = { + clientId: this.clientId, + protocolVersion: 5, + } + if (username) options.username = username + if (password) options.password = password + + this.client = mqtt.connect(brokerUrl, options) + this.subscriptions = [] // array of { topic, callback } + + this.client.on('connect', () => { + console.log(`Connected to MQTT broker as ${this.clientId}`) + }) this.client.on('message', (topic, payload) => { - // iterate over subscriptions and check for matches + const message = payload.toString() this.subscriptions.forEach(({ topic: subTopic, callback }) => { if (mqttMatch(subTopic, topic)) { - callback(payload.toString(), topic) + callback(message, topic) } }) }) } + /** + * Subscribe to a topic + * @param {string} topic + * @param {function} callback + */ subscribe(topic, callback) { this.subscriptions.push({ topic, callback }) this.client.subscribe(topic) } + /** + * Unsubscribe from a topic + * @param {string} topic + * @param {function|null} callback - optional, remove only this callback + */ + unsubscribe(topic, callback = null) { + this.subscriptions = this.subscriptions.filter((sub) => { + if (sub.topic !== topic) return true + if (callback && sub.callback !== callback) return true + return false + }) + + // Actually tell the broker to unsubscribe only if no more callbacks exist for this topic + const stillSubscribed = this.subscriptions.some((sub) => sub.topic === topic) + if (!stillSubscribed) { + this.client.unsubscribe(topic) + } + } + + /** + * Publish a message + * @param {string} topic + * @param {string|Buffer} message + * @param {object} options + */ publish(topic, message, options = {}) { this.client.publish(topic, message, options) } @@ -30,6 +82,6 @@ export default class MQTTService { // helper function for MQTT wildcards function mqttMatch(subTopic, topic) { // replace MQTT wildcards with RegExp - const regex = '^' + subTopic.replace('+', '[^/]+').replace('#', '.+') + '$' + const regex = '^' + subTopic.replace(/\+/g, '[^/]+').replace(/#/g, '.+') + '$' return new RegExp(regex).test(topic) }