Environments.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <script setup lang="ts">
  2. import type ReconnectingWebSocket from 'reconnecting-websocket'
  3. import type { Ref } from 'vue'
  4. import type { Node } from '@/api/environment'
  5. import Icon, { LinkOutlined, SendOutlined, ThunderboltOutlined } from '@ant-design/icons-vue'
  6. import analytic from '@/api/analytic'
  7. import environment from '@/api/environment'
  8. import logo from '@/assets/img/logo.png'
  9. import pulse from '@/assets/svg/pulse.svg?component'
  10. import { formatDateTime } from '@/lib/helper'
  11. import { useSettingsStore } from '@/pinia'
  12. import { version } from '@/version.json'
  13. import NodeAnalyticItem from './components/NodeAnalyticItem.vue'
  14. const data = ref([]) as Ref<Node[]>
  15. const nodeMap = computed(() => {
  16. const o = {} as Record<number, Node>
  17. data.value.forEach(v => {
  18. o[v.id] = v
  19. })
  20. return o
  21. })
  22. let websocket: ReconnectingWebSocket | WebSocket
  23. onMounted(() => {
  24. environment.getList({ enabled: true }).then(r => {
  25. data.value.push(...r.data)
  26. })
  27. })
  28. onMounted(() => {
  29. websocket = analytic.nodes()
  30. websocket.onmessage = async m => {
  31. const nodes = JSON.parse(m.data)
  32. Object.keys(nodes).forEach((v: string) => {
  33. const key = Number.parseInt(v)
  34. // update node online status
  35. if (nodeMap.value[key]) {
  36. Object.assign(nodeMap.value[key], nodes[key])
  37. nodeMap.value[key].response_at = new Date()
  38. }
  39. })
  40. }
  41. })
  42. onUnmounted(() => {
  43. websocket.close()
  44. })
  45. const { environment: env } = useSettingsStore()
  46. function linkStart(node: Node) {
  47. env.id = node.id
  48. env.name = node.name
  49. }
  50. const visible = computed(() => {
  51. if (env.id > 0)
  52. return false
  53. else
  54. return data.value?.length
  55. })
  56. </script>
  57. <template>
  58. <ACard
  59. v-if="visible"
  60. class="env-list-card"
  61. :title="$gettext('Environments')"
  62. >
  63. <AList
  64. item-layout="horizontal"
  65. :data-source="data"
  66. >
  67. <template #renderItem="{ item }">
  68. <AListItem>
  69. <AListItemMeta>
  70. <template #title>
  71. <div class="mb-1">
  72. {{ item.name }}
  73. <ATag
  74. v-if="item.status"
  75. color="blue"
  76. class="ml-2"
  77. >
  78. {{ $gettext('Online') }}
  79. </ATag>
  80. <ATag
  81. v-else
  82. color="error"
  83. class="ml-2"
  84. >
  85. {{ $gettext('Offline') }}
  86. </ATag>
  87. </div>
  88. <template v-if="item.status">
  89. <div class="runtime-meta mr-2 mb-1">
  90. <Icon :component="pulse" /> {{ formatDateTime(item.response_at) }}
  91. </div>
  92. <div class="runtime-meta mr-2 mb-1">
  93. <ThunderboltOutlined />{{ item.version }}
  94. </div>
  95. </template>
  96. <div class="runtime-meta">
  97. <LinkOutlined />{{ item.url }}
  98. </div>
  99. </template>
  100. <template #avatar>
  101. <AAvatar :src="logo" />
  102. </template>
  103. <template #description>
  104. <div class="md:flex lg:flex justify-between md:items-center">
  105. <NodeAnalyticItem
  106. :item="item"
  107. class="mt-1 mb-1"
  108. />
  109. <AButton
  110. v-if="item.version === version"
  111. type="primary"
  112. :disabled="!item.status || env.id === item.id"
  113. ghost
  114. @click="linkStart(item)"
  115. >
  116. <SendOutlined />
  117. {{ env.id !== item.id ? $gettext('Link Start') : $gettext('Connected') }}
  118. </AButton>
  119. <ATooltip
  120. v-else
  121. placement="topLeft"
  122. >
  123. <template #title>
  124. {{ $gettext('The remote Nginx UI version is not compatible with the local Nginx UI version. '
  125. + 'To avoid potential errors, please upgrade the remote Nginx UI to match the local version.') }}
  126. </template>
  127. <AButton
  128. ghost
  129. disabled
  130. >
  131. <SendOutlined />
  132. {{ $gettext('Link Start') }}
  133. </AButton>
  134. </ATooltip>
  135. </div>
  136. </template>
  137. </AListItemMeta>
  138. </AListItem>
  139. </template>
  140. </AList>
  141. </ACard>
  142. </template>
  143. <style scoped lang="less">
  144. :deep(.ant-list-item-meta-title) {
  145. display: flex;
  146. align-items: center;
  147. @media (max-width: 700px) {
  148. display: block;
  149. }
  150. }
  151. .env-list-card {
  152. margin-top: 16px;
  153. .runtime-meta {
  154. display: inline-flex;
  155. @media (max-width: 700px) {
  156. align-items: center;
  157. }
  158. font-weight: 400;
  159. color: #9b9b9b;
  160. .anticon {
  161. margin-right: 4px;
  162. }
  163. }
  164. }
  165. :deep(.ant-list-item-action) {
  166. @media(max-width: 500px) {
  167. display: none;
  168. }
  169. }
  170. </style>