Quellcode durchsuchen

feat(json-editor, locales): enhance JSON editor and update localization files

- Updated the JsonEditor component to improve height handling and error display for invalid JSON.
- Added new localization keys for English and Chinese, including terms related to IP, PTZ, and stream control.
- Improved the layout and styling of various components in the live stream and LSS views for better user experience.
- Streamlined input components and pagination for enhanced readability and maintainability.
yb vor 1 Woche
Ursprung
Commit
63a34ee7f6
5 geänderte Dateien mit 116 neuen und 50 gelöschten Zeilen
  1. 11 6
      src/components/JsonEditor.vue
  2. 42 15
      src/locales/en.json
  3. 42 15
      src/locales/zh-cn.json
  4. 8 7
      src/views/live-stream/index.vue
  5. 13 7
      src/views/lss/index.vue

+ 11 - 6
src/components/JsonEditor.vue

@@ -1,22 +1,24 @@
 <template>
-  <div class="json-editor">
+  <div class="json-editor" :style="{ height: height }">
     <div class="editor-wrapper">
       <el-button class="format-btn" size="small" :icon="DocumentCopy" @click="handleFormat" :disabled="!isValidJson">
         格式化
       </el-button>
       <Codemirror
         v-model="localValue"
-        :style="{ height: height }"
+        :height="`${parseInt(height) - 24}`"
         :extensions="extensions"
         :autofocus="autofocus"
         :indent-with-tab="true"
         :tab-size="2"
         @change="handleChange"
       />
-    </div>
-    <div v-if="!isValidJson && modelValue" class="json-error">
-      <el-icon><WarningFilled /></el-icon>
-      JSON 格式错误
+      <div v-show="!isValidJson && modelValue" class="json-error">
+        <el-icon>
+          <WarningFilled />
+        </el-icon>
+        JSON 格式错误
+      </div>
     </div>
   </div>
 </template>
@@ -147,10 +149,12 @@ defineExpose({
 <style lang="scss" scoped>
 .json-editor {
   width: 100%;
+  height: 100%;
 }
 
 .editor-wrapper {
   position: relative;
+  height: 100%;
 }
 
 .format-btn {
@@ -170,6 +174,7 @@ defineExpose({
   align-items: center;
   gap: 4px;
   margin-top: 6px;
+  height: 24px;
   color: #f56c6c;
   font-size: 12px;
 

+ 42 - 15
src/locales/en.json

@@ -1,18 +1,20 @@
 {
   "Cloudflare Stream": "Cloudflare Stream",
   "Cloudflare Stream 配置": "Cloudflare Stream Configuration",
+  "IP": "IP",
   "IP地址": "IP Address",
   "LSS": "LSS",
   "Logo URL": "Logo URL",
+  "PTZ": "PTZ",
   "RTSP URL模板": "RTSP URL Template",
   "RTSP 地址": "RTSP URL",
   "RTSP 流": "RTSP Stream",
   "RTSP 流需要通过服务端转换为 HLS/WebRTC 后才能在浏览器播放": "RTSP streams need to be converted to HLS/WebRTC via server before playing in browser",
   "RTSP地址": "RTSP URL",
   "RTSP端口": "RTSP Port",
-  "Stream SN": "Stream SN",
   "Stream 测试": "Stream Test",
   "Video ID": "Video ID",
+  "ably信息": "ably Information",
   "button.cancel": "Cancel",
   "button.confirm": "Confirm",
   "button.disable": "Disable",
@@ -26,7 +28,6 @@
   "login.confirmPassword": "Confirm Password",
   "login.editpassWord": "Edit Password",
   "login.passWord": "Password",
-  "name": "Name",
   "pop.ScanQRcode": "Scan QR Code",
   "pop.getsecretkey": "Get Secret Key",
   "pop.secretkey": "Secret Key",
@@ -44,14 +45,16 @@
   "仪表盘": "Dashboard",
   "位置": "Location",
   "保存配置": "Save Configuration",
+  "修改失败": "Update failed",
   "修改密码": "Change Password",
   "修改成功": "Updated successfully",
   "停止": "Stop",
+  "停止失败": "Stop failed",
+  "停止推流失败": "Stop stream failed",
   "全屏": "Fullscreen",
   "全部": "All",
+  "关键词": "Keyword",
   "关闭": "Close",
-  "关闭失败": "Close failed",
-  "关闭时间": "Close time",
   "创建时间": "Created At",
   "初始化失败": "Initialization failed",
   "初始化成功": "Initialization successful",
@@ -60,7 +63,6 @@
   "删除失败": "Delete failed",
   "删除成功": "Deleted successfully",
   "刷新数据": "Refresh",
-  "功能": "Function",
   "协议支持": "Protocol Support",
   "厂家代码": "Factory Code",
   "厂家名称": "Factory Name",
@@ -73,10 +75,8 @@
   "名称": "Name",
   "吗?": "?",
   "否": "No",
-  "启动": "Start",
   "启动失败": "Start failed",
-  "启动成功": "Start successful",
-  "启动时间": "Start time",
+  "启动推流失败": "Start stream failed",
   "启用": "Enabled",
   "启用状态": "Status",
   "命令模板": "Command Template",
@@ -87,11 +87,13 @@
   "如何获取 Customer Subdomain": "How to get Customer Subdomain",
   "密码": "Password",
   "密码长度不能少于6位": "Password length must be at least 6 characters",
-  "已关闭": "Closed",
+  "已停止": "Stopped",
   "已启用": "Enabled",
   "已禁用": "Disabled",
+  "已跳转到预置位": "Jumped to preset",
   "已选择": "Selected",
   "序号": "No.",
+  "开启": "Start",
   "开始日期": "Start Date",
   "当前任务": "Current Task",
   "当前状态": "Current Status",
@@ -107,18 +109,21 @@
   "技术支持": "Support",
   "排序": "Sort",
   "排序号": "Sort No.",
+  "推流中": "Streaming",
+  "推流任务已停止": "Stream task stopped",
+  "推流任务已启动": "Stream task started",
+  "推流控制": "Push Stream Control",
   "推流方式": "Push Stream Method",
+  "推流状态": "Push Stream Status",
   "推荐通过后端代理调用,避免暴露 Token": "Recommended to call through the backend proxy to avoid exposing the Token",
   "描述": "Description",
   "提示": "Notice",
-  "摄像头": "Camera",
   "摄像头ID": "Camera ID",
   "摄像头在线率": "Camera Online Rate",
   "摄像头总数": "Total Cameras",
   "摄像头数": "Cameras",
   "摄像头管理": "Camera Management",
   "摄像头管理系统": "Camera Management",
-  "摄像头编号": "Camera ID",
   "摄像头连接失败": "Camera connection failed",
   "摄像头连接正常": "Camera connection successful",
   "播放": "Play",
@@ -127,10 +132,12 @@
   "播放方式": "Playback Method",
   "播放测试视频": "Play Test Video",
   "操作": "Actions",
+  "放大": "",
   "数据更新时间": "Last Updated",
   "新增": "Add",
-  "新增 LiveStream": "Add LiveStream",
+  "新增 Live Stream": "Add Live Stream",
   "新增厂家": "Add Factory",
+  "新增失败": "Add failed",
   "新增成功": "Added successfully",
   "新增摄像头": "Add Camera",
   "新增机器": "Add Machine",
@@ -139,7 +146,13 @@
   "是": "Yes",
   "暂停": "Pause",
   "暂无日志": "No logs",
+  "暂无视频流": "No video stream",
+  "暂无预置位": "No preset",
+  "更新": "Update",
+  "更新失败": "Update failed",
+  "更新成功": "Updated successfully",
   "有声": "Sound",
+  "未配置摄像头": "No camera configured",
   "机器ID": "Machine ID",
   "机器总数": "Total Machines",
   "机器管理": "Machine Management",
@@ -150,9 +163,11 @@
   "欢迎回来": "Welcome Back",
   "欢迎回来,这是您的数据概览": "Welcome back, here is your data overview",
   "正常": "Normal",
+  "流媒体播放": "Media Playback",
   "测试播放": "Test Playback",
   "测试视频": "Test Video",
   "测试连接": "Test Connection",
+  "添加": "Add",
   "清空": "Clear",
   "版本": "Version",
   "状态": "Status",
@@ -167,36 +182,44 @@
   "登录成功": "Login successful",
   "直接 URL": "Direct URL",
   "直接 URL 播放": "Direct URL Playback",
+  "直播中": "Live",
   "确定": "Confirm",
+  "确定要停止该推流任务吗?": "Are you sure you want to stop the stream task?",
   "确定要初始化默认厂家数据吗?这将添加预设的摄像头厂家信息。": "Are you sure you want to initialize default factory data? This will add preset camera factory information.",
   "确定要删除厂家": "Are you sure you want to delete factory",
   "确定要删除摄像头": "Are you sure you want to delete camera",
   "确定要删除机器": "Are you sure you want to delete machine",
+  "确定要删除该 Live Stream 吗?": "Are you sure you want to delete the Live Stream?",
   "确定要删除选中的": "Are you sure you want to delete the selected",
   "确认密码": "Confirm Password",
+  "禁用": "Disable",
   "离线": "Offline",
   "稳定性": "Uptime",
+  "空闲": "Idle",
   "端口": "Port",
   "系统信息": "System Info",
   "系统状态": "System Status",
   "系统运行正常": "System running normally",
   "结束日期": "End Date",
   "编辑": "Edit",
-  "编辑 LiveStream": "Edit LiveStream",
+  "编辑 Live Stream": "Edit Live Stream",
   "编辑厂家": "Edit Factory",
   "编辑摄像头": "Edit Camera",
   "编辑机器": "Edit Machine",
+  "缩小": "Zoom Out",
   "能力": "Capabilities",
   "自动播放": "Autoplay",
   "自定义域名": "Custom Domain",
   "至": "to",
   "获取机器列表失败": "Failed to get machine list",
   "获取统计数据失败": "Failed to get statistics",
-  "观看": "Watch",
   "观看统计": "Watching Statistics",
   "视频地址": "Video URL",
   "视频播放测试": "Video Playback Test",
   "记住我": "Remember me",
+  "设备ID": "Device ID",
+  "设备列表": "Device List",
+  "请先配置摄像头": "Please configure the camera first",
   "请再次输入新密码": "Please enter the new password again",
   "请联系管理员重置密码": "Please contact the administrator to reset your password",
   "请输入IP地址": "Please enter IP address",
@@ -211,8 +234,9 @@
   "请输入正确的IP地址": "Please enter a valid IP address",
   "请输入用户名": "Please enter username",
   "请输入视频地址并点击播放": "Please enter video URL and click play",
-  "请选择推流方式": "Please select push stream method",
+  "请选择 LSS 节点": "",
   "请选择视频源并点击播放": "Please select video source and click play",
+  "跳转失败": "Jump failed",
   "转换服务地址": "Proxy Service URL",
   "退出登录": "Logout",
   "选择测试源": "Select Test Source",
@@ -220,10 +244,13 @@
   "通道ID": "Channel ID",
   "通道列表": "Channel List",
   "通道总数": "Total Channels",
+  "速度": "Speed",
   "配置说明": "Configuration Description",
   "重置": "Reset",
+  "错误": "",
   "静音": "Muted",
   "项": "items",
+  "预置位": "",
   "默认分辨率": "Default Resolution",
   "默认端口": "Default Port",
   "默认视角": "Default View"

+ 42 - 15
src/locales/zh-cn.json

@@ -1,18 +1,20 @@
 {
   "Cloudflare Stream": "Cloudflare Stream",
   "Cloudflare Stream 配置": "Cloudflare Stream 配置",
+  "IP": "IP",
   "IP地址": "IP地址",
   "LSS": "LSS",
   "Logo URL": "Logo URL",
+  "PTZ": "PTZ",
   "RTSP URL模板": "RTSP URL模板",
   "RTSP 地址": "RTSP 地址",
   "RTSP 流": "RTSP 流",
   "RTSP 流需要通过服务端转换为 HLS/WebRTC 后才能在浏览器播放": "RTSP 流需要通过服务端转换为 HLS/WebRTC 后才能在浏览器播放",
   "RTSP地址": "RTSP地址",
   "RTSP端口": "RTSP端口",
-  "Stream SN": "Stream SN",
   "Stream 测试": "Stream 测试",
   "Video ID": "Video ID",
+  "ably信息": "ably信息",
   "button.cancel": "button.cancel",
   "button.confirm": "button.confirm",
   "button.disable": "button.disable",
@@ -26,7 +28,6 @@
   "login.confirmPassword": "login.confirmPassword",
   "login.editpassWord": "login.editpassWord",
   "login.passWord": "login.passWord",
-  "name": "name",
   "pop.ScanQRcode": "pop.ScanQRcode",
   "pop.getsecretkey": "pop.getsecretkey",
   "pop.secretkey": "pop.secretkey",
@@ -44,14 +45,16 @@
   "仪表盘": "仪表盘",
   "位置": "位置",
   "保存配置": "保存配置",
+  "修改失败": "修改失败",
   "修改密码": "修改密码",
   "修改成功": "修改成功",
   "停止": "停止",
+  "停止失败": "停止失败",
+  "停止推流失败": "停止推流失败",
   "全屏": "全屏",
   "全部": "全部",
+  "关键词": "关键词",
   "关闭": "关闭",
-  "关闭失败": "关闭失败",
-  "关闭时间": "关闭时间",
   "创建时间": "创建时间",
   "初始化失败": "初始化失败",
   "初始化成功": "初始化成功",
@@ -60,7 +63,6 @@
   "删除失败": "删除失败",
   "删除成功": "删除成功",
   "刷新数据": "刷新数据",
-  "功能": "功能",
   "协议支持": "协议支持",
   "厂家代码": "厂家代码",
   "厂家名称": "厂家名称",
@@ -73,10 +75,8 @@
   "名称": "名称",
   "吗?": "吗?",
   "否": "否",
-  "启动": "启动",
   "启动失败": "启动失败",
-  "启动成功": "启动成功",
-  "启动时间": "启动时间",
+  "启动推流失败": "启动推流失败",
   "启用": "启用",
   "启用状态": "启用状态",
   "命令模板": "命令模板",
@@ -87,11 +87,13 @@
   "如何获取 Customer Subdomain": "如何获取 Customer Subdomain",
   "密码": "密码",
   "密码长度不能少于6位": "密码长度不能少于6位",
-  "已关闭": "已关闭",
+  "已停止": "已停止",
   "已启用": "已启用",
   "已禁用": "已禁用",
+  "已跳转到预置位": "已跳转到预置位",
   "已选择": "已选择",
   "序号": "序号",
+  "开启": "开启",
   "开始日期": "开始日期",
   "当前任务": "当前任务",
   "当前状态": "当前状态",
@@ -107,18 +109,21 @@
   "技术支持": "技术支持",
   "排序": "排序",
   "排序号": "排序号",
+  "推流中": "推流中",
+  "推流任务已停止": "推流任务已停止",
+  "推流任务已启动": "推流任务已启动",
+  "推流控制": "推流控制",
   "推流方式": "推流方式",
+  "推流状态": "推流状态",
   "推荐通过后端代理调用,避免暴露 Token": "推荐通过后端代理调用,避免暴露 Token",
   "描述": "描述",
   "提示": "提示",
-  "摄像头": "摄像头",
   "摄像头ID": "摄像头ID",
   "摄像头在线率": "摄像头在线率",
   "摄像头总数": "摄像头总数",
   "摄像头数": "摄像头数",
   "摄像头管理": "摄像头管理",
   "摄像头管理系统": "摄像头管理系统",
-  "摄像头编号": "摄像头编号",
   "摄像头连接失败": "摄像头连接失败",
   "摄像头连接正常": "摄像头连接正常",
   "播放": "播放",
@@ -127,10 +132,12 @@
   "播放方式": "播放方式",
   "播放测试视频": "播放测试视频",
   "操作": "操作",
+  "放大": "放大",
   "数据更新时间": "数据更新时间",
   "新增": "新增",
-  "新增 LiveStream": "新增 LiveStream",
+  "新增 Live Stream": "新增 Live Stream",
   "新增厂家": "新增厂家",
+  "新增失败": "新增失败",
   "新增成功": "新增成功",
   "新增摄像头": "新增摄像头",
   "新增机器": "新增机器",
@@ -139,7 +146,13 @@
   "是": "是",
   "暂停": "暂停",
   "暂无日志": "暂无日志",
+  "暂无视频流": "暂无视频流",
+  "暂无预置位": "暂无预置位",
+  "更新": "更新",
+  "更新失败": "更新失败",
+  "更新成功": "更新成功",
   "有声": "有声",
+  "未配置摄像头": "未配置摄像头",
   "机器ID": "机器ID",
   "机器总数": "机器总数",
   "机器管理": "机器管理",
@@ -150,9 +163,11 @@
   "欢迎回来": "欢迎回来",
   "欢迎回来,这是您的数据概览": "欢迎回来,这是您的数据概览",
   "正常": "正常",
+  "流媒体播放": "流媒体播放",
   "测试播放": "测试播放",
   "测试视频": "测试视频",
   "测试连接": "测试连接",
+  "添加": "添加",
   "清空": "清空",
   "版本": "版本",
   "状态": "状态",
@@ -167,36 +182,44 @@
   "登录成功": "登录成功",
   "直接 URL": "直接 URL",
   "直接 URL 播放": "直接 URL 播放",
+  "直播中": "直播中",
   "确定": "确定",
+  "确定要停止该推流任务吗?": "确定要停止该推流任务吗?",
   "确定要初始化默认厂家数据吗?这将添加预设的摄像头厂家信息。": "确定要初始化默认厂家数据吗?这将添加预设的摄像头厂家信息。",
   "确定要删除厂家": "确定要删除厂家",
   "确定要删除摄像头": "确定要删除摄像头",
   "确定要删除机器": "确定要删除机器",
+  "确定要删除该 Live Stream 吗?": "确定要删除该 Live Stream 吗?",
   "确定要删除选中的": "确定要删除选中的",
   "确认密码": "确认密码",
+  "禁用": "禁用",
   "离线": "离线",
   "稳定性": "稳定性",
+  "空闲": "空闲",
   "端口": "端口",
   "系统信息": "系统信息",
   "系统状态": "系统状态",
   "系统运行正常": "系统运行正常",
   "结束日期": "结束日期",
   "编辑": "编辑",
-  "编辑 LiveStream": "编辑 LiveStream",
+  "编辑 Live Stream": "编辑 Live Stream",
   "编辑厂家": "编辑厂家",
   "编辑摄像头": "编辑摄像头",
   "编辑机器": "编辑机器",
+  "缩小": "缩小",
   "能力": "能力",
   "自动播放": "自动播放",
   "自定义域名": "自定义域名",
   "至": "至",
   "获取机器列表失败": "获取机器列表失败",
   "获取统计数据失败": "获取统计数据失败",
-  "观看": "观看",
   "观看统计": "观看统计",
   "视频地址": "视频地址",
   "视频播放测试": "视频播放测试",
   "记住我": "记住我",
+  "设备ID": "设备ID",
+  "设备列表": "设备列表",
+  "请先配置摄像头": "请先配置摄像头",
   "请再次输入新密码": "请再次输入新密码",
   "请联系管理员重置密码": "请联系管理员重置密码",
   "请输入IP地址": "请输入IP地址",
@@ -211,8 +234,9 @@
   "请输入正确的IP地址": "请输入正确的IP地址",
   "请输入用户名": "请输入用户名",
   "请输入视频地址并点击播放": "请输入视频地址并点击播放",
-  "请选择推流方式": "请选择推流方式",
+  "请选择 LSS 节点": "请选择 LSS 节点",
   "请选择视频源并点击播放": "请选择视频源并点击播放",
+  "跳转失败": "跳转失败",
   "转换服务地址": "转换服务地址",
   "退出登录": "退出登录",
   "选择测试源": "选择测试源",
@@ -220,10 +244,13 @@
   "通道ID": "通道ID",
   "通道列表": "通道列表",
   "通道总数": "通道总数",
+  "速度": "速度",
   "配置说明": "配置说明",
   "重置": "重置",
+  "错误": "错误",
   "静音": "静音",
   "项": "项",
+  "预置位": "预置位",
   "默认分辨率": "默认分辨率",
   "默认端口": "默认端口",
   "默认视角": "默认视角"

+ 8 - 7
src/views/live-stream/index.vue

@@ -102,13 +102,11 @@
             {{ formatDateTime(row.createdAt) }}
           </template>
         </el-table-column>
-        <el-table-column :label="t('操作')" width="200" align="center" fixed="right">
+        <el-table-column :label="t('操作')" align="center" fixed="right">
           <template #default="{ row }">
-            <el-button :icon="VideoPlay" type="primary" link @click="handleViewCloudflare(row)">
-              {{ t('流媒体') }}
-            </el-button>
-            <el-button type="primary" link @click="handleEdit(row)">{{ t('编辑') }}</el-button>
-            <el-button type="danger" link @click="handleDelete(row)">{{ t('删除') }}</el-button>
+            <el-button :icon="VideoPlay" type="primary" link @click="handleViewCloudflare(row)"></el-button>
+            <el-button type="primary" :icon="Edit" link @click="handleEdit(row)"></el-button>
+            <el-button type="danger" :icon="Delete" link @click="handleDelete(row)"></el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -414,12 +412,15 @@ import {
   BottomRight,
   Refresh,
   ZoomIn,
-  ZoomOut
+  ZoomOut,
+  Edit,
+  Delete
 } from '@element-plus/icons-vue'
 import { listLiveStreams, addLiveStream, updateLiveStream, deleteLiveStream } from '@/api/live-stream'
 import { listAllLssNodes } from '@/api/lss'
 import { adminListCameras } from '@/api/camera'
 import { listAllStreamChannels } from '@/api/stream-channel'
+
 import { startStreamTask, stopStreamTask, getStreamPlayback } from '@/api/stream-push'
 import VideoPlayer from '@/components/VideoPlayer.vue'
 import { ptzStart, ptzStop, getPresets, gotoPreset, type PresetInfo } from '@/api/camera'

+ 13 - 7
src/views/lss/index.vue

@@ -54,10 +54,16 @@
         <el-table-column prop="lssName" :label="t('名称')" min-width="140" sortable="custom" show-overflow-tooltip />
         <el-table-column prop="address" :label="t('地址')" min-width="180" sortable="custom" show-overflow-tooltip />
         <el-table-column prop="ip" :label="t('IP')" min-width="180" sortable="custom" show-overflow-tooltip />
-        <el-table-column label="心跳" width="220" align="center">
+        <el-table-column :label="t('心跳')" width="220" align="center">
           <template #default="{ row }">
             {{
-              row.status === 'active' ? '活跃' : row.status === 'hold' ? 'hold' : row.status === 'dead' ? '离线' : '-'
+              row.status === 'active'
+                ? t('活跃')
+                : row.status === 'hold'
+                ? t('hold')
+                : row.status === 'dead'
+                ? t('离线')
+                : '-'
             }}
             |
             {{ formatTime(row.lastHeartbeatAt) }}
@@ -70,7 +76,7 @@
             </el-button>
           </template>
         </el-table-column>
-        <el-table-column :label="t('ably信息')" align="center" fixed="right">
+        <el-table-column :label="t('ably')" align="center" fixed="right">
           <template #default="{ row }">
             {{ row.ablyInfo || '-' }}
           </template>
@@ -241,7 +247,7 @@
             </div>
             <el-empty v-if="!cameraLoading && cameraList.length === 0" description="暂无关联设备" />
             <el-table v-else :data="cameraList" stripe size="small" border>
-              <el-table-column prop="ip" label="本地IP" min-width="110" />
+              <!-- <el-table-column prop="ip" label="本地IP" min-width="110" /> -->
               <el-table-column prop="cameraId" label="设备ID" min-width="100" show-overflow-tooltip />
               <el-table-column prop="name" label="名称" min-width="100" show-overflow-tooltip />
               <el-table-column label="状态(心跳)" min-width="140">
@@ -497,10 +503,10 @@
           <el-input v-model="cameraForm.password" type="password" show-password placeholder="请输入密码" />
         </el-form-item> -->
         <el-form-item label="参数配置">
-          <JsonEditor v-model="cameraForm.paramConfig" height="150px" placeholder="请输入参数配置 (JSON)" />
+          <JsonEditor v-model="cameraForm.paramConfig" height="140px" placeholder="请输入参数配置 (JSON)" />
         </el-form-item>
         <el-form-item label="设备运行参数">
-          <JsonEditor v-model="cameraForm.runtimeParams" height="150px" placeholder="设备运行参数 (JSON)" />
+          <JsonEditor v-model="cameraForm.runtimeParams" height="140px" placeholder="设备运行参数 (JSON)" />
         </el-form-item>
       </el-form>
       <template #footer>
@@ -652,7 +658,7 @@ function formatBrand(brand: string | undefined): string {
 // 格式化心跳状态
 function formatHeartbeat(lss: LssNodeDTO | null | undefined): string {
   if (!lss) return '-'
-  const status = lss.heartbeat || (lss.status === 'ONLINE' ? 'active' : 'dead')
+  const status = lss.heartbeat || (lss.status === 'active' ? 'active' : lss.status === 'hold' ? 'hold' : 'dead')
   const time = lss.heartbeatTime || lss.updatedAt
   if (status === 'active') {
     return `active [${formatTime(time)}]`