blob: fde99e0b1f5575c118a0b8fd9b4b11a33d3af319 [file] [edit]
<template>
<q-page padding class="row justify-center full-height" :class="getuitheme">
<Transition name="slide-up" mode="out-in" appear>
<q-card flat class="q-mt-lg q-rounded-borders-xl bg-glass col-10 column">
<q-scroll-area class="col q-px-xl">
<div class="q-py-lg">
<Transition name="slide-up" mode="out-in" appear>
<div class="row justify-center q-col-gutter-sm text-center">
<img
v-for="(image, index) in getLogos(selectedZclPropertiesData)"
:key="index"
:src="image"
height="40"
class="q-mt-md w-fit-content"
/>
</div>
</Transition>
<div
v-if="isMultiProtocolConfiguration"
class="q-mx-auto w-fit-content text-primary"
>
Multiprotocol
</div>
<div class="row justify-center q-mt-md">
<q-radio
v-if="loadPreSessionData.length"
v-model="customConfig"
checked-icon="task_alt"
unchecked-icon="panorama_fish_eye"
val="select"
label="Generate New Session"
/>
<q-radio
v-if="loadPreSessionData.length"
v-model="customConfig"
checked-icon="task_alt"
class="q-ml-xl"
unchecked-icon="panorama_fish_eye"
val="load"
label="Restore Unsaved Session"
/>
</div>
<p class="text-center" v-if="isPackageSelected" style="color: red">
Warning: Please select atleast one package each from ZCL metadata
and Templates. If a package is not selected then internal packages
used for testing will be loaded automatically.
</p>
<p
class="text-center"
v-if="isMultiProtocolConfiguration"
style="color: red"
>
Warning: More than one ZCL packages with different categories have
been selected. You are initiating a ZAP configuration for
multi-protocol.
</p>
<p
class="text-center"
v-if="isMissalignedZclAndTemplateConfig"
style="color: red"
>
Warning: Corresponding ZCL and Template packages are not enabled
based on the same category.
</p>
<p
class="text-center"
v-if="isMultiplePackage && customConfig === 'select'"
>
There are multiple packages of ZCL metadata loaded. Please select
the one you wish to use with this configuration. Packages within
an existing .zap file will come pre-selected when available.
However please update the packages in case of a SDK upgrade such
that you are using the latest version of the packages.
</p>
<p class="text-center" v-else-if="customConfig === 'load'">
These are sessions found in the database that were not saved into
a .zap file. You can select them here, and continue the work with
the configuration.
</p>
<template v-if="customConfig === 'select'">
<q-table
title="Zigbee Cluster Library metadata"
:rows="zclPropertiesRow"
:columns="newSessionCol"
row-key="name"
:pagination="newGenerationPagination"
hide-bottom
flat
:card-style="{ backgroundColor: 'transparent' }"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props" class="table_body">
<q-td key="select" :props="props">
<q-checkbox
v-model="selectedZclPropertiesDataIds"
:val="props.row.id"
/>
</q-td>
<q-td key="category" :props="props">
<div>{{ props.row.category }}</div>
<q-tooltip :offset="[5, 5]">
{{ props.row.path }}
</q-tooltip>
</q-td>
<q-td key="description" :props="props">
<div>{{ props.row.description }}</div>
<q-tooltip :offset="[5, 5]">
{{ props.row.path }}
</q-tooltip>
</q-td>
<q-td key="version" :props="props">
<div>{{ props.row.version }}</div>
</q-td>
<q-td key="status" :props="props">
<div v-if="props.row.hasWarning || props.row.hasError">
<q-icon
class="cursor-pointer"
:name="props.row.hasError ? 'error' : 'warning'"
:color="props.row.hasError ? 'red' : 'orange'"
size="2.5em"
@click="propertyDataDialog[props.row.id] = true"
/>
<q-dialog v-model="propertyDataDialog[props.row.id]">
<q-card>
<q-card-section>
<div class="row items-center">
<div class="col-1">
<q-icon
:name="
props.row.hasError ? 'error' : 'warning'
"
:color="
props.row.hasError ? 'red' : 'orange'
"
size="2em"
/>
</div>
<div class="text-h6 col">
{{ props.row.description }}
</div>
<div class="col-1 text-right">
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip>Close</q-tooltip>
</q-btn>
</div>
</div>
<div v-if="props.row.hasError">
<div
class="text-h6"
style="margin-top: 15px; padding-left: 20px"
>
Errors
</div>
<ul>
<li
v-for="(error, index) in props.row.errors"
:key="'error' + index"
style="margin-bottom: 10px"
>
{{ error }}
</li>
</ul>
</div>
<div v-if="props.row.hasWarning">
<div
class="text-h6"
style="margin-top: 15px; padding-left: 20px"
>
Warnings
</div>
<ul>
<li
v-for="(warning, index) in props.row
.warnings"
:key="index"
style="margin-bottom: 10px"
>
{{ warning }}
</li>
</ul>
</div>
</q-card-section>
</q-card>
</q-dialog>
</div>
<q-icon
v-else
name="check_circle"
color="green"
size="2em"
/>
</q-td>
</q-tr>
</template>
</q-table>
<q-table
title="Zap Generation Templates"
:rows="zclGenRow"
:columns="newSessionCol"
row-key="name"
:pagination="newGenerationPagination"
hide-bottom
flat
:card-style="{ backgroundColor: 'transparent' }"
>
<template v-slot:top>
<div class="q-table__title q-mr-md">
Zap Generation Templates
</div>
<small>( Please select a package for generation )</small>
</template>
<template v-slot:header="props">
<q-tr :props="props">
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props" class="table_body">
<q-td key="select" :props="props">
<q-checkbox
v-model="selectedZclGenData"
:val="props.row.id"
data-test="gen-template"
/>
</q-td>
<q-td key="category" :props="props">
<div>{{ props.row.category }}</div>
<q-tooltip :offset="[5, 5]">
{{ props.row.path }}
</q-tooltip>
</q-td>
<q-td key="description" :props="props">
<div>{{ props.row.description }}</div>
<q-tooltip :offset="[5, 5]">
{{ props.row.path }}
</q-tooltip>
</q-td>
<q-td key="version" :props="props">
<div>{{ props.row.version }}</div>
</q-td>
<q-td key="status" :props="props">
<div v-if="props.row.hasWarning || props.row.hasError">
<q-icon
class="cursor-pointer"
:name="props.row.hasError ? 'error' : 'warning'"
:color="props.row.hasError ? 'red' : 'orange'"
size="2.5em"
@click="genDataDialog[props.row.id] = true"
></q-icon>
<q-dialog v-model="genDataDialog[props.row.id]">
<q-card>
<q-card-section>
<div class="row items-center">
<div class="col-1">
<q-icon
:name="
props.row.hasError ? 'error' : 'warning'
"
:color="
props.row.hasError ? 'red' : 'orange'
"
size="2em"
/>
</div>
<div class="text-h6 col">
{{ props.row.description }}
</div>
<div class="col-1 text-right">
<q-btn dense flat icon="close" v-close-popup>
<q-tooltip>Close</q-tooltip>
</q-btn>
</div>
</div>
<div v-if="props.row.hasError">
<div
class="text-h6"
style="margin-top: 15px; padding-left: 20px"
>
Errors
</div>
<ul>
<li
v-for="(error, index) in props.row.errors"
:key="'error' + index"
style="margin-bottom: 10px"
>
{{ error }}
</li>
</ul>
</div>
<div v-if="props.row.hasWarning">
<div
class="text-h6"
style="margin-top: 15px; padding-left: 20px"
>
Warnings
</div>
<ul>
<li
v-for="(warning, index) in props.row
.warnings"
:key="index"
style="margin-bottom: 10px"
>
{{ warning }}
</li>
</ul>
</div>
</q-card-section>
</q-card>
</q-dialog>
</div>
<q-icon
v-else
name="check_circle"
color="green"
size="2em"
/>
</q-td>
</q-tr>
</template>
</q-table>
</template>
<template v-else>
<q-table
title=""
:rows="loadPreSessionData"
:columns="loadPreSessionCol"
row-key="name"
:pagination="pagination"
flat
:card-style="{ backgroundColor: 'transparent' }"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props" class="table_body">
<q-td key="select" :props="props">
<q-radio
v-model="selectedZclSessionData"
checked-icon="task_alt"
unchecked-icon="panorama_fish_eye"
:val="props.row"
/>
</q-td>
<q-td key="zclproperty" :props="props">
<div>{{ props.row.zclProperty.description }}</div>
</q-td>
<q-td key="gen template file" :props="props">
<div>{{ props.row.genTemplateFile.version }}</div>
</q-td>
<q-td key="creation time" :props="props">
<div>
{{ new Date(props.row.creationTime).toDateString() }}
</div>
</q-td>
</q-tr>
</template>
</q-table>
</template>
<div class="row justify-center q-mt-xl">
<q-btn
color="primary"
@click="submitForm"
label="Submit"
data-test="login-submit"
/>
</div>
</div>
</q-scroll-area>
</q-card>
</Transition>
</q-page>
</template>
<script>
import restApi from '../../src-shared/rest-api.js'
import dbEnum from '../../src-shared/db-enum.js'
import { QSpinnerGears } from 'quasar'
import { setCssVar } from 'quasar'
import CommonMixin from '../util/common-mixin'
const generateNewSessionCol = [
{
name: 'select',
label: '',
align: 'center',
style: 'width: 20%'
},
{
name: 'category',
align: 'left',
label: 'Category',
style: 'width: 20%'
},
{
name: 'description',
label: 'Description',
align: 'left',
style: 'width: 25%'
},
{
name: 'version',
label: 'version',
align: 'left',
style: 'width: 20%'
},
{
name: 'status',
label: 'status',
align: 'left',
style: 'width: 15%'
}
]
const loadPreSessionCol = [
{
name: 'select',
label: 'Session',
align: 'center'
},
{
name: 'zclproperty',
label: 'ZCL Property',
align: 'center'
},
{
name: 'gen template file',
align: 'left',
label: 'Generation Template File'
},
{
name: 'creation time',
label: 'Creation Time',
align: 'left'
}
]
export default {
name: 'ZapConfig',
mixins: [CommonMixin],
data() {
return {
customConfig: 'select',
selected: [],
selectedZclPropertiesDataIds: [],
selectedZclPropertiesData: [],
selectedZclGenData: [],
selectZclGenInfo: [],
selectedZclSessionData: null,
zclPropertiesRow: [],
newSessionCol: generateNewSessionCol,
loadPreSessionCol: loadPreSessionCol,
zclGenRow: [],
newConfig: false,
path: window.location,
open: true,
filePath: '',
loadPreSessionData: [],
pagination: {
rowsPerPage: 10
},
newGenerationPagination: {
rowsPerPage: 0
},
propertyDataDialog: {},
genDataDialog: {},
currentZapFilePackages: []
}
},
computed: {
// Checks if atleast one zcl and template packages have been selected
isPackageSelected: function () {
if (this.customConfig === 'select')
return (
this.selectedZclPropertiesData.length == 0 ||
this.selectedZclGenData.length == 0
)
else return this.selectedZclSessionData == null
},
// Checks if package selection is leading to a multi-protocol configuration
isMultiProtocolConfiguration: function () {
let categorySet = []
this.selectedZclPropertiesData.forEach((prop) => {
if (!categorySet.includes(prop.category)) {
categorySet.push(prop.category)
}
})
let result = false
result =
this.customConfig === 'select'
? categorySet.length > 1
: this.selectedZclSessionData == null
this.$store.commit('zap/setMultiConfig', result)
return result
},
// Checks for missaligned zcl and template package selection
isMissalignedZclAndTemplateConfig: function () {
if (this.customConfig === 'select') {
let zclCategorySet = []
let templateCategorySet = []
this.selectedZclPropertiesData.forEach((prop) => {
if (!zclCategorySet.includes(prop.category)) {
zclCategorySet.push(prop.category)
}
})
this.selectZclGenInfo.forEach((prop) => {
if (!templateCategorySet.includes(prop.category)) {
templateCategorySet.push(prop.category)
}
})
if (zclCategorySet.length !== templateCategorySet.length) {
return true
}
zclCategorySet.sort()
templateCategorySet.sort()
for (let i = 0; i < zclCategorySet.length; i++) {
if (zclCategorySet[i] != templateCategorySet[i]) {
return true
}
}
return false
} else {
return this.selectedZclSessionData == null
}
},
isMultiplePackage: function () {
return this.zclPropertiesRow.length > 1
},
getuitheme: function () {
if (this.isMultiProtocolConfiguration) {
return 'multiprotocol'
} else {
return this.selectedZclPropertiesData[0]?.category
}
}
},
watch: {
getuitheme() {
this.addClassToBody()
},
// Updating this.selectedZclPropertiesData based on UI selections
selectedZclPropertiesDataIds() {
this.selectedZclPropertiesData = this.zclPropertiesRow.filter((zpr) =>
this.selectedZclPropertiesDataIds.includes(zpr.id)
)
},
// Updating this.selectZclGenInfo based on UI selections
selectedZclGenData() {
this.selectZclGenInfo = this.zclGenRow.filter((zgr) =>
this.selectedZclGenData.includes(zgr.id)
)
}
},
methods: {
addClassToBody() {
document.body.classList.remove('matter', 'zigbee', 'multiprotocol')
document.body.classList.add(this.getuitheme)
},
submitForm() {
if (this.customConfig === 'select') {
let data = {
zclProperties: this.selectedZclPropertiesData,
genTemplate: this.selectedZclGenData
}
this.$router.push({ path: '/' })
this.$q.loading.show({
spinner: QSpinnerGears,
messageColor: 'white',
message: 'Please wait while zap is loading...',
spinnerSize: 300
})
if (this.open) {
this.$serverPost(restApi.uri.sessionCreate, data)
.then(() => this.$serverPost(restApi.ide.open, this.path))
.then(() => {
this.$store.commit('zap/selectZapConfig', {
zclProperties: this.selectedZclPropertiesData,
genTemplate: this.selectedZclGenData,
newConfig: false
})
})
} else {
this.$serverPost(restApi.uri.sessionCreate, data).then(() => {
this.$store.commit('zap/selectZapConfig', {
zclProperties: this.selectedZclPropertiesData,
genTemplate: this.selectedZclGenData,
newConfig: true
})
})
}
} else {
this.$serverPost(restApi.uri.reloadSession, {
sessionId: this.selectedZclSessionData.id
}).then((result) => {
this.$store.commit('zap/selectZapConfig', {
sessionId: this.selectedZclSessionData.id,
zclProperties: this.selectedZclSessionData.zclProperty
})
})
}
},
// classify all notifications by type and package ref
classifyMessages(data) {
return data.reduce(
(map, row) => {
const key = row.type === 'ERROR' ? 'errorMap' : 'warningMap'
if (!map[key][row.ref]) {
map[key][row.ref] = []
}
map[key][row.ref].push(row.message)
return map
},
{ warningMap: {}, errorMap: {} }
)
},
// then assign classfied notifications to proper packages
assignWarningsAndErrors(data, warningMap, errorMap) {
data.forEach((row) => {
if (warningMap[row.id]) {
row.hasWarning = true
row.warnings = warningMap[row.id]
} else {
row.hasWarning = false
row.warnings = []
}
if (errorMap[row.id]) {
row.hasError = true
row.errors = errorMap[row.id]
} else {
row.hasError = false
row.errors = []
}
// set up var for dialog component of each package
this.propertyDataDialog[row.id] = false
this.genDataDialog[row.id] = false
})
},
// Create an absolute path for relative paths of the packages
createAbsolutePath(basePath, relativePath) {
let lastIndex = basePath.lastIndexOf('/')
basePath = basePath.substring(0, lastIndex)
let relativePathSegment = relativePath.split('/')
while (relativePathSegment[0] === '..') {
basePath = basePath.split('/')
basePath.pop()
basePath = basePath.join('/')
relativePathSegment.shift()
}
let absolutePath = basePath + '/' + relativePathSegment.join('/')
return absolutePath
}
},
created() {
this.$serverPost(restApi.uri.sessionAttempt, this.path).then((result) => {
this.zclPropertiesRow = result.data.zclProperties
this.zclGenRow = result.data.zclGenTemplates
this.filePath = result.data.filePath
this.open = result.data.open
this.currentZapFilePackages = result.data.zapFilePackages
let currentZapFileZclPackages = []
let currentTopLevelZapFilePackages = []
let currentZapFileTemplatePackages = []
let currentZclPackagesAbsolutePaths = []
let currentTemplatePackagesAbsolutePaths = []
if (this.currentZapFilePackages) {
currentTopLevelZapFilePackages = this.currentZapFilePackages.filter(
(zfp) => zfp.type != dbEnum.packageType.zclXmlStandalone
)
currentZapFileZclPackages = this.currentZapFilePackages.filter(
(zfp) => zfp.type == dbEnum.packageType.zclProperties
)
currentZapFileTemplatePackages = this.currentZapFilePackages.filter(
(zfp) => zfp.type == dbEnum.packageType.genTemplatesJson
)
currentZclPackagesAbsolutePaths = currentZapFileZclPackages.map((zfp) =>
this.createAbsolutePath(this.filePath, zfp.path)
)
currentTemplatePackagesAbsolutePaths =
currentZapFileTemplatePackages.map((zfp) =>
this.createAbsolutePath(this.filePath, zfp.path)
)
}
if (
this.zclPropertiesRow.length == currentZapFileZclPackages.length ||
this.zclPropertiesRow.length == 1
) {
if (
this.zclGenRow.length == currentZapFileTemplatePackages.length ||
this.zclGenRow.length == 1
) {
this.selectedZclGenData = this.zclGenRow.map((zgr) => zgr.id)
this.selectedZclPropertiesDataIds = this.zclPropertiesRow.map(
(zpr) => zpr.id
)
this.selectedZclPropertiesData = this.zclPropertiesRow
}
this.customConfig = 'select'
this.submitForm()
} else {
let selectableZclPackages = this.zclPropertiesRow.filter((zp) =>
currentZclPackagesAbsolutePaths.includes(zp.path)
)
let selectableTemplatePackages = this.zclGenRow.filter((zt) =>
currentTemplatePackagesAbsolutePaths.includes(zt.path)
)
this.selectedZclPropertiesDataIds = selectableZclPackages.map(
(zp) => zp.id
)
this.selectedZclPropertiesData = selectableTemplatePackages
this.selectedZclGenData = selectableTemplatePackages.map((zt) => zt.id)
this.customConfig = 'select'
// Do not show the config page when the packages from the .zap file are found
if (
this.selectedZclPropertiesData &&
this.selectedZclPropertiesData.length > 0 &&
this.selectedZclGenData &&
this.selectedZclGenData.length > 0 &&
this.selectedZclPropertiesData.length +
this.selectedZclGenData.length ==
currentTopLevelZapFilePackages.length
) {
this.submitForm()
}
}
result.data.sessions.forEach((item) => {
let atts = null
let gen = null
item.packageRef.forEach((element) => {
!atts
? (atts = this.zclPropertiesRow.find((data) => data.id === element))
: ''
!gen ? (gen = this.zclGenRow.find((data) => data.id === element)) : ''
})
this.loadPreSessionData.push({
zclProperty: atts,
genTemplateFile: gen,
creationTime: item.creationTime,
id: item.sessionId
})
})
// load package notification data, and assign to both zclProperty & zclGen packages
this.$serverGet(restApi.uri.packageNotification)
.then((resp) => {
const { warningMap, errorMap } = this.classifyMessages(resp.data)
this.assignWarningsAndErrors(
this.zclPropertiesRow,
warningMap,
errorMap
)
this.assignWarningsAndErrors(this.zclGenRow, warningMap, errorMap)
})
.catch((err) => {
console.log(err)
})
})
}
}
</script>
<style lang="scss" scoped>
.q-page {
background: url('/bg-matter.jpg');
background-size: cover;
transition: 2s;
&.zigbee {
background: url('/bg-zigbee.jpg');
}
&.multiprotocol {
background: url('/bg-multiprotocol.jpg');
}
}
.slide-up-enter-active,
.slide-up-leave-active {
transition: all 0.55s ease-out;
}
.slide-up-enter-from {
opacity: 0;
transform: translateY(30px);
}
.slide-up-leave-to {
opacity: 0;
transform: translateY(-30px);
}
</style>