Browse Source

重构项目工作空间-在线设备

李富豪 1 year ago
parent
commit
05282cc415

+ 16 - 0
Web/src/api/custom/index.ts

@@ -77,6 +77,8 @@ export type FetchPilotPasswordApiParams = {
 
 // Api函数类型
 export type SignLoginApi = (data: SignLoginApiParams) => Promise<any>;
+export type FetchDeviceModelApi = () => Promise<any>;
+export type FetchDeviceStatusApi = (params: { snList: string }) => Promise<any>;
 export type FetchPayloadListApi = () => Promise<any>;
 export type FetchFeedbackRecordListApi = (data: FetchFeedbackRecordListApiParams) => Promise<any>;
 export type FetchChangeRecordListApi = (params: FetchFeedbackRecordListApiParams) => Promise<any>;
@@ -101,6 +103,18 @@ const signLoginApi: SignLoginApi = async (data) => {
     return res.data;
 }
 
+// 获取设备型号
+const fetchDeviceModelApi: FetchDeviceModelApi = async () => {
+    const res = await request.get(`/manage/api/v1/devices/${getWorkspaceId()}/getDeviceType`);
+    return res.data;
+};
+
+// 获取设备状态
+const fetchDeviceStatusApi: FetchDeviceStatusApi = async (params) => {
+    const res = await request.get(`/manage/api/v1/devices/${getWorkspaceId()}/devices/status`, { params: params });
+    return res.data;
+};
+
 // 获取负载列表
 const fetchPayloadListApi: FetchPayloadListApi = async () => {
     const res = await request.get(`/manage/api/v1/devices/${getWorkspaceId()}/payloads`);
@@ -218,6 +232,8 @@ const fetchPilotPasswordApi: FetchPilotPasswordApi = async (params) => {
 
 export const apis = {
     signLogin: signLoginApi,
+    fetchDeviceModel: fetchDeviceModelApi,
+    fetchDeviceStatus: fetchDeviceStatusApi,
     fetchPayloadList: fetchPayloadListApi,
     fetchFeedbackRecordList: fetchFeedbackRecordListApi,
     fetchChangeRecordList: fetchChangeRecordListApi,

+ 10 - 3
Web/src/api/manage.ts

@@ -127,9 +127,16 @@ export const getDeviceBySn = async function (workspace_id: string, device_sn: st
  * @param domain
  * @returns
  */
-export const getBindingDevices = async function (workspace_id: string, body: { page: number, page_size: number }): Promise<IListWorkspaceResponse<Device>> {
-  const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/bound?&page=${body.page}&page_size=${body.page_size}`
-  const result = await request.get(url)
+export const getBindingDevices = async function (
+  workspace_id: string,
+  body: {
+    page: number,
+    page_size: number,
+    device_type: string,
+    search_info: string,
+  }): Promise<IListWorkspaceResponse<Device>> {
+  const url = `${HTTP_PREFIX}/devices/${workspace_id}/devices/bound`
+  const result = await request.get(url, { params: body })
   return result.data
 }
 

+ 41 - 22
Web/src/components/devices/deviceList/components/Search.vue

@@ -32,7 +32,7 @@
     </a-col>
     <a-col>
       <a-form ref="formRef" layout="inline" :model="formModel" :colon="false">
-        <a-form-item name="status">
+        <!-- <a-form-item name="status">
           <a-select style="width: 200px;" placeholder="请选择状态" v-model:value="formModel.status">
             <a-select-option value="1">
               设备空闲中
@@ -47,25 +47,16 @@
               离线
             </a-select-option>
           </a-select>
-        </a-form-item>
-        <a-form-item name="device_name">
-          <a-select style="width: 200px;" placeholder="请选择设备型号" v-model:value="formModel.device_name">
-            <a-select-option value="1">
-              Dock 2
-            </a-select-option>
-            <a-select-option value="2">
-              M30
-            </a-select-option>
-            <a-select-option value="3">
-              Mavice 3E
-            </a-select-option>
-            <a-select-option value="4">
-              DJI
+        </a-form-item> -->
+        <a-form-item name="device_type">
+          <a-select style="width: 200px;" placeholder="请选择设备型号" v-model:value="formModel.device_type">
+            <a-select-option v-for="item in state.deviceModelList" :value="item.value">
+              {{ item.label }}
             </a-select-option>
           </a-select>
         </a-form-item>
-        <a-form-item name="keyword">
-          <a-input style="width: 200px;" placeholder="设备SN、设备名称" v-model:value="formModel.keyword" />
+        <a-form-item name="search_info">
+          <a-input style="width: 200px;" placeholder="设备SN、设备名称" v-model:value="formModel.search_info" />
         </a-form-item>
         <a-form-item>
           <a-button style="margin-right: 10px;" @click="handleClicSekarch">
@@ -106,8 +97,9 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive } from 'vue';
+import { ref, reactive, onMounted } from 'vue';
 import { ReadOutlined, MenuOutlined, SearchOutlined, ReloadOutlined } from '@ant-design/icons-vue';
+import { apis } from '/@/api/custom';
 
 interface Props {
   selectedRowKeys: string[],
@@ -123,17 +115,44 @@ const props = withDefaults(defineProps<Props>(), {
 const formRef = ref();
 
 const formModel = reactive({
-  status: undefined,
-  device_name: undefined,
-  keyword: '',
+  device_type: undefined,
+  search_info: undefined,
 })
 
-const state = reactive({
+interface State {
+  visible: boolean,
+  deviceInfo: {
+    name: string,
+    code: string,
+  },
+  deviceModelList: {
+    value: string,
+    label: string,
+  }[],
+};
+
+const state: State = reactive({
   visible: false,
   deviceInfo: {
     name: '上海展域航空技术有限公司',
     code: 'PB97VR',
   },
+  deviceModelList: [],
+})
+
+onMounted(async () => {
+  try {
+    const res = await apis.fetchDeviceModel();
+    const list = res.data.map((item: any) => {
+      return {
+        value: item.device_value,
+        label: item.device_name,
+      }
+    })
+    state.deviceModelList = list;
+  } catch (e) {
+    console.error(e);
+  }
 })
 
 // 点击查询

+ 56 - 6
Web/src/components/devices/deviceList/index.vue

@@ -28,7 +28,7 @@
         </template>
         <!-- 当前状态 -->
         <template #status="{ record }">
-          <CustomCell :record="record" fieldName="status" />
+          <CustomCell :record="record" fieldName="status_text" />
         </template>
         <!-- 加入项目时间 -->
         <template #bound_time="{ record }">
@@ -75,7 +75,7 @@
 </template>
 
 <script lang="ts" setup>
-import { reactive, onMounted } from 'vue';
+import { reactive, onMounted, onUnmounted } from 'vue';
 import { Modal } from 'ant-design-vue';
 import { EditOutlined, CheckOutlined, CloseOutlined, DeleteOutlined, FileSearchOutlined, CloudServerOutlined } from '@ant-design/icons-vue';
 import Search from './components/Search.vue';
@@ -84,10 +84,12 @@ import DeviceFirmwareUpgrade from '/@/components/devices/device-upgrade/DeviceFi
 import DeviceLogUploadRecordDrawer from '/@/components/devices/device-log/DeviceLogUploadRecordDrawer.vue';
 import DeviceHmsDrawer from '/@/components/devices/device-hms/DeviceHmsDrawer.vue';
 import { getBindingDevices, updateDevice, unbindDevice } from '/@/api/manage';
+import { apis } from '/@/api/custom';
 import { getWorkspaceId } from '/@/utils/index';
 
 interface State {
   workspaceId: string,
+  interval: number | null,
   query: any,
   listLoading: boolean,
   list: any[],
@@ -102,6 +104,7 @@ interface State {
 
 const state: State = reactive({
   workspaceId: getWorkspaceId(),
+  interval: null,
   query: undefined,
   listLoading: false,
   list: [],
@@ -130,9 +133,10 @@ const fetchList = async () => {
       page_size: paginationConfig.pageSize,
     });
     state.list = res.data.list;
-    paginationConfig.total = res.data.pagination.total
-    paginationConfig.current = res.data.pagination.page
-    paginationConfig.pageSize = res.data.pagination.page_size
+    paginationConfig.total = res.data.pagination.total;
+    paginationConfig.current = res.data.pagination.page;
+    paginationConfig.pageSize = res.data.pagination.page_size;
+    await autoUpdateDeviceStatus()
   } catch (e) {
     console.error(e);
   } finally {
@@ -140,9 +144,55 @@ const fetchList = async () => {
   }
 }
 
+// 自动更新设备状态
+const autoUpdateDeviceStatus = async () => {
+  if (state.list.length === 0) {
+    return;
+  }
+  const snList: string[] = [];
+  state.list.forEach(item => {
+    if (item.children) {
+      snList.push(item.children.device_sn);
+    }
+    snList.push(item.device_sn);
+  })
+  const data = {
+    snList: snList.join(','),
+  }
+  const res = await apis.fetchDeviceStatus(data);
+  const deviceStatusMap = new Map();
+  res.data.forEach((item: any) => {
+    deviceStatusMap.set(item.device_sn, item.status_text);
+  });
+  state.list.forEach(item => {
+    if (item.children) {
+      item.children.status_text = deviceStatusMap.get(item.children.device_sn);
+    }
+    item.status_text = deviceStatusMap.get(item.device_sn);
+  })
+}
+
+// 开始定时器
+const startAutoUpdate = () => {
+  state.interval = window.setInterval(autoUpdateDeviceStatus, 1000);
+};
+
+// 清除定时器
+const clearAutoUpdate = () => {
+  if (state.interval !== null) {
+    clearInterval(state.interval);
+    state.interval = null;
+  }
+};
+
 onMounted(async () => {
   await fetchList();
-})
+  startAutoUpdate(); // 页面加载完成后启动定时器
+});
+
+onUnmounted(() => {
+  clearAutoUpdate(); // 页面卸载前清除定时器
+});
 
 const columns = [
   {

+ 271 - 0
Web/src/components/onLineDevice/index.vue

@@ -0,0 +1,271 @@
+<template>
+  <div class="content" @click="onClickLocation">
+    <div class="content-title">
+      <div class="content-title-left">
+        <img class="content-title-left-icon" :src="taskSrc">
+        <div>
+          无任务
+        </div>
+      </div>
+      <div class="content-title-right">
+        N/A
+      </div>
+    </div>
+    <div class="content-info">
+      <div class="content-info-left">
+        <div class="content-info-left-one">
+          {{ device.callsign }}
+        </div>
+        <div class="content-info-left-two">
+          <div class="content-info-left-two-info">
+            <img class="content-info-left-three-info-icon" :src="aircraftSrc">
+            <div class="content-info-left-two-info-text">
+              <span class="scrollable-text">
+                {{ getTextByModeCode(deviceInfo[device.sn] ? deviceInfo[device.sn].mode_code : EModeCode.Disconnected)
+                }}
+              </span>
+            </div>
+          </div>
+          <div class="content-info-left-two-other">
+            原因
+          </div>
+        </div>
+        <div class="content-info-left-three">
+          <div class="content-info-left-three-info">
+            <img class="content-info-left-three-info-icon" :src="controllerSrc">
+            <div class="content-info-left-three-info-text">
+              {{ device.gateway.callsign }}
+            </div>
+          </div>
+          <div class="content-info-left-three-other">
+            <div class="capacity" v-if="capacity">
+              <img :src="batteryOneSrc" v-if="capacity >= 75 && capacity < 100">
+              <img :src="batteryTwoSrc" v-else-if="capacity >= 50 && capacity < 75">
+              <img :src="batteryThreeSrc" v-else-if="capacity >= 25 && capacity < 50">
+              <img :src="batteryFourSrc" v-else-if="capacity < 25">
+              <div>
+                {{ capacity }}%
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="content-info-right">
+        <img :src="deviceInfoSrc" @click.stop="onClickLookInfo">
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { computed } from 'vue';
+import taskSrc from './icon/task.svg';
+import controllerSrc from './icon/controller.svg';
+import aircraftSrc from './icon/aircraft.svg';
+import batteryOneSrc from './icon/batteryOne.svg';
+import batteryTwoSrc from './icon/batteryTwo.svg';
+import batteryThreeSrc from './icon/batteryThree.svg';
+import batteryFourSrc from './icon/batteryFour.svg';
+import deviceInfoSrc from './icon/deviceInfo.svg';
+import { useMyStore } from '/@/store';
+import { getTextByModeCode } from '/@/utils/index'
+import { OnlineDevice, EModeCode } from '/@/types/device';
+import { wgs84togcj02 } from '/@/vendors/coordtransform'
+import { getRoot } from '/@/root';
+
+interface Props {
+  device: OnlineDevice,
+  onClickLookInfo: () => void,
+};
+
+const props = withDefaults(defineProps<Props>(), {
+
+});
+
+const root = getRoot()
+
+const store = useMyStore()
+
+const deviceInfo = computed(() => store.state.deviceState.deviceInfo)
+
+// 电池容量
+const capacity = computed(() => {
+  const device: any = deviceInfo.value[props.device.sn];
+  if (device) {
+    return device.battery.capacity_percent;
+  } else {
+    return 0;
+  }
+});
+
+const onClickLocation = () => {
+  const sn = props.device.gateway.sn;
+  const gatewayInfo = store.state.deviceState.gatewayInfo[sn];
+  if (gatewayInfo) {
+    const coordinate = wgs84togcj02(gatewayInfo.longitude, gatewayInfo.latitude);
+    root.$map.setCenter(coordinate)
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.content {
+  width: 208px;
+  border: 1px solid #3d3d3d;
+  color: #FFFFFF;
+  cursor: pointer;
+
+  &-title {
+    width: 100%;
+    height: 30px;
+    border-bottom: 1px solid #3d3d3d;
+    display: flex;
+    align-items: center;
+
+    &-left {
+      width: 80px;
+      height: 100%;
+      padding-left: 5px;
+      background: #4d4d4d;
+      display: flex;
+      align-items: center;
+
+      &-icon {
+        width: 15px;
+        height: 15px;
+        margin-right: 5px;
+      }
+    }
+
+    &-right {
+      margin-left: 5px;
+    }
+  }
+
+  &-info {
+    display: flex;
+
+    &-left {
+      width: calc(100% - 20px);
+      padding: 5px;
+      font-size: 12px;
+
+      &-one {
+        width: 100%;
+        height: 24px;
+      }
+
+      &-two {
+        width: 100%;
+        height: 24px;
+        display: flex;
+        margin-bottom: 5px;
+
+        &-info {
+          width: 75px;
+          padding-left: 5px;
+          background: #3d3d3d;
+          display: flex;
+          align-items: center;
+
+          &-icon {
+            width: 14px;
+            height: 14px;
+            margin-right: 5px;
+          }
+
+          &-text {
+            width: 50px;
+            white-space: nowrap;
+            overflow: hidden;
+          }
+        }
+
+        &-other {
+          width: calc(100% - 75px);
+          padding-left: 5px;
+          background: #4a4d4e;
+          display: flex;
+          align-items: center;
+        }
+      }
+
+      &-three {
+        width: 100%;
+        height: 24px;
+        display: flex;
+        margin-bottom: 5px;
+
+        &-info {
+          width: 75px;
+          padding-left: 5px;
+          background: #3d3d3d;
+          display: flex;
+          align-items: center;
+
+          &-icon {
+            width: 14px;
+            height: 14px;
+            margin-right: 5px;
+          }
+
+          &-text {
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+          }
+        }
+
+        &-other {
+          width: calc(100% - 75px);
+          height: 100%;
+          padding-left: 5px;
+          background: #4a4d4e;
+
+          .capacity {
+            width: 100%;
+            height: 100%;
+            display: flex;
+            align-items: center;
+
+            img {
+              width: 14px;
+              height: 14px;
+              margin-right: 5px;
+            }
+          }
+        }
+      }
+    }
+
+    &-right {
+      width: 24px;
+      background: #3d3d3d;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+
+      img {
+        width: 14px;
+        height: 14px;
+      }
+    }
+  }
+
+  .scrollable-text {
+    width: 100%;
+    display: inline-block;
+    animation: scroll-left 5s linear infinite;
+
+    @keyframes scroll-left {
+      0% {
+        transform: translateX(100%);
+      }
+
+      100% {
+        transform: translateX(-100%);
+      }
+    }
+  }
+}
+</style>

+ 7 - 117
Web/src/pages/page-web/projects/tsa.vue

@@ -157,69 +157,11 @@
           <div v-if="onlineDevices.data.length === 0" style="height: 100px; color: white;">
             <a-empty :image="noData" :image-style="{ height: '60px' }" />
           </div>
-          <div v-else class="fz12" style="color: white;">
-            <div :class="['device-item', (state.selectedDevice === device.gateway.sn && 'device-item-selected')]"
-              v-for="device in onlineDevices.data" :key="device.sn" @click="onClickSelectedDevice(device)">
-              <div class="battery-slide" v-if="deviceInfo[device.sn]">
-                <div style="background: #535759; width: 100%;"></div>
-                <div class="capacity-percent" :style="{ width: deviceInfo[device.sn].battery.capacity_percent + '%' }">
-                </div>
-                <div class="return-home" :style="{ width: deviceInfo[device.sn].battery.return_home_power + '%' }">
-                </div>
-                <div class="landing" :style="{ width: deviceInfo[device.sn].battery.landing_power + '%' }"></div>
-                <div class="battery" :style="{ left: deviceInfo[device.sn].battery.capacity_percent + '%' }"></div>
-              </div>
-              <div style="border-bottom: 1px solid #515151; border-radius: 2px; height: 50px; width: 100%;"
-                class="flex-row flex-justify-between flex-align-center">
-                <div style="float: left; padding: 5px 5px 8px 8px; width: 88%">
-                  <div style="width: 100%; height: 100%;">
-                    <a-tooltip>
-                      <template #title>
-                        {{ device.model ? `${device.model} - ${device.callsign}` : 'NoDrone' }}</template>
-                      <span class="text-hidden" style="max-width: 200px; display: block; height: 20px;">
-                        {{ device.model ? `${device.model} - ${device.callsign}` : 'No Drone' }}
-                      </span>
-                    </a-tooltip>
-                  </div>
-                  <div class="mt5" style="background: #595959;">
-                    <span class="ml5 mr5">
-                      <RocketOutlined />
-                    </span>
-                    <span class="font-bold">
-                      {{
-                        getTextByModeCode(deviceInfo[device.sn] ? deviceInfo[device.sn].mode_code :
-                          EModeCode.Disconnected)
-                      }}
-                    </span>
-                  </div>
-                </div>
-                <div style="float: right; background: #595959; height: 50px; width: 40px;"
-                  class="flex-row flex-justify-center flex-align-center">
-                  <div class="fz16"
-                    @click.stop="switchVisible($event, device, false, deviceInfo[device.sn] && deviceInfo[device.sn].mode_code !== EModeCode.Disconnected)">
-                    <a v-if="osdVisible.sn === device.sn && osdVisible.visible">
-                      <EyeOutlined />
-                    </a>
-                    <a v-else>
-                      <EyeInvisibleOutlined />
-                    </a>
-                  </div>
-                </div>
-              </div>
-              <div class="flex-row flex-justify-center flex-align-center" style="height: 40px;">
-                <div class="flex-row" style="height: 20px; background: #595959; width: 94%;">
-                  <span class="mr5">
-                    <a-image :preview="false" style="margin-left: 2px; margin-top: -2px; height: 20px; width: 20px;"
-                      :src="rc" />
-                  </span>
-                  <a-tooltip>
-                    <template #title>{{ device.gateway.model }} - {{ device.gateway.callsign }} </template>
-                    <div class="text-hidden" style="max-width: 200px;">
-                      {{ device.gateway.model }} - {{ device.gateway.callsign }}
-                    </div>
-                  </a-tooltip>
-                </div>
-              </div>
+          <div v-else>
+            <div :style="{ 'margin-top': index === 0 ? '' : '10px' }" v-for="(device, index) in onlineDevices.data"
+              :key="device.sn">
+              <OnLineDevice :device="device"
+                :onClickLookInfo="() => switchVisible($event, device, false, deviceInfo[device.sn] && deviceInfo[device.sn].mode_code !== EModeCode.Disconnected)" />
             </div>
           </div>
         </a-collapse-panel>
@@ -229,11 +171,6 @@
         </a-collapse-panel>
       </a-collapse>
     </div>
-    <!-- <div style="width: 100%;padding-top: 10px; display: flex;justify-content: center;align-items: center;">
-      <a-button style="margin-right: 10px;" type="primary" @click="onClickGoHome">
-        返回
-      </a-button>
-    </div> -->
   </div>
 </template>
 
@@ -242,15 +179,12 @@ import { computed, onMounted, reactive, ref, watch } from 'vue'
 import { RocketOutlined, EyeInvisibleOutlined, EyeOutlined, RobotOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'
 import Layer from './layer/index.vue'
 import noData from '/@/assets/icons/no-data.png'
-import rc from '/@/assets/icons/rc.png'
 import { useMyStore } from '/@/store'
 import { getDeviceTopo, getUnreadDeviceHms, updateDeviceHms } from '/@/api/manage'
-import { EHmsLevel, ERouterName } from '/@/types/enums'
+import { EHmsLevel } from '/@/types/enums'
+import OnLineDevice from '/@/components/onLineDevice/index.vue'
 import { OnlineDevice, EModeCode, EDockModeCode } from '/@/types/device'
 import { EDeviceTypeName } from '/@/types'
-import { getRoot } from '/@/root'
-import { wgs84togcj02 } from '/@/vendors/coordtransform'
-import { getTextByModeCode } from '/@/utils/index'
 import { getWorkspaceId } from '/@/utils/index'
 
 const store = useMyStore()
@@ -258,7 +192,6 @@ const workspaceId = ref(getWorkspaceId())
 const osdVisible = computed(() => store.state.osdVisible)
 const hmsVisible = new Map<string, boolean>()
 const scorllHeight = ref()
-const root = getRoot()
 
 const onlineDevices = reactive({
   data: [] as OnlineDevice[]
@@ -307,21 +240,6 @@ onMounted(() => {
   scorllHeight.value = parent?.clientHeight - parent?.firstElementChild?.clientHeight
 })
 
-// 点击选中设备
-const onClickSelectedDevice = (record: OnlineDevice) => {
-  const sn = record.gateway.sn;
-  if (state.selectedDevice === sn) {
-    state.selectedDevice = '';
-  } else {
-    state.selectedDevice = sn;
-    const gatewayInfo = store.state.deviceState.gatewayInfo[sn];
-    if (gatewayInfo) {
-      const coordinate = wgs84togcj02(gatewayInfo.longitude, gatewayInfo.latitude);
-      root.$map.setCenter(coordinate)
-    }
-  }
-}
-
 function getOnlineTopo() {
   getDeviceTopo(workspaceId.value).then((res) => {
     if (res.code !== 0) {
@@ -421,18 +339,6 @@ function readHms(visiable: boolean, sn: string) {
     })
   }
 }
-
-const onClickGoHome = () => {
-  root.$router.push('/' + ERouterName.DEVICES)
-}
-
-function openLivestreamOthers() {
-  store.commit('SET_LIVESTREAM_OTHERS_VISIBLE', true)
-}
-
-function openLivestreamAgora() {
-  store.commit('SET_LIVESTREAM_AGORA_VISIBLE', true)
-}
 </script>
 
 <style lang="scss">
@@ -448,22 +354,6 @@ function openLivestreamAgora() {
   margin-top: 10px;
 }
 
-.device-item {
-  background: #3c3c3c;
-  width: 100%;
-  height: 100px;
-  box-sizing: border-box;
-  padding-top: 10px;
-  border-radius: 2px;
-  border: 2px solid transparent;
-  cursor: pointer;
-  margin-top: 10px;
-}
-
-.device-item-selected {
-  border-color: #2d8cf0;
-}
-
 .project-tsa-wrapper> :first-child {
   height: 50px;
   line-height: 50px;

+ 6 - 2
Web/src/pages/page-web/projects/workspace.vue

@@ -32,6 +32,10 @@ const messageHandler = async (payload: any) => {
       store.commit('SET_REAL_TIME_TRAJECTORY', payload.data)
       break
     }
+    // case EBizCode.ControlSourceChange: {// 设备原因
+    //   console.log(payload,'payload');
+    //   break
+    // }
     case EBizCode.GatewayOsd: {
       store.commit('SET_GATEWAY_INFO', payload.data)
       break
@@ -147,8 +151,8 @@ useConnectWebSocket(messageHandler)
 
   .left {
     display: flex;
-    width: 300px;
-    flex: 0 0 300px;
+    width: 240px;
+    flex: 0 0 240px;
     background-color: #232323;
 
     .main-content {