NgxUpstream.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. <script setup lang="ts">
  2. import type ReconnectingWebSocket from 'reconnecting-websocket'
  3. import type { NgxDirective } from '@/api/ngx'
  4. import type { UpstreamStatus } from '@/api/upstream'
  5. import { MoreOutlined, PlusOutlined } from '@ant-design/icons-vue'
  6. import { Modal } from 'ant-design-vue'
  7. import { throttle } from 'lodash'
  8. import upstream from '@/api/upstream'
  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 v-model:directives="v.directives">
  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 v-else>
  123. <ABadge color="red" />
  124. {{ $gettext('Offline') }}
  125. </template>
  126. </template>
  127. </DirectiveEditor>
  128. </div>
  129. </ATabPane>
  130. <template #rightExtra>
  131. <AButton
  132. type="link"
  133. size="small"
  134. @click="addUpstream"
  135. >
  136. <PlusOutlined />
  137. {{ $gettext('Add') }}
  138. </AButton>
  139. </template>
  140. </ATabs>
  141. <div v-else>
  142. <AEmpty />
  143. <div class="flex justify-center">
  144. <AButton
  145. type="primary"
  146. @click="addUpstream"
  147. >
  148. {{ $gettext('Create') }}
  149. </AButton>
  150. </div>
  151. </div>
  152. <AModal
  153. v-model:open="open"
  154. :title="$gettext('Upstream Name')"
  155. centered
  156. @ok="renameOK"
  157. >
  158. <AForm layout="vertical">
  159. <AFormItem :label="$gettext('Name')">
  160. <AInput v-model:value="buffer" />
  161. </AFormItem>
  162. </AForm>
  163. </AModal>
  164. </div>
  165. </template>
  166. <style scoped lang="less">
  167. </style>