mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
Refactored nginx configuration editor
This commit is contained in:
parent
f526cd0ade
commit
b19ecdda9c
31 changed files with 1476 additions and 956 deletions
|
@ -27,8 +27,8 @@ const domain = {
|
||||||
return http.post(base_url + '/' + name + '/disable')
|
return http.post(base_url + '/' + name + '/disable')
|
||||||
},
|
},
|
||||||
|
|
||||||
get_template(name) {
|
get_template() {
|
||||||
return http.get('template/' + name)
|
return http.get('template')
|
||||||
},
|
},
|
||||||
|
|
||||||
cert_info(domain) {
|
cert_info(domain) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import user from './user'
|
||||||
import install from './install'
|
import install from './install'
|
||||||
import analytic from './analytic'
|
import analytic from './analytic'
|
||||||
import settings from './settings'
|
import settings from './settings'
|
||||||
|
import ngx from './ngx'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
domain,
|
domain,
|
||||||
|
@ -13,5 +14,6 @@ export default {
|
||||||
user,
|
user,
|
||||||
install,
|
install,
|
||||||
analytic,
|
analytic,
|
||||||
settings
|
settings,
|
||||||
|
ngx
|
||||||
}
|
}
|
||||||
|
|
13
frontend/src/api/ngx.js
Normal file
13
frontend/src/api/ngx.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import http from '@/lib/http'
|
||||||
|
|
||||||
|
const ngx = {
|
||||||
|
build_config(ngxConfig) {
|
||||||
|
return http.post('/ngx/build_config', ngxConfig)
|
||||||
|
},
|
||||||
|
|
||||||
|
tokenize_config(content) {
|
||||||
|
return http.post('/ngx/tokenize_config', {content})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ngx
|
|
@ -72,7 +72,7 @@
|
||||||
:okText="ok_text"
|
:okText="ok_text"
|
||||||
:title="restore_title_text"
|
:title="restore_title_text"
|
||||||
@confirm="restore(record[rowKey])">
|
@confirm="restore(record[rowKey])">
|
||||||
<a href="javascript:;">{{restore_action_text}}</a>
|
<a href="javascript:;">{{ restore_action_text }}</a>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
<a-popconfirm
|
<a-popconfirm
|
||||||
v-else
|
v-else
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
:okText="ok_text"
|
:okText="ok_text"
|
||||||
:title="destroy_title_text"
|
:title="destroy_title_text"
|
||||||
@confirm="destroy(record[rowKey])">
|
@confirm="destroy(record[rowKey])">
|
||||||
<a href="javascript:;">{{destroy_action_text}}</a>
|
<a href="javascript:;">{{ destroy_action_text }}</a>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,6 +93,7 @@
|
||||||
import StdPagination from './StdPagination'
|
import StdPagination from './StdPagination'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import StdDataEntry from '@/components/StdDataEntry/StdDataEntry'
|
import StdDataEntry from '@/components/StdDataEntry/StdDataEntry'
|
||||||
|
import $gettext, {$interpolate} from '@/lib/translate/gettext'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'StdTable',
|
name: 'StdTable',
|
||||||
|
@ -230,10 +231,10 @@ export default {
|
||||||
destroy(id) {
|
destroy(id) {
|
||||||
this.api.destroy(id).then(() => {
|
this.api.destroy(id).then(() => {
|
||||||
this.get_list()
|
this.get_list()
|
||||||
this.$message.success('删除 ID: ' + id + ' 成功')
|
this.$message.success($interpolate($gettext('Delete ID: %{id}'), {id: id}))
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
this.$message.error(e?.message ?? '系统错误')
|
this.$message.error(e?.message ?? $gettext('Server error'))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
get_list(page_num = null) {
|
get_list(page_num = null) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<editor v-model="current_value" @init="editorInit" lang="nginx" theme="monokai" width="100%" height="1000"></editor>
|
<editor v-model="current_value" @init="editorInit" lang="nginx" theme="monokai" width="100%" :height="defaultTextHeight"></editor>
|
||||||
</template>
|
</template>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.cm-s-monokai {
|
.cm-s-monokai {
|
||||||
|
@ -20,6 +20,10 @@ export default {
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
value: {},
|
value: {},
|
||||||
|
defaultTextHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 1000
|
||||||
|
}
|
||||||
},
|
},
|
||||||
model: {
|
model: {
|
||||||
prop: 'value',
|
prop: 'value',
|
||||||
|
@ -36,16 +40,6 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
current_value: this.value ?? '',
|
current_value: this.value ?? '',
|
||||||
cmOptions: {
|
|
||||||
tabSize: 4,
|
|
||||||
mode: 'text/x-nginx-conf',
|
|
||||||
theme: 'monokai',
|
|
||||||
lineNumbers: true,
|
|
||||||
line: true,
|
|
||||||
highlightDifferences: true,
|
|
||||||
defaultTextHeight: 1000,
|
|
||||||
// more CodeMirror options...
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -57,7 +57,6 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
collapsed: this.collapse(),
|
collapsed: this.collapse(),
|
||||||
zh_CN,
|
|
||||||
clientWidth: document.body.clientWidth,
|
clientWidth: document.body.clientWidth,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,39 +2,62 @@
|
||||||
<a-card :title="$gettext('Add Site')">
|
<a-card :title="$gettext('Add Site')">
|
||||||
<div class="domain-add-container">
|
<div class="domain-add-container">
|
||||||
<a-steps :current="current_step" size="small">
|
<a-steps :current="current_step" size="small">
|
||||||
<a-step :title="$gettext('Base information')" />
|
<a-step :title="$gettext('Base information')"/>
|
||||||
<a-step :title="$gettext('Configure SSL')" />
|
<a-step :title="$gettext('Configure SSL')"/>
|
||||||
<a-step :title="$gettext('Finished')" />
|
<a-step :title="$gettext('Finished')"/>
|
||||||
</a-steps>
|
</a-steps>
|
||||||
|
|
||||||
<std-data-entry :data-list="columns" :data-source="config" :error="error" v-show="current_step===0"/>
|
<template v-if="current_step===0">
|
||||||
|
<a-form-item :label="$gettext('Configuration Name')">
|
||||||
|
<a-input v-model="config.name"/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
<template v-if="current_step===1">
|
<directive-editor :ngx_directives="ngx_config.servers[0].directives"/>
|
||||||
<a-button
|
|
||||||
@click="issue_cert"
|
<location-editor :locations="ngx_config.servers[0].locations"/>
|
||||||
type="primary" ghost
|
|
||||||
style="margin: 10px 0"
|
<a-alert
|
||||||
:disabled="is_demo"
|
v-if="!has_server_name"
|
||||||
:loading="issuing_cert"
|
:message="$gettext('Warning')"
|
||||||
|
type="warning"
|
||||||
|
show-icon
|
||||||
>
|
>
|
||||||
<translate>Getting Certificate from Let's Encrypt</translate>
|
<template slot="description">
|
||||||
</a-button>
|
<span v-translate>
|
||||||
<p v-if="is_demo" v-translate>This feature is not available in demo.</p>
|
server_name parameter is required
|
||||||
|
</span>
|
||||||
<std-data-entry :data-list="columnsSSL" :data-source="config" :error="error" />
|
</template>
|
||||||
|
</a-alert>
|
||||||
<a-space style="margin-right: 10px">
|
<br/>
|
||||||
<a-button
|
|
||||||
v-if="current_step===1"
|
|
||||||
@click="current_step++"
|
|
||||||
>
|
|
||||||
<translate>Skip</translate>
|
|
||||||
</a-button>
|
|
||||||
</a-space>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="current_step===1">
|
||||||
|
|
||||||
|
<a-form-item :label="$gettext('Enable TLS')">
|
||||||
|
<a-switch @change="change_tls"/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<ngx-config-editor
|
||||||
|
ref="ngx_config"
|
||||||
|
:ngx_config="ngx_config"
|
||||||
|
v-model="auto_cert"
|
||||||
|
:enabled="enabled"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-space v-if="current_step<2">
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
@click="save"
|
||||||
|
:disabled="!config.name||!has_server_name"
|
||||||
|
>
|
||||||
|
<translate>Next</translate>
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
|
||||||
<a-result
|
<a-result
|
||||||
v-if="current_step===2"
|
v-else-if="current_step===2"
|
||||||
status="success"
|
status="success"
|
||||||
:title="$gettext('Domain Config Created Successfully')"
|
:title="$gettext('Domain Config Created Successfully')"
|
||||||
>
|
>
|
||||||
|
@ -48,118 +71,139 @@
|
||||||
</template>
|
</template>
|
||||||
</a-result>
|
</a-result>
|
||||||
|
|
||||||
<a-space v-if="current_step<2">
|
|
||||||
<a-button
|
|
||||||
type="primary"
|
|
||||||
@click="save"
|
|
||||||
:disabled="!config.name"
|
|
||||||
>
|
|
||||||
<translate>Next</translate>
|
|
||||||
</a-button>
|
|
||||||
</a-space>
|
|
||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import StdDataEntry from '@/components/StdDataEntry/StdDataEntry'
|
import DirectiveEditor from '@/views/domain/ngx_conf/directive/DirectiveEditor'
|
||||||
import {columns, columnsSSL} from '@/views/domain/columns'
|
import LocationEditor from '@/views/domain/ngx_conf/LocationEditor'
|
||||||
import {unparse, issue_cert} from '@/views/domain/methods'
|
import $gettext, {$interpolate} from '@/lib/translate/gettext'
|
||||||
import $gettext, {$interpolate} from "@/lib/translate/gettext"
|
import NgxConfigEditor from '@/views/domain/ngx_conf/NgxConfigEditor'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DomainAdd',
|
name: 'DomainAdd',
|
||||||
components: {StdDataEntry},
|
components: {NgxConfigEditor, LocationEditor, DirectiveEditor},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
config: {
|
config: {},
|
||||||
http_listen_port: 80,
|
ngx_config: {
|
||||||
https_listen_port: 443
|
servers: [{}]
|
||||||
},
|
},
|
||||||
columns: columns.slice(0, -1), // 隐藏SSL支持开关
|
|
||||||
error: {},
|
error: {},
|
||||||
current_step: 0,
|
current_step: 0,
|
||||||
columnsSSL,
|
enabled: true,
|
||||||
issuing_cert: false
|
auto_cert: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
created() {
|
||||||
'config.auto_cert'() {
|
this.init()
|
||||||
this.change_auto_cert()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
init() {
|
||||||
|
this.$api.domain.get_template().then(r => {
|
||||||
|
this.ngx_config = r.tokenized
|
||||||
|
})
|
||||||
|
},
|
||||||
save() {
|
save() {
|
||||||
if (this.current_step===0) {
|
this.$api.ngx.build_config(this.ngx_config).then(r => {
|
||||||
this.$api.domain.get_template('http-conf').then(r => {
|
this.$api.domain.save(this.config.name, {content: r.content, enabled: true}).then(() => {
|
||||||
let text = unparse(r.template, this.config)
|
this.$message.success($gettext('Saved successfully'))
|
||||||
|
|
||||||
this.$api.domain.save(this.config.name, {content: text, enabled: true}).then(() => {
|
this.$api.domain.enable(this.config.name).then(() => {
|
||||||
this.$message.success($gettext('Saved successfully'))
|
this.$message.success($gettext('Enabled successfully'))
|
||||||
|
|
||||||
this.$api.domain.enable(this.config.name).then(() => {
|
|
||||||
this.$message.success($gettext('Enabled successfully'))
|
|
||||||
this.current_step++
|
|
||||||
}).catch(r => {
|
|
||||||
this.$message.error(r.message ?? $gettext('Enable failed'), 10)
|
|
||||||
})
|
|
||||||
|
|
||||||
}).catch(r => {
|
|
||||||
this.$message.error($interpolate($gettext('Save error %{msg}'), {msg: r.message ?? ""}), 10)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else if (this.current_step === 1) {
|
|
||||||
this.$api.domain.get_template('https-conf').then(r => {
|
|
||||||
let text = unparse(r.template, this.config)
|
|
||||||
|
|
||||||
this.$api.domain.save(this.config.name, {content: text, enabled: true}).then(() => {
|
|
||||||
this.$message.success($gettext('Saved successfully'))
|
|
||||||
this.current_step++
|
this.current_step++
|
||||||
}).catch(r => {
|
}).catch(r => {
|
||||||
this.$message.error($interpolate($gettext('Save error %{msg}'), {msg: r.message ?? ""}), 10)
|
this.$message.error(r.message ?? $gettext('Enable failed'), 10)
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
}).catch(r => {
|
||||||
issue_cert() {
|
this.$message.error($interpolate($gettext('Save error %{msg}'), {msg: r.message ?? ''}), 10)
|
||||||
this.issuing_cert = true
|
})
|
||||||
issue_cert(this.config.server_name, this.callback)
|
})
|
||||||
},
|
|
||||||
callback(ssl_certificate, ssl_certificate_key) {
|
|
||||||
this.$set(this.config, 'ssl_certificate', ssl_certificate)
|
|
||||||
this.$set(this.config, 'ssl_certificate_key', ssl_certificate_key)
|
|
||||||
this.issuing_cert = false
|
|
||||||
},
|
},
|
||||||
goto_modify() {
|
goto_modify() {
|
||||||
this.$router.push('/domain/'+this.config.name)
|
this.$router.push('/domain/' + this.config.name)
|
||||||
},
|
},
|
||||||
create_another() {
|
create_another() {
|
||||||
this.current_step = 0
|
this.current_step = 0
|
||||||
this.config = {
|
this.config = {}
|
||||||
http_listen_port: 80,
|
this.ngx_config = {
|
||||||
https_listen_port: 443
|
servers: [{}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
change_auto_cert() {
|
change_tls(r) {
|
||||||
if (this.config.auto_cert) {
|
if (r) {
|
||||||
this.$api.domain.add_auto_cert(this.config.name).then(() => {
|
// deep copy servers[0] to servers[1]
|
||||||
this.$message.success($interpolate($gettext('Auto-renewal enabled for %{name}'), {name: this.config.name}))
|
const server = JSON.parse(JSON.stringify(this.ngx_config.servers[0]))
|
||||||
}).catch(e => {
|
|
||||||
this.$message.error(e.message ?? $interpolate($gettext('Enable auto-renewal failed for %{name}'), {name: this.config.name}))
|
this.ngx_config.servers.push(server)
|
||||||
|
|
||||||
|
this.$refs.ngx_config.current_server_index = 1
|
||||||
|
|
||||||
|
const servers = this.ngx_config.servers
|
||||||
|
|
||||||
|
|
||||||
|
let i = 0
|
||||||
|
while (i < servers[1].directives.length) {
|
||||||
|
const v = servers[1].directives[i]
|
||||||
|
if (v.directive === 'listen') {
|
||||||
|
servers[1].directives.splice(i, 1)
|
||||||
|
} else {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
servers[1].directives.splice(0, 0, {
|
||||||
|
directive: 'listen',
|
||||||
|
params: '443 ssl http2'
|
||||||
|
}, {
|
||||||
|
directive: 'listen',
|
||||||
|
params: '[::]:443 ssl http2'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const directivesMap = this.$refs.ngx_config.directivesMap
|
||||||
|
|
||||||
|
const server_name = directivesMap['server_name'][0]
|
||||||
|
|
||||||
|
if (!directivesMap['ssl_certificate']) {
|
||||||
|
servers[1].directives.splice(server_name.idx + 1, 0, {
|
||||||
|
directive: 'ssl_certificate',
|
||||||
|
params: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!directivesMap['ssl_certificate_key']) {
|
||||||
|
servers[1].directives.splice(server_name.idx + 2, 0, {
|
||||||
|
directive: 'ssl_certificate_key',
|
||||||
|
params: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.$api.domain.remove_auto_cert(this.config.name).then(() => {
|
// remove servers[1]
|
||||||
this.$message.success($interpolate($gettext('Auto-renewal disabled for %{name}'), {name: this.config.name}))
|
this.$refs.ngx_config.current_server_index = 0
|
||||||
}).catch(e => {
|
if (this.ngx_config.servers.length === 2) {
|
||||||
this.$message.error(e.message ?? $interpolate($gettext('Disable auto-renewal failed for %{name}'), {name: this.config.name}))
|
this.ngx_config.servers.splice(1, 1)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
is_demo() {
|
has_server_name() {
|
||||||
return this.$store.getters.env.demo === true
|
const servers = this.ngx_config.servers
|
||||||
|
for (const server_key in servers) {
|
||||||
|
for (const k in servers[server_key].directives) {
|
||||||
|
const v = servers[server_key].directives[k]
|
||||||
|
if (v.directive === 'server_name' && v.params.trim() !== '') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,12 @@
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:extra>
|
<template v-slot:extra>
|
||||||
<a-switch size="small" v-model="advance_mode"/>
|
<a-switch size="small" v-model="advance_mode" @change="on_mode_change"/>
|
||||||
<template v-if="advance_mode">
|
<template v-if="advance_mode">
|
||||||
{{ $gettext('Advance') }}
|
{{ $gettext('Advance Mode') }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ $gettext('Basic') }}
|
{{ $gettext('Basic Mode') }}
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -29,22 +29,13 @@
|
||||||
<a-form-item :label="$gettext('Enabled')">
|
<a-form-item :label="$gettext('Enabled')">
|
||||||
<a-switch v-model="enabled" @change="checked=>{checked?enable():disable()}"/>
|
<a-switch v-model="enabled" @change="checked=>{checked?enable():disable()}"/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<p v-translate>The following values will only take effect if you have the corresponding fields in your configuration file. The configuration filename cannot be changed after it has been created.</p>
|
|
||||||
<std-data-entry :data-list="columns" v-model="config"/>
|
<ngx-config-editor
|
||||||
<template v-if="config.support_ssl">
|
ref="ngx_config"
|
||||||
<cert-info :domain="name" ref="cert-info" v-if="name"/>
|
:ngx_config="ngx_config"
|
||||||
<a-button
|
v-model="auto_cert"
|
||||||
@click="issue_cert"
|
:enabled="enabled"
|
||||||
type="primary" ghost
|
/>
|
||||||
style="margin: 10px 0"
|
|
||||||
:disabled="is_demo"
|
|
||||||
:loading="issuing_cert"
|
|
||||||
>
|
|
||||||
<translate>Getting Certificate from Let's Encrypt</translate>
|
|
||||||
</a-button>
|
|
||||||
<p v-if="is_demo" v-translate>This feature is not available in demo.</p>
|
|
||||||
<p v-else v-translate>Make sure you have configured a reverse proxy for .well-known directory to HTTPChallengePort (default: 9180) before getting the certificate.</p>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
|
@ -55,7 +46,7 @@
|
||||||
<a-button @click="$router.go(-1)">
|
<a-button @click="$router.go(-1)">
|
||||||
<translate>Back</translate>
|
<translate>Back</translate>
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="primary" @click="save">
|
<a-button type="primary" @click="save" :loading="saving">
|
||||||
<translate>Save</translate>
|
<translate>Save</translate>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
|
@ -65,60 +56,39 @@
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import StdDataEntry from '@/components/StdDataEntry/StdDataEntry'
|
|
||||||
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar'
|
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar'
|
||||||
import VueItextarea from '@/components/VueItextarea/VueItextarea'
|
import VueItextarea from '@/components/VueItextarea/VueItextarea'
|
||||||
import {columns, columnsSSL} from '@/views/domain/columns'
|
|
||||||
import {unparse, issue_cert} from '@/views/domain/methods'
|
|
||||||
import CertInfo from '@/views/domain/CertInfo'
|
|
||||||
import {$gettext, $interpolate} from '@/lib/translate/gettext'
|
import {$gettext, $interpolate} from '@/lib/translate/gettext'
|
||||||
|
import NgxConfigEditor from '@/views/domain/ngx_conf/NgxConfigEditor'
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DomainEdit',
|
name: 'DomainEdit',
|
||||||
components: {CertInfo, FooterToolBar, StdDataEntry, VueItextarea},
|
components: {NgxConfigEditor, FooterToolBar, VueItextarea},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
name: this.$route.params.name.toString(),
|
name: this.$route.params.name.toString(),
|
||||||
config: {
|
update: 0,
|
||||||
http_listen_port: 80,
|
ngx_config: {
|
||||||
https_listen_port: null,
|
filename: '',
|
||||||
server_name: '',
|
upstreams: [],
|
||||||
index: '',
|
servers: []
|
||||||
root: '',
|
|
||||||
ssl_certificate: '',
|
|
||||||
ssl_certificate_key: '',
|
|
||||||
support_ssl: false,
|
|
||||||
auto_cert: false
|
|
||||||
},
|
},
|
||||||
|
auto_cert: false,
|
||||||
|
current_server_index: 0,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
configText: '',
|
configText: '',
|
||||||
ws: null,
|
ws: null,
|
||||||
ok: false,
|
ok: false,
|
||||||
issuing_cert: false,
|
issuing_cert: false,
|
||||||
advance_mode: false,
|
advance_mode: false,
|
||||||
|
saving: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'$route'() {
|
'$route'() {
|
||||||
this.init()
|
this.init()
|
||||||
},
|
},
|
||||||
config: {
|
|
||||||
handler() {
|
|
||||||
this.unparse()
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
},
|
|
||||||
'config.support_ssl'() {
|
|
||||||
if (this.ok) {
|
|
||||||
this.change_support_ssl()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'config.auto_cert'() {
|
|
||||||
if (this.ok) {
|
|
||||||
this.change_auto_cert()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.init()
|
this.init()
|
||||||
|
@ -133,106 +103,53 @@ export default {
|
||||||
if (this.name) {
|
if (this.name) {
|
||||||
this.$api.domain.get(this.name).then(r => {
|
this.$api.domain.get(this.name).then(r => {
|
||||||
this.configText = r.config
|
this.configText = r.config
|
||||||
this.config.auto_cert = r.auto_cert
|
|
||||||
this.enabled = r.enabled
|
this.enabled = r.enabled
|
||||||
this.parse(r).then(() => {
|
this.ngx_config = r.tokenized
|
||||||
this.ok = true
|
this.auto_cert = r.auto_cert
|
||||||
})
|
|
||||||
}).catch(r => {
|
}).catch(r => {
|
||||||
console.log(r)
|
this.$message.error(r.message ?? $gettext('Server error'))
|
||||||
this.$message.error($gettext('Server error'))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async parse(r) {
|
on_mode_change(advance_mode) {
|
||||||
const text = r.config
|
if (advance_mode) {
|
||||||
const reg = {
|
this.build_config()
|
||||||
http_listen_port: /listen[\s](.*);/i,
|
|
||||||
https_listen_port: /listen[\s](.*) ssl/i,
|
|
||||||
server_name: /server_name[\s](.*);/i,
|
|
||||||
index: /index[\s](.*);/i,
|
|
||||||
root: /root[\s](.*);/i,
|
|
||||||
ssl_certificate: /ssl_certificate[\s](.*);/i,
|
|
||||||
ssl_certificate_key: /ssl_certificate_key[\s](.*);/i
|
|
||||||
}
|
|
||||||
this.config['name'] = r.name
|
|
||||||
for (let r in reg) {
|
|
||||||
const match = text.match(reg[r])
|
|
||||||
// console.log(r, match)
|
|
||||||
if (match !== null) {
|
|
||||||
if (match[1] !== undefined) {
|
|
||||||
this.config[r] = match[1].trim()
|
|
||||||
} else {
|
|
||||||
this.config[r] = match[0].trim()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.config.https_listen_port) {
|
|
||||||
this.config.support_ssl = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async unparse() {
|
|
||||||
this.configText = unparse(this.configText, this.config)
|
|
||||||
},
|
|
||||||
async get_template() {
|
|
||||||
if (this.config.support_ssl) {
|
|
||||||
await this.$api.domain.get_template('https-conf').then(r => {
|
|
||||||
this.configText = r.template
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
await this.$api.domain.get_template('http-conf').then(r => {
|
return this.$api.ngx.tokenize_config(this.configText).then(r => {
|
||||||
this.configText = r.template
|
this.ngx_config = r
|
||||||
|
}).catch(r => {
|
||||||
|
this.$message.error(r.message ?? $gettext('Server error'))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
await this.unparse()
|
|
||||||
},
|
},
|
||||||
change_support_ssl() {
|
build_config() {
|
||||||
const that = this
|
return this.$api.ngx.build_config(this.ngx_config).then(r => {
|
||||||
this.$confirm({
|
this.configText = r.content
|
||||||
title: $gettext('Do you want to change the template to support the TLS?'),
|
}).catch(r => {
|
||||||
content: $gettext('This operation will lose the custom configuration.'),
|
this.$message.error(r.message ?? $gettext('Server error'))
|
||||||
onOk() {
|
|
||||||
that.get_template()
|
|
||||||
},
|
|
||||||
onCancel() {
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
save() {
|
async save() {
|
||||||
|
this.saving = true
|
||||||
|
|
||||||
|
if (!this.advance_mode) {
|
||||||
|
await this.build_config()
|
||||||
|
}
|
||||||
|
|
||||||
this.$api.domain.save(this.name, {content: this.configText}).then(r => {
|
this.$api.domain.save(this.name, {content: this.configText}).then(r => {
|
||||||
this.parse(r)
|
this.configText = r.config
|
||||||
|
this.enabled = r.enabled
|
||||||
|
this.ngx_config = r.tokenized
|
||||||
this.$message.success($gettext('Saved successfully'))
|
this.$message.success($gettext('Saved successfully'))
|
||||||
if (this.name) {
|
|
||||||
if (this.$refs['cert-info']) this.$refs['cert-info'].get()
|
this.$refs.ngx_config.update_cert_info()
|
||||||
}
|
|
||||||
}).catch(r => {
|
}).catch(r => {
|
||||||
this.$message.error($interpolate($gettext('Save error %{msg}'), {msg: r.message ?? ''}), 10)
|
this.$message.error($interpolate($gettext('Save error %{msg}'), {msg: r.message ?? ''}), 10)
|
||||||
|
}).finally(() => {
|
||||||
|
this.saving = false
|
||||||
})
|
})
|
||||||
},
|
|
||||||
issue_cert() {
|
|
||||||
this.issuing_cert = true
|
|
||||||
issue_cert(this.config.server_name, this.callback)
|
|
||||||
},
|
|
||||||
callback(ssl_certificate, ssl_certificate_key) {
|
|
||||||
this.$set(this.config, 'ssl_certificate', ssl_certificate)
|
|
||||||
this.$set(this.config, 'ssl_certificate_key', ssl_certificate_key)
|
|
||||||
if (this.$refs['cert-info']) this.$refs['cert-info'].get()
|
|
||||||
this.issuing_cert = false
|
|
||||||
},
|
|
||||||
change_auto_cert() {
|
|
||||||
if (this.config.auto_cert) {
|
|
||||||
this.$api.domain.add_auto_cert(this.name).then(() => {
|
|
||||||
this.$message.success($interpolate($gettext('Auto-renewal enabled for %{name}'), {name: this.name}))
|
|
||||||
}).catch(e => {
|
|
||||||
this.$message.error(e.message ?? $interpolate($gettext('Enable auto-renewal failed for %{name}'), {name: this.name}))
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.$api.domain.remove_auto_cert(this.name).then(() => {
|
|
||||||
this.$message.success($interpolate($gettext('Auto-renewal disabled for %{name}'), {name: this.name}))
|
|
||||||
}).catch(e => {
|
|
||||||
this.$message.error(e.message ?? $interpolate($gettext('Disable auto-renewal failed for %{name}'), {name: this.name}))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
enable() {
|
enable() {
|
||||||
this.$api.domain.enable(this.name).then(() => {
|
this.$api.domain.enable(this.name).then(() => {
|
||||||
|
@ -252,15 +169,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
columns: {
|
|
||||||
get() {
|
|
||||||
if (this.config.support_ssl) {
|
|
||||||
return [...columns, ...columnsSSL]
|
|
||||||
} else {
|
|
||||||
return [...columns]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
is_demo() {
|
is_demo() {
|
||||||
return this.$store.getters.env.demo === true
|
return this.$store.getters.env.demo === true
|
||||||
}
|
}
|
||||||
|
@ -274,16 +182,15 @@ export default {
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.ant-card {
|
.ant-card {
|
||||||
// margin: 10px;
|
margin: 10px 0;
|
||||||
@media (max-width: 512px) {
|
box-shadow: unset;
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.domain-edit-container {
|
.domain-edit-container {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
/deep/.ant-form-item-label > label::after {
|
|
||||||
|
/deep/ .ant-form-item-label > label::after {
|
||||||
content: none;
|
content: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,12 +198,26 @@ export default {
|
||||||
.slide-fade-enter-active {
|
.slide-fade-enter-active {
|
||||||
transition: all .5s ease-in-out;
|
transition: all .5s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slide-fade-leave-active {
|
.slide-fade-leave-active {
|
||||||
transition: all .5s cubic-bezier(1.0, 0.5, 0.8, 1.0);
|
transition: all .5s cubic-bezier(1.0, 0.5, 0.8, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.slide-fade-enter, .slide-fade-leave-to
|
.slide-fade-enter, .slide-fade-leave-to
|
||||||
/* .slide-fade-leave-active for below version 2.1.8 */ {
|
/* .slide-fade-leave-active for below version 2.1.8 */ {
|
||||||
transform: translateX(10px);
|
transform: translateX(10px);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.location-block {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.directive-params-wrapper {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
44
frontend/src/views/domain/cert/Cert.vue
Normal file
44
frontend/src/views/domain/cert/Cert.vue
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<cert-info ref="info" :domain="name" v-if="name"/>
|
||||||
|
<issue-cert
|
||||||
|
:current_server_directives="current_server_directives"
|
||||||
|
:directives-map="directivesMap"
|
||||||
|
v-model="auto_cert"
|
||||||
|
@callback="callback"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CertInfo from '@/views/domain/cert/CertInfo'
|
||||||
|
import IssueCert from '@/views/domain/cert/IssueCert'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Cert',
|
||||||
|
components: {IssueCert, CertInfo},
|
||||||
|
props: {
|
||||||
|
directivesMap: Object,
|
||||||
|
current_server_directives: Array,
|
||||||
|
auto_cert: Boolean
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
prop: 'auto_cert',
|
||||||
|
event: 'change_auto_cert'
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
callback() {
|
||||||
|
this.$refs.info.get()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
name() {
|
||||||
|
return this.directivesMap['server_name'][0].params.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="ok">
|
<div class="cert-info" v-if="ok">
|
||||||
<h3 v-translate>Certificate Status</h3>
|
<h4 v-translate>Certificate Status</h4>
|
||||||
<p v-translate="{issuer: cert.issuer_name}">Intermediate Certification Authorities: %{issuer}</p>
|
<p v-translate="{issuer: cert.issuer_name}">Intermediate Certification Authorities: %{issuer}</p>
|
||||||
<p v-translate="{name: cert.subject_name}">Subject Name: %{name}</p>
|
<p v-translate="{name: cert.subject_name}">Subject Name: %{name}</p>
|
||||||
<p v-translate="{date: moment(cert.not_after).format('YYYY-MM-DD HH:mm:ss').toString()}">
|
<p v-translate="{date: moment(cert.not_after).format('YYYY-MM-DD HH:mm:ss').toString()}">
|
||||||
|
@ -57,6 +57,14 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
h4 {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cert-info {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
span {
|
span {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
163
frontend/src/views/domain/cert/IssueCert.vue
Normal file
163
frontend/src/views/domain/cert/IssueCert.vue
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-form-item :label="$gettext('Encrypt website with Let\'s Encrypt')">
|
||||||
|
<a-switch
|
||||||
|
:loading="issuing_cert"
|
||||||
|
v-model="M_enabled"
|
||||||
|
@change="onchange"
|
||||||
|
:disabled="no_server_name||server_name_more_than_one"
|
||||||
|
/>
|
||||||
|
<a-alert
|
||||||
|
v-if="no_server_name||server_name_more_than_one"
|
||||||
|
:message="$gettext('Warning')"
|
||||||
|
type="warning"
|
||||||
|
show-icon
|
||||||
|
>
|
||||||
|
<template slot="description">
|
||||||
|
<span v-if="no_server_name" v-translate>
|
||||||
|
server_name parameter is required
|
||||||
|
</span>
|
||||||
|
<span v-if="server_name_more_than_one" v-translate>
|
||||||
|
server_name parameters more than one
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</a-form-item>
|
||||||
|
<p v-translate>Note: The server_name in the current configuration must be the domain name you need to get the
|
||||||
|
certificate.</p>
|
||||||
|
<p v-if="enabled" v-translate>The certificate for the domain will be checked every hour,
|
||||||
|
and will be renewed if it has been more than 1 month since it was last issued.</p>
|
||||||
|
<p v-translate>Make sure you have configured a reverse proxy for .well-known
|
||||||
|
directory to HTTPChallengePort (default: 9180) before getting the certificate.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {issue_cert} from '@/views/domain/methods'
|
||||||
|
import $gettext, {$interpolate} from '@/lib/translate/gettext'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'IssueCert',
|
||||||
|
props: {
|
||||||
|
directivesMap: Object,
|
||||||
|
current_server_directives: Array,
|
||||||
|
enabled: Boolean
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
prop: 'enabled',
|
||||||
|
event: 'changeEnabled'
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
issuing_cert: false,
|
||||||
|
M_enabled: this.enabled,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onchange(r) {
|
||||||
|
this.$emit('changeEnabled', r)
|
||||||
|
this.change_auto_cert(r)
|
||||||
|
if (r) {
|
||||||
|
this.job()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
job() {
|
||||||
|
this.issuing_cert = true
|
||||||
|
|
||||||
|
if (this.no_server_name) {
|
||||||
|
this.$message.error($gettext('server_name not found in directives'))
|
||||||
|
this.issuing_cert = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.server_name_more_than_one) {
|
||||||
|
this.$message.error($gettext('server_name parameters more than one'))
|
||||||
|
this.issuing_cert = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const server_name = this.directivesMap['server_name'][0]
|
||||||
|
|
||||||
|
if (!this.directivesMap['ssl_certificate']) {
|
||||||
|
this.current_server_directives.splice(server_name.idx + 1, 0, {
|
||||||
|
directive: 'ssl_certificate',
|
||||||
|
params: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (!this.directivesMap['ssl_certificate_key']) {
|
||||||
|
const ssl_certificate = this.directivesMap['ssl_certificate'][0]
|
||||||
|
this.current_server_directives.splice(ssl_certificate.idx + 1, 0, {
|
||||||
|
directive: 'ssl_certificate_key',
|
||||||
|
params: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
issue_cert(this.name, this.callback)
|
||||||
|
}, 100)
|
||||||
|
},
|
||||||
|
callback(ssl_certificate, ssl_certificate_key) {
|
||||||
|
this.$set(this.directivesMap['ssl_certificate'][0], 'params', ssl_certificate)
|
||||||
|
this.$set(this.directivesMap['ssl_certificate_key'][0], 'params', ssl_certificate_key)
|
||||||
|
this.issuing_cert = false
|
||||||
|
this.$emit('callback')
|
||||||
|
},
|
||||||
|
change_auto_cert(r) {
|
||||||
|
if (r) {
|
||||||
|
this.$api.domain.add_auto_cert(this.name).then(() => {
|
||||||
|
this.$message.success($interpolate($gettext('Auto-renewal enabled for %{name}'), {name: this.name}))
|
||||||
|
}).catch(e => {
|
||||||
|
this.$message.error(e.message ?? $interpolate($gettext('Enable auto-renewal failed for %{name}'), {name: this.name}))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.$api.domain.remove_auto_cert(this.name).then(() => {
|
||||||
|
this.$message.success($interpolate($gettext('Auto-renewal disabled for %{name}'), {name: this.name}))
|
||||||
|
}).catch(e => {
|
||||||
|
this.$message.error(e.message ?? $interpolate($gettext('Disable auto-renewal failed for %{name}'), {name: this.name}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
server_name_more_than_one() {
|
||||||
|
this.M_enabled = false
|
||||||
|
this.onchange(false)
|
||||||
|
},
|
||||||
|
no_server_name() {
|
||||||
|
this.M_enabled = false
|
||||||
|
this.onchange(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
is_demo() {
|
||||||
|
return this.$store.getters.env.demo === true
|
||||||
|
},
|
||||||
|
server_name_more_than_one() {
|
||||||
|
return this.directivesMap['server_name'] && (this.directivesMap['server_name'].length > 1 ||
|
||||||
|
this.directivesMap['server_name'][0].params.trim().indexOf(' ') > 0)
|
||||||
|
},
|
||||||
|
no_server_name() {
|
||||||
|
return !this.directivesMap['server_name']
|
||||||
|
},
|
||||||
|
name() {
|
||||||
|
return this.directivesMap['server_name'][0].params.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.switch-wrapper {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,4 +1,4 @@
|
||||||
import $gettext from "@/lib/translate/gettext";
|
import $gettext from '@/lib/translate/gettext'
|
||||||
|
|
||||||
const columns = [{
|
const columns = [{
|
||||||
title: $gettext('Configuration Name'),
|
title: $gettext('Configuration Name'),
|
||||||
|
@ -12,6 +12,13 @@ const columns = [{
|
||||||
edit: {
|
edit: {
|
||||||
type: 'input'
|
type: 'input'
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
title: $gettext('HTTP Listen Port'),
|
||||||
|
dataIndex: 'http_listen_port',
|
||||||
|
edit: {
|
||||||
|
type: 'number',
|
||||||
|
min: 80
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
title: $gettext('Root Directory (root)'),
|
title: $gettext('Root Directory (root)'),
|
||||||
dataIndex: 'root',
|
dataIndex: 'root',
|
||||||
|
@ -24,51 +31,46 @@ const columns = [{
|
||||||
edit: {
|
edit: {
|
||||||
type: 'input'
|
type: 'input'
|
||||||
}
|
}
|
||||||
}, {
|
|
||||||
title: $gettext('HTTP Listen Port'),
|
|
||||||
dataIndex: 'http_listen_port',
|
|
||||||
edit: {
|
|
||||||
type: 'number',
|
|
||||||
min: 80
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
title: $gettext('Enable TLS'),
|
|
||||||
dataIndex: 'support_ssl',
|
|
||||||
edit: {
|
|
||||||
type: 'switch',
|
|
||||||
event: 'change_support_ssl'
|
|
||||||
}
|
|
||||||
}]
|
}]
|
||||||
|
|
||||||
const columnsSSL = [{
|
const columnsSSL = [
|
||||||
title: $gettext('Certificate Auto-renewal'),
|
{
|
||||||
dataIndex: 'auto_cert',
|
title: $gettext('Enable TLS'),
|
||||||
edit: {
|
dataIndex: 'support_ssl',
|
||||||
type: 'switch',
|
edit: {
|
||||||
event: 'change_auto_cert'
|
type: 'switch',
|
||||||
},
|
event: 'change_support_ssl'
|
||||||
description: $gettext('The certificate for the domain will be checked every hour, ' +
|
}
|
||||||
'and will be renewed if it has been more than 1 month since it was last issued.' +
|
}, {
|
||||||
'<br/>If you do not have a certificate before, please click "Getting Certificate from Let\'s Encrypt" first.')
|
title: $gettext('Certificate Auto-renewal'),
|
||||||
}, {
|
dataIndex: 'auto_cert',
|
||||||
title: $gettext('HTTPS Listen Port'),
|
edit: {
|
||||||
dataIndex: 'https_listen_port',
|
type: 'switch',
|
||||||
edit: {
|
event: 'change_auto_cert'
|
||||||
type: 'number',
|
},
|
||||||
min: 443
|
description: $gettext('The certificate for the domain will be checked every hour, ' +
|
||||||
|
'and will be renewed if it has been more than 1 month since it was last issued.' +
|
||||||
|
'<br/>If you do not have a certificate before, please click "Getting Certificate from Let\'s Encrypt" first.')
|
||||||
|
}, {
|
||||||
|
title: $gettext('HTTPS Listen Port'),
|
||||||
|
dataIndex: 'https_listen_port',
|
||||||
|
edit: {
|
||||||
|
type: 'number',
|
||||||
|
min: 443
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
title: $gettext('Certificate Path (ssl_certificate)'),
|
||||||
|
dataIndex: 'ssl_certificate',
|
||||||
|
edit: {
|
||||||
|
type: 'input'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
title: $gettext('Private Key Path (ssl_certificate_key)'),
|
||||||
|
dataIndex: 'ssl_certificate_key',
|
||||||
|
edit: {
|
||||||
|
type: 'input'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, {
|
]
|
||||||
title: $gettext('Certificate Path (ssl_certificate)'),
|
|
||||||
dataIndex: 'ssl_certificate',
|
|
||||||
edit: {
|
|
||||||
type: 'input'
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
title: $gettext('Private Key Path (ssl_certificate_key)'),
|
|
||||||
dataIndex: 'ssl_certificate_key',
|
|
||||||
edit: {
|
|
||||||
type: 'input'
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
export {columns, columnsSSL}
|
export {columns, columnsSSL}
|
||||||
|
|
|
@ -1,36 +1,8 @@
|
||||||
import $gettext from '@/lib/translate/gettext'
|
import $gettext from '@/lib/translate/gettext'
|
||||||
import store from '@/lib/store'
|
import store from '@/lib/store'
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
const unparse = (text, config) => {
|
|
||||||
// http_listen_port: /listen (.*);/i,
|
|
||||||
// https_listen_port: /listen (.*) ssl/i,
|
|
||||||
const reg = {
|
|
||||||
server_name: /server_name[\s](.*);/ig,
|
|
||||||
index: /index[\s](.*);/i,
|
|
||||||
root: /root[\s](.*);/i,
|
|
||||||
ssl_certificate: /ssl_certificate[\s](.*);/i,
|
|
||||||
ssl_certificate_key: /ssl_certificate_key[\s](.*);/i
|
|
||||||
}
|
|
||||||
text = text.replace(/listen[\s](.*);/i, 'listen\t'
|
|
||||||
+ config['http_listen_port'] + ';')
|
|
||||||
text = text.replace(/listen[\s](.*) ssl/i, 'listen\t'
|
|
||||||
+ config['https_listen_port'] + ' ssl')
|
|
||||||
|
|
||||||
text = text.replace(/listen(.*):(.*);/i, 'listen\t[::]:'
|
|
||||||
+ config['http_listen_port'] + ';')
|
|
||||||
text = text.replace(/listen(.*):(.*) ssl/i, 'listen\t[::]:'
|
|
||||||
+ config['https_listen_port'] + ' ssl')
|
|
||||||
|
|
||||||
for (let k in reg) {
|
|
||||||
text = text.replace(new RegExp(reg[k]), k + '\t' +
|
|
||||||
(config[k] !== undefined ? config[k] : ' ') + ';')
|
|
||||||
}
|
|
||||||
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
const issue_cert = (server_name, callback) => {
|
const issue_cert = (server_name, callback) => {
|
||||||
Vue.prototype.$message.info($gettext('Note: The server_name in the current configuration must be the domain name you need to get the certificate.'), 15)
|
|
||||||
Vue.prototype.$message.info($gettext('Getting the certificate, please wait...'), 15)
|
Vue.prototype.$message.info($gettext('Getting the certificate, please wait...'), 15)
|
||||||
const ws = new WebSocket(Vue.prototype.getWebSocketRoot() + '/cert/issue/' + server_name
|
const ws = new WebSocket(Vue.prototype.getWebSocketRoot() + '/cert/issue/' + server_name
|
||||||
+ '?token=' + btoa(store.state.user.token))
|
+ '?token=' + btoa(store.state.user.token))
|
||||||
|
@ -57,6 +29,9 @@ const issue_cert = (server_name, callback) => {
|
||||||
callback(r.ssl_certificate, r.ssl_certificate_key)
|
callback(r.ssl_certificate, r.ssl_certificate_key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// setTimeout(() => {
|
||||||
|
// callback('a', 'b')
|
||||||
|
// }, 10000)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {unparse, issue_cert}
|
export {issue_cert}
|
||||||
|
|
78
frontend/src/views/domain/ngx_conf/LocationEditor.vue
Normal file
78
frontend/src/views/domain/ngx_conf/LocationEditor.vue
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
<template>
|
||||||
|
<a-form-item :label="$gettext('Locations')" :key="update">
|
||||||
|
<a-empty v-if="!locations"/>
|
||||||
|
<a-card v-for="(v,k) in locations" :key="k"
|
||||||
|
:title="$gettext('Location')" size="small">
|
||||||
|
<a-form-item :label="$gettext('Comments')" v-if="v.comments">
|
||||||
|
<p style="white-space: pre-wrap;">{{ v.comments }}</p>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="$gettext('Path')">
|
||||||
|
<a-input addon-before="location" v-model="v.path"/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="$gettext('Content')">
|
||||||
|
<vue-itextarea v-model="v.content" :default-text-height="200"/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<a-modal :title="$gettext('Add Location')" v-model="adding" @ok="save">
|
||||||
|
<a-form-item :label="$gettext('Comments')">
|
||||||
|
<a-textarea v-model="location.comments"></a-textarea>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="$gettext('Path')">
|
||||||
|
<a-input addon-before="location" v-model="location.path"/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="$gettext('Content')">
|
||||||
|
<vue-itextarea v-model="location.content" :default-text-height="200"/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a-button block @click="add">{{ $gettext('Add Location') }}</a-button>
|
||||||
|
</div>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import VueItextarea from '@/components/VueItextarea/VueItextarea'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'LocationEditor',
|
||||||
|
components: {VueItextarea},
|
||||||
|
props: {
|
||||||
|
locations: Array
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
adding: false,
|
||||||
|
location: {},
|
||||||
|
update: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
add() {
|
||||||
|
this.adding = true
|
||||||
|
this.location = {}
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
this.adding = false
|
||||||
|
if (this.locations) {
|
||||||
|
this.locations.push(this.location)
|
||||||
|
} else {
|
||||||
|
this.locations = [this.location]
|
||||||
|
}
|
||||||
|
this.update++
|
||||||
|
},
|
||||||
|
remove(index) {
|
||||||
|
this.update++
|
||||||
|
this.locations.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.ant-card {
|
||||||
|
margin: 10px 0;
|
||||||
|
box-shadow: unset;
|
||||||
|
}
|
||||||
|
</style>
|
104
frontend/src/views/domain/ngx_conf/NgxConfigEditor.vue
Normal file
104
frontend/src/views/domain/ngx_conf/NgxConfigEditor.vue
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
<template>
|
||||||
|
<a-tabs v-model="current_server_index">
|
||||||
|
<a-tab-pane :tab="'Server '+(k+1)" v-for="(v,k) in ngx_config.servers" :key="k">
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
|
<template v-if="support_ssl&&enabled">
|
||||||
|
<cert-info :domain="name" v-if="name"/>
|
||||||
|
<issue-cert
|
||||||
|
:current_server_directives="current_server_directives"
|
||||||
|
:directives-map="directivesMap"
|
||||||
|
v-model="auto_cert"
|
||||||
|
/>
|
||||||
|
<cert-info :current_server_directives="current_server_directives"
|
||||||
|
:directives-map="directivesMap"
|
||||||
|
v-model="auto_cert"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-form-item :label="$gettext('Comments')" v-if="v.comments">
|
||||||
|
<p style="white-space: pre-wrap;">{{ v.comments }}</p>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<directive-editor :ngx_directives="v.directives" :key="update"/>
|
||||||
|
|
||||||
|
<location-editor :locations="v.locations"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CertInfo from '@/views/domain/cert/CertInfo'
|
||||||
|
import IssueCert from '@/views/domain/cert/IssueCert'
|
||||||
|
import DirectiveEditor from '@/views/domain/ngx_conf/directive/DirectiveEditor'
|
||||||
|
import LocationEditor from '@/views/domain/ngx_conf/LocationEditor'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'NgxConfigEditor',
|
||||||
|
components: {LocationEditor, DirectiveEditor, IssueCert, CertInfo},
|
||||||
|
props: {
|
||||||
|
ngx_config: Object,
|
||||||
|
auto_cert: Boolean,
|
||||||
|
enabled: Boolean
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
current_server_index: 0,
|
||||||
|
update: 0,
|
||||||
|
name: this.$route.params?.name?.toString() ?? '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
prop: 'auto_cert',
|
||||||
|
event: 'change_auto_cert'
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
update_cert_info() {
|
||||||
|
if (this.name && this.$refs['cert-info' + this.current_server_index]) {
|
||||||
|
this.$refs['cert-info' + this.current_server_index].get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
directivesMap: {
|
||||||
|
get() {
|
||||||
|
const map = {}
|
||||||
|
|
||||||
|
this.current_server_directives.forEach((v, k) => {
|
||||||
|
v.idx = k
|
||||||
|
if (map[v.directive]) {
|
||||||
|
map[v.directive].push(v)
|
||||||
|
} else {
|
||||||
|
map[v.directive] = [v]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
},
|
||||||
|
current_server_directives: {
|
||||||
|
get() {
|
||||||
|
return this.ngx_config.servers[this.current_server_index].directives
|
||||||
|
}
|
||||||
|
},
|
||||||
|
support_ssl: {
|
||||||
|
get() {
|
||||||
|
if (this.directivesMap.listen) {
|
||||||
|
for (const v of this.directivesMap.listen) {
|
||||||
|
if (v?.params.indexOf('ssl') > 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,74 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="add-directive-temp" v-if="adding">
|
||||||
|
<a-select v-model="mode" default-value="default" style="min-width: 150px">
|
||||||
|
<a-select-option value="default">
|
||||||
|
{{ $gettext('Single Directive') }}
|
||||||
|
</a-select-option>
|
||||||
|
<a-select-option value="if">
|
||||||
|
if
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<vue-itextarea v-if="mode===If" :default-text-height="100" v-model="directive.params"/>
|
||||||
|
<a-input-group compact v-else>
|
||||||
|
<a-input style="width: 30%" :placeholder="$gettext('Directive')" v-model="directive.directive"/>
|
||||||
|
<a-input style="width: 70%" :placeholder="$gettext('Params')" v-model="directive.params">
|
||||||
|
<a-icon slot="suffix" type="close" style="color: rgba(0,0,0,.45);font-size: 10px;"
|
||||||
|
@click="adding=false"/>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
|
</div>
|
||||||
|
<a-button block v-if="!adding" @click="add">{{ $gettext('Add Directive Below') }}</a-button>
|
||||||
|
<a-button type="primary" v-else block @click="save"
|
||||||
|
:disabled="!directive.directive&&!directive.params">{{ $gettext('Save Directive') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {If} from '@/views/domain/ngx_conf/ngx_constant'
|
||||||
|
import VueItextarea from '@/components/VueItextarea/VueItextarea'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DirectiveAdd',
|
||||||
|
components: {
|
||||||
|
VueItextarea
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
ngx_directives: Array,
|
||||||
|
idx: Number,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
adding: false,
|
||||||
|
directive: {},
|
||||||
|
mode: 'default',
|
||||||
|
If
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
add() {
|
||||||
|
this.adding = true
|
||||||
|
this.directive = {}
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
this.adding = false
|
||||||
|
if (this.mode === If) {
|
||||||
|
this.directive.directive = If
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.idx) {
|
||||||
|
this.ngx_directives.splice(this.idx + 1, 0, this.directive)
|
||||||
|
} else {
|
||||||
|
this.ngx_directives.push(this.directive)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('save', this.idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,92 @@
|
||||||
|
<template>
|
||||||
|
<a-form-item :label="$gettext('Directives')">
|
||||||
|
<div v-for="(directive,k) in ngx_directives" :key="k" @click="current_idx=k">
|
||||||
|
<vue-itextarea v-if="directive.directive === If" v-model="directive.params" :default-text-height="100"/>
|
||||||
|
<a-input :addon-before="directive.directive" v-model="directive.params" @click="current_idx=k" v-else>
|
||||||
|
<a-popconfirm slot="suffix" @confirm="remove(k)"
|
||||||
|
:title="$gettext('Are you sure you want to remove this directive?')"
|
||||||
|
:ok-text="$gettext('Yes')"
|
||||||
|
:cancel-text="$gettext('No')">
|
||||||
|
<a-icon type="close"
|
||||||
|
style="color: rgba(0,0,0,.45);font-size: 10px;"
|
||||||
|
/>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-input>
|
||||||
|
<transition name="slide">
|
||||||
|
<div v-if="current_idx===k" class="extra">
|
||||||
|
<div class="extra-content">
|
||||||
|
<a-form-item :label="$gettext('Comments')">
|
||||||
|
<a-textarea v-model="directive.comments"/>
|
||||||
|
</a-form-item>
|
||||||
|
<directive-add :ngx_directives="ngx_directives" :idx="k" @save="onSave(k)"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
<directive-add :ngx_directives="ngx_directives"/>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import VueItextarea from '@/components/VueItextarea/VueItextarea'
|
||||||
|
import {If} from '../ngx_constant'
|
||||||
|
import DirectiveAdd from '@/views/domain/ngx_conf/directive/DirectiveAdd'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DirectiveEditor',
|
||||||
|
props: {
|
||||||
|
ngx_directives: Array
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
DirectiveAdd,
|
||||||
|
VueItextarea
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
adding: false,
|
||||||
|
directive: {},
|
||||||
|
If,
|
||||||
|
current_idx: -1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
add() {
|
||||||
|
this.adding = true
|
||||||
|
this.directive = {}
|
||||||
|
},
|
||||||
|
save() {
|
||||||
|
this.adding = false
|
||||||
|
this.ngx_directives.push(this.directive)
|
||||||
|
},
|
||||||
|
remove(index) {
|
||||||
|
this.ngx_directives.splice(index, 1)
|
||||||
|
},
|
||||||
|
onSave(idx) {
|
||||||
|
const that = this
|
||||||
|
setTimeout(() => {
|
||||||
|
that.current_idx = idx + 1
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.extra {
|
||||||
|
background-color: #fafafa;
|
||||||
|
padding: 10px 20px 20px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-enter-active, .slide-leave-active {
|
||||||
|
transition: max-height .5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-enter, .slide-leave-to {
|
||||||
|
max-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-enter-to, .slide-leave {
|
||||||
|
max-height: 600px;
|
||||||
|
}
|
||||||
|
</style>
|
1
frontend/src/views/domain/ngx_conf/ngx_constant.js
Normal file
1
frontend/src/views/domain/ngx_conf/ngx_constant.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const If = "if"
|
|
@ -1,164 +1,128 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"github.com/0xJacky/Nginx-UI/server/tool"
|
||||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
||||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/gin-gonic/gin"
|
"log"
|
||||||
"github.com/gorilla/websocket"
|
"net/http"
|
||||||
"log"
|
"os"
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func CertInfo(c *gin.Context) {
|
func CertInfo(c *gin.Context) {
|
||||||
domain := c.Param("domain")
|
domain := c.Param("domain")
|
||||||
|
|
||||||
key, err := tool.GetCertInfo(domain)
|
key, err := tool.GetCertInfo(domain)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"error": err,
|
"error": err,
|
||||||
"subject_name": key.Subject.CommonName,
|
"subject_name": key.Subject.CommonName,
|
||||||
"issuer_name": key.Issuer.CommonName,
|
"issuer_name": key.Issuer.CommonName,
|
||||||
"not_after": key.NotAfter,
|
"not_after": key.NotAfter,
|
||||||
"not_before": key.NotBefore,
|
"not_before": key.NotBefore,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func IssueCert(c *gin.Context) {
|
func IssueCert(c *gin.Context) {
|
||||||
domain := c.Param("domain")
|
domain := c.Param("domain")
|
||||||
var upGrader = websocket.Upgrader{
|
var upGrader = websocket.Upgrader{
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// upgrade http to websocket
|
// upgrade http to websocket
|
||||||
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func(ws *websocket.Conn) {
|
defer func(ws *websocket.Conn) {
|
||||||
err := ws.Close()
|
err := ws.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("defer websocket close err", err)
|
log.Println("defer websocket close err", err)
|
||||||
}
|
}
|
||||||
}(ws)
|
}(ws)
|
||||||
|
|
||||||
for {
|
// read
|
||||||
// read
|
mt, message, err := ws.ReadMessage()
|
||||||
mt, message, err := ws.ReadMessage()
|
if err != nil {
|
||||||
if err != nil {
|
log.Println(err)
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
if string(message) == "go" {
|
|
||||||
var m []byte
|
|
||||||
|
|
||||||
if settings.ServerSettings.Demo {
|
if mt == websocket.TextMessage && string(message) == "go" {
|
||||||
m, _ = json.Marshal(gin.H{
|
|
||||||
"status": "error",
|
|
||||||
"message": "this feature is not available in demo",
|
|
||||||
})
|
|
||||||
_ = ws.WriteMessage(mt, m)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tool.IssueCert(domain)
|
err = tool.IssueCert(domain)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
|
|
||||||
m, err = json.Marshal(gin.H{
|
err = ws.WriteJSON(gin.H{
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"message": err.Error(),
|
"message": err.Error(),
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ws.WriteMessage(mt, m)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
sslCertificatePath := nginx.GetNginxConfPath("ssl/" + domain + "/fullchain.cer")
|
||||||
log.Println(err)
|
_, err = os.Stat(sslCertificatePath)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
if err != nil {
|
||||||
}
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sslCertificatePath := nginx.GetNginxConfPath("ssl/" + domain + "/fullchain.cer")
|
log.Println("[found]", "fullchain.cer")
|
||||||
_, err = os.Stat(sslCertificatePath)
|
|
||||||
|
|
||||||
if err != nil {
|
err = ws.WriteJSON(gin.H{
|
||||||
log.Println(err)
|
"status": "success",
|
||||||
return
|
"message": "[found] fullchain.cer",
|
||||||
}
|
})
|
||||||
|
|
||||||
log.Println("[found]", "fullchain.cer")
|
if err != nil {
|
||||||
m, err = json.Marshal(gin.H{
|
log.Println(err)
|
||||||
"status": "success",
|
return
|
||||||
"message": "[found] fullchain.cer",
|
}
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
sslCertificateKeyPath := nginx.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key")
|
||||||
log.Println(err)
|
_, err = os.Stat(sslCertificateKeyPath)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ws.WriteMessage(mt, m)
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
log.Println("[found]", "cert key")
|
||||||
log.Println(err)
|
err = ws.WriteJSON(gin.H{
|
||||||
return
|
"status": "success",
|
||||||
}
|
"message": "[found] Certificate Key",
|
||||||
|
})
|
||||||
|
|
||||||
sslCertificateKeyPath := nginx.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key")
|
if err != nil {
|
||||||
_, err = os.Stat(sslCertificateKeyPath)
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
err = ws.WriteJSON(gin.H{
|
||||||
log.Println(err)
|
"status": "success",
|
||||||
return
|
"message": "Issued certificate successfully",
|
||||||
}
|
"ssl_certificate": sslCertificatePath,
|
||||||
|
"ssl_certificate_key": sslCertificateKeyPath,
|
||||||
|
})
|
||||||
|
|
||||||
log.Println("[found]", "cert key")
|
if err != nil {
|
||||||
m, err = json.Marshal(gin.H{
|
log.Println(err)
|
||||||
"status": "success",
|
return
|
||||||
"message": "[found] cert key",
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ws.WriteMessage(mt, m)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("申请成功")
|
|
||||||
m, err = json.Marshal(gin.H{
|
|
||||||
"status": "success",
|
|
||||||
"message": "申请成功",
|
|
||||||
"ssl_certificate": sslCertificatePath,
|
|
||||||
"ssl_certificate_key": sslCertificateKeyPath,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ws.WriteMessage(mt, m)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,267 +1,270 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/0xJacky/Nginx-UI/server/model"
|
"github.com/0xJacky/Nginx-UI/server/model"
|
||||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
"github.com/0xJacky/Nginx-UI/server/tool"
|
||||||
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetDomains(c *gin.Context) {
|
func GetDomains(c *gin.Context) {
|
||||||
orderBy := c.Query("order_by")
|
orderBy := c.Query("order_by")
|
||||||
sort := c.DefaultQuery("sort", "desc")
|
sort := c.DefaultQuery("sort", "desc")
|
||||||
|
|
||||||
mySort := map[string]string{
|
mySort := map[string]string{
|
||||||
"enabled": "bool",
|
"enabled": "bool",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"modify": "time",
|
"modify": "time",
|
||||||
}
|
}
|
||||||
|
|
||||||
configFiles, err := ioutil.ReadDir(nginx.GetNginxConfPath("sites-available"))
|
configFiles, err := ioutil.ReadDir(nginx.GetNginxConfPath("sites-available"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
enabledConfig, err := ioutil.ReadDir(filepath.Join(nginx.GetNginxConfPath("sites-enabled")))
|
enabledConfig, err := ioutil.ReadDir(filepath.Join(nginx.GetNginxConfPath("sites-enabled")))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
enabledConfigMap := make(map[string]bool)
|
enabledConfigMap := make(map[string]bool)
|
||||||
for i := range enabledConfig {
|
for i := range enabledConfig {
|
||||||
enabledConfigMap[enabledConfig[i].Name()] = true
|
enabledConfigMap[enabledConfig[i].Name()] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var configs []gin.H
|
var configs []gin.H
|
||||||
|
|
||||||
for i := range configFiles {
|
for i := range configFiles {
|
||||||
file := configFiles[i]
|
file := configFiles[i]
|
||||||
if !file.IsDir() {
|
if !file.IsDir() {
|
||||||
configs = append(configs, gin.H{
|
configs = append(configs, gin.H{
|
||||||
"name": file.Name(),
|
"name": file.Name(),
|
||||||
"size": file.Size(),
|
"size": file.Size(),
|
||||||
"modify": file.ModTime(),
|
"modify": file.ModTime(),
|
||||||
"enabled": enabledConfigMap[file.Name()],
|
"enabled": enabledConfigMap[file.Name()],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configs = tool.Sort(orderBy, sort, mySort[orderBy], configs)
|
configs = tool.Sort(orderBy, sort, mySort[orderBy], configs)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"configs": configs,
|
"configs": configs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDomain(c *gin.Context) {
|
func GetDomain(c *gin.Context) {
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
|
path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
|
||||||
|
|
||||||
enabled := true
|
enabled := true
|
||||||
if _, err := os.Stat(filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) {
|
if _, err := os.Stat(filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) {
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := nginx.ParseNgxConfig(path)
|
config, err := nginx.ParseNgxConfig(path)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = model.FirstCert(name)
|
_, err = model.FirstCert(name)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"enabled": enabled,
|
"enabled": enabled,
|
||||||
"name": name,
|
"name": name,
|
||||||
"config": config.BuildConfig(),
|
"config": config.BuildConfig(),
|
||||||
"tokenized": config,
|
"tokenized": config,
|
||||||
})
|
"auto_cert": err == nil,
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func EditDomain(c *gin.Context) {
|
func EditDomain(c *gin.Context) {
|
||||||
var err error
|
var err error
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
request := make(gin.H)
|
request := make(gin.H)
|
||||||
err = c.BindJSON(&request)
|
err = c.BindJSON(&request)
|
||||||
path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
|
path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
|
||||||
|
|
||||||
err = ioutil.WriteFile(path, []byte(request["content"].(string)), 0644)
|
err = ioutil.WriteFile(path, []byte(request["content"].(string)), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
|
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
|
||||||
if _, err = os.Stat(enabledConfigFilePath); err == nil {
|
if _, err = os.Stat(enabledConfigFilePath); err == nil {
|
||||||
// 测试配置文件
|
// Test nginx configuration
|
||||||
err = nginx.TestNginxConf()
|
err = nginx.TestNginxConf()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"message": err.Error(),
|
"message": err.Error(),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
output := nginx.ReloadNginx()
|
output := nginx.ReloadNginx()
|
||||||
|
|
||||||
if output != "" && strings.Contains(output, "error") {
|
if output != "" && strings.Contains(output, "error") {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"message": output,
|
"message": output,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GetDomain(c)
|
GetDomain(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EnableDomain(c *gin.Context) {
|
func EnableDomain(c *gin.Context) {
|
||||||
configFilePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), c.Param("name"))
|
configFilePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), c.Param("name"))
|
||||||
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
||||||
|
|
||||||
_, err := os.Stat(configFilePath)
|
_, err := os.Stat(configFilePath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Symlink(configFilePath, enabledConfigFilePath)
|
if _, err = os.Stat(enabledConfigFilePath); os.IsNotExist(err) {
|
||||||
|
err = os.Symlink(configFilePath, enabledConfigFilePath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test nginx config, if not pass then rollback.
|
// Test nginx config, if not pass then rollback.
|
||||||
err = nginx.TestNginxConf()
|
err = nginx.TestNginxConf()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = os.Remove(enabledConfigFilePath)
|
_ = os.Remove(enabledConfigFilePath)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"message": err.Error(),
|
"message": err.Error(),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
output := nginx.ReloadNginx()
|
output := nginx.ReloadNginx()
|
||||||
|
|
||||||
if output != "" && strings.Contains(output, "error") {
|
if output != "" && strings.Contains(output, "error") {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"message": output,
|
"message": output,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"message": "ok",
|
"message": "ok",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func DisableDomain(c *gin.Context) {
|
func DisableDomain(c *gin.Context) {
|
||||||
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
||||||
|
|
||||||
_, err := os.Stat(enabledConfigFilePath)
|
_, err := os.Stat(enabledConfigFilePath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Remove(enabledConfigFilePath)
|
err = os.Remove(enabledConfigFilePath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete auto cert record
|
// delete auto cert record
|
||||||
cert := model.Cert{Domain: c.Param("name")}
|
cert := model.Cert{Domain: c.Param("name")}
|
||||||
err = cert.Remove()
|
err = cert.Remove()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
output := nginx.ReloadNginx()
|
output := nginx.ReloadNginx()
|
||||||
|
|
||||||
if output != "" {
|
if output != "" {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"message": output,
|
"message": output,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"message": "ok",
|
"message": "ok",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteDomain(c *gin.Context) {
|
func DeleteDomain(c *gin.Context) {
|
||||||
var err error
|
var err error
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
availablePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
|
availablePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
|
||||||
enabledPath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
|
enabledPath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
|
||||||
|
|
||||||
if _, err = os.Stat(availablePath); os.IsNotExist(err) {
|
if _, err = os.Stat(availablePath); os.IsNotExist(err) {
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
"message": "site not found",
|
"message": "site not found",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = os.Stat(enabledPath); err == nil {
|
if _, err = os.Stat(enabledPath); err == nil {
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{
|
c.JSON(http.StatusNotAcceptable, gin.H{
|
||||||
"message": "site is enabled",
|
"message": "site is enabled",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cert := model.Cert{Domain: name}
|
cert := model.Cert{Domain: name}
|
||||||
_ = cert.Remove()
|
_ = cert.Remove()
|
||||||
|
|
||||||
err = os.Remove(availablePath)
|
err = os.Remove(availablePath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"message": "ok",
|
"message": "ok",
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddDomainToAutoCert(c *gin.Context) {
|
func AddDomainToAutoCert(c *gin.Context) {
|
||||||
domain := c.Param("domain")
|
domain := c.Param("domain")
|
||||||
cert, err := model.FirstOrCreateCert(domain)
|
cert, err := model.FirstOrCreateCert(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, cert)
|
c.JSON(http.StatusOK, cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoveDomainFromAutoCert(c *gin.Context) {
|
func RemoveDomainFromAutoCert(c *gin.Context) {
|
||||||
cert := model.Cert{
|
cert := model.Cert{
|
||||||
Domain: c.Param("domain"),
|
Domain: c.Param("domain"),
|
||||||
}
|
}
|
||||||
err := cert.Remove()
|
err := cert.Remove()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrHandler(c, err)
|
ErrHandler(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, nil)
|
c.JSON(http.StatusOK, nil)
|
||||||
}
|
}
|
||||||
|
|
42
server/api/ngx.go
Normal file
42
server/api/ngx.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BuildNginxConfig(c *gin.Context) {
|
||||||
|
var ngxConf nginx.NgxConfig
|
||||||
|
if !BindAndValid(c, &ngxConf) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"content": ngxConf.BuildConfig(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TokenizeNginxConfig(c *gin.Context) {
|
||||||
|
var json struct {
|
||||||
|
Content string `json:"content" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if !BindAndValid(c, &json) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(strings.NewReader(json.Content))
|
||||||
|
|
||||||
|
ngxConfig, err := nginx.ParseNgxConfigByScanner("", scanner)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, ngxConfig)
|
||||||
|
|
||||||
|
}
|
|
@ -2,34 +2,58 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||||
"github.com/0xJacky/Nginx-UI/server/template"
|
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetTemplate(c *gin.Context) {
|
func GetTemplate(c *gin.Context) {
|
||||||
name := c.Param("name")
|
content := `proxy_set_header Host $host;
|
||||||
content, err := template.DistFS.ReadFile(name)
|
proxy_set_header X-Real_IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
|
||||||
_content := string(content)
|
proxy_pass http://127.0.0.1:{{ HTTP01PORT }};
|
||||||
_content = strings.ReplaceAll(_content, "{{ HTTP01PORT }}",
|
`
|
||||||
|
content = strings.ReplaceAll(content, "{{ HTTP01PORT }}",
|
||||||
settings.ServerSettings.HTTPChallengePort)
|
settings.ServerSettings.HTTPChallengePort)
|
||||||
|
|
||||||
if err != nil {
|
var ngxConfig *nginx.NgxConfig
|
||||||
if os.IsNotExist(err) {
|
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
ngxConfig = &nginx.NgxConfig{
|
||||||
"message": err.Error(),
|
Servers: []*nginx.NgxServer{
|
||||||
})
|
{
|
||||||
return
|
Directives: []*nginx.NgxDirective{
|
||||||
}
|
{
|
||||||
ErrHandler(c, err)
|
Directive: "listen",
|
||||||
return
|
Params: "80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Directive: "listen",
|
||||||
|
Params: "[::]:80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Directive: "server_name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Directive: "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Directive: "index",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Locations: []*nginx.NgxLocation{
|
||||||
|
{
|
||||||
|
Path: "/.well-known/acme-challenge",
|
||||||
|
Content: content,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"message": "ok",
|
"message": "ok",
|
||||||
"template": _content,
|
"template": ngxConfig.BuildConfig(),
|
||||||
|
"tokenized": ngxConfig,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ func authRequired() gin.HandlerFunc {
|
||||||
token = string(tmp)
|
token = string(tmp)
|
||||||
if token == "" {
|
if token == "" {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
"message": "auth fail",
|
"message": "Authorization failed",
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
|
@ -45,7 +45,7 @@ func authRequired() gin.HandlerFunc {
|
||||||
|
|
||||||
if n < 1 {
|
if n < 1 {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
"message": "auth fail",
|
"message": "Authorization failed",
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,92 +1,100 @@
|
||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"github.com/0xJacky/Nginx-UI/server/api"
|
"github.com/0xJacky/Nginx-UI/server/api"
|
||||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||||
"github.com/gin-contrib/static"
|
"github.com/gin-contrib/static"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitRouter() *gin.Engine {
|
func InitRouter() *gin.Engine {
|
||||||
r := gin.New()
|
r := gin.New()
|
||||||
r.Use(gin.Logger())
|
r.Use(gin.Logger())
|
||||||
|
|
||||||
r.Use(recovery())
|
r.Use(recovery())
|
||||||
|
|
||||||
r.Use(cacheJs())
|
r.Use(cacheJs())
|
||||||
|
|
||||||
r.Use(static.Serve("/", mustFS("")))
|
r.Use(static.Serve("/", mustFS("")))
|
||||||
|
|
||||||
r.NoRoute(func(c *gin.Context) {
|
r.NoRoute(func(c *gin.Context) {
|
||||||
accept := c.Request.Header.Get("Accept")
|
accept := c.Request.Header.Get("Accept")
|
||||||
if strings.Contains(accept, "text/html") {
|
if strings.Contains(accept, "text/html") {
|
||||||
file, _ := mustFS("").Open("index.html")
|
file, _ := mustFS("").Open("index.html")
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
stat, _ := file.Stat()
|
stat, _ := file.Stat()
|
||||||
c.DataFromReader(http.StatusOK, stat.Size(), "text/html",
|
c.DataFromReader(http.StatusOK, stat.Size(), "text/html",
|
||||||
bufio.NewReader(file), nil)
|
bufio.NewReader(file), nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
g := r.Group("/api")
|
g := r.Group("/api")
|
||||||
{
|
{
|
||||||
|
|
||||||
g.GET("settings", func(c *gin.Context) {
|
g.GET("settings", func(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"demo": settings.ServerSettings.Demo,
|
"demo": settings.ServerSettings.Demo,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
g.GET("install", api.InstallLockCheck)
|
g.GET("install", api.InstallLockCheck)
|
||||||
g.POST("install", api.InstallNginxUI)
|
g.POST("install", api.InstallNginxUI)
|
||||||
|
|
||||||
g.POST("/login", api.Login)
|
g.POST("/login", api.Login)
|
||||||
g.DELETE("/logout", api.Logout)
|
g.DELETE("/logout", api.Logout)
|
||||||
|
|
||||||
g := g.Group("/", authRequired())
|
g := g.Group("/", authRequired())
|
||||||
{
|
{
|
||||||
g.GET("/analytic", api.Analytic)
|
g.GET("analytic", api.Analytic)
|
||||||
g.GET("/analytic/init", api.GetAnalyticInit)
|
g.GET("analytic/init", api.GetAnalyticInit)
|
||||||
|
|
||||||
g.GET("/users", api.GetUsers)
|
g.GET("users", api.GetUsers)
|
||||||
g.GET("/user/:id", api.GetUser)
|
g.GET("user/:id", api.GetUser)
|
||||||
g.POST("/user", api.AddUser)
|
g.POST("user", api.AddUser)
|
||||||
g.POST("/user/:id", api.EditUser)
|
g.POST("user/:id", api.EditUser)
|
||||||
g.DELETE("/user/:id", api.DeleteUser)
|
g.DELETE("user/:id", api.DeleteUser)
|
||||||
|
|
||||||
g.GET("domains", api.GetDomains)
|
g.GET("domains", api.GetDomains)
|
||||||
g.GET("domain/:name", api.GetDomain)
|
g.GET("domain/:name", api.GetDomain)
|
||||||
g.POST("domain/:name", api.EditDomain)
|
|
||||||
g.POST("domain/:name/enable", api.EnableDomain)
|
|
||||||
g.POST("domain/:name/disable", api.DisableDomain)
|
|
||||||
g.DELETE("domain/:name", api.DeleteDomain)
|
|
||||||
|
|
||||||
g.GET("configs", api.GetConfigs)
|
// Modify site configuration directly
|
||||||
g.GET("config/:name", api.GetConfig)
|
g.POST("domain/:name", api.EditDomain)
|
||||||
g.POST("config", api.AddConfig)
|
|
||||||
g.POST("config/:name", api.EditConfig)
|
|
||||||
|
|
||||||
g.GET("backups", api.GetFileBackupList)
|
// Transform NgxConf to nginx configuration
|
||||||
g.GET("backup/:id", api.GetFileBackup)
|
g.POST("ngx/build_config", api.BuildNginxConfig)
|
||||||
|
// Tokenized nginx configuration to NgxConf
|
||||||
|
g.POST("ngx/tokenize_config", api.TokenizeNginxConfig)
|
||||||
|
|
||||||
g.GET("template/:name", api.GetTemplate)
|
g.POST("domain/:name/enable", api.EnableDomain)
|
||||||
|
g.POST("domain/:name/disable", api.DisableDomain)
|
||||||
|
g.DELETE("domain/:name", api.DeleteDomain)
|
||||||
|
|
||||||
g.GET("cert/issue/:domain", api.IssueCert)
|
g.GET("configs", api.GetConfigs)
|
||||||
g.GET("cert/:domain/info", api.CertInfo)
|
g.GET("config/:name", api.GetConfig)
|
||||||
|
g.POST("config", api.AddConfig)
|
||||||
|
g.POST("config/:name", api.EditConfig)
|
||||||
|
|
||||||
// 添加域名到自动续期列表
|
//g.GET("backups", api.GetFileBackupList)
|
||||||
g.POST("cert/:domain", api.AddDomainToAutoCert)
|
//g.GET("backup/:id", api.GetFileBackup)
|
||||||
// 从自动续期列表中删除域名
|
|
||||||
g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
|
|
||||||
|
|
||||||
// pty
|
g.GET("template", api.GetTemplate)
|
||||||
g.GET("pty", api.Pty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
g.GET("cert/issue/:domain", api.IssueCert)
|
||||||
|
g.GET("cert/:domain/info", api.CertInfo)
|
||||||
|
|
||||||
|
// Add domain to auto-renew cert list
|
||||||
|
g.POST("cert/:domain", api.AddDomainToAutoCert)
|
||||||
|
// Delete domain from auto-renew cert list
|
||||||
|
g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
|
||||||
|
|
||||||
|
// pty
|
||||||
|
g.GET("pty", api.Pty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
server {
|
|
||||||
listen {{ http_listen_port }};
|
|
||||||
listen [::]:{{ http_listen_port }};
|
|
||||||
|
|
||||||
server_name {{ server_name }};
|
|
||||||
|
|
||||||
location /.well-known {
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real_IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
|
|
||||||
proxy_pass http://127.0.0.1:{{ HTTP01PORT }};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
server {
|
|
||||||
listen {{ http_listen_port }};
|
|
||||||
listen [::]:{{ http_listen_port }};
|
|
||||||
|
|
||||||
server_name {{ server_name }};
|
|
||||||
|
|
||||||
rewrite ^(.*)$ https://$host$1 permanent;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen {{ https_listen_port }} ssl http2;
|
|
||||||
listen [::]:{{ https_listen_port }} ssl http2;
|
|
||||||
|
|
||||||
server_name {{ server_name }};
|
|
||||||
|
|
||||||
ssl_certificate {{ ssl_certificate }};
|
|
||||||
ssl_certificate_key {{ ssl_certificate_key }};
|
|
||||||
|
|
||||||
location /.well-known {
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real_IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
|
|
||||||
proxy_pass http://127.0.0.1:{{ HTTP01PORT }};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
package template
|
|
||||||
|
|
||||||
import "embed"
|
|
||||||
|
|
||||||
//go:embed http-conf https-conf
|
|
||||||
var DistFS embed.FS
|
|
|
@ -1,185 +1,189 @@
|
||||||
package tool
|
package tool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"github.com/0xJacky/Nginx-UI/server/model"
|
"github.com/0xJacky/Nginx-UI/server/model"
|
||||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||||
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
"github.com/0xJacky/Nginx-UI/server/tool/nginx"
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
"github.com/go-acme/lego/v4/challenge/http01"
|
"github.com/go-acme/lego/v4/challenge/http01"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MyUser You'll need a user or account type that implements acme.User
|
// MyUser You'll need a user or account type that implements acme.User
|
||||||
type MyUser struct {
|
type MyUser struct {
|
||||||
Email string
|
Email string
|
||||||
Registration *registration.Resource
|
Registration *registration.Resource
|
||||||
key crypto.PrivateKey
|
key crypto.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *MyUser) GetEmail() string {
|
func (u *MyUser) GetEmail() string {
|
||||||
return u.Email
|
return u.Email
|
||||||
}
|
}
|
||||||
func (u MyUser) GetRegistration() *registration.Resource {
|
func (u MyUser) GetRegistration() *registration.Resource {
|
||||||
return u.Registration
|
return u.Registration
|
||||||
}
|
}
|
||||||
func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
|
func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
|
||||||
return u.key
|
return u.key
|
||||||
}
|
}
|
||||||
|
|
||||||
func AutoCert() {
|
func AutoCert() {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
log.Println("[AutoCert] Recover", err)
|
log.Println("[AutoCert] Recover", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
log.Println("[AutoCert] Start")
|
log.Println("[AutoCert] Start")
|
||||||
autoCertList := model.GetAutoCertList()
|
autoCertList := model.GetAutoCertList()
|
||||||
for i := range autoCertList {
|
for i := range autoCertList {
|
||||||
domain := autoCertList[i].Domain
|
domain := autoCertList[i].Domain
|
||||||
key, err := GetCertInfo(domain)
|
key, err := GetCertInfo(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("GetCertInfo Err", err)
|
log.Println("GetCertInfo Err", err)
|
||||||
// 获取证书信息失败,本次跳过
|
// 获取证书信息失败,本次跳过
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// 未到一个月
|
// 未到一个月
|
||||||
if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) {
|
if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// 过一个月了,重新申请证书
|
// 过一个月了,重新申请证书
|
||||||
err = IssueCert(domain)
|
err = IssueCert(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCertInfo(domain string) (key *x509.Certificate, err error) {
|
func GetCertInfo(domain string) (key *x509.Certificate, err error) {
|
||||||
|
|
||||||
var response *http.Response
|
var response *http.Response
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
DialContext: (&net.Dialer{
|
DialContext: (&net.Dialer{
|
||||||
Timeout: 5 * time.Second,
|
Timeout: 5 * time.Second,
|
||||||
}).DialContext,
|
}).DialContext,
|
||||||
DisableKeepAlives: true,
|
DisableKeepAlives: true,
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
},
|
},
|
||||||
Timeout: 5 * time.Second,
|
Timeout: 5 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err = client.Get("https://" + domain)
|
response, err = client.Get("https://" + domain)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "get cert info error")
|
err = errors.Wrap(err, "get cert info error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func(Body io.ReadCloser) {
|
defer func(Body io.ReadCloser) {
|
||||||
err = Body.Close()
|
err = Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}(response.Body)
|
}(response.Body)
|
||||||
|
|
||||||
key = response.TLS.PeerCertificates[0]
|
key = response.TLS.PeerCertificates[0]
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func IssueCert(domain string) error {
|
func IssueCert(domain string) error {
|
||||||
// Create a user. New accounts need an email and private key to start.
|
// Create a user. New accounts need an email and private key to start.
|
||||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "issue cert generate key error")
|
return errors.Wrap(err, "issue cert generate key error")
|
||||||
}
|
}
|
||||||
|
|
||||||
myUser := MyUser{
|
myUser := MyUser{
|
||||||
Email: settings.ServerSettings.Email,
|
Email: settings.ServerSettings.Email,
|
||||||
key: privateKey,
|
key: privateKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
config := lego.NewConfig(&myUser)
|
config := lego.NewConfig(&myUser)
|
||||||
|
|
||||||
if settings.ServerSettings.Demo {
|
if settings.ServerSettings.Demo {
|
||||||
config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
}
|
}
|
||||||
config.Certificate.KeyType = certcrypto.RSA2048
|
|
||||||
|
|
||||||
// A client facilitates communication with the CA server.
|
config.Certificate.KeyType = certcrypto.RSA2048
|
||||||
client, err := lego.NewClient(config)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "issue cert new client error")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = client.Challenge.SetHTTP01Provider(
|
// A client facilitates communication with the CA server.
|
||||||
http01.NewProviderServer("",
|
client, err := lego.NewClient(config)
|
||||||
settings.ServerSettings.HTTPChallengePort,
|
if err != nil {
|
||||||
),
|
return errors.Wrap(err, "issue cert new client error")
|
||||||
)
|
}
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "issue cert challenge fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
// New users will need to register
|
err = client.Challenge.SetHTTP01Provider(
|
||||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
http01.NewProviderServer("",
|
||||||
if err != nil {
|
settings.ServerSettings.HTTPChallengePort,
|
||||||
log.Println(err)
|
),
|
||||||
return errors.Wrap(err, "issue cert register fail")
|
)
|
||||||
}
|
if err != nil {
|
||||||
myUser.Registration = reg
|
return errors.Wrap(err, "issue cert challenge fail")
|
||||||
|
}
|
||||||
|
|
||||||
request := certificate.ObtainRequest{
|
// New users will need to register
|
||||||
Domains: []string{domain},
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
Bundle: true,
|
if err != nil {
|
||||||
}
|
log.Println(err)
|
||||||
certificates, err := client.Certificate.Obtain(request)
|
return errors.Wrap(err, "issue cert register fail")
|
||||||
if err != nil {
|
}
|
||||||
return errors.Wrap(err, "issue cert fail to obtain")
|
myUser.Registration = reg
|
||||||
}
|
|
||||||
saveDir := nginx.GetNginxConfPath("ssl/" + domain)
|
|
||||||
if _, err := os.Stat(saveDir); os.IsNotExist(err) {
|
|
||||||
err = os.Mkdir(saveDir, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "issue cert fail to create")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Each certificate comes back with the cert bytes, the bytes of the client's
|
request := certificate.ObtainRequest{
|
||||||
// private key, and a certificate URL. SAVE THESE TO DISK.
|
Domains: []string{domain},
|
||||||
err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
|
Bundle: true,
|
||||||
certificates.Certificate, 0644)
|
}
|
||||||
if err != nil {
|
certificates, err := client.Certificate.Obtain(request)
|
||||||
log.Println(err)
|
if err != nil {
|
||||||
return errors.Wrap(err, "issue cert write fullchain.cer fail")
|
return errors.Wrap(err, "issue cert fail to obtain")
|
||||||
}
|
}
|
||||||
err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"),
|
saveDir := nginx.GetNginxConfPath("ssl/" + domain)
|
||||||
certificates.PrivateKey, 0644)
|
if _, err = os.Stat(saveDir); os.IsNotExist(err) {
|
||||||
if err != nil {
|
err = os.Mkdir(saveDir, 0755)
|
||||||
log.Println(err)
|
if err != nil {
|
||||||
return errors.Wrap(err, "issue cert write key fail")
|
return errors.Wrap(err, "issue cert fail to create")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nginx.ReloadNginx()
|
// Each certificate comes back with the cert bytes, the bytes of the client's
|
||||||
|
// private key, and a certificate URL. SAVE THESE TO DISK.
|
||||||
|
err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
|
||||||
|
certificates.Certificate, 0644)
|
||||||
|
|
||||||
return nil
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return errors.Wrap(err, "issue cert write fullchain.cer fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"),
|
||||||
|
certificates.PrivateKey, 0644)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return errors.Wrap(err, "issue cert write key fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
nginx.ReloadNginx()
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ func (c *NgxConfig) BuildConfig() (content string) {
|
||||||
}
|
}
|
||||||
if directive.Directive == If {
|
if directive.Directive == If {
|
||||||
server += fmt.Sprintf("%s%s\n", comments, fmtCodeWithIndent(directive.Params, 1))
|
server += fmt.Sprintf("%s%s\n", comments, fmtCodeWithIndent(directive.Params, 1))
|
||||||
} else {
|
} else if directive.Params != "" {
|
||||||
server += fmt.Sprintf("%s\t%s;\n", comments, directive.Orig())
|
server += fmt.Sprintf("%s\t%s;\n", comments, directive.Orig())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,15 +105,9 @@ func parseDirective(scanner *bufio.Scanner) (d NgxDirective) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseNgxConfig(filename string) (c *NgxConfig, err error) {
|
func ParseNgxConfigByScanner(filename string, scanner *bufio.Scanner) (c *NgxConfig, err error) {
|
||||||
file, err := os.Open(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "error open file in ParseNgxConfig")
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
c = NewNgxConfig(filename)
|
c = NewNgxConfig(filename)
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
d := parseDirective(scanner)
|
d := parseDirective(scanner)
|
||||||
paramsScanner := bufio.NewScanner(strings.NewReader(d.Params))
|
paramsScanner := bufio.NewScanner(strings.NewReader(d.Params))
|
||||||
|
@ -142,3 +136,15 @@ func ParseNgxConfig(filename string) (c *NgxConfig, err error) {
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseNgxConfig(filename string) (c *NgxConfig, err error) {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error open file in ParseNgxConfig")
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
|
||||||
|
return ParseNgxConfigByScanner(filename, scanner)
|
||||||
|
}
|
||||||
|
|
|
@ -36,8 +36,6 @@ type NgxDirective struct {
|
||||||
Comments string `json:"comments"`
|
Comments string `json:"comments"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NgxDirectives map[string][]NgxDirective
|
|
||||||
|
|
||||||
type NgxLocation struct {
|
type NgxLocation struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue