index.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <template>
  2. <wrapper
  3. fill
  4. margin
  5. padding
  6. background
  7. >
  8. <platform-page
  9. class="l-flex__fill"
  10. @change="onTenantChanged"
  11. >
  12. <schema-table
  13. ref="table"
  14. :schema="schema"
  15. />
  16. </platform-page>
  17. <confirm-dialog
  18. ref="editDialog"
  19. title="日志抓取配置"
  20. @confirm="onConfirm"
  21. >
  22. <div class="c-sibling-item--v c-grid-form u-align-self--center">
  23. <div class="c-grid-form__row u-font-size--xs u-bold">
  24. <button
  25. class="o-button"
  26. @click="showDocument"
  27. >
  28. 抓取说明
  29. </button>
  30. </div>
  31. <div class="c-grid-form__row u-font-size--xs u-bold">当前状态:{{ logSetting.activate ? '已下发配置' : '未下发配置' }}</div>
  32. <span class="c-grid-form__label u-required">抓取时长</span>
  33. <div class="l-flex--row">
  34. <el-input-number
  35. v-model="logSetting.duration"
  36. :min="60"
  37. :max="86400"
  38. step-strictly
  39. />&nbsp;秒
  40. </div>
  41. <span class="c-grid-form__label u-required">抓取指令</span>
  42. <div
  43. class="has-info"
  44. data-info="多条指令以分号分隔"
  45. >
  46. <el-input
  47. v-model="logSetting.commands"
  48. type="textarea"
  49. :rows="5"
  50. />
  51. </div>
  52. <span class="c-grid-form__label u-required">是否重启</span>
  53. <el-switch
  54. v-model="logSetting.reboot"
  55. class="c-grid-form__option"
  56. active-color="#13ce66"
  57. inactive-color="#ff4949"
  58. />
  59. </div>
  60. <template #footer="{ confirm, cancel }">
  61. <button
  62. class="c-sibling-item o-button"
  63. @click="confirm"
  64. >
  65. 开启抓取
  66. </button>
  67. <button
  68. class="c-sibling-item o-button o-button--cancel"
  69. @click="cancel"
  70. >
  71. 取消
  72. </button>
  73. </template>
  74. </confirm-dialog>
  75. <table-dialog
  76. ref="resultDialog"
  77. size="lg"
  78. title="抓取结果"
  79. :schema="resultSchema"
  80. />
  81. <table-dialog
  82. ref="heartbeatDialog"
  83. size="lg fixed"
  84. title="心跳记录"
  85. :schema="heartbeatSchema"
  86. @hook:beforeDestroy="closeTimer"
  87. />
  88. </wrapper>
  89. </template>
  90. <script>
  91. import { parseTime } from '@/utils'
  92. import { getAssetUrl } from '@/api/asset'
  93. import { getDevicesByTenant } from '@/api/device'
  94. import {
  95. getRemoteLogConfig,
  96. startRemoteLog,
  97. stopRemoteLog,
  98. getRemoteLogs,
  99. getHeartbeats,
  100. getDownloadUrl
  101. } from './api'
  102. const defaultLogSettingForm = {
  103. activate: false,
  104. duration: 60,
  105. commands: 'logcat -vtime | grep -i -E "MqttClient|PushCallback|MqttService"',
  106. reboot: false
  107. }
  108. export default {
  109. name: 'DeviceLog',
  110. data () {
  111. return {
  112. logSetting: {},
  113. schema: {
  114. list: this.getDevicesByTenant,
  115. buttons: [
  116. { label: '心跳记录', on: this.onHeartbeats }
  117. ],
  118. filters: [
  119. { key: 'name', type: 'search', placeholder: '设备名称' }
  120. ],
  121. cols: [
  122. { type: 'refresh' },
  123. { prop: 'name', label: '设备名称' },
  124. { prop: 'serialNumber', label: '序列号' },
  125. { prop: 'mac', label: 'MAC' },
  126. { type: 'tag', render: ({ activate, onlineStatus }) => activate
  127. ? onlineStatus === 0
  128. ? { type: 'primary', label: '待接入' }
  129. : onlineStatus === 1
  130. ? { type: 'success', label: '在线' }
  131. : { type: 'danger', label: '离线' }
  132. : { type: 'warning', label: '未激活' } },
  133. { type: 'invoke', render: [
  134. { label: '开始抓取', on: this.onStart },
  135. { label: '停止抓取', on: this.onStop },
  136. { label: '抓取结果', on: this.onResult },
  137. { label: '心跳记录', on: this.onHeartbeat }
  138. ], width: 300 }
  139. ]
  140. },
  141. heartbeatSchema: {
  142. nonPagination: true,
  143. list: this.getheartbeatData,
  144. filters: [
  145. { key: 'name', type: 'search', placeholder: '设备名称' }
  146. ],
  147. cols: [
  148. { type: 'refresh' },
  149. { prop: 'name', label: '设备名称', sortable: true },
  150. { prop: 'sn', label: '序列号', sortable: true },
  151. { prop: 'mac', label: 'MAC', width: 140 },
  152. { prop: 'ip', label: 'ip', width: 140 },
  153. { prop: 'settingId', label: '当前事件' },
  154. { label: '上报时间', render: ({ timestamp }) => parseTime(timestamp), sortable: true, sortBy: 'timestamp', width: 160 }
  155. ]
  156. },
  157. curDeviceId: null
  158. }
  159. },
  160. computed: {
  161. resultSchema () {
  162. return {
  163. list: getRemoteLogs,
  164. condition: { deviceId: this.curDeviceId },
  165. cols: [
  166. { type: 'refresh' },
  167. { prop: 'settingId', label: '事件' },
  168. { label: '执行状态', type: 'tag', render: ({ status }) => {
  169. return {
  170. type: status ? 'success' : 'danger',
  171. label: status ? '成功' : '失败'
  172. }
  173. } },
  174. { prop: 'message', label: '备注' },
  175. { prop: 'commandArrayString', label: '抓取命令数组' },
  176. { prop: 'releaseTime', label: '命令发布时间', width: 180 },
  177. { prop: 'createTime', label: '上传时间', width: 180 },
  178. { type: 'invoke', render: [
  179. { label: '下载日志', allow: ({ status }) => status, on: this.onDownload }
  180. ] }
  181. ]
  182. }
  183. }
  184. },
  185. beforeDestroy () {
  186. this.closeTimer()
  187. },
  188. methods: {
  189. closeTimer () {
  190. clearTimeout(this.$timer)
  191. this.$timer = null
  192. },
  193. onTenantChanged (tenant) {
  194. this.$tenant = tenant
  195. this.$refs.table?.pageTo(1)
  196. },
  197. getDevicesByTenant (params) {
  198. if (!this.$tenant) {
  199. return Promise.resolve({ data: [] })
  200. }
  201. return getDevicesByTenant(this.$tenant.path, params)
  202. },
  203. onStop () {
  204. stopRemoteLog({
  205. duration: 60,
  206. deviceId: this.curDeviceId,
  207. activate: false
  208. })
  209. },
  210. onStart (device) {
  211. getRemoteLogConfig(device.id).then(({ data }) => {
  212. if (data) {
  213. const { activate, duration, commands, reboot } = data
  214. this.logSetting = {
  215. activate,
  216. duration,
  217. commands: commands ? commands.join(';') : [],
  218. reboot
  219. }
  220. } else {
  221. this.logSetting = { ...defaultLogSettingForm }
  222. }
  223. this.curDeviceId = device.id
  224. this.$refs.editDialog.show()
  225. })
  226. },
  227. async onConfirm (done) {
  228. const { duration, commands, reboot } = this.logSetting
  229. if (~commands.indexOf(';')) {
  230. this.$message({
  231. type: 'warning',
  232. message: '请使用英文输入下的【;】符号'
  233. })
  234. return
  235. }
  236. await startRemoteLog({
  237. deviceId: this.curDeviceId,
  238. duration,
  239. activate: true,
  240. commands: commands.split(';'),
  241. reboot
  242. })
  243. done()
  244. },
  245. onResult (device) {
  246. this.$deviceName = device.name
  247. this.curDeviceId = device.id
  248. this.$refs.resultDialog.show()
  249. },
  250. getheartbeatData () {
  251. this.closeTimer()
  252. return getHeartbeats(this.$mac).then(({ data }) => {
  253. this.$timer = setTimeout(() => {
  254. this.$refs?.heartbeatDialog.getTable()?.onPagination()
  255. }, 5 * 1000)
  256. if (!data) {
  257. return { data: [] }
  258. }
  259. if (Array.isArray(data)) {
  260. if (this.$refs?.heartbeatDialog.getTable()?.options.params.name?.length) {
  261. return { data: data.filter(i => i).map(i => JSON.parse(i))
  262. .filter(i => new RegExp(this.$refs.heartbeatDialog.getTable().options.params.name).test(i.name)) }
  263. }
  264. return { data: data.filter(i => i).map(i => JSON.parse(i)) }
  265. }
  266. data = JSON.parse(data)
  267. if (this.$mac) {
  268. const timestamp = data.timestamp
  269. data.name = this.$deviceName
  270. this.$heartBeatData = this.$heartBeatData || []
  271. if (!(this.$heartBeatData.length && this.$heartBeatData[0].timestamp === timestamp)) {
  272. this.$heartBeatData.unshift(data)
  273. }
  274. return { data: this.$heartBeatData }
  275. }
  276. return { data }
  277. })
  278. },
  279. onHeartbeats () {
  280. this.$heartBeatData = []
  281. this.$mac = null
  282. this.heartbeatSchema.filters = [
  283. { key: 'name', type: 'search', placeholder: '设备名称' }
  284. ]
  285. this.$refs.heartbeatDialog.show()
  286. },
  287. onHeartbeat (device) {
  288. this.$heartBeatData = []
  289. this.$deviceName = device.name
  290. this.$mac = device.mac
  291. this.heartbeatSchema.filters = null
  292. this.$refs.heartbeatDialog.show()
  293. },
  294. showDocument () {
  295. window.open('https://inspur-rd.feishu.cn/docx/M4oIdhmU1oFyvAxErn3cYv6Cnuf', '_blank')
  296. },
  297. onDownload ({ recordId, createTime }) {
  298. getDownloadUrl(recordId).then(
  299. ({ data: url }) => {
  300. const a = document.createElement('a')
  301. a.style.display = 'none'
  302. a.setAttribute('target', '_blank')
  303. a.setAttribute('download', `${this.$deviceName}-${createTime}日志`)
  304. a.href = getAssetUrl(url)
  305. document.body.appendChild(a)
  306. a.click()
  307. document.body.removeChild(a)
  308. }
  309. )
  310. }
  311. }
  312. }
  313. </script>