ObtainCert.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <script setup lang="ts">
  2. import {useGettext} from 'vue3-gettext'
  3. import {computed, inject, nextTick, provide, reactive, Ref, ref} from 'vue'
  4. import websocket from '@/lib/websocket'
  5. import Modal from 'ant-design-vue/lib/modal'
  6. import {message} from 'ant-design-vue'
  7. import template from '@/api/template'
  8. import domain from '@/api/domain'
  9. import AutoCertStepOne from '@/views/domain/cert/components/AutoCertStepOne.vue'
  10. const {$gettext, interpolate} = useGettext()
  11. const modalVisible = ref(false)
  12. const step = ref(1)
  13. const progressStrokeColor = {
  14. from: '#108ee9',
  15. to: '#87d068'
  16. }
  17. const data: any = reactive({
  18. challenge_method: 'http01',
  19. code: '',
  20. configuration: {
  21. credentials: {},
  22. additional: {}
  23. }
  24. })
  25. const progressPercent = ref(0)
  26. const progressStatus = ref('active')
  27. const modalClosable = ref(true)
  28. provide('data', data)
  29. const logContainer = ref(null)
  30. const save_site_config: Function = inject('save_site_config')!
  31. const no_server_name: Ref = inject('no_server_name')!
  32. const props: any = inject('props')!
  33. const issuing_cert: Ref<boolean> = inject('issuing_cert')!
  34. async function callback(ssl_certificate: string, ssl_certificate_key: string) {
  35. props.directivesMap['ssl_certificate'][0]['params'] = ssl_certificate
  36. props.directivesMap['ssl_certificate_key'][0]['params'] = ssl_certificate_key
  37. save_site_config()
  38. }
  39. function change_auto_cert(status: boolean) {
  40. if (status) {
  41. domain.add_auto_cert(props.config_name, {domains: name.value.trim().split(' ')}).then(() => {
  42. message.success(interpolate($gettext('Auto-renewal enabled for %{name}'), {name: name.value}))
  43. }).catch(e => {
  44. message.error(e.message ?? interpolate($gettext('Enable auto-renewal failed for %{name}'), {name: name.value}))
  45. })
  46. } else {
  47. domain.remove_auto_cert(props.config_name).then(() => {
  48. message.success(interpolate($gettext('Auto-renewal disabled for %{name}'), {name: name.value}))
  49. }).catch(e => {
  50. message.error(e.message ?? interpolate($gettext('Disable auto-renewal failed for %{name}'), {name: name.value}))
  51. })
  52. }
  53. }
  54. async function onchange(r: boolean) {
  55. if (r) {
  56. await template.get_block('letsencrypt.conf').then(r => {
  57. props.ngx_config.servers.forEach(async (v: any) => {
  58. v.locations = v.locations.filter((l: any) => l.path !== '/.well-known/acme-challenge')
  59. v.locations.push(...r.locations)
  60. })
  61. })
  62. // if ssl_certificate is empty, do not save, just use the config from last step.
  63. if (!props.directivesMap['ssl_certificate']?.[0]) {
  64. await save_site_config()
  65. }
  66. job()
  67. } else {
  68. await props.ngx_config.servers.forEach((v: any) => {
  69. v.locations = v.locations.filter((l: any) => l.path !== '/.well-known/acme-challenge')
  70. })
  71. save_site_config()
  72. change_auto_cert(r)
  73. }
  74. }
  75. function job() {
  76. modalClosable.value = false
  77. issuing_cert.value = true
  78. if (no_server_name.value) {
  79. message.error($gettext('server_name not found in directives'))
  80. issuing_cert.value = false
  81. return
  82. }
  83. const server_name = props.directivesMap['server_name'][0]
  84. if (!props.directivesMap['ssl_certificate']) {
  85. props.current_server_directives.splice(server_name.idx + 1, 0, {
  86. directive: 'ssl_certificate',
  87. params: ''
  88. })
  89. }
  90. nextTick(() => {
  91. if (!props.directivesMap['ssl_certificate_key']) {
  92. const ssl_certificate = props.directivesMap['ssl_certificate'][0]
  93. props.current_server_directives.splice(ssl_certificate.idx + 1, 0, {
  94. directive: 'ssl_certificate_key',
  95. params: ''
  96. })
  97. }
  98. }).then(() => {
  99. issue_cert(props.config_name, name.value, callback)
  100. })
  101. }
  102. function log(msg: string) {
  103. const para = document.createElement('p')
  104. para.appendChild(document.createTextNode($gettext(msg)));
  105. (logContainer.value as any as Node).appendChild(para);
  106. (logContainer.value as any as Element).scroll({top: 320, left: 0, behavior: 'smooth'})
  107. }
  108. const issue_cert = async (config_name: string, server_name: string, callback: Function) => {
  109. progressStatus.value = 'active'
  110. modalClosable.value = false
  111. modalVisible.value = true
  112. progressPercent.value = 0;
  113. (logContainer.value as any as Element).innerHTML = ''
  114. log($gettext('Getting the certificate, please wait...'))
  115. const ws = websocket(`/api/domain/${config_name}/cert`, false)
  116. ws.onopen = () => {
  117. ws.send(JSON.stringify({
  118. server_name: server_name.trim().split(' '),
  119. ...data
  120. }))
  121. }
  122. ws.onmessage = m => {
  123. const r = JSON.parse(m.data)
  124. log(r.message)
  125. switch (r.status) {
  126. case 'info':
  127. progressPercent.value += 5
  128. break
  129. default:
  130. modalClosable.value = true
  131. issuing_cert.value = false
  132. if (r.status === 'success' && r.ssl_certificate !== undefined && r.ssl_certificate_key !== undefined) {
  133. progressStatus.value = 'success'
  134. progressPercent.value = 100
  135. callback(r.ssl_certificate, r.ssl_certificate_key)
  136. change_auto_cert(true)
  137. } else {
  138. progressStatus.value = 'exception'
  139. }
  140. break
  141. }
  142. }
  143. }
  144. const name = computed(() => {
  145. return props.directivesMap['server_name'][0].params.trim()
  146. })
  147. function toggle(status: boolean) {
  148. if (status) {
  149. Modal.confirm({
  150. title: $gettext('Do you want to disable auto-cert renewal?'),
  151. content: $gettext('We will remove the HTTPChallenge configuration from ' +
  152. 'this file and reload the Nginx. Are you sure you want to continue?'),
  153. mask: false,
  154. centered: true,
  155. onOk: () => onchange(false)
  156. })
  157. } else {
  158. modalVisible.value = true
  159. modalClosable.value = true
  160. }
  161. }
  162. defineExpose({
  163. toggle
  164. })
  165. const can_next = computed(() => {
  166. if (step.value === 2) {
  167. return false
  168. } else {
  169. if (data.challenge_method === 'http01') {
  170. return true
  171. } else if (data.challenge_method === 'dns01') {
  172. return data?.code ?? false
  173. }
  174. }
  175. })
  176. function next() {
  177. step.value++
  178. onchange(true)
  179. }
  180. </script>
  181. <template>
  182. <a-modal
  183. :title="$gettext('Obtain certificate')"
  184. v-model:visible="modalVisible"
  185. :mask-closable="modalClosable"
  186. :footer="null" :closable="modalClosable" force-render>
  187. <template v-if="step===1">
  188. <auto-cert-step-one/>
  189. </template>
  190. <template v-else-if="step===2">
  191. <a-progress
  192. :stroke-color="progressStrokeColor"
  193. :percent="progressPercent"
  194. :status="progressStatus"
  195. />
  196. <div class="issue-cert-log-container" ref="logContainer"/>
  197. </template>
  198. <div class="control-btn" v-if="can_next">
  199. <a-button type="primary" @click="next">
  200. {{ $gettext('Next') }}
  201. </a-button>
  202. </div>
  203. </a-modal>
  204. </template>
  205. <style lang="less" scoped>
  206. .control-btn {
  207. display: flex;
  208. justify-content: flex-end;
  209. }
  210. </style>