Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
xieyonghong 2023-03-01 14:23:53 +08:00
commit a6c5a40a76
9 changed files with 645 additions and 42 deletions

View File

@ -485,11 +485,60 @@ export const getPropertiesInfo = (deviceId: string, data: Record<string, unknown
export const getPropertiesList = (deviceId: string, property: string, data: Record<string, unknown>) => server.post(`/device-instance/${deviceId}/property/${property}/_query`, data)
/**
*
* @param id
* @param transport
* @returns
*/
export const getProtocal = (id: string, transport: string) => server.get(`/protocol/${id}/transport/${transport}`)
/**
*
* @param productId
* @returns
*/
export const productCode = (productId: string) => server.get(`/device/transparent-codec/${productId}`)
/**
*
* @param productId
* @returns
*/
export const saveProductCode = (productId: string,data: Record<string, unknown>) => server.post(`/device/transparent-codec/${productId}`,data)
/**
*
* @param productId
* @param deviceId
* @returns
*/
export const deviceCode = (productId: string,deviceId:string) => server.get(`device/transparent-codec/${productId}/${deviceId}`)
/**
*
* @param productId
*
* @param deviceId
* @param data
* @returns
*/
export const saveDeviceCode = (productId: string,deviceId:string,data: Record<string, unknown>) => server.post(`/device/transparent-codec/${productId}/${deviceId}`,data)
/**
*
* @param data
* @returns
*/
export const testCode = (data: Record<string, unknown>) => server.post(`/device/transparent-codec/decode-test`,data)
/**
*
* @param productId
* @param deviceId
* @returns
*/
export const delDeviceCode = (productId: string, deviceId: string) => server.remove(`/device/transparent-codec/${productId}/${deviceId}`)
/**
*
* @param productId
* @returns
*/
export const delProductCode = (productId: string) => server.remove(`/device/transparent-codec/${productId}`)
export const queryLog = (deviceId: string, data: Record<string, unknown>) => server.post(`/device-instance/${deviceId}/logs`, data)
/**

View File

@ -58,6 +58,7 @@ const iconKeys = [
'PauseOutlined',
'ControlOutlined',
'RedoOutlined',
'ExpandOutlined',
'VideoCameraOutlined',
'HistoryOutlined',
'ToolOutlined',

View File

@ -1,12 +1,13 @@
<template>
<a-card>
<a-empty
v-if="!metadata || (metadata && !metadata.functions)"
style="margin-top: 100px"
v-if="!metadata || (metadata && !metadata.functions.length)"
style="margin-top: 50px"
>
<template #description>
暂无数据请配置
<a @click="emits('onJump', 'Metadata')">物模型</a>
请配置对应产品的
<!-- <a @click="emits('onJump', 'Metadata')">物模型属性功能</a> -->
<a @click="onJump">物模型属性功能</a>
</template>
</a-empty>
<template v-else>
@ -23,9 +24,12 @@
import { useInstanceStore } from '@/store/instance';
import Simple from './components/Simple.vue';
import Advance from './components/Advance.vue';
import { useMenuStore } from 'store/menu';
const menuStory = useMenuStore();
const instanceStore = useInstanceStore();
const emits = defineEmits(['onJump']);
// const emits = defineEmits(['onJump']);
const metadata = computed(() => JSON.parse(instanceStore.detail.metadata));
@ -34,6 +38,14 @@ const tabs = {
Simple,
Advance,
};
</script>
<style lang="less" scoped></style>
const onJump = () => {
menuStory.jumpPage(
'device/Product/Detail',
{
id: instanceStore.current.productId,
},
{ key: 'metadata' },
);
};
</script>

View File

@ -0,0 +1,306 @@
<template>
<a-card>
<div>
<div class="top">
<div class="top-left">
<div>
<AIcon type="ExclamationCircleOutlined" />
<template v-if="topTitle === 'rest'">
当前数据解析内容已脱离产品影响
<PermissionButton type="link" hasPermission="device/Instance:update" @click="rest()">
重置
</PermissionButton>
后将继承产品数据解析内容
</template>
<template v-else>
当前数据解析内容继承自产品,
<PermissionButton type="link" hasPermission="device/Instance:update" @click="readOnly = false"
:style="color">
修改
</PermissionButton>
后将脱离产品影响
</template>
</div>
</div>
<div>
脚本语言:
<a-select :defaultValue="'JavaScript'" style="width: 200;margin-left: 5px;">
<a-select-option value="JavaScript">JavaScript(ECMAScript 5)</a-select-option>
</a-select>
<AIcon type="ExpandOutlined" style="margin-left: 20px;" @click="toggle" />
</div>
</div>
<div class="edit" ref="el">
<div v-show="readOnly" class="edit-only" @click="() => {
message.warning({
key: 1,
content: () => '请点击上方修改字样,用以编辑脚本',
style: {
marginTop: '260px'
}
})
}"></div>
<MonacoEditor language="javascript" style="height: 100%;" theme="vs" v-model:modelValue="editorValue" />
</div>
<div class="bottom">
<div style="width: 49.5%;">
<div class="bottom-title">
<div class="bottom-title-text">模拟输入</div>
<div class="bottom-title-topic">
<template v-if="instanceStore.current.transport === 'MQTT'">
<div style="margin-right: 5px;">Topic:</div>
<a-auto-complete placeholder="请输入Topic" style="width: 300px" :options="topicList"
:allowClear="true" :filterOption="(inputValue: any, option: any) =>
option!.value.indexOf(inputValue) !== -1" v-model:value="topic" />
</template>
<template v-else>
<div style="margin-right: 5px;">URL:</div>
<a-input placeholder="请输入URL" v-model:value="url" style="width: 300px"></a-input>
</template>
</div>
</div>
<a-textarea :rows="5" placeholder="// 二进制数据以0x开头的十六进制输入字符串数据输入原始字符串" style="margin-top: 10px;"
v-model:value="simulation" />
</div>
<div style="width: 49.5%;">
<div class="bottom-title">
<div class="bottom-title-text">运行结果</div>
</div>
<a-textarea :autoSize="{ minRows: 5 }" :style="resStyle" v-model:value="result" />
</div>
</div>
</div>
<div style="margin-top: 10px;margin-left: 10px;">
<PermissionButton type="primary" hasPermission="device/Instance:update" :loading="loading"
:disabled="isDisabled" @click="debug()" :tooltip="{
title: '需输入脚本和模拟数据后再点击',
}">
调试
</PermissionButton>
<PermissionButton hasPermission="device/Instance:update" :loading="loading" :disabled="!isTest" @click="save()"
:style="{ marginLeft: '10px' }" :tooltip="{
title: isTest ? '' : '请先调试',
}">
保存
</PermissionButton>
</div>
</a-card>
</template>
<script setup lang='ts' name="Parsing">
import AIcon from '@/components/AIcon'
import PermissionButton from '@/components/PermissionButton/index.vue'
import MonacoEditor from '@/components/MonacoEditor/index.vue';
import { useFullscreen } from '@vueuse/core'
import { useInstanceStore } from '@/store/instance';
import {
deviceCode,
getProtocal,
testCode,
saveDeviceCode,
delDeviceCode
} from '@/api/device/instance'
import { message } from 'ant-design-vue';
import { isBoolean } from 'lodash';
const defaultValue =
'//解码函数\r\nfunction decode(context) {\r\n //原始报文\r\n var buffer = context.payload();\r\n // 转为json\r\n // var json = context.json();\r\n //mqtt 时通过此方法获取topic\r\n // var topic = context.topic();\r\n\r\n // 提取变量\r\n // var topicVars = context.pathVars("/{deviceId}/**",topic)\r\n //温度属性\r\n var temperature = buffer.getShort(3) * 10;\r\n //湿度属性\r\n var humidity = buffer.getShort(6) * 10;\r\n return {\r\n "temperature": temperature,\r\n "humidity": humidity\r\n };\r\n}\r\n';
const el = ref<HTMLElement | null>(null)
const { toggle } = useFullscreen(el)
const instanceStore = useInstanceStore();
const topTitle = ref<string>('')
const readOnly = ref<boolean>(true)
const url = ref<string>('')
const topic = ref<string>('')
const topicList = ref([])
const simulation = ref<string>('')
const resultValue = ref<any>({})
const loading = ref<boolean>(false)
const isTest = ref<boolean>(false)
const editorValue = ref<string>('')
const color = computed(() => ({
color: readOnly.value ? '#415ed1' : '#a6a6a6'
}))
const resStyle = computed(() => (isBoolean(resultValue.value.success) ? {
'margin-top': '10px',
'border-color': resultValue.value.success ? 'green' : 'red'
} : {
'margin-top': '10px',
}))
const isDisabled = computed(() => simulation.value === '')
const result = computed(() => resultValue.value.success ? JSON.stringify(resultValue.value.outputs?.[0]) : resultValue.value.reason)
//
const rest = async () => {
const res = await delDeviceCode(instanceStore.current.productId, instanceStore.current.id)
if (res.status === 200) {
getDeviceCode();
message.success('操作成功')
}
// service.delDeviceCode(productId, deviceId).then((res) => {
// if (res.status === 200) {
// getDeviceCode(productId, deviceId);
// onlyMessage('');
// }
// });
};
//topic
const getTopic = async () => {
const res: any = await getProtocal(instanceStore.current.protocol, instanceStore.current.transport)
if (res.status === 200) {
const item = res.result.routes?.map((items: any) => ({
value: items.topic,
}));
// setTopicList(item);
topicList.value = item
}
};
//
const getDeviceCode = async () => {
const res: any = await deviceCode(instanceStore.current.productId, instanceStore.current.id)
if (res.status === 200) {
const item = res.result?.configuration?.script ? res.result?.configuration?.script : defaultValue
if (res.result?.deviceId) {
readOnly.value = false
topTitle.value = 'rest'
editorValue.value = item
} else {
readOnly.value = true
topTitle.value = 'edit'
editorValue.value = item
}
}
}
//
const test = async (dataTest: any) => {
loading.value = true
const res = await testCode(dataTest)
if (res.status === 200) {
loading.value = false
resultValue.value = res?.result
} else {
loading.value = false
}
};
//
const save = async () => {
const item = {
provider: 'jsr223',
configuration: {
script: editorValue.value,
lang: 'javascript',
},
}
const res = await saveDeviceCode(instanceStore.current.productId, instanceStore.current.id, item)
if (res.status === 200) {
message.success('保存成功');
getDeviceCode();
}
};
const debug = () => {
if (instanceStore.current.transport === 'MQTT') {
if (topic.value !== '') {
test({
headers: {
topic: topic.value,
},
configuration: {
script: editorValue.value,
lang: 'javascript',
},
provider: 'jsr223',
payload: simulation.value,
})
isTest.value = true
} else {
message.error('请输入topic');
}
} else {
if (url.value !== '') {
test({
headers: {
url: url.value,
},
provider: 'jsr223',
configuration: {
script: editorValue.value,
lang: 'javascript',
},
payload: simulation.value,
});
isTest.value = true
} else {
message.error('请输入url');
}
}
}
onMounted(() => {
getDeviceCode()
getTopic()
})
</script>
<style scoped lang='less'>
.top {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
.top-left {
display: flex;
align-items: center;
}
}
.edit {
height: 550px;
border: 1px solid #dcdcdc;
.edit-only {
height: 550px;
width: 97%;
position: absolute;
z-index: 1;
background-color: #eeeeee70;
cursor: not-allowed;
}
}
.bottom {
display: flex;
justify-content: space-between;
padding: 10px;
background-color: '#f7f7f7';
.bottom-title {
display: flex;
justify-content: space-between;
.bottom-title-text {
font-weight: 600;
font-size: 14px;
margin-top: 10px;
}
.bottom-title-topic {
display: flex;
align-items: center;
}
}
}
</style>

View File

@ -79,7 +79,7 @@
<a-descriptions-item label="所属产品">
<PermissionButton
type="link"
style="margin-top: -5px; padding: 0"
style="margin-top: -5px; padding: 0"
@click="jumpProduct"
hasPermission="device/Product:view"
>
@ -116,6 +116,7 @@ import Function from './Function/index.vue';
import Modbus from './Modbus/index.vue';
import OPCUA from './OPCUA/index.vue';
import EdgeMap from './EdgeMap/index.vue';
import Parsing from './Parsing/index.vue'
import Log from './Log/index.vue'
import { _deploy, _disconnect } from '@/api/device/instance';
import { message } from 'ant-design-vue';
@ -172,6 +173,7 @@ const tabs = {
Modbus,
OPCUA,
EdgeMap,
Parsing,
Log
};
@ -281,6 +283,15 @@ watchEffect(() => {
tab: '边缘端映射',
});
}
if (
instanceStore.current.features?.find((item: any) => item.id === 'transparentCodec') &&
!keys.includes('Parsing')
) {
list.value.push({
key: 'Parsing',
tab: '数据解析',
});
}
});
onUnmounted(() => {

View File

@ -1,4 +1,246 @@
<!-- 数据解析 -->
<template></template>
<script></script>
<style></style>
<template>
<a-card>
<div>
<div class="top">
<div>
脚本语言:
<a-select :defaultValue="'JavaScript'" style="width: 200;margin-left: 5px;">
<a-select-option value="JavaScript">JavaScript(ECMAScript 5)</a-select-option>
</a-select>
<AIcon type="ExpandOutlined" style="margin-left: 20px;" @click="toggle" />
</div>
</div>
<div class="edit" ref="el">
<MonacoEditor language="javascript" style="height: 100%;" theme="vs" v-model:modelValue="editorValue" />
</div>
<div class="bottom">
<div style="width: 49.5%;">
<div class="bottom-title">
<div class="bottom-title-text">模拟输入</div>
<div class="bottom-title-topic">
<template v-if="productStore.current.transportProtocol === 'MQTT'">
<div style="margin-right: 5px;">Topic:</div>
<a-auto-complete placeholder="请输入Topic" style="width: 300px" :options="topicList"
:allowClear="true" :filterOption="(inputValue: any, option: any) =>
option!.value.indexOf(inputValue) !== -1" v-model:value="topic" />
</template>
<template v-else>
<div style="margin-right: 5px;">URL:</div>
<a-input placeholder="请输入URL" v-model:value="url" style="width: 300px"></a-input>
</template>
</div>
</div>
<a-textarea :rows="5" placeholder="// 二进制数据以0x开头的十六进制输入字符串数据输入原始字符串" style="margin-top: 10px;"
v-model:value="simulation" />
</div>
<div style="width: 49.5%;">
<div class="bottom-title">
<div class="bottom-title-text">运行结果</div>
</div>
<a-textarea :autoSize="{ minRows: 5 }" :style="resStyle" v-model:value="result" />
</div>
</div>
</div>
<div style="margin-top: 10px;margin-left: 10px;">
<PermissionButton type="primary" hasPermission="device/Instance:update" :loading="loading"
:disabled="isDisabled" @click="debug()" :tooltip="{
title: '需输入脚本和模拟数据后再点击',
}">
调试
</PermissionButton>
<PermissionButton hasPermission="device/Instance:update" :loading="loading" :disabled="!isTest" @click="save()"
:style="{ marginLeft: '10px' }" :tooltip="{
title: isTest ? '' : '请先调试',
}">
保存
</PermissionButton>
</div>
</a-card>
</template>
<script setup lang='ts' name="Parsing">
import AIcon from '@/components/AIcon'
import PermissionButton from '@/components/PermissionButton/index.vue'
import MonacoEditor from '@/components/MonacoEditor/index.vue';
import { useFullscreen } from '@vueuse/core'
import { useProductStore } from '@/store/product';
import {
productCode,
getProtocal,
testCode,
saveProductCode,
} from '@/api/device/instance'
import { message } from 'ant-design-vue';
import { isBoolean } from 'lodash';
const defaultValue =
'//解码函数\r\nfunction decode(context) {\r\n //原始报文\r\n var buffer = context.payload();\r\n // 转为json\r\n // var json = context.json();\r\n //mqtt 时通过此方法获取topic\r\n // var topic = context.topic();\r\n\r\n // 提取变量\r\n // var topicVars = context.pathVars("/{deviceId}/**",topic)\r\n //温度属性\r\n var temperature = buffer.getShort(3) * 10;\r\n //湿度属性\r\n var humidity = buffer.getShort(6) * 10;\r\n return {\r\n "temperature": temperature,\r\n "humidity": humidity\r\n };\r\n}\r\n';
const el = ref<HTMLElement | null>(null)
const { toggle } = useFullscreen(el)
const productStore = useProductStore()
const url = ref<string>('')
const topic = ref<string>('')
const topicList = ref([])
const simulation = ref<string>('')
const resultValue = ref<any>({})
const loading = ref<boolean>(false)
const isTest = ref<boolean>(false)
const editorValue = ref<string>('')
const resStyle = computed(() => (isBoolean(resultValue.value.success) ? {
'margin-top': '10px',
'border-color': resultValue.value.success ? 'green' : 'red'
} : {
'margin-top': '10px',
}))
const isDisabled = computed(() => simulation.value === '')
const result = computed(() => resultValue.value.success ? JSON.stringify(resultValue.value.outputs?.[0]) : resultValue.value.reason)
//topic
const getTopic = async () => {
const res: any = await getProtocal(productStore.current.messageProtocol, productStore.current.transportProtocol)
if (res.status === 200) {
const item = res.result.routes?.map((items: any) => ({
value: items.topic,
}));
topicList.value = item
}
};
//
const getProductCode = async () => {
const res: any = await productCode(productStore.current.id)
if (res.status === 200) {
if(res.result){
editorValue.value = res.result?.configuration?.script
}else{
editorValue.value = defaultValue
}
}
}
//
const test = async (dataTest: any) => {
loading.value = true
const res = await testCode(dataTest)
if (res.status === 200) {
loading.value = false
resultValue.value = res?.result
} else {
loading.value = false
}
};
//
const save = async () => {
const item = {
provider: 'jsr223',
configuration: {
script: editorValue.value,
lang: 'javascript',
},
}
const res = await saveProductCode(productStore.current.id, item)
if (res.status === 200) {
message.success('保存成功');
getProductCode();
}
};
const debug = () => {
if (productStore.current.transportProtocol === 'MQTT') {
if (topic.value !== '') {
test({
headers: {
topic: topic.value,
},
configuration: {
script: editorValue.value,
lang: 'javascript',
},
provider: 'jsr223',
payload: simulation.value,
})
isTest.value = true
} else {
message.error('请输入topic');
}
} else {
if (url.value !== '') {
test({
headers: {
url: url.value,
},
provider: 'jsr223',
configuration: {
script: editorValue.value,
lang: 'javascript',
},
payload: simulation.value,
});
isTest.value = true
} else {
message.error('请输入url');
}
}
}
onMounted(() => {
getProductCode()
getTopic()
})
</script>
<style scoped lang='less'>
.top {
display: flex;
justify-content: flex-end;
margin-bottom: 10px;
}
.edit {
height: 550px;
border: 1px solid #dcdcdc;
.edit-only {
height: 550px;
width: 97%;
position: absolute;
z-index: 1;
background-color: #eeeeee70;
cursor: not-allowed;
}
}
.bottom {
display: flex;
justify-content: space-between;
padding: 10px;
background-color: '#f7f7f7';
.bottom-title {
display: flex;
justify-content: space-between;
.bottom-title-text {
font-weight: 600;
font-size: 14px;
margin-top: 10px;
}
.bottom-title-topic {
display: flex;
align-items: center;
}
}
}
</style>

View File

@ -123,6 +123,7 @@ const tabs = {
Info,
Metadata,
Device,
DataAnalysis
};
watch(
@ -188,7 +189,7 @@ const handleUndeploy = async () => {
*/
const getProtocol = async () => {
if (productStore.current?.messageProtocol) {
const res = await getProtocolDetail(
const res:any = await getProtocolDetail(
productStore.current?.messageProtocol,
);
if (res.status === 200) {

View File

@ -637,33 +637,14 @@ const setPorts = () => {
const getDetail = async () => {
if (!route.query.id) return;
const res = await CascadeApi.detail(route.query.id as string);
const { id, name, proxyStream, sipConfigs } = res.result;
formData.value = {
id,
cascadeName: name,
proxyStream,
clusterNodeId: sipConfigs[0]?.clusterNodeId,
name: sipConfigs[0]?.name,
sipId: sipConfigs[0]?.sipId,
domain: sipConfigs[0]?.domain,
remoteAddress: sipConfigs[0]?.remoteAddress,
remotePort: sipConfigs[0]?.remotePort,
localSipId: sipConfigs[0]?.localSipId,
host: sipConfigs[0]?.host,
port: sipConfigs[0]?.port,
publicHost: sipConfigs[0]?.publicHost,
publicPort: sipConfigs[0]?.publicPort,
transport: sipConfigs[0]?.transport,
user: sipConfigs[0]?.user,
password: sipConfigs[0]?.password,
manufacturer: sipConfigs[0]?.manufacturer,
model: sipConfigs[0]?.model,
firmware: sipConfigs[0]?.firmware,
keepaliveInterval: sipConfigs[0]?.keepaliveInterval,
registerInterval: sipConfigs[0]?.registerInterval,
};
console.log('formData.value: ', formData.value);
const { id, name, proxyStream, sipConfigs, ...others } = res.result;
Object.keys(formData.value).forEach((key: string) => {
if (key === 'id') formData.value[key] = id;
else if (key === 'cascadeName') formData.value[key] = name;
else if (key === 'proxyStream') formData.value[key] = proxyStream;
else formData.value[key] = sipConfigs[0][key];
});
// console.log('formData.value: ', formData.value);
};
onMounted(() => {

View File

@ -55,8 +55,8 @@
:status="item.state?.value"
:statusText="item.state?.text"
:statusNames="{
online: 'enabled',
offline: 'disabled',
enabled: 'processing',
disabled: 'error',
}"
>
<template #img>