NgxUpstream.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. <script setup lang="ts">
  2. import type { NgxDirective } from '@/api/ngx'
  3. import type { UpstreamStatus } from '@/api/upstream'
  4. import type ReconnectingWebSocket from 'reconnecting-websocket'
  5. import upstream from '@/api/upstream'
  6. import { MoreOutlined, PlusOutlined } from '@ant-design/icons-vue'
  7. import { Modal } from 'ant-design-vue'
  8. import { throttle } from 'lodash'
  9. import { DirectiveEditor, Server, useNgxConfigStore } from '.'
  10. const [modal, ContextHolder] = Modal.useModal()
  11. const ngxConfigStore = useNgxConfigStore()
  12. const { ngxConfig } = storeToRefs(ngxConfigStore)
  13. const currentUpstreamIdx = ref(0)
  14. async function addUpstream() {
  15. if (!ngxConfig.value.upstreams)
  16. ngxConfig.value.upstreams = []
  17. ngxConfig.value.upstreams?.push({
  18. name: '',
  19. comments: '',
  20. directives: [],
  21. })
  22. rename(ngxConfig.value.upstreams.length - 1)
  23. }
  24. function removeUpstream(index: number) {
  25. modal.confirm({
  26. title: $gettext('Do you want to remove this upstream?'),
  27. mask: false,
  28. centered: true,
  29. okText: $gettext('OK'),
  30. cancelText: $gettext('Cancel'),
  31. onOk() {
  32. ngxConfig.value.upstreams?.splice(index, 1)
  33. currentUpstreamIdx.value = (index > 1 ? index - 1 : 0)
  34. },
  35. })
  36. }
  37. const curUptreamDirectives = computed(() => {
  38. return ngxConfig.value.upstreams?.[currentUpstreamIdx.value]?.directives
  39. })
  40. const open = ref(false)
  41. const renameIdx = ref(-1)
  42. const buffer = ref('')
  43. function rename(idx: number) {
  44. open.value = true
  45. renameIdx.value = idx
  46. buffer.value = ngxConfig.value.upstreams?.[renameIdx.value].name ?? ''
  47. }
  48. function renameOK() {
  49. if (ngxConfig.value.upstreams?.[renameIdx.value])
  50. ngxConfig.value.upstreams[renameIdx.value].name = buffer.value
  51. open.value = false
  52. }
  53. const availabilityResult = ref({}) as Ref<Record<string, UpstreamStatus>>
  54. const websocket = shallowRef<ReconnectingWebSocket | WebSocket>()
  55. function availabilityTest() {
  56. const sockets: string[] = []
  57. for (const u of ngxConfig.value.upstreams ?? []) {
  58. for (const d of u.directives ?? []) {
  59. if (d.directive === Server)
  60. sockets.push(d.params.split(' ')[0])
  61. }
  62. }
  63. if (sockets.length > 0) {
  64. websocket.value = upstream.availability_test()
  65. websocket.value.onopen = () => {
  66. websocket.value!.send(JSON.stringify(sockets))
  67. }
  68. websocket.value.onmessage = (e: MessageEvent) => {
  69. availabilityResult.value = JSON.parse(e.data)
  70. }
  71. }
  72. }
  73. onMounted(() => {
  74. availabilityTest()
  75. })
  76. onBeforeUnmount(() => {
  77. websocket.value?.close()
  78. })
  79. async function _restartTest() {
  80. websocket.value?.close()
  81. availabilityTest()
  82. }
  83. const restartTest = throttle(_restartTest, 5000)
  84. watch(curUptreamDirectives, () => {
  85. restartTest()
  86. }, { deep: true })
  87. </script>
  88. <template>
  89. <div>
  90. <ContextHolder />
  91. <ATabs
  92. v-if="ngxConfig.upstreams && ngxConfig.upstreams.length > 0"
  93. v-model:active-key="currentUpstreamIdx"
  94. >
  95. <ATabPane
  96. v-for="(v, k) in ngxConfig.upstreams"
  97. :key="k"
  98. >
  99. <template #tab>
  100. Upstream {{ v.name }}
  101. <ADropdown>
  102. <MoreOutlined />
  103. <template #overlay>
  104. <AMenu>
  105. <AMenuItem>
  106. <a @click="rename(k)">{{ $gettext('Rename') }}</a>
  107. </AMenuItem>
  108. <AMenuItem>
  109. <a @click="removeUpstream(k)">{{ $gettext('Delete') }}</a>
  110. </AMenuItem>
  111. </AMenu>
  112. </template>
  113. </ADropdown>
  114. </template>
  115. <div class="tab-content">
  116. <DirectiveEditor>
  117. <template #directiveSuffix="{ directive }: {directive: NgxDirective}">
  118. <template v-if="availabilityResult[directive.params]?.online">
  119. <ABadge color="green" />
  120. {{ availabilityResult[directive.params]?.latency.toFixed(2) }}ms
  121. </template>
  122. </template>
  123. </DirectiveEditor>
  124. </div>
  125. </ATabPane>
  126. <template #rightExtra>
  127. <AButton
  128. type="link"
  129. size="small"
  130. @click="addUpstream"
  131. >
  132. <PlusOutlined />
  133. {{ $gettext('Add') }}
  134. </AButton>
  135. </template>
  136. </ATabs>
  137. <div v-else>
  138. <AEmpty />
  139. <div class="flex justify-center">
  140. <AButton
  141. type="primary"
  142. @click="addUpstream"
  143. >
  144. {{ $gettext('Create') }}
  145. </AButton>
  146. </div>
  147. </div>
  148. <AModal
  149. v-model:open="open"
  150. :title="$gettext('Upstream Name')"
  151. centered
  152. @ok="renameOK"
  153. >
  154. <AForm layout="vertical">
  155. <AFormItem :label="$gettext('Name')">
  156. <AInput v-model:value="buffer" />
  157. </AFormItem>
  158. </AForm>
  159. </AModal>
  160. </div>
  161. </template>
  162. <style scoped lang="less">
  163. </style>