1
0

EnvGroupTabs.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <script setup lang="ts">
  2. import type { EnvGroup } from '@/api/env_group'
  3. import type { Environment } from '@/api/environment'
  4. import nodeApi from '@/api/node'
  5. import { useUserStore } from '@/pinia'
  6. import { message } from 'ant-design-vue'
  7. import { SSE } from 'sse.js'
  8. const props = defineProps<{
  9. envGroups: EnvGroup[]
  10. }>()
  11. const modelValue = defineModel<string | number>('activeKey')
  12. const { token } = storeToRefs(useUserStore())
  13. const environments = ref<Environment[]>([])
  14. const environmentsMap = ref<Record<number, Environment>>({})
  15. const sse = shallowRef<SSE>()
  16. const loading = ref({
  17. reload: false,
  18. restart: false,
  19. })
  20. // Get node data when tab is not 'All'
  21. watch(modelValue, newVal => {
  22. if (newVal && newVal !== 0) {
  23. connectSSE()
  24. }
  25. else {
  26. disconnectSSE()
  27. }
  28. }, { immediate: true })
  29. onUnmounted(() => {
  30. disconnectSSE()
  31. })
  32. function connectSSE() {
  33. disconnectSSE()
  34. const s = new SSE('api/environments/enabled', {
  35. headers: {
  36. Authorization: token.value,
  37. },
  38. })
  39. s.onmessage = e => {
  40. environments.value = JSON.parse(e.data)
  41. environmentsMap.value = environments.value.reduce((acc, node) => {
  42. acc[node.id] = node
  43. return acc
  44. }, {} as Record<number, Environment>)
  45. }
  46. s.onerror = () => {
  47. setTimeout(() => {
  48. connectSSE()
  49. }, 5000)
  50. }
  51. sse.value = s
  52. }
  53. function disconnectSSE() {
  54. if (sse.value) {
  55. sse.value.close()
  56. sse.value = undefined
  57. }
  58. }
  59. // Get the current environment group data
  60. const currentEnvGroup = computed(() => {
  61. if (!modelValue.value || modelValue.value === 0)
  62. return null
  63. return props.envGroups.find(g => g.id === Number(modelValue.value))
  64. })
  65. // Get the list of nodes in the current group
  66. const syncNodes = computed(() => {
  67. if (!currentEnvGroup.value)
  68. return []
  69. if (!currentEnvGroup.value.sync_node_ids)
  70. return []
  71. return currentEnvGroup.value.sync_node_ids
  72. .map(id => environmentsMap.value[id])
  73. .filter(Boolean)
  74. })
  75. // Handle reload Nginx on all sync nodes
  76. async function handleReloadNginx() {
  77. if (!currentEnvGroup.value || !syncNodes.value.length)
  78. return
  79. const nodeIds = syncNodes.value.map(node => node.id)
  80. loading.value.reload = true
  81. try {
  82. await nodeApi.reloadNginx(nodeIds)
  83. }
  84. catch (error) {
  85. console.error(error)
  86. message.error($gettext('Reload request failed, please check your network connection'))
  87. }
  88. finally {
  89. loading.value.reload = false
  90. }
  91. }
  92. // Handle restart Nginx on all sync nodes
  93. async function handleRestartNginx() {
  94. if (!currentEnvGroup.value || !syncNodes.value.length)
  95. return
  96. const nodeIds = syncNodes.value.map(node => node.id)
  97. loading.value.restart = true
  98. try {
  99. await nodeApi.restartNginx(nodeIds)
  100. }
  101. catch (error) {
  102. console.error(error)
  103. message.error($gettext('Restart request failed, please check your network connection'))
  104. }
  105. finally {
  106. loading.value.restart = false
  107. }
  108. }
  109. </script>
  110. <template>
  111. <div>
  112. <ATabs :active-key="modelValue" @update:active-key="modelValue = $event">
  113. <ATabPane :key="0" :tab="$gettext('All')" />
  114. <ATabPane v-for="c in envGroups" :key="c.id" :tab="c.name" />
  115. </ATabs>
  116. <!-- Display node information -->
  117. <ACard
  118. v-if="modelValue && modelValue !== 0 && syncNodes.length > 0"
  119. :title="$gettext('Sync Nodes')"
  120. size="small"
  121. class="mb-4"
  122. >
  123. <template #extra>
  124. <ASpace>
  125. <APopconfirm
  126. :title="$gettext('Are you sure you want to reload Nginx on the following sync nodes?')"
  127. :ok-text="$gettext('Yes')"
  128. :cancel-text="$gettext('No')"
  129. placement="bottom"
  130. @confirm="handleReloadNginx"
  131. >
  132. <AButton type="link" size="small" :loading="loading.reload">
  133. {{ $gettext('Reload Nginx') }}
  134. </AButton>
  135. </APopconfirm>
  136. <APopconfirm
  137. :title="$gettext('Are you sure you want to restart Nginx on the following sync nodes?')"
  138. :ok-text="$gettext('Yes')"
  139. :cancel-text="$gettext('No')"
  140. placement="bottomRight"
  141. @confirm="handleRestartNginx"
  142. >
  143. <AButton type="link" danger size="small" :loading="loading.restart">
  144. {{ $gettext('Restart Nginx') }}
  145. </AButton>
  146. </APopconfirm>
  147. </ASpace>
  148. </template>
  149. <ARow :gutter="[16, 16]">
  150. <ACol v-for="node in syncNodes" :key="node.id" :xs="24" :sm="12" :md="8" :lg="6" :xl="4">
  151. <div class="node-item">
  152. <span class="node-name">{{ node.name }}</span>
  153. <ATag :color="node.status ? 'green' : 'error'">
  154. {{ node.status ? $gettext('Online') : $gettext('Offline') }}
  155. </ATag>
  156. </div>
  157. </ACol>
  158. </ARow>
  159. </ACard>
  160. </div>
  161. </template>
  162. <style scoped>
  163. .node-name {
  164. margin-right: 8px;
  165. white-space: nowrap;
  166. overflow: hidden;
  167. text-overflow: ellipsis;
  168. }
  169. </style>