ObtainCert.vue 7.5 KB

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