Skip to content

Commit

Permalink
Thing page: Add support for invoking Thing actions & Viewing action o…
Browse files Browse the repository at this point in the history
…utput

Refs openhab/openhab-core#4392.
Closes #2817.

This adds a new section "Actions" to the Thing tab of the Thing page,
which provides a button for each UI-supported Thing action.
Clicking on that button will open a popup,
where action input can be configured and action output can be viewed.

Currently, action output is displayed pretty for the `result`, `qrPairingCode` and `manualPairingCode` keys of the response object.
In addition to that, the raw output can be viewed.

Signed-off-by: Florian Hotze <dev@florianhotze.com>
  • Loading branch information
florian-h05 committed Oct 21, 2024
1 parent a1787ed commit e2d6cc7
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<template>
<f7-popup ref="modulePopup" class="moduleconfig-popup">
<f7-page>
<f7-navbar>
<f7-nav-left>
<f7-link icon-ios="f7:arrow_left" icon-md="material:arrow_back" icon-aurora="f7:arrow_left" popup-close />
</f7-nav-left>
<f7-nav-title>
{{ action.label }}
</f7-nav-title>
<f7-nav-right>
<f7-link @click="close">
Close
</f7-link>
</f7-nav-right>
</f7-navbar>
<f7-block class="no-margin no-padding">
<!-- Action Inputs -->
<f7-col>
<f7-block-title class="parameter-group-title">
Action Input
</f7-block-title>
<config-sheet v-if="action.inputConfigDescriptions.length > 0" ref="configSheet"
:parameter-groups="[]" :parameters="action.inputConfigDescriptions"
:configuration="actionInput" :read-only="executing" />
<div class="margin" v-else>
There is no input to be configured for this action.
</div>
</f7-col>
<!-- Executing Spinner -->
<f7-block v-if="executing" class="text-align-center padding-top margin-top">
<f7-block-title>
<f7-preloader :size="30" />
<div>Executing...</div>
</f7-block-title>
</f7-block>
<!-- Execute Button -->
<f7-col v-if="!executing">
<f7-list>
<f7-list-button color="blue" title="Execute Action" @click="execute" />
</f7-list>
</f7-col>
<!-- Action Outputs -->
<f7-col v-if="!executing && actionOutput">
<f7-block-title class="parameter-group-title">
Action Output
</f7-block-title>
<div v-if="Object.keys(actionOutput).length === 0" class="margin">
There is either no output for this action or something went wrong - please check the logs.
</div>
<div v-else>
<f7-list>
<f7-list-item v-if="actionOutput.result" :floating-label="$theme.md" title="Result">
{{ actionOutput.result }}
</f7-list-item>
<f7-list-item v-if="actionOutput.qrPairingCode" :floating-label="$theme.md" title="QR Pairing Code">
<vue-qrcode :value="actionOutput.qrPairingCode" />
</f7-list-item>
<f7-list-item v-if="actionOutput.manualPairingCode" :floating-label="$theme.md" title="Manual Pairing Code">
{{ actionOutput.manualPairingCode }}
</f7-list-item>
<f7-list-item accordion-item title="Raw Response">
<f7-accordion-content class="thing-type-description">
<div class="margin">
<code> {{ actionOutput }} </code>
</div>
</f7-accordion-content>
</f7-list-item>
</f7-list>
</div>
</f7-col>
</f7-block>
</f7-page>
</f7-popup>
</template>

