github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/static_source/admin/src/views/Entities/edit.vue (about)

     1  <script setup lang="ts">
     2  import {computed, onMounted, onUnmounted, reactive, ref, unref} from 'vue'
     3  import {useI18n} from '@/hooks/web/useI18n'
     4  import {ElButton, ElMessage, ElPopconfirm, ElTabPane, ElTabs} from 'element-plus'
     5  import {useRoute, useRouter} from 'vue-router'
     6  import api from "@/api/api";
     7  import Form from './components/Form.vue'
     8  import {ContentWrap} from "@/components/ContentWrap";
     9  import {Entity, EntityAction, EntityState, Plugin} from "@/views/Entities/components/types";
    10  import Actions from "@/views/Entities/components/Actions.vue";
    11  import {useEmitt} from "@/hooks/web/useEmitt";
    12  import {
    13    ApiArea,
    14    ApiAttribute,
    15    ApiEntityAction,
    16    ApiEntityState,
    17    ApiPlugin,
    18    ApiScript,
    19    ApiUpdateEntityRequestAction,
    20    ApiUpdateEntityRequestState
    21  } from "@/api/stub";
    22  import States from "@/views/Entities/components/States.vue";
    23  import {AttributesEditor} from "@/components/Attributes";
    24  import {Dialog} from '@/components/Dialog'
    25  import {JsonViewer} from "@/components/JsonViewer";
    26  import {copyToClipboard} from "@/utils/clipboard";
    27  import {EventStateChange} from "@/api/types";
    28  import {UUID} from "uuid-generator-ts";
    29  import stream from "@/api/stream";
    30  import Storage from "@/views/Entities/components/Storage.vue";
    31  import Metrics from "@/views/Entities/components/Metrics.vue";
    32  
    33  const {push} = useRouter()
    34  const route = useRoute();
    35  const {t} = useI18n()
    36  
    37  const writeRef = ref<ComponentRef<typeof Form>>()
    38  const loading = ref(false)
    39  const entityId = computed(() => route.params.id as string);
    40  const currentEntity = ref<Nullable<Entity>>(null)
    41  const activeTab = ref('attributes')
    42  const dialogSource = ref({})
    43  const dialogVisible = ref(false)
    44  const lastEvent = ref<Nullable<EventStateChange>>(null)
    45  
    46  interface Internal {
    47    attributes: ApiAttribute[];
    48    settings: ApiAttribute[];
    49  }
    50  
    51  const internal = reactive<Internal>(
    52      {
    53        attributes: [],
    54        settings: [],
    55      },
    56  );
    57  
    58  const fetch = async () => {
    59    loading.value = true
    60    const res = await api.v1.entityServiceGetEntity(entityId.value)
    61        .catch(() => {
    62        })
    63        .finally(() => {
    64          loading.value = false
    65        })
    66    if (res) {
    67      const entity = res.data as Entity
    68  
    69      // attributes
    70      internal.attributes = [];
    71      if (entity.attributes) {
    72        for (const key in entity.attributes) {
    73          internal.attributes.push(entity.attributes[key]);
    74        }
    75      }
    76  
    77      // settings
    78      internal.settings = [];
    79      if (entity.settings) {
    80        for (const key in entity.settings) {
    81          internal.settings.push(entity.settings[key]);
    82        }
    83      }
    84  
    85      currentEntity.value = entity
    86  
    87      await fetchPlugin()
    88  
    89      loading.value = false
    90    } else {
    91      currentEntity.value = null
    92    }
    93  }
    94  
    95  const fetchPlugin = async () => {
    96    loading.value = true
    97    const res = await api.v1.pluginServiceGetPlugin(currentEntity.value.pluginName)
    98    if (res) {
    99      const plugin = res.data as ApiPlugin;
   100  
   101      // attributes
   102      let actorAttrs: ApiAttribute[] = [];
   103      if (plugin.options?.actorAttrs) {
   104        for (const key in plugin.options.actorAttrs) {
   105          actorAttrs.push(plugin.options.actorAttrs[key]);
   106        }
   107      }
   108  
   109      // actorSetts
   110      let actorSetts: ApiAttribute[] = [];
   111      if (plugin.options?.actorSetts) {
   112        for (const key in plugin.options.actorSetts) {
   113          actorSetts.push(plugin.options.actorSetts[key]);
   114        }
   115      }
   116  
   117      // setts
   118      let setts: ApiAttribute[] = [];
   119      if (plugin.options?.setts) {
   120        for (const key in plugin.options?.setts) {
   121          setts.push(plugin.options?.setts[key]);
   122        }
   123      }
   124  
   125      // actorActions
   126      let actorActions: EntityAction[] = [];
   127      if (plugin.options?.actorActions) {
   128        for (const key in plugin.options?.actorActions) {
   129          actorActions.push(plugin.options?.actorActions[key]);
   130        }
   131      }
   132  
   133      // actorStates
   134      let actorStates: EntityState[] = [];
   135      if (plugin.options?.actorStates) {
   136        for (const key in plugin.options?.actorStates) {
   137          actorStates.push(plugin.options?.actorStates[key]);
   138        }
   139      }
   140  
   141      currentEntity.value.plugin = {
   142        name: plugin.name,
   143        version: plugin.version,
   144        enabled: plugin.enabled,
   145        system: plugin.system,
   146        actor: plugin.actor,
   147        actorCustomAttrs: plugin.options?.actorCustomAttrs,
   148        actorCustomActions: plugin.options?.actorCustomActions,
   149        actorCustomStates: plugin.options?.actorCustomStates,
   150        actorCustomSetts: plugin.options?.actorCustomSetts,
   151        triggers: plugin.options?.triggers,
   152        actorAttrs: actorAttrs,
   153        actorSetts: actorSetts,
   154        setts: setts,
   155        actorActions: actorActions,
   156        actorStates: actorStates,
   157      } as Plugin;
   158      // console.log(plugin)
   159    }
   160  }
   161  
   162  const prepareForSave = async () => {
   163    const write = unref(writeRef)
   164    const validate = await write?.elFormRef?.validate()?.catch(() => {
   165    })
   166    if (validate) {
   167      const data = (await write?.getFormData()) as Entity
   168      let actions: ApiUpdateEntityRequestAction[] = []
   169      let states: ApiUpdateEntityRequestState[] = []
   170      for (const a of data?.actions) {
   171        actions.push({
   172          name: a.name,
   173          description: a.description,
   174          icon: a.icon,
   175          imageId: a.image?.id || a.imageId,
   176          scriptId: a.script?.id || a.scriptId,
   177          type: a.type,
   178        })
   179      }
   180      for (const a of data?.states) {
   181        states.push({
   182          name: a.name,
   183          description: a.description,
   184          imageId: a.image?.id || a.imageId,
   185        })
   186      }
   187      let attributes: { [key: string]: ApiAttribute } = {};
   188      for (const index in internal.attributes) {
   189        attributes[internal.attributes[index].name] = internal.attributes[index];
   190      }
   191      let settings: { [key: string]: ApiAttribute } = {};
   192      for (const index in internal.settings) {
   193        settings[internal.settings[index].name] = internal.settings[index];
   194      }
   195      const body = {
   196        id: data.id,
   197        pluginName: data.plugin?.name || data.pluginName,
   198        description: data.description,
   199        areaId: data.area?.id,
   200        icon: data.icon,
   201        imageId: data.image?.id,
   202        autoLoad: data.autoLoad,
   203        restoreState: data.restoreState,
   204        parentId: data.parent?.id,
   205        actions: actions,
   206        states: states,
   207        attributes: attributes,
   208        settings: settings,
   209        scriptIds: data.scriptIds,
   210        metrics: data.metrics,
   211        tags: data.tags,
   212      }
   213      return body
   214    }
   215    return null
   216  }
   217  
   218  let _scriptsPromises = []
   219  let _scripts: Map<string, ApiScript> = []
   220  const fetchScript = async (id: number) => {
   221    const res = await api.v1.scriptServiceGetScriptById(id)
   222    if (res) {
   223      _scripts[id] = res.data
   224    }
   225  }
   226  
   227  const prepareForExport = async () => {
   228    const write = unref(writeRef)
   229    const validate = await write?.elFormRef?.validate()?.catch(() => {
   230    })
   231    if (validate) {
   232      const data = (await write?.getFormData()) as Entity
   233      let actions: ApiEntityAction[] = []
   234      let states: ApiEntityState[] = []
   235  
   236      data.actions.forEach(action => {
   237        if (action.scriptId) {
   238          _scripts[action.scriptId] = null
   239        }
   240        if (action.script?.id) {
   241          _scripts[action.script.id] = null
   242        }
   243      })
   244      data.scripts.forEach(script => {
   245        if (script.id) {
   246          _scripts[script.id] = null
   247        }
   248      })
   249  
   250      _scripts.forEach((value: ApiScript, key: string) => {
   251        _scriptsPromises.push(fetchScript(key))
   252      })
   253  
   254      await Promise.all(_scriptsPromises)
   255  
   256      for (const a of data?.actions) {
   257        let script: ApiScript = null;
   258        if (a.script) {
   259          script = {
   260            id: a.script.id,
   261            lang: _scripts[a.script.id]?.lang,
   262            name: a.script.name,
   263            source: _scripts[a.script.id]?.source || '',
   264            description: a.script.description,
   265          } as ApiScript
   266        }
   267        actions.push({
   268          name: a.name,
   269          description: a.description,
   270          icon: a.icon,
   271          image: a.image,
   272          script: script,
   273          scriptId: script?.id || 0,
   274          type: a.type,
   275        } as ApiEntityAction)
   276      }
   277      for (const a of data?.states) {
   278        states.push({
   279          name: a.name,
   280          description: a.description,
   281          image: a.image,
   282          icon: a.icon,
   283        } as ApiEntityState)
   284      }
   285      let attributes: { [key: string]: ApiAttribute } = {};
   286      for (const index in internal.attributes) {
   287        attributes[internal.attributes[index].name] = internal.attributes[index];
   288      }
   289      let settings: { [key: string]: ApiAttribute } = {};
   290      for (const index in internal.settings) {
   291        settings[internal.settings[index].name] = internal.settings[index];
   292      }
   293      let area: ApiArea = null
   294      if (data.area) {
   295        area = {
   296          id: data.area.id,
   297          name: data.area.name,
   298          description: data.area.description,
   299          polygon: data.area.polygon,
   300        } as ApiArea
   301      }
   302      let scripts: ApiScript[] = [];
   303      if (data.scripts) {
   304        data.scripts.forEach((value: ApiScript) => {
   305          scripts.push({
   306            id: value.id,
   307            lang: _scripts[value.id]?.lang,
   308            name: value.name,
   309            source: _scripts[value.id]?.source || '',
   310            description: value.description,
   311          } as ApiScript)
   312        })
   313      }
   314      const body = {
   315        id: data.id,
   316        pluginName: data.plugin?.name || data.pluginName,
   317        description: data.description,
   318        area: area,
   319        icon: data.icon,
   320        image: data.image,
   321        autoLoad: data.autoLoad,
   322        restoreState: data.restoreState,
   323        parent: data.parent,
   324        actions: actions,
   325        states: states,
   326        attributes: attributes,
   327        settings: data.settings,
   328        scripts: scripts,
   329        metrics: data.metrics,
   330        tags: data.tags,
   331      }
   332      return body
   333    }
   334    return null
   335  }
   336  
   337  const save = async () => {
   338    const body = await prepareForSave()
   339    if (!body) {
   340      return
   341    }
   342    const res = await api.v1.entityServiceUpdateEntity(entityId.value, body)
   343        .catch(() => {
   344        })
   345        .finally(() => {
   346  
   347        })
   348    if (res) {
   349      fetch()
   350      ElMessage({
   351        title: t('Success'),
   352        message: t('message.uploadSuccessfully'),
   353        type: 'success',
   354        duration: 2000
   355      })
   356    }
   357  }
   358  
   359  const callAction = async (name: string) => {
   360    await api.v1.interactServiceEntityCallAction({id: entityId.value, name: name});
   361    ElMessage({
   362      title: t('Success'),
   363      message: t('message.callSuccessful'),
   364      type: 'success',
   365      duration: 2000
   366    });
   367  }
   368  
   369  const setState = async (name: string) => {
   370    await api.v1.developerToolsServiceEntitySetState({id: entityId.value, name: name});
   371    ElMessage({
   372      title: t('Success'),
   373      message: t('message.callSuccessful'),
   374      type: 'success',
   375      duration: 2000
   376    });
   377  }
   378  
   379  useEmitt({
   380    name: 'updateActions',
   381    callback: (val: EntityAction[]) => {
   382      const second = JSON.parse(JSON.stringify(val))
   383      currentEntity.value.actions = JSON.parse(JSON.stringify(second))
   384    }
   385  })
   386  
   387  useEmitt({
   388    name: 'updateStates',
   389    callback: (val: EntityState[]) => {
   390      const second = JSON.parse(JSON.stringify(val))
   391      currentEntity.value.states = JSON.parse(JSON.stringify(second))
   392    }
   393  })
   394  
   395  useEmitt({
   396    name: 'callAction',
   397    callback: (val: string) => {
   398      callAction(val)
   399    }
   400  })
   401  
   402  useEmitt({
   403    name: 'setState',
   404    callback: (val: string) => {
   405      setState(val)
   406    }
   407  })
   408  
   409  const onAttrsUpdated = (attrs: ApiAttribute[]) => {
   410    const second = JSON.parse(JSON.stringify(attrs))
   411    internal.attributes = JSON.parse(JSON.stringify(second))
   412  }
   413  
   414  const onSettingsUpdated = (attrs: ApiAttribute[]) => {
   415    const second = JSON.parse(JSON.stringify(attrs))
   416    internal.settings = JSON.parse(JSON.stringify(second))
   417  }
   418  
   419  const cancel = () => {
   420    push('/entities')
   421  }
   422  
   423  const copy = async () => {
   424    const body = await prepareForExport()
   425    copyToClipboard(JSON.stringify(body, null, 2))
   426  }
   427  
   428  const exportEntity = async () => {
   429    const body = await prepareForExport()
   430    dialogSource.value = body
   431    dialogVisible.value = true
   432  }
   433  
   434  const restart = async () => {
   435    await api.v1.developerToolsServiceReloadEntity({id: entityId.value});
   436    ElMessage({
   437      title: t('Success'),
   438      message: t('message.reloadSuccessful'),
   439      type: 'success',
   440      duration: 2000
   441    });
   442  }
   443  
   444  const remove = async () => {
   445    loading.value = true
   446    const res = await api.v1.entityServiceDeleteEntity(entityId.value)
   447        .catch(() => {
   448        })
   449        .finally(() => {
   450          loading.value = false
   451        })
   452    if (res) {
   453      cancel()
   454    }
   455  }
   456  
   457  const currentID = ref('')
   458  
   459  const onStateChanged = (event: EventStateChange) => {
   460    if (event.entity_id != entityId.value) {
   461      return;
   462    }
   463  
   464    lastEvent.value = event;
   465  }
   466  
   467  const requestCurrentState = () => {
   468    stream.send({
   469      id: UUID.createUUID(),
   470      query: 'event_get_last_state',
   471      body: btoa(JSON.stringify({'entity_id': entityId.value}))
   472    });
   473  }
   474  
   475  const handleTabChange = (tab: any, event: any) => {
   476    const {paneName} = tab;
   477    if (paneName == 'currentState') {
   478      requestCurrentState();
   479    }
   480  }
   481  
   482  onMounted(() => {
   483    const uuid = new UUID()
   484    currentID.value = uuid.getDashFreeUUID()
   485  
   486    setTimeout(() => {
   487      stream.subscribe('state_changed', currentID.value, onStateChanged);
   488    }, 1000)
   489  })
   490  
   491  onUnmounted(() => {
   492    stream.unsubscribe('state_changed', currentID.value);
   493  })
   494  
   495  fetch()
   496  
   497  </script>
   498  
   499  <template>
   500    <ContentWrap>
   501      <el-tabs class="demo-tabs" v-model="activeTab" @tab-click="handleTabChange">
   502        <!-- main -->
   503        <el-tab-pane :label="$t('entities.main')" name="main">
   504          <Form ref="writeRef" :current-row="currentEntity"/>
   505        </el-tab-pane>
   506        <!-- /main -->
   507  
   508        <!-- actions -->
   509        <el-tab-pane :label="$t('entities.actions')" name="actions">
   510          <Actions
   511              :actions="currentEntity?.actions"
   512              :custom-actions="currentEntity?.plugin?.actorCustomActions"
   513              :plugin-actions="currentEntity?.plugin?.actorActions"
   514          />
   515        </el-tab-pane>
   516        <!-- /actions -->
   517  
   518        <!-- states -->
   519        <el-tab-pane :label="$t('entities.states')" name="states">
   520          <States
   521              :states="currentEntity?.states"
   522              :custom-states="currentEntity?.plugin?.actorCustomStates"
   523              :plugin-states="currentEntity?.plugin?.actorStates"
   524          />
   525        </el-tab-pane>
   526        <!-- /states -->
   527  
   528        <!-- attributes -->
   529        <el-tab-pane :label="$t('entities.attributes')" name="attributes">
   530          <AttributesEditor
   531              v-model="internal.attributes"
   532              :custom-attrs="currentEntity?.plugin?.actorCustomAttrs"
   533              :plugin-attrs="currentEntity?.plugin?.actorAttrs"
   534              @change="onAttrsUpdated"
   535          />
   536        </el-tab-pane>
   537        <!-- /attributes -->
   538  
   539        <!-- settings -->
   540        <el-tab-pane :label="$t('entities.settings')" name="settings">
   541          <AttributesEditor
   542              v-model="internal.settings"
   543              :custom-attrs="currentEntity?.plugin?.actorCustomSetts"
   544              :plugin-attrs="currentEntity?.plugin?.actorSetts"
   545              @change="onSettingsUpdated"
   546          />
   547        </el-tab-pane>
   548        <!-- /settings -->
   549  
   550        <!-- metrics -->
   551        <el-tab-pane :label="$t('entities.metrics')" name="metrics">
   552          <Metrics :metrics="currentEntity?.metrics"/>
   553        </el-tab-pane>
   554        <!-- /metrics -->
   555  
   556        <!-- storage -->
   557        <el-tab-pane :label="$t('entities.storage')" name="storage">
   558          <Storage v-model="currentEntity"/>
   559        </el-tab-pane>
   560        <!-- /storage -->
   561  
   562        <!-- current state -->
   563        <el-tab-pane :label="$t('entities.currentState')" name="currentState">
   564          <ElButton type="default" @click.prevent.stop="requestCurrentState()" class="mb-20px">
   565            <Icon icon="ep:refresh" class="mr-5px"/>
   566            {{ $t('main.currentState') }}
   567          </ElButton>
   568  
   569          <JsonViewer v-model="lastEvent"/>
   570        </el-tab-pane>
   571        <!-- /current state -->
   572  
   573      </el-tabs>
   574  
   575  
   576      <div style="text-align: right" class="mt-20px">
   577  
   578        <ElButton type="primary" @click="save()">
   579          {{ t('main.save') }}
   580        </ElButton>
   581  
   582        <ElButton type="primary" @click="restart()">
   583          <Icon icon="ep:refresh" class="mr-5px"/>
   584          {{ t('main.restart') }}
   585        </ElButton>
   586  
   587        <ElButton type="primary" @click="exportEntity()">
   588          <Icon icon="uil:file-export" class="mr-5px"/>
   589          {{ t('main.export') }}
   590        </ElButton>
   591  
   592        <ElButton type="default" @click="fetch()">
   593          {{ t('main.loadFromServer') }}
   594        </ElButton>
   595  
   596        <ElButton type="default" @click="cancel()">
   597          {{ t('main.return') }}
   598        </ElButton>
   599  
   600        <ElPopconfirm
   601            :confirm-button-text="$t('main.ok')"
   602            :cancel-button-text="$t('main.no')"
   603            width="250"
   604            style="margin-left: 10px;"
   605            :title="$t('main.are_you_sure_to_do_want_this?')"
   606            @confirm="remove"
   607        >
   608          <template #reference>
   609            <ElButton class="mr-10px" type="danger" plain>
   610              <Icon icon="ep:delete" class="mr-5px"/>
   611              {{ t('main.remove') }}
   612            </ElButton>
   613          </template>
   614        </ElPopconfirm>
   615  
   616      </div>
   617    </ContentWrap>
   618  
   619    <!-- export dialog -->
   620    <Dialog v-model="dialogVisible" :title="t('entities.dialogExportTitle')" :maxHeight="400" width="80%">
   621      <JsonViewer v-model="dialogSource"/>
   622      <!--    <template #footer>-->
   623      <!--      <ElButton @click="copy()">{{ t('setting.copy') }}</ElButton>-->
   624      <!--      <ElButton @click="dialogVisible = false">{{ t('main.closeDialog') }}</ElButton>-->
   625      <!--    </template>-->
   626    </Dialog>
   627    <!-- /export dialog -->
   628  
   629  </template>
   630  
   631  <style lang="less" scoped>
   632  
   633  </style>