From 4dccbb68399da2d46943b8d613585afb49aa5455 Mon Sep 17 00:00:00 2001
From: sean.zhou <sean.zhou@dji.com>
Date: Fri, 22 Jul 2022 20:48:54 +0800
Subject: [PATCH] V1.1.0 for dock

---
 src/pages/page-pilot/pilot-home.vue |  601 +++++++++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 495 insertions(+), 106 deletions(-)

diff --git a/src/pages/page-pilot/pilot-home.vue b/src/pages/page-pilot/pilot-home.vue
index 0d3f434..42416fb 100644
--- a/src/pages/page-pilot/pilot-home.vue
+++ b/src/pages/page-pilot/pilot-home.vue
@@ -1,132 +1,501 @@
 <template>
-  <div class="page">
-    <div class="left flex-column flex-justify-start flex-align-center">
-      <p class="fz26 mb0 mt10" style="color: #727272">
-        {{ platformName }}
-      </p>
-      <p class="fz16 ml10 mb0 mt10" style="color: #2d8cf0">
-        status:{{ connect }}
-      </p>
-      <p class="fz32 mb0 mt50" style="color: #000000">{{ workspaceName }}</p>
-      <a-button
-        class="fz20 mt20 flex-column flex-justify-center flex-align-center"
-        style="width: 30vw; height: 12vh;"
-        type="default"
-        @click="onOpen3rdApp"
-        >Open 3rd Party APP</a-button
-      >
-      <a-button
-        class="fz20"
-        style="width: 15vw; height: 12vh; position: fixed; bottom: 7vh"
-        type="primary"
-        @click="onExit"
-        >Quit</a-button
-      >
-    </div>
-    <div class="right flex-column flex-justify-start flex-align-center">
-      <p class="fz24 mb0 mt10 ">Setting</p>
-      <a-button class="mt10 fz16" style="width:90%" @click="onMediaSetting"
-        >Media File Upload Setting</a-button
-      >
-      <a-button class="mt10 fz16" style="width:90%" @click="onLiveshareSetting"
-        >Manual Live Share Setting</a-button
-      >
-    </div>
-  </div>
+  <a-layout class="page">
+    <a-layout-sider class="left" width="40%" style="border-radius: 4px;">
+      <div style="width:90%; height: 90%; margin: 4vh">
+        <a-layout style="height: 20%; margin-top: 3vh; background-color: white; ">
+          <a-layout-sider width="25%" theme="light" align="center">
+            <a-avatar :size="60" :src="cloudapi">
+            </a-avatar>
+          </a-layout-sider>
+          <a-layout-content style="margin-left: 1vw;" @click="showStatus">
+            <div style="height: 50%;">
+              <span style="font-size: 16px; font-weight: bolder">{{ workspaceName }}</span>
+              <RightOutlined style="float: right; margin-top: 5px; color: #8894a0" />
+            </div>
+            <div style="height: 50%;">
+              <CloudSyncOutlined v-if="state === EStatusValue.CONNECTED" style="color: #75c5f6" />
+              <SyncOutlined spin v-else/>
+              <span style="color: #737373; margin-left: 3px;">{{ state }}</span>
+            </div>
+            <a-drawer  placement="right" v-model:visible="drawerVisible" width="340px">
+              <div class="mb10 flex-row flex-justify-center flex-align-center">
+                <p class="fz14" style="font-weight: 100;">Module State</p>
+              </div>
+              <div class= "width-100 mb10 flex-align-start" v-for="m in modules" :key="m.name" style="height: 30px;">
+
+                <div class="ml5" style="float: left; color: #000000;">{{m.name}}:</div>
+                <div class="ml10" style="float: right; margin-bottom: 8px;">
+                  <span :key="m.state" :class="m.state.value === EStatusValue.CONNECTED ? 'green' : 'red'">{{ m.state.value }}&nbsp;</span>
+                  <a-button-group >
+                  <a-button class="ml5" type="primary" size="small" @click.stop="moduleInstall(m)">install</a-button>
+                  <a-button class="ml5 mr5" type="danger" size="small" @click.stop="moduleUninstall(m)">uninstall</a-button>
+                  </a-button-group>
+                </div>
+                <a-divider />
+
+              </div>
+            </a-drawer>
+          </a-layout-content>
+
+        </a-layout>
+        <a-divider  style="height: 2px; background-color: #f5f5f5; margin-top: 3vh;" />
+
+        <a-button id="exitBtn" class="fz18" @click="confirmAgain"
+        style="width: 10vw; height: 10vh; position: fixed; bottom: 13vh; left: 15vw; background-color: #e6e6e6; color: red; border: 0;"
+        type="primary">Exit
+        </a-button>
+        <a-modal v-model:visible="exitVisible" width="300px" :closable="false">
+          <template #footer>
+            <a-button type="text" style="width: 48%; float: left;" @click="onBack">Cancel</a-button>
+            <a-button type="text" style="width: 48%;" @click="onExit">Exit</a-button>
+          </template>
+          <p>Data will not be synchronized between DJI Pilot and this server after exiting.</p>
+        </a-modal>
+      </div>
+    </a-layout-sider>
+    <a-layout-content class="right flex-column">
+      <div class="mb5">
+        <span class="ml5" style="color: #939393;">Serial Number</span>
+      </div>
+      <div class="fz16" style="background-color: white; border-radius: 4px;">
+        <a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle">
+          <a-col :span="1"></a-col>
+            <a-col :span="9">
+            Remote Control Sn
+            </a-col>
+          <a-col :span="13" class="flex-align-end flex-column">
+            <span style="color: #737373">{{ device.data.gateway_sn }}</span>
+          </a-col>
+        </a-row>
+        <a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" v-if="device.data.online_status">
+          <a-col :span="1"></a-col>
+          <a-col :span="9">Aircraft Sn</a-col>
+          <a-col :span="13" class="flex-align-end flex-column" >
+            <span style="color: #737373">{{ device.data.sn }}</span>
+          </a-col>
+        </a-row>
+      </div>
+      <div class="mt5 mb5">
+        <span class="ml5" style="color: #939393;">Settings</span>
+      </div>
+      <div class="fz16" style="background-color: white; border-radius: 4px;">
+        <a-row v-if="device.data.online_status" style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" @click="bindingDevice">
+          <a-col :span="1"></a-col>
+          <a-col :span="11">
+            Device Binding
+          </a-col>
+          <a-col :span="10" style="text-align: right">
+            <span v-if="device.data.bound_status" style="color: #737373">Aircraft bound</span>
+            <span v-else style="color: #737373">Aircraft not bound</span>
+          </a-col>
+          <a-col :span="2" class="flex-align-center flex-column" >
+            <RightOutlined style="color: #8894a0; font-size: 20px;" />
+          </a-col>
+        </a-row>
+        <a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" @click="onMediaSetting">
+          <a-col :span="1"></a-col>
+          <a-col :span="21">
+            Media File Upload
+          </a-col>
+          <a-col :span="2" class="flex-align-center flex-column" >
+            <RightOutlined style="color: #8894a0; font-size: 20px;" />
+          </a-col>
+        </a-row>
+        <a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" @click="onLiveshareSetting">
+          <a-col :span="1"></a-col>
+          <a-col :span="21">Livestream Manually</a-col>
+          <a-col :span="2" class="flex-align-center flex-column">
+            <RightOutlined style="color: #8894a0; font-size: 20px;" />
+          </a-col>
+        </a-row>
+        <a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" @click="onOpen3rdApp">
+          <a-col :span="1"></a-col>
+          <a-col :span="21">Open 3rd Party APP</a-col>
+          <a-col :span="2" class="flex-align-center flex-column">
+            <RightOutlined style="color: #8894a0; font-size: 20px;" />
+          </a-col>
+        </a-row>
+      </div>
+    </a-layout-content>
+  </a-layout>
 </template>
 <script lang="ts" setup>
-import { message } from 'ant-design-vue'
-import { onMounted, ref } from 'vue'
+import { message, Popconfirm } from 'ant-design-vue'
+import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'
 import { CURRENT_CONFIG } from '/@/api/http/config'
-import { getPlatformInfo, getUserInfo } from '/@/api/manage'
-import apiPilot from '/@/api/pilot-bridge'
+import { BindBody, bindDevice, getDeviceBySn, getPlatformInfo, getUserInfo } from '/@/api/manage'
+import apiPilot, { ApiParam, MapParam, ThingParam, WsParam } from '/@/api/pilot-bridge'
 import { getRoot } from '/@/root'
+import { EBizCode, EComponentName, EDownloadOwner, ELocalStorageKey, ERouterName, EStatusValue } from '/@/types'
+import cloudapi from '/@/assets/icons/cloudapi.png'
+import { RightOutlined, CloudOutlined, CloudSyncOutlined, SyncOutlined } from '@ant-design/icons-vue'
+import { useMyStore } from '/@/store'
+import ReconnectingWebSocket from 'reconnecting-websocket'
+import websocket from '/@/api/websocket'
+import { DeviceStatus } from '/@/types/device'
 
 const root = getRoot()
-const connect = ref('Disconnect')
-const platformName = ref('Unknown')
-const workspaceName = ref('Unknown')
-const workspaceDesc = ref('Unknown')
-const wsId = ref()
+const gatewayState = ref<boolean>(localStorage.getItem(ELocalStorageKey.GatewayOnline) === 'true')
+const state = ref(EStatusValue.DISCONNECT)
+const thingState = ref(EStatusValue.DISCONNECT)
+const apiState = ref(EStatusValue.DISCONNECT)
+const liveState = ref(EStatusValue.DISCONNECT)
+const wsState = ref(EStatusValue.DISCONNECT)
+const mapState = ref(EStatusValue.DISCONNECT)
+const tsaState = ref(EStatusValue.DISCONNECT)
+const mediaState = ref(EStatusValue.DISCONNECT)
+const waylineState = ref(EStatusValue.DISCONNECT)
+const workspaceName = ref<string>(localStorage.getItem(ELocalStorageKey.WorkspaceName)!)
+const username = ref(localStorage.getItem(ELocalStorageKey.Username)!)
+const wsId = ref(localStorage.getItem(ELocalStorageKey.WorkspaceId)!)
+const components = apiPilot.init()
+const exitVisible = ref(false)
+const drawerVisible = ref(false)
+let minitor = -1
+
+interface DeviceInfoData {
+  data: DeviceStatus
+}
+const device = reactive<DeviceInfoData>({
+  data: {
+    sn: EStatusValue.DISCONNECT,
+    online_status: false,
+    device_callsign: '',
+    user_id: '',
+    user_callsign: '',
+    bound_status: false,
+    model: '',
+    gateway_sn: EStatusValue.DISCONNECT,
+    domain: ''
+  }
+})
+const bindParam: BindBody = {
+  device_sn: '',
+  user_id: '',
+  workspace_id: wsId.value
+}
+
+const modules = [{
+  name: 'Cloud',
+  state: thingState,
+  module: EComponentName.Thing
+}, {
+  name: 'Api',
+  state: apiState,
+  module: EComponentName.Api
+}, {
+  name: 'Live',
+  state: liveState,
+  module: EComponentName.Liveshare
+}, {
+  name: 'Ws',
+  state: wsState,
+  module: EComponentName.Ws
+}, {
+  name: 'Map',
+  state: mapState,
+  module: EComponentName.Map
+}, {
+  name: 'Tsa',
+  state: tsaState,
+  module: EComponentName.Tsa
+}, {
+  name: 'Media',
+  state: mediaState,
+  module: EComponentName.Media
+}, {
+  name: 'Wayline',
+  state: waylineState,
+  module: EComponentName.Mission
+}]
+
+const store = useMyStore()
+
+const wsGetMsg = async (res: any) => {
+  const payload = JSON.parse(res.data)
+  switch (payload.biz_code) {
+    case EBizCode.DeviceOnline: {
+      console.info('online: ', payload)
+      if (payload.data.sn === device.data.gateway_sn) {
+        gatewayState.value = true
+        localStorage.setItem(ELocalStorageKey.GatewayOnline, gatewayState.value.toString())
+        state.value = gatewayState.value && thingState.value === EStatusValue.CONNECTED ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
+        break
+      }
+      if (payload.data.gateway_sn === device.data.gateway_sn) {
+        device.data = payload.data
+        localStorage.setItem(ELocalStorageKey.Device, JSON.stringify(device.data))
+      }
+      break
+    }
+    case EBizCode.DeviceOffline: {
+      console.info('offline: ', payload)
+      if (payload.data.sn === device.data.sn) {
+        device.data.online_status = payload.data.online_status
+        localStorage.setItem(ELocalStorageKey.Device, JSON.stringify(device.data))
+      }
+      break
+    }
+
+    default:
+      break
+  }
+}
+let bindNum: number
+let socket: ReconnectingWebSocket
 
 onMounted(() => {
-  apiPilot.init()
-  const token = apiPilot.getToken()
-  if (token) {
-    getPlatformInfo({}).then(res => {
-      console.log(res)
-      platformName.value = res.data.platform_name
-      workspaceName.value = res.data.workspace_name
-      workspaceDesc.value = res.data.workspace_desc
-      wsId.value = res.data.workspace_id
-      apiPilot.setPlatformMessage(
-        platformName.value,
-        workspaceName.value,
-        workspaceDesc.value
-      )
-      apiPilot.setWorkspaceId(wsId.value)
-    })
+  apiPilot.onBackClickReg()
+  apiPilot.onStopPlatform()
+
+  device.data.gateway_sn = apiPilot.getRemoteControllerSN()
+  if (device.data.gateway_sn === EStatusValue.DISCONNECT.toString()) {
+    message.warn('Data is not available, please restart the remote control.')
+    return
   }
-  if (JSON.parse(apiPilot.isComponentLoaded('thing')).data === false || token) {
-    getUserInfo({}).then(res => {
-      const param = {
-        host: res.data.mqtt_addr,
-        username: res.data.mqtt_username,
-        password: res.data.mqtt_password,
-        connectCallback: 'connectCallback'
-      }
-      apiPilot.setComponentParam('thing', param)
-      apiPilot.loadComponent('thing', apiPilot.getComponentParam('thing'))
-    })
-  } else {
-    const connectState = JSON.parse(window.djiBridge.thingGetConnectState())
-    if (connectState.code === 0 && connectState.data) {
-      connect.value = 'Connected'
-    } else {
-      connect.value = 'Disconnect'
+  const oldDevice = localStorage.getItem(ELocalStorageKey.Device)
+  if (oldDevice) {
+    device.data = JSON.parse(oldDevice)
+  }
+  device.data.sn = apiPilot.getAircraftSN()
+  getDeviceInfo()
+
+  socket = websocket.init(wsGetMsg)
+
+  const isLoaded = apiPilot.isComponentLoaded(EComponentName.Thing)
+  if (isLoaded) {
+    username.value = '' + localStorage.getItem(ELocalStorageKey.Username)
+    workspaceName.value = '' + localStorage.getItem(ELocalStorageKey.WorkspaceName)
+    refreshStatus()
+    apiPilot.setPlatformMessage(
+      '' + localStorage.getItem(ELocalStorageKey.PlatformName),
+      workspaceName.value,
+      '' + localStorage.getItem(ELocalStorageKey.WorkspaceDesc)
+    )
+    return
+  }
+
+  setWorkspaceInfo()
+
+  getUserInfo().then(res => {
+    username.value = res.data.username
+    localStorage.setItem(ELocalStorageKey.Username, username.value)
+    // thing
+    const param: ThingParam = {
+      host: res.data.mqtt_addr,
+      username: username.value,
+      password: res.data.mqtt_password,
+      connectCallback: 'connectCallback'
     }
-  }
-  apiPilot.loadComponent('liveshare', apiPilot.getComponentParam('liveshare'))
-  console.log('ws token:', token)
-  apiPilot.setComponentParam('ws', {
-    host: CURRENT_CONFIG.websocketURL,
-    token: token
+    components.set(EComponentName.Thing, param)
+    apiPilot.loadComponent(EComponentName.Thing, components.get(EComponentName.Thing))
+
+    bindParam.device_sn = device.data.gateway_sn
+    bindParam.user_id = res.data.user_id
+    bindParam.workspace_id = res.data.workspace_id
   })
-  apiPilot.loadComponent('ws', apiPilot.getComponentParam('ws'))
-  apiPilot.setComponentParam('map', {
-    userName: 'pilot1',
-    elementPreName: 'PILOT'
-  })
-  apiPilot.loadComponent('map', apiPilot.getComponentParam('map'))
-  apiPilot.loadComponent('tsa', apiPilot.getComponentParam('tsa'))
-  apiPilot.loadComponent('media', apiPilot.getComponentParam('media'))
-  apiPilot.loadComponent('mission', {})
   window.connectCallback = arg => {
     connectCallback(arg)
   }
-  apiPilot.onBackClickReg()
+  window.wsConnectCallback = arg => {
+    wsConnectCallback(arg)
+  }
 })
-const connectCallback = (arg: any) => {
-  console.info('into callback', arg)
+
+onUnmounted(() => {
+  socket.close()
+})
+
+const connectCallback = async (arg: any) => {
   if (arg) {
-    connect.value = 'Connected'
-    window.djiBridge.mediaSetDownloadOwner(0)
-    window.djiBridge.mediaSetUploadPhotoType(1)
+    thingState.value = EStatusValue.CONNECTED
+    // liveshare
+    apiPilot.loadComponent(EComponentName.Liveshare, components.get(EComponentName.Liveshare))
+
+    // ws
+    const wsParam: WsParam = components.get(EComponentName.Ws)
+    wsParam.token = apiPilot.getToken()
+    apiPilot.loadComponent(EComponentName.Ws, components.get(EComponentName.Ws))
+
+    // map
+    const mapParam: MapParam = components.get(EComponentName.Map)
+    mapParam.userName = username.value
+    apiPilot.loadComponent(EComponentName.Map, components.get(EComponentName.Map))
+
+    // tsa
+    apiPilot.loadComponent(EComponentName.Tsa, components.get(EComponentName.Tsa))
+
+    // media
+    apiPilot.loadComponent(EComponentName.Media, components.get(EComponentName.Media))
+    apiPilot.setDownloadOwner(EDownloadOwner.Mine.valueOf())
+
+    // mission
+    apiPilot.loadComponent(EComponentName.Mission, {})
+
+    bindNum = setInterval(() => {
+      bindDevice(bindParam).then(bindRes => {
+        if (bindRes.code !== 0) {
+          message.error(bindRes.message)
+          console.error(bindRes.message)
+        } else {
+          clearInterval(bindNum)
+        }
+      })
+    }, 2000)
+    setTimeout(getDeviceInfo, 3000)
   } else {
-    connect.value = 'Disconnect'
+    thingState.value = EStatusValue.DISCONNECT
+  }
+  refreshStatus()
+}
+const wsConnectCallback = async (arg: any) => {
+  if (arg) {
+    wsState.value = EStatusValue.CONNECTED
+  } else {
+    wsState.value = EStatusValue.DISCONNECT
   }
 }
-const onExit = async (e: any) => {
+
+const confirmAgain = () => {
+  exitVisible.value = true
+}
+
+const onBack = () => {
+  exitVisible.value = false
+}
+
+const onExit = () => {
+  localStorage.clear()
   apiPilot.stopwebview()
 }
+
+const bindingDevice = async () => {
+  root.$router.push(ERouterName.PILOT_BIND)
+}
+
 const onMediaSetting = async (e: any) => {
-  root.$router.push('/pilot-media')
+  root.$router.push(ERouterName.PILOT_MEDIA)
 }
 const onLiveshareSetting = async (e: any) => {
-  root.$router.push('/pilot-liveshare')
+  root.$router.push(ERouterName.PILOT_LIVESHARE)
 }
 const onOpen3rdApp = () => {
-  window.open('mydjischeme://www.dji.com')
+  const packageName = 'com.dji.sample'
+  const isInstalled = apiPilot.isAppInstalled(packageName)
+  if (isInstalled) {
+    window.open('https://www.dji.com')
+  } else {
+    message.error(packageName + ' is not installed.')
+  }
+}
+
+const showStatus = async () => {
+  minitor = setInterval(() => {
+    refreshStatus()
+    if (!drawerVisible.value) {
+      clearInterval(minitor)
+    }
+  }, 2000)
+  drawerVisible.value = true
+}
+
+function setWorkspaceInfo () {
+  if (localStorage.getItem(ELocalStorageKey.WorkspaceName)) {
+    apiPilot.setPlatformMessage(
+      '' + localStorage.getItem(ELocalStorageKey.PlatformName),
+      workspaceName.value,
+      '' + localStorage.getItem(ELocalStorageKey.WorkspaceDesc)
+    )
+    apiPilot.setWorkspaceId(wsId.value)
+
+    return
+  }
+  getPlatformInfo().then(res => {
+    console.log(res)
+    workspaceName.value = res.data.workspace_name
+    wsId.value = res.data.workspace_id
+    localStorage.setItem(ELocalStorageKey.PlatformName, res.data.platform_name)
+    localStorage.setItem(ELocalStorageKey.WorkspaceName, workspaceName.value)
+    localStorage.setItem(ELocalStorageKey.WorkspaceDesc, res.data.workspace_desc)
+    apiPilot.setPlatformMessage(
+      res.data.platform_name,
+      workspaceName.value,
+      res.data.workspace_desc
+    )
+    apiPilot.setWorkspaceId(wsId.value)
+  })
+}
+
+function refreshStatus () {
+  thingState.value = apiPilot.thingGetConnectState() ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
+  apiState.value = apiPilot.isComponentLoaded(EComponentName.Api) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
+  liveState.value = apiPilot.isComponentLoaded(EComponentName.Liveshare) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
+  wsState.value = apiPilot.isComponentLoaded(EComponentName.Ws) && apiPilot.wsGetConnectState()
+    ? EStatusValue.CONNECTED
+    : EStatusValue.DISCONNECT
+  mapState.value = apiPilot.isComponentLoaded(EComponentName.Map) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
+  tsaState.value = apiPilot.isComponentLoaded(EComponentName.Tsa) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
+  mediaState.value = apiPilot.isComponentLoaded(EComponentName.Media) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
+  waylineState.value = apiPilot.isComponentLoaded(EComponentName.Mission) ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
+  state.value = thingState.value === EStatusValue.CONNECTED && gatewayState.value ? EStatusValue.CONNECTED : EStatusValue.DISCONNECT
+}
+
+function moduleInstall (m: any) {
+  let param
+  switch (m.module) {
+    case EComponentName.Thing:
+      param = apiPilot.thingGetConfigs()
+      break
+    case EComponentName.Api: {
+      const apiParam: ApiParam = {
+        host: apiPilot.getHost(),
+        token: apiPilot.getToken()
+      }
+      param = apiParam
+      break
+    }
+    case EComponentName.Map: {
+      const mapParam: MapParam = components.get(EComponentName.Map)
+      mapParam.userName = '' + localStorage.getItem(ELocalStorageKey.Username)
+      param = mapParam
+      break
+    }
+    case EComponentName.Ws: {
+      const wsParam: WsParam = components.get(EComponentName.Ws)
+      wsParam.token = '' + localStorage.getItem(ELocalStorageKey.Token)
+      param = wsParam
+      break
+    }
+    default:
+      param = components.get(m.module)
+  }
+
+  components.set(m.module, param)
+  console.info(components.get(m.module))
+  apiPilot.loadComponent(m.module, components.get(m.module))
+  refreshStatus()
+}
+
+function moduleUninstall (m: any) {
+  message.info('uninstall ' + m.module)
+  apiPilot.unloadComponent(m.module)
+  refreshStatus()
+}
+
+function getDeviceInfo () {
+  if (device.data.sn === EStatusValue.DISCONNECT) {
+    return
+  }
+  getDeviceBySn(bindParam.workspace_id, device.data.sn).then(res => {
+    if (res.code !== 0) {
+      return
+    }
+    device.data.online_status = res.data.status
+    device.data.bound_status = res.data.bound_status
+    device.data.device_callsign = res.data.nickname
+    device.data.model = res.data.device_name
+    localStorage.setItem(ELocalStorageKey.Device, JSON.stringify(device.data))
+  })
 }
 </script>
 
@@ -139,12 +508,32 @@
   height: 100%;
   width: 100%;
   .left {
-    width: 50%;
-    border-right: red solid 2px;
+    height: 90%;
+    background-color: white;
+    margin-top: 6vh;
+    margin-left: 2vh;
   }
   .right {
-    width: 100%;
-    height: 100%;
+    height: 90%;
+    margin-top: 6vh;
+    margin-left: 5vh;
+    margin-right: 5vh;
   }
 }
+.green {
+  color: green
+}
+.red {
+  color: red;
+}
+#exitBtn:hover :active {
+  background-color: rgb(77, 75, 75);
+  width: 10vw;
+  height: 10vh;
+  position: fixed;
+  bottom: 13vh;
+  left: 15vw;
+  line-height: 10vh;
+}
+
 </style>

--
Gitblit v1.9.3