<script>
import ConfigSheet from '@/components/config/config-sheet.vue'
export default {
components: {
ConfigSheet,
'vue-qrcode': () => import(/* webpackChunkName: "vue-qrcode" */ 'vue-qrcode')
},
props: ['thingUID', 'action'],
data () {
return {
executing: false,
actionInput: {},
actionOutput: null
}
},
methods: {
execute () {
if (this.$refs.configSheet?.isValid() === false) {
this.$f7.dialog.alert('Please review the input and correct validation errors')
return
}
this.executing = true
this.$oh.api.post(`/rest/actions/${this.thingUID}/${this.action.actionUid}`, this.actionInput)
.then((data) => {
this.actionOutput = data
this.executing = false
})
},
close () {
this.$refs.modulePopup.close()
}
}
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@
:status="configStatusInfo"
:set-empty-config-as-null="true"
:read-only="!editable" />

<template v-if="thingActions.length > 0">
<f7-block-title medium class="no-margin-top">
Actions
</f7-block-title>
<f7-list class="margin-top">
<f7-list-button v-for="action in thingActions" color="blue" :key="action.name" :title="action.label" @click="doThingAction(action)" />
</f7-list>
</template>
</f7-col>
</f7-block>
<!-- skeletons for not ready -->
Expand Down Expand Up @@ -266,6 +275,7 @@ import buildTextualDefinition from './thing-textual-definition'
import ThingStatus from '@/components/thing/thing-status-mixin'
import DirtyMixin from '../dirty-mixin'
import ThingActionPopup from '@/pages/settings/things/thing-action-popup.vue'
let copyToast = null
Expand All @@ -291,7 +301,11 @@ export default {
thingType: {},
channelTypes: {},
configDescriptions: {},
thingActions: [],
configStatusInfo: [],
/**
* @deprecated
*/
configActionsByGroup: [],
thingEnabled: true,
codePopupOpened: false,
Expand Down Expand Up @@ -397,6 +411,24 @@ export default {
this.thingYaml = this.toYaml()
}
},
/**
* Loads the Thing actions.
*
* @returns {Promise<void>}
*/
loadThingActions () {
return this.$oh.api.get('/rest/actions/' + this.thingId).then(data => {
this.thingActions = data
return Promise.resolve()
}).catch(err => {
if (err === 'Not Found' || err === 404) {
console.log('No actions available for this Thing')
return Promise.resolve()
}
console.error('Error loading thing actions: ' + err)
return Promise.reject(err)
})
},
load () {
// if (this.ready) return
if (this.loading) return
Expand All @@ -413,10 +445,11 @@ export default {
this.$oh.api.get('/rest/things/' + this.thingId).then(data => {
this.$set(this, 'thing', data)
let typePromises = [this.$oh.api.get('/rest/thing-types/' + this.thing.thingTypeUID),
this.$oh.api.get('/rest/channel-types?prefixes=system,' + this.thing.thingTypeUID.split(':')[0])]
const promises = [this.$oh.api.get('/rest/thing-types/' + this.thing.thingTypeUID),
this.$oh.api.get('/rest/channel-types?prefixes=system,' + this.thing.thingTypeUID.split(':')[0]),
this.loadThingActions()]
Promise.all(typePromises).then(data2 => {
Promise.all(promises).then(data2 => {
this.thingType = data2[0]
this.channelTypes = data2[1]
Expand Down Expand Up @@ -490,6 +523,9 @@ export default {
}
return uiActions
},
/**
* @deprecated to be removed once all Things that use config actions use real Thing actions instead
*/
getBindingActions (configDescriptionsResponse) {
// Returns an array of parameters which qualify as "actions", grouped by the paramGroup. The actions themselves are enriched by execute() method
let actionContextGroups = configDescriptionsResponse.parameterGroups.filter((pg) => pg.context === 'actions')
Expand Down Expand Up @@ -546,6 +582,23 @@ export default {
}).open()
})
},
doThingAction (action) {
const popup = {
component: ThingActionPopup
}
this.$f7router.navigate({
url: 'thing-action',
route: {
path: 'thing-action',
popup
}
}, {
props: {
thingUID: this.thingId,
action
}
})
},
doConfigAction (action) {
if (action.type !== 'BOOLEAN') {
console.warn('Invalid action type', action)
Expand Down

0 comments on commit e2d6cc7

Please sign in to comment.