Skip to content

Commit

Permalink
Thing page: Support invoking Thing actions & Viewing their output (#2818
Browse files Browse the repository at this point in the history
)

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.

All keys of the action output response object from REST are rendered as
list Items, the labels are taken from the action output definitions and fallback to the key.
If the key is `qrCode` or its output type is defined as `qrCode`, its value is rendered as QR code.

For actions without inputs or without outputs, messages are shown indicating that there is no such.

---------

Signed-off-by: Florian Hotze <dev@florianhotze.com>
  • Loading branch information
florian-h05 authored Oct 22, 2024
1 parent 4100b56 commit 5febf02
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<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>
<template v-for="key of Object.keys(actionOutput)">
<!-- Render result as a list item, works without action output definition from REST -->
<f7-list-item v-if="key === 'result'" :key="key" :floating-label="$theme.md" title="Result">
{{ actionOutput[key] }}
</f7-list-item>
<!-- Render QR code if the key is qrCode -->
<f7-list-item v-else-if="key === 'qrCode'" :key="key" :floating-label="$theme.md" :title="action.outputs.find(o => o.name === key)?.label || 'QR Code'">
<vue-qrcode :value="actionOutput[key]" />
</f7-list-item>
<!-- Render QR code if the action output type is qrCode in the action output definition from REST -->
<f7-list-item v-else-if="action.outputs.find(o => o.name === key)?.type === 'qrCode'" :key="key" :floating-label="$theme.md" :title="action.outputs.find(o => o.name === key).label">
<vue-qrcode :value="actionOutput[key]" />
</f7-list-item>
<!-- Render other keys as list items with the label defined by the action output definition from REST or the key as label -->
<f7-list-item v-else :key="key" :floating-label="$theme.md" :title="action.outputs.find(o => o.name === key)?.label || key">
{{ actionOutput[key] }}
</f7-list-item>
</template>
<f7-list-item accordion-item title="Raw Output Value">
<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
@@ -1,7 +1,7 @@
<template>
<f7-page @page:afterin="onPageAfterIn" @page:beforeout="onPageBeforeOut" class="thing-details-page">
<f7-navbar :title="pageTitle + dirtyIndicator" back-link="Back" no-hairline>
<f7-nav-right v-show="!error && ready">
<f7-nav-right v-if="!error && ready">
<f7-link v-if="!editable" icon-f7="lock_fill" icon-only tooltip="This Thing is not editable through the UI" />
<f7-link v-else-if="$theme.md" icon-md="material:save" icon-only @click="save()" />
<f7-link v-else @click="save()">
Expand Down 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 5febf02

Please sign in to comment